说在前面 双括号示例
Map source = new HashMap(){{
put("lastName", "Smith");
}};
个人理解:第一层括号代表创建匿名内部类, 第二层括号代表"非静态代码块",非静态代码块会在创建类实例的时候执行.
使用双括号时,需要谨慎.它可能导致两个问题,效率低且内存泄漏.
效率问题
利用双大括号初始化集合从效率上来说可能不如标准的集合初始化步骤。原因在于使用双大括号初始化会导致内部类文件的产生,而这个过程就会影响代码的执行效率。
首先查看不同初始化方式生成的.class文件
例如以下代码:
public class Test1 {
public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
ArrayList<String> list1 = new ArrayList<String>() {{
add("Harry");
add("Tony");
add("Tom");
add("Jerry");
}};
ArrayList<String> list2 = new ArrayList<String>() {{
add("Harry");
add("Tony");
add("Tom");
add("Jerry");
}};
ArrayList<String> list3 = new ArrayList<String>() {{
add("Harry");
add("Tony");
add("Tom");
add("Jerry");
}};
ArrayList<String> list4 = new ArrayList<String>() {{
add("Harry");
add("Tony");
add("Tom");
add("Jerry");
}};
ArrayList<String> list5 = new ArrayList<String>() {{
add("Harry");
add("Tony");
add("Tom");
add("Jerry");
}};
……
…snip…
……
ArrayList<String> list1000 = new ArrayList<String>() {{
add("Harry");
add("Tony");
add("Tom");
add("Jerry");
}};
System.out.println(System.currentTimeMillis());
}
}
Test1编译后生成的.class列表为:
Test1$1.class
Test1$2.class
Test1$3.class
Test1$4.class
Test1$5.class
……
…snip…
……
Test1$1000.class
Test1.class
生成了 1001个.class文件
public class Test2 {
public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
ArrayList<String> list1 = new ArrayList<>();
list1.add("Harry");
list1.add("Tony");
list1.add("Tom");
list1.add("Jerry");
ArrayList<String> list2 = new ArrayList<>();
list2.add("Harry");
list2.add("Tony");
list2.add("Tom");
list2.add("Jerry");
ArrayList<String> list3 = new ArrayList<>();
list3.add("Harry");
list3.add("Tony");
list3.add("Tom");
list3.add("Jerry");
ArrayList<String> list4 = new ArrayList<>();
list4.add("Harry");
list4.add("Tony");
list4.add("Tom");
list4.add("Jerry");
ArrayList<String> list5 = new ArrayList<>();
list5.add("Harry");
list5.add("Tony");
list5.add("Tom");
list5.add("Jerry");
……
…snip…
……
ArrayList<String> list1000 = new ArrayList<>();
list1000.add("Harry");
list1000.add("Tony");
list1000.add("Tom");
list1000.add("Jerry");
System.out.println(System.currentTimeMillis());
}
}
Test2编译后生成的.class列表为:
Test2.class
只生成了1个.class文件
运行时间
第一段代码Test1运行结果:
1508379452224
1508379452784
运行时间为:560毫秒
第二段代码Test2运行结果:
1508379671505
1508379671507
运行时间为:2毫秒
内存泄漏
这是所有匿名内部类都有的一个致命的问题 --他们会在内部维护一个外部容器类的引用, 想像一下, 你在应用中创建了一个如下代码类:
public class ReallyHeavyObject {
// Just to illustrate...
private int[] tonsOfValues;
private Resource[] tonsOfResources;
// This method almost does nothing
public Map quickHarmlessMethod() {
Map source = new HashMap(){{
put("firstName", "John");
put("lastName", "Smith");
}};
}
}
我们可以通过如下形式发现,这个匿名内部类持有了一个外部类的引用,实际上它还持有外部类子类的引用.
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
}
想避免持有外部类引用,我们可以将使用花括号生成匿名内部类的地方进行静态化.例如将quickHarmlessMethod方法静态.
关于this$0的含义
java中this$0的含义及用法,具体case是这样的:通过测试工具去探测界面UI上的控件FlatButton实例对象,但是拿到的最终对象是FlatButton的内部类TextButton对象,
所以这里需要从一个内部类的实例拿到所在外部类的实例,this$0这时就派上用处了。
示例方案如下:
//通过工具获取到TextButton实例对象
textButton=getButtonByToolMethod ();
//获取内部类TextButton的一个字段this$0信息
//this$0特指该内部类所在的外部类的引用,不需要手动定义,编译时自动加上
Filed outerField=textButton.getClass().getDeclaredField("this$0");
//this$0是私有的,提升访问权限
outerField.setAccessible(true);
//拿到该字段上的实例值
FlatButton flatButton=(FlatButton)outerField.get(textButton);
this$0就是内部类所自动保留的一个指向所在外部类的引用。
另外,受到$后的数字0启发,发现原来数字还可以有1,2,3..., 具体可以看如下代码就一清二楚了.
//Outer.java
public class Outer {//this$0
public class FirstInner {//this$1
public class SecondInner {//this$2
public class ThirdInner {
}
}
}
public static void main(String[] args) throws Exception{
//初始化各内外实例
Outer test = new Outer();
FirstInner first = test.new FirstInner();
FirstInner.SecondInner second = first.new SecondInner();
FirstInner.SecondInner.ThirdInner third=second.new ThirdInner();
//Outer,this$0
Field outerfield = first.getClass().getDeclaredField("this$0");
outerfield.setAccessible(true);
Object object = outerfield.get(first);
System.out.println(object instanceof Outer);
//FirstInner,this$1
Field firstInnerfied = second.getClass().getDeclaredField("this$1");
firstInnerfied.setAccessible(true);
object = firstInnerfied.get(second);
System.out.println(object instanceof FirstInner);
//SecondInner,this$2
Field secondInnerfield = third.getClass().getDeclaredField("this$2");
secondInnerfield.setAccessible(true);
object = secondInnerfield.get(third);
System.out.println(object instanceof FirstInner.SecondInner);
}
}
其实用 Java 反编译工具生成内部类可以更加清楚
public class Outer$FirstInner{//FirstInner内部类有一个带外部类参数的构造子
public Outer$FirstInner(Outer paramOuter) {}
....
}
public class Outer$FirstInner$SecondInner{
public Outer$FirstInner$SecondInner(Outer.FirstInner paramFirstInner){}
....
}
public class Outer$FirstInner$SecondInner$ThirdInner{
public Outer$FirstInner$SecondInner$ThirdInner(Outer.FirstInner.SecondInner paramSecondInner){}
}
可见每个内部类都有一个指向所在最近的外部类的一个引用,只不过该引用不需要手动添加,编译器会负责。