转载

不要“自作聪明”:{{是一种反模式

我时常发现有人在用 {{ 这种反模式(也叫做 双大括号初始化 )。这次是在 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"); }

本质上,这些初始者就是构造方法代码。所以,这是为什么我们说{{ 是反模式。

以下是{{是反模式的三个理由:

1、可读性

可读性是最不重要的理由。然而它却容易写,也有点像 JSON 数据的初始化:

{   "firstName"     : "John" , "lastName"      : "Smith" , "organizations" :   {     "0"   : { "id", "1234" }   , "abc" : { "id", "5678" }   } }

是的。 如果Java有List literal或者Map literal ,那真是太赞了。但是从语法上来说,用{{ 来模仿有点奇怪,也感觉不太正确。但是可以先把他留在关于大括号的讨论中( 以前我们也这么做过 )。

译者注:literal不好翻译,保留英文。list literal指如{1,2,3}这样的列表 )。

2、每个实例一种类型

我 们每一次{{实例化,都创建了一种类型。每次我们用这种方式创建一个 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 是唯一有意义的类,是封装类。但这还不是最严重的问题。

3、内存泄露!

内存泄露是所有匿名类最严重的问题。他们持有他们封装实例的引用,这点是致命的。想象一下,你把你的聪明的 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

译文链接:[

]

正文到此结束
Loading...