Ok,目前为止,我还只是Android coder,一切对其他的学习都是以Android 为主线的支线任务。所以为什么会提到这个方法呢?是来自于google官方的性能建议文档的这样一句话:
Consider Package Instead of Private Access with Private Inner Classes
考虑包内访问来取代访问私有内部类的私有修饰的方法或变量
为什么呢?下文的回答是:因为使用私有的内部类会产生静态的合成方法,影响性能。
如果有看过我的EventBus源码讲解的同学,会发现以下的一个常量 。
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
/**
* 省略内容
*
*/
//判断是否为PUBLIC,MODIFIERS_IGNORE 包含(抽象,静态,桥接,合成,后2者是编译器添加的)
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0)
可以看到我在其上的注释 MODIFIERS_IGNORE 包含(抽象,静态,桥接,合成,后2者是编译器添加的)
我的注释写的没有错,后面两种方法是编译器添加的。在此我给大家来一个小demo,让看看合成方法是在怎么生成的:
public class OutClass {
private static class InnerClass {
public InnerClass(){}
private int x;
private void y() {
};
}
public static void main(String[] args) {
InnerClass inner = new InnerClass();
inner.x = 2;
System.out.println(inner.x);
inner.y();
for (Method m : InnerClass.class.getDeclaredMethods()) {
System.out.println(String.format("%08X", m.getModifiers()) + " "
+ m.getName());
}
}
简单的一个静态内部类,实列化之后通过反射遍历其所有方法。输出方法名的同时,也将所有方法修饰词的十六进制码的格式输出。
运行结果如下:
先不看前面的十六进制码,我们看看后面输出的方法名,看上面的代码,我们知道,我们只写了一个名为 y 的方法。那么前面的两个access$1,2,3 是哪里来鬼怪?
为了解开谜底,我们再继续看看 java.lang.reflect.Modifier 里的几个修饰词的十六进制码:
00001008 SYNTHETIC|STATIC
00000002 PRIVATE
00000111 NATIVE|FINAL|PUBLIC
00000011 FINAL|PUBLIC
00000001 PUBLIC
00001000 SYNTHETIC
00000008 STATIC
我们看到了 00001008 SYNTHETIC|STATIC 合成的静态方法修饰词。
所以access$1什么的都是我们编译器合成的方法,且是静态的。
为什么呢!(ಠ౪ಠ)
为什么生成这些方法?平时我反射其他没有内部类的class的时候没有出现过的啊….
JVM是如何处理这个的class的?它可不知道什么是内部类或者嵌套类的。JVM对所有的类都一视同仁,它都认为是顶级类。被Javac编译后所有类都会被编译成顶级类,而那些内部类编译完后会生成…$… class的类文件。注意,是所有的内部类,我写静态内部类是因为这样的demo简单,所以如果你创建一个内部类的话,它会被彻底编译成一个顶级类。(原来JVM也是也挺笨的…..)。
既然同时身为顶级类,你OutClass 为什么可以直接调用我的InnerClass的私有方法和私有属性!你算老几?
为了解决这种矛盾,我们的圣母玛丽亚式的Javac编译的时候想到了一个解决冲突办法,在从中做了点手脚,在InnerClass里,为所有私有方法,和私有变量又写了一个合成的静态方法。为了还原真相我写了这样的代码:
private static class InnerClass {
public InnerClass(){}
private int x;
synthetic static void access$1(int x) { this.x=x; }
synthetic static int access$2(int x) { return x }
private void y() {
};
synthetic static int access$3() { y() }
}
所有说,等于是为了获取我们的x写了getter/setter方法。为了调用 y()写了一个嵌套的调用方法….
真是用心良苦啊…..
看完了上文,我们知道了,原来内部类在编译的时候为了给外部类调用,提供了几个静态方法给外部类使用。
1. 如果将y方法的修饰词改为public,就不会出现access$3()合成方法,对x也是同理。;
2.当你对x只赋值,不调用时,或者只调用,不赋值。只会产生相应的getter或setter方法;
3.将构造函数变为private 也会生成一个非静态的合成方法,如下:
synthetic void OutClass$InnerClass(OuClass this$0) { }
等下…this$0 这个又是什么鬼?
别急…我先下文介绍
作为一个Android Coder ,有一点我们都知道:
内部类会持有外部类的指针,有可能发生内存泄漏。所以,我们常常建议在合适的时候,将内部类改为静态内部类,这样就可以不持有外部的指针。
可是,Do you know the reasons ?
我们还是上文的Class,不过将其简单的改造下,不过是将其输出隔离出来
public class OutClass {
class InnerClass {
public int x;
public InnerClass() {}
public void y() {};
}
}
public class Demo {
/**
* @param args
*/
public static void main(String[] args) {
//遍历所有字段
for (Field c : OutClass.InnerClass.class.getDeclaredFields()) {
System.out.println(String.format("%08X", c.getModifiers()) + " "
+ c.getName());
}
System.out.println("-----------------------------------------");
//遍历构造函数
for( Constructor> c : OutClass.InnerClass.class.getDeclaredConstructors() ){
System.out.println(String.format("%08X",
c.getModifiers()) + " " + c.getName()+" ");
//遍历构造函数入参
for(Class temp:c.getParameterTypes()){
System.out.println(temp.getSimpleName());
}
}
}
}
运行~,以下为结果~
我们可以看到,我们内部的字段有两个,一个是x, 一个是 this$0 。而我们的构造函数只有一个没错,可是却偷偷的传入了一个OutClass对象。
(/= _ =)/~┴┴
在javac 的编译之下…被改的东西还真多啊….为了让大家更直观。我再模拟下编译后的class 文件内的情况。
public class OutClass {
class InnerClass {
public int x;
synthetic OutClass this$0;
public InnerClass(OutClass args) {
this.this$0 =args;
}
public void y() {};
}
}
这样就明白了吧~,我们的内部类之所以可以被外部类调用。原来是传了一个外部类的一个对象!有了this$0,我们可以随意的调用外部类的方法。
看完了上文,我们知道了,原来内部类在编译的时候为了调用外部类的方法,在其构造函数里传入了一个外部类的对象。
再接下上文,Android中为什么要将内部类改为静态内部类。是因为静态内部类是共享给当前类的所有对象的,所以不需要传入当前类的引用。
1.如果内部类没有构造函数,就能避免传参么?NO!
即使是默认的构造函数,也会传外部类引用。 000000不在修饰词内,可以当作没有修饰词的构造方法。
归根结底,作为一个Android的coder。那么多的static方法不适合移动的不算充裕的内存。因此Google不提倡使用私有内部类,最好将其拆开独立成一个Class。还有内部类尽量改为静态内部类,可以避免内存泄漏。有空看看google 的性能建议文档还是挺不错的!XD~~