Java:"失效"的private修饰符
在Java编程中,使用private关键字修饰了某个成员,只有这个成员所在的类和这个类的方法可以使用,其他的类都无法访问到这个private成员。
上面描述了private修饰符的基本职能,今天来研究一下private功能失效的情况。
Java内部类
在Java中相信很多人都用过内部类,Java允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类。一个简单的内部类实现可以如下
classOuterClass{ classInnerClass{ } }
今天的问题和Java内部类相关,只涉及到部分和本文研究相关的内部类知识,具体关于Java内部类后续的文章会介绍。
第一次失效?
一个我们在编程中经常用到的场景,就是在一个内部类里面访问外部类的private成员变量或者方法,这是可以的。如下面的代码实现。
publicclassOuterClass{ privateStringlanguage="en"; privateStringregion="US"; publicclassInnerClass{ publicvoidprintOuterClassPrivateFields(){ Stringfields="language="+language+";region="+region; System.out.println(fields); } } publicstaticvoidmain(String[]args){ OuterClassouter=newOuterClass(); OuterClass.InnerClassinner=outer.newInnerClass(); inner.printOuterClassPrivateFields(); } }
这是为什么呢,不是private修饰的成员只能被成员所述的类才能访问么?难道private真的失效了么?
编译器在捣鬼?
我们使用javap命令查看一下生成的两个class文件
OuterClass的反编译结果
15:30$javap-cOuterClass Compiledfrom"OuterClass.java" publicclassOuterClassextendsjava.lang.Object{ publicOuterClass(); Code: 0:aload_0 1:invokespecial#11;//Methodjava/lang/Object."<init>":()V 4:aload_0 5:ldc#13;//Stringen 7:putfield#15;//Fieldlanguage:Ljava/lang/String; 10:aload_0 11:ldc#17;//StringUS 13:putfield#19;//Fieldregion:Ljava/lang/String; 16:return publicstaticvoidmain(java.lang.String[]); Code: 0:new#1;//classOuterClass 3:dup 4:invokespecial#27;//Method"<init>":()V 7:astore_1 8:new#28;//classOuterClass$InnerClass 11:dup 12:aload_1 13:dup 14:invokevirtual#30;//Methodjava/lang/Object.getClass:()Ljava/lang/Class; 17:pop 18:invokespecial#34;//MethodOuterClass$InnerClass."<init>":(LOuterClass;)V 21:astore_2 22:aload_2 23:invokevirtual#37;//MethodOuterClass$InnerClass.printOuterClassPrivateFields:()V 26:return staticjava.lang.Stringaccess$0(OuterClass); Code: 0:aload_0 1:getfield#15;//Fieldlanguage:Ljava/lang/String; 4:areturn staticjava.lang.Stringaccess$1(OuterClass); Code: 0:aload_0 1:getfield#19;//Fieldregion:Ljava/lang/String; 4:areturn }
咦?不对,在OuterClass中我们并没有定义这两个方法
staticjava.lang.Stringaccess$0(OuterClass); Code: 0:aload_0 1:getfield#15;//Fieldlanguage:Ljava/lang/String; 4:areturn staticjava.lang.Stringaccess$1(OuterClass); Code: 0:aload_0 1:getfield#19;//Fieldregion:Ljava/lang/String; 4:areturn }
从给出来的注释来看,access$0返回outerClass的language属性;access$1返回outerClass的region属性。并且这两个方法都接受OuterClass的实例作为参数。这两个方法为什么生成呢,有什么作用呢?我们看一下内部类的反编译结果就知道了。
OuterClass$InnerClass的反编译结果
15:37$javap-cOuterClass\$InnerClass Compiledfrom"OuterClass.java" publicclassOuterClass$InnerClassextendsjava.lang.Object{ finalOuterClassthis$0; publicOuterClass$InnerClass(OuterClass); Code: 0:aload_0 1:aload_1 2:putfield#10;//Fieldthis$0:LOuterClass; 5:aload_0 6:invokespecial#12;//Methodjava/lang/Object."<init>":()V 9:return publicvoidprintOuterClassPrivateFields(); Code: 0:new#20;//classjava/lang/StringBuilder 3:dup 4:ldc#22;//Stringlanguage= 6:invokespecial#24;//Methodjava/lang/StringBuilder."<init>":(Ljava/lang/String;)V 9:aload_0 10:getfield#10;//Fieldthis$0:LOuterClass; 13:invokestatic#27;//MethodOuterClass.access$0:(LOuterClass;)Ljava/lang/String; 16:invokevirtual#33;//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 19:ldc#37;//String;region= 21:invokevirtual#33;//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24:aload_0 25:getfield#10;//Fieldthis$0:LOuterClass; 28:invokestatic#39;//MethodOuterClass.access$1:(LOuterClass;)Ljava/lang/String; 31:invokevirtual#33;//Methodjava/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 34:invokevirtual#42;//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String; 37:astore_1 38:getstatic#46;//Fieldjava/lang/System.out:Ljava/io/PrintStream; 41:aload_1 42:invokevirtual#52;//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V 45:return }
下面代码调用access$0的代码,其目的是得到OuterClass的language私有属性。
13: invokestatic#27;//MethodOuterClass.access$0:(LOuterClass;)Ljava/lang/String;
下面代码调用了access$1的代码,其目的是得到OutherClass的region私有属性。
28: invokestatic#39;//MethodOuterClass.access$1:(LOuterClass;)Ljava/lang/String;
注意:在内部类构造的时候,会将外部类的引用传递进来,并且作为内部类的一个属性,所以内部类会持有一个其外部类的引用。
this$0就是内部类持有的外部类引用,通过构造方法传递引用并赋值。
finalOuterClassthis$0; publicOuterClass$InnerClass(OuterClass); Code: 0:aload_0 1:aload_1 2:putfield#10;//Fieldthis$0:LOuterClass; 5:aload_0 6:invokespecial#12;//Methodjava/lang/Object."<init>":()V 9:return
小结
这部分private看上去失效可,实际上并没有失效,因为当内部类调用外部类的私有属性时,其真正的执行是调用了编译器生成的属性的静态方法(即acess$0,access$1等)来获取这些属性值。这一切都是编译器的特殊处理。
这次也失效?
如果说上面的写法很常用,那么这样的写法是不是很少接触,但是却可以运行。
publicclassAnotherOuterClass{ publicstaticvoidmain(String[]args){ InnerClassinner=newAnotherOuterClass().newInnerClass(); System.out.println("InnerClassFiled="+inner.x); } classInnerClass{ privateintx=10; } }
和上面一样,使用javap反编译看一下。不过这次我们先看一下InnerClass的结果
16:03$javap-cAnotherOuterClass\$InnerClass Compiledfrom"AnotherOuterClass.java" classAnotherOuterClass$InnerClassextendsjava.lang.Object{ finalAnotherOuterClassthis$0; AnotherOuterClass$InnerClass(AnotherOuterClass); Code: 0:aload_0 1:aload_1 2:putfield#12;//Fieldthis$0:LAnotherOuterClass; 5:aload_0 6:invokespecial#14;//Methodjava/lang/Object."<init>":()V 9:aload_0 10:bipush10 12:putfield#17;//Fieldx:I 15:return staticintaccess$0(AnotherOuterClass$InnerClass); Code: 0:aload_0 1:getfield#17;//Fieldx:I 4:ireturn }
又出现了,编译器又自动生成了一个获取私有属性的后门方法access$0一次来获取x的值。
AnotherOuterClass.class的反编译结果
16:08$javap-cAnotherOuterClass Compiledfrom"AnotherOuterClass.java" publicclassAnotherOuterClassextendsjava.lang.Object{ publicAnotherOuterClass(); Code: 0:aload_0 1:invokespecial#8;//Methodjava/lang/Object."<init>":()V 4:return publicstaticvoidmain(java.lang.String[]); Code: 0:new#16;//classAnotherOuterClass$InnerClass 3:dup 4:new#1;//classAnotherOuterClass 7:dup 8:invokespecial#18;//Method"<init>":()V 11:dup 12:invokevirtual#19;//Methodjava/lang/Object.getClass:()Ljava/lang/Class; 15:pop 16:invokespecial#23;//MethodAnotherOuterClass$InnerClass."<init>":(LAnotherOuterClass;)V 19:astore_1 20:getstatic#26;//Fieldjava/lang/System.out:Ljava/io/PrintStream; 23:new#32;//classjava/lang/StringBuilder 26:dup 27:ldc#34;//StringInnerClassFiled= 29:invokespecial#36;//Methodjava/lang/StringBuilder."<init>":(Ljava/lang/String;)V 32:aload_1 33:invokestatic#39;//MethodAnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I 36:invokevirtual#43;//Methodjava/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder; 39:invokevirtual#47;//Methodjava/lang/StringBuilder.toString:()Ljava/lang/String; 42:invokevirtual#51;//Methodjava/io/PrintStream.println:(Ljava/lang/String;)V 45:return }
其中这句调用就是外部类通过内部类的实例获取私有属性x的操作
33: invokestatic#39;//MethodAnotherOuterClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I
再来个总结
其中java官方文档有这样一句话
ifthememberorconstructorisdeclaredprivate,thenaccessispermittedifandonlyifitoccurswithinthebodyofthetoplevelclass(§7.6)thatenclosesthedeclarationofthememberorconstructor.
意思是如果(内部类的)成员和构造方法设定成了私有修饰符,当且仅当其外部类访问时是允许的。
如何让内部类私有成员不被外部访问
相信看完上面两部分,你会觉得,内部类的私有成员想不被外部类访问都很困难吧,谁让编译器“爱管闲事”呢,其实也是可以做到的。那就是使用匿名内部类。
由于mRunnable对象的类型为Runnable,而不是匿名内部类的类型(我们无法正常拿到),而Runanble中没有x这个属性,所以mRunnable.x是不被允许的。
publicclassPrivateToOuter{ RunnablemRunnable=newRunnable(){ privateintx=10; @Override publicvoidrun(){ System.out.println(x); } }; publicstaticvoidmain(String[]args){ PrivateToOuterp=newPrivateToOuter(); //System.out.println("anonymousclassprivatefiled="+p.mRunnable.x);//notallowed p.mRunnable.run();//allowed } }
最后总结
在本文中,private表面上看上去失效了,但实际上是没有的,而是在调用时通过间接的方法来获取私有的属性。
Java的内部类构造时持有对外部类的应用,C++不会,这一点和C++不一样。
深入Java细节的书籍
Java编程思想
Sun公司核心技术丛书:EffectiveJava中文版
深入理解Java虚拟机:JVM高级特性与最佳实践
以上就是对Javaprivate修饰符的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!