配色: 字号:
Java失效的private修饰符
2016-10-08 | 阅:  转:  |  分享 
  
Java:"失效"的private修饰符

本文主要介绍Java失效的private修饰符,这里整理了相关资料说明private修饰符的作用,如何使用并与C++做比较,有兴趣的小伙伴可以参考下

在Java编程中,使用private关键字修饰了某个成员,只有这个成员所在的类和这个类的方法可以使用,其他的类都无法访问到这个private成员。

上面描述了private修饰符的基本职能,今天来研究一下private功能失效的情况。

Java内部类

在Java中相信很多人都用过内部类,Java允许在一个类里面定义另一个类,类里面的类就是内部类,也叫做嵌套类。一个简单的内部类实现可以如下

?

1

2

3

4 classOuterClass{

classInnerClass{

}

} 今天的问题和Java内部类相关,只涉及到部分和本文研究相关的内部类知识,具体关于Java内部类后续的文章会介绍。

第一次失效?

一个我们在编程中经常用到的场景,就是在一个内部类里面访问外部类的private成员变量或者方法,这是可以的。如下面的代码实现。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18 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的反编译结果

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46 15:30$javap-cOuterClass

Compiledfrom"OuterClass.java"

publicclassOuterClassextendsjava.lang.Object{

publicOuterClass();

Code:

0:aload_0

1:invokespecial#11;//Methodjava/lang/Object."":()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"":()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."":(LOuterClass;)V

21:astore_2

22:aload_2

23:invokevirtual#37;//MethodOuterClass$InnerClass.printOuterClassPrivateFields:(www.hunanwang.net)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中我们并没有定义这两个方法

?

1

2

3

4

5

6

7

8

9

10

11

12

13 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的反编译结果

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37 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."":()V

9:return

publicvoidprintOuterClassPrivateFields();

Code:

0:new#20;//classjava/lang/StringBuilder

3:dup

4:ldc#22;//Stringlanguage=

6:invokespecial#24;//Methodjava/lang/StringBuilder."":(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就是内部类持有的外部类引用,通过构造方法传递引用并赋值。

?

1

2

3

4

5

6

7

8

9

10 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."":()V

9:return 小结

这部分private看上去失效可,实际上并没有失效,因为当内部类调用外部类的私有属性时,其真正的执行是调用了编译器生成的属性的静态方法(即acess$0,access$1等)来获取这些属性值。这一切都是编译器的特殊处理。

这次也失效?

如果说上面的写法很常用,那么这样的写法是不是很少接触,但是却可以运行。

?

1

2

3

4

5

6

7

8

9

10

11 publicclassAnotherOuterClass{

publicstaticvoidmain(String[]args){

InnerClassinner=newAnotherOuterClass().newInnerClass();

System.out.println("InnerClassFiled="+inner.x);

}

classInnerClass{

privateintx=10;

}

} 和上面一样,使用javap反编译看一下。不过这次我们先看一下InnerClass的结果

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24 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."":()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的反编译结果

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34 16:08$javap-cAnotherOuterClass

Compiledfrom"AnotherOuterClass.java"

publicclassAnotherOuterClassextendsjava.lang.Object{

publicAnotherOuterClass();

Code:

0:aload_0

1:invokespecial#8;//Methodjava/lang/Object."":()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"":()V

11:dup

12:invokevirtual#19;//Methodjava/lang/Object.getClass:()Ljava/lang/Class;

15:pop

16:invokespecial#23;//MethodAnotherOuterClass$InnerClass."":(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."":(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;//MethodAnotherOuterwww.visa158.comClass$InnerClass.access$0:(LAnotherOuterClass$InnerClass;)I

再来个总结

其中java官方文档有这样一句话

ifthememberorconstructorisdeclaredprivate,thenaccessispermittedifandonlyifitoccurswithinthebodyofthetoplevelclass(§7.6)thatenclosesthedeclarationofthememberorconstructor.

意思是如果(内部类的)成员和构造方法设定成了私有修饰符,当且仅当其外部类访问时是允许的。

如何让内部类私有成员不被外部访问

相信看完上面两部分,你会觉得,内部类的私有成员想不被外部类访问都很困难吧,谁让编译器“爱管闲事”呢,其实也是可以做到的。那就是使用匿名内部类。

由于mRunnable对象的类型为Runnable,而不是匿名内部类的类型(我们无法正常拿到),而Runanble中没有x这个属性,所以mRunnable.x是不被允许的。

?

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15 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++不一样。























献花(0)
+1
(本文系白狐一梦首藏)