我时常发现有人在用 {{ 这种反模式(也叫做 双大括号初始化 )。这次是在 stack Overflow 上:
Map source = new HashMap(){{ put("firstName", "John"); put("lastName", "Smith"); put("organizations", new HashMap(){{ put("0", new HashMap(){{ put("id", "1234"); }}); put("abc", new HashMap(){{ put("id", "5678"); }}); }}); }};
如果你不知道它的语法,实际上它非常简单,包含两个元素:
1、以下的代码创建了一个继承 HashMap 的匿名类:
new HashMap() { }
2、在匿名类内部,我们用一个实例初始者来初始化这个匿名 HashMap 子类:
{ put("id", "1234"); }
本质上,这些初始者就是构造方法代码。所以,这是为什么我们说{{ 是反模式。
以下是{{是反模式的三个理由:
可读性是最不重要的理由。然而它却容易写,也有点像 JSON 数据的初始化:
{ "firstName" : "John" , "lastName" : "Smith" , "organizations" : { "0" : { "id", "1234" } , "abc" : { "id", "5678" } } }
是的。 如果Java有List literal或者Map literal ,那真是太赞了。但是从语法上来说,用{{ 来模仿有点奇怪,也感觉不太正确。但是可以先把他留在关于大括号的讨论中( 以前我们也这么做过 )。
( 译者注:literal不好翻译,保留英文。list literal指如{1,2,3}这样的列表 )。
我 们每一次{{实例化,都创建了一种类型。每次我们用这种方式创建一个 Map,我们同时隐式的为这个简单的 hashMap 实例创建了一个不可复用的类。如果你只这么做一次,那还是可行的。但是如果类似这样的代码存在于一个大型的应用中,这将增加 ClassLoader 一些不必要的负担,ClassLoader 需要在堆上持有所有这些类的引用。不相信么?编译一下上面的代码然后查看一下编译结果。它看起来是这样的:
Test$1$1$1.class Test$1$1$2.class Test$1$1.class Test$1.class Test.class
在这里,只有 Test.class 是唯一有意义的类,是封装类。但这还不是最严重的问题。
内存泄露是所有匿名类最严重的问题。他们持有他们封装实例的引用,这点是致命的。想象一下,你把你的聪明的 HashMap 实例化放进一个 EJB 或者是其他的有着管理良好的生命周期的重量级 object 里,像下面这样:
public class ReallyHeavyObject { // Just to illustrate... private int[] tonsOfValues; private Resource[] tonsOfResources; // This method almost does nothing public void quickHarmlessMethod() { Map source = new HashMap(){{ put("firstName", "John"); put("lastName", "Smith"); put("organizations", new HashMap(){{ put("0", new HashMap(){{ put("id", "1234"); }}); put("abc", new HashMap(){{ put("id", "5678"); }}); }}); }}; // Some more code here } }
所以这个ReallyHeavyObject 包含很多资源,一旦 ReallyHeavyObject 被回收,这些资源需要被正确的清除。但是如果你调用马上执行的 quickHarmlessMethod()方法,这个就没有影响了。
好吧。
想象一下其他开发人员,重写了你的方法去返回这个 map,或者仅仅是 map 的一部分。
public Map quickHarmlessMethod() { Map source = new HashMap(){{ put("firstName", "John"); put("lastName", "Smith"); put("organizations", new HashMap(){{ put("0", new HashMap(){{ put("id", "1234"); }}); put("abc", new HashMap(){{ put("id", "5678"); }}); }}); }}; return source; }
现在你就麻烦大了!你不可避免的把 ReallyHeavyObject 的所有状态暴露给了外界,因为每个内部类都持有它的封装类的引用,也即ReallyHeavyObject 的实例。不相信么,来 run 以下代码:
public static void main(String[] args) throws Exception { Map map = new ReallyHeavyObject().quickHarmlessMethod(); Field field = map.getClass().getDeclaredField("this$0"); field.setAccessible(true); System.out.println(field.get(map).getClass()); }
这段代码返回
class ReallyHeavyObject
是的,确实如此!如果你还不相信,你可以用 debugger 去检查返回的 map:
你可以发现,封装类的引用就存在匿名 HashMap 的子类里。 而且所有的嵌套匿名 HashMap 子类也都持有这样一个引用。
你可能会说,把 quickHarmlessMethod()变成静态方法可以绕开内存泄露的问题, 你是对的。但是,在上面的例子中我们看到,事实上你可能可以创建一个静态的上下文,但是下一个开发人员可能 不会注意到,他会改写你的方法,或者直接把 static 去掉。他们可能把 Map 存在其他的一些单例 中,而且通过代码本身是不可能看出它包含 ReallyHeavyObject 的无用引用。内部类非常讨厌,他们过去带来了很多麻烦和明细的不协调。匿名内部类更讨厌,因为看代码的人很容易忽略他们持有一个 外部类的引用而且他们还把这个引用到处传递。 结论就是: 不要自作聪明,千万不要使用{{实例化。
原文链接: JOOQ 翻译:ImportNew.com -poppy
译文链接:[]