java私有域有何用_学以致用,通过字节码理解:Java的内部类与外部类之私有域访问...

目录:

内部类的定义及用处

打开字节码理解内部类

一、内部类的定义及用处

内部类(inner class)是定义在另一个类中的类。使用内部类,我们可以:

访问该类定义所在的作用域中的数据,包括私有的数据

可以对同一个包中的其他类隐藏起来

当想要定义一个回调函数且不想编写大量代码时,使用匿名(anonymous)内部类比较便捷

本文旨在讲解内部类与外部类可以相互访问对方的私有域的原理,内部类的用法等大家可以自行查阅(官网介绍简单明了:Nested Class);

二、打开字节码理解内部类

我们知道,内部类其实是Java语言的一种语法糖。经过编译会生成一个"外部类名$内部类名.class"的class文件。如下:

c1bdbc72d68b8881916ad4b110df2ea5.png

非常简单的一个类OuterCls,包含了一个InnerCls内部类。通过javac编译,我们可以看到列表中多了一个文件:OuterCls$InnerCls.class。

接着,我们通过javap -verbose查看生成的OuterCls.class:

273d079fd0d43ef2a3f41ca303013bab.gif

3bc5db1bf6d47e0c347d03e4fd2006d5.gif

$ javap -verbose OuterCls

Warning: File ./OuterCls.class does not contain class OuterCls

Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls.class

Last modified Aug 14, 2018; size 434 bytes

MD5 checksum b9a1f41c67c8ae3be427c578ea205d20

Compiled from "OuterCls.java"

public class com.ntchan.nestedcls.OuterCls

minor version: 0

major version: 53

flags: (0x0021) ACC_PUBLIC, ACC_SUPER

this_class: #3 // com/ntchan/nestedcls/OuterCls

super_class: #4 // java/lang/Object

interfaces: 0, fields: 1, methods: 2, attributes: 2

Constant pool:

#1 = Fieldref #3.#18 // com/ntchan/nestedcls/OuterCls.outerField:I

#2 = Methodref #4.#19 // java/lang/Object."":()V

#3 = Class #20 // com/ntchan/nestedcls/OuterCls

#4 = Class #21 // java/lang/Object

#5 = Class #22 // com/ntchan/nestedcls/OuterCls$InnerCls

#6 = Utf8 InnerCls

#7 = Utf8 InnerClasses

#8 = Utf8 outerField

#9 = Utf8 I

#10 = Utf8

#11 = Utf8 ()V

#12 = Utf8 Code

#13 = Utf8 LineNumberTable

#14 = Utf8 access$000

#15 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)I

#16 = Utf8 SourceFile

#17 = Utf8 OuterCls.java

#18 = NameAndType #8:#9 // outerField:I

#19 = NameAndType #10:#11 // "":()V

#20 = Utf8 com/ntchan/nestedcls/OuterCls

#21 = Utf8 java/lang/Object

#22 = Utf8 com/ntchan/nestedcls/OuterCls$InnerCls

{

public com.ntchan.nestedcls.OuterCls();

descriptor: ()V

flags: (0x0001) ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: aload_0

1: invokespecial #2 // Method java/lang/Object."":()V

4: aload_0

5: iconst_5

6: putfield #1 // Field outerField:I

9: return

LineNumberTable:

line 3: 0

line 4: 4

static int access$000(com.ntchan.nestedcls.OuterCls);

descriptor: (Lcom/ntchan/nestedcls/OuterCls;)I

flags: (0x1008) ACC_STATIC, ACC_SYNTHETIC

Code:

stack=1, locals=1, args_size=1

0: aload_0

1: getfield #1 // Field outerField:I

4: ireturn

LineNumberTable:

line 3: 0

}

SourceFile: "OuterCls.java"

InnerClasses:

#6= #5 of #3; // InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls

View Code

其中,我们发现OuterCls多了一个静态方法access$000:

277d9561964c6dce38cc3288bd159a67.png

我们看一下这个静态方法做了什么:

缺省修饰符,表示这个方法的域是包可见

这个静态方法只有一个参数:OuterCls

ACC_SYNTHETIC:表示这个方法是由编译器自动生成的

aload_0表示把局部变量表的第一个变量加载到操作栈

getfield 访问实例字段 outerField

ireturn 返回传参进来的OuterCls的outerFiled的值

好像发现了什么,对比代码,我们在内部类使用了外部类的私有域outerField,编译器就自动帮我们生成了一个仅包可见的静态方法来返回outerField的值。

接着,我们继续查看内部类InnerCls的字节码:

1df6bd51ee1529d82e29afdaa0883750.gif

b2b79e3ca13352c3c4c39e79f6d585bf.gif

$ javap -verbose OuterCls\$InnerCls

Warning: File ./OuterCls$InnerCls.class does not contain class OuterCls$InnerCls

Classfile /Users/ntchan/code/demo/concepts/src/com/ntchan/nestedcls/OuterCls$InnerCls.class

Last modified Aug 14, 2018; size 648 bytes

MD5 checksum 344420034b48389a027a2f303cd2617c

Compiled from "OuterCls.java"

class com.ntchan.nestedcls.OuterCls$InnerCls

minor version: 0

major version: 53

flags: (0x0020) ACC_SUPER

this_class: #6 // com/ntchan/nestedcls/OuterCls$InnerCls

super_class: #7 // java/lang/Object

interfaces: 0, fields: 1, methods: 2, attributes: 2

Constant pool:

#1 = Fieldref #6.#18 // com/ntchan/nestedcls/OuterCls$InnerCls.this$0:Lcom/ntchan/nestedcls/OuterCls;

#2 = Methodref #7.#19 // java/lang/Object."":()V

#3 = Fieldref #20.#21 // java/lang/System.out:Ljava/io/PrintStream;

#4 = Methodref #22.#23 // com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I

#5 = Methodref #24.#25 // java/io/PrintStream.println:(I)V

#6 = Class #26 // com/ntchan/nestedcls/OuterCls$InnerCls

#7 = Class #29 // java/lang/Object

#8 = Utf8 this$0

#9 = Utf8 Lcom/ntchan/nestedcls/OuterCls;

#10 = Utf8

#11 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)V

#12 = Utf8 Code

#13 = Utf8 LineNumberTable

#14 = Utf8 printOuterField

#15 = Utf8 ()V

#16 = Utf8 SourceFile

#17 = Utf8 OuterCls.java

#18 = NameAndType #8:#9 // this$0:Lcom/ntchan/nestedcls/OuterCls;

#19 = NameAndType #10:#15 // "":()V

#20 = Class #30 // java/lang/System

#21 = NameAndType #31:#32 // out:Ljava/io/PrintStream;

#22 = Class #33 // com/ntchan/nestedcls/OuterCls

#23 = NameAndType #34:#35 // access$000:(Lcom/ntchan/nestedcls/OuterCls;)I

#24 = Class #36 // java/io/PrintStream

#25 = NameAndType #37:#38 // println:(I)V

#26 = Utf8 com/ntchan/nestedcls/OuterCls$InnerCls

#27 = Utf8 InnerCls

#28 = Utf8 InnerClasses

#29 = Utf8 java/lang/Object

#30 = Utf8 java/lang/System

#31 = Utf8 out

#32 = Utf8 Ljava/io/PrintStream;

#33 = Utf8 com/ntchan/nestedcls/OuterCls

#34 = Utf8 access$000

#35 = Utf8 (Lcom/ntchan/nestedcls/OuterCls;)I

#36 = Utf8 java/io/PrintStream

#37 = Utf8 println

#38 = Utf8 (I)V

{

final com.ntchan.nestedcls.OuterCls this$0;

descriptor: Lcom/ntchan/nestedcls/OuterCls;

flags: (0x1010) ACC_FINAL, ACC_SYNTHETIC

com.ntchan.nestedcls.OuterCls$InnerCls(com.ntchan.nestedcls.OuterCls);

descriptor: (Lcom/ntchan/nestedcls/OuterCls;)V

flags: (0x0000)

Code:

stack=2, locals=2, args_size=2

0: aload_0

1: aload_1

2: putfield #1 // Field this$0:Lcom/ntchan/nestedcls/OuterCls;

5: aload_0

6: invokespecial #2 // Method java/lang/Object."":()V

9: return

LineNumberTable:

line 5: 0

public void printOuterField();

descriptor: ()V

flags: (0x0001) ACC_PUBLIC

Code:

stack=2, locals=1, args_size=1

0: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;

3: aload_0

4: getfield #1 // Field this$0:Lcom/ntchan/nestedcls/OuterCls;

7: invokestatic #4 // Method com/ntchan/nestedcls/OuterCls.access$000:(Lcom/ntchan/nestedcls/OuterCls;)I

10: invokevirtual #5 // Method java/io/PrintStream.println:(I)V

13: return

LineNumberTable:

line 7: 0

line 8: 13

}

SourceFile: "OuterCls.java"

InnerClasses:

#27= #6 of #22; // InnerCls=class com/ntchan/nestedcls/OuterCls$InnerCls of class com/ntchan/nestedcls/OuterCls

View Code

首先,我们发现编译器自动生成了一个声明为final的成员:

606621f2678343875605bfcf5b07ba61.png

我们知道,final修饰的成员都是编译器可以确定的常量。经过final修饰的变量,都会放到class的常量池。

然后再看一下编译器自动生成的构造函数:

3b58824dd6037d8721e04c0ec6721bec.png

具体的字节码指令我就不再一一贴出来,我简单解释一下,这个构造函数通过外部传参OuterCls实例,赋值给this$0(上面那个被final修饰的变量)

最后看一下我们的printOutField方法:

698b76156e6acf199fe6dbec0f38f434.png

我们看到,原本调用outerField的地方,变成了OuterField.access$000(this$0),意思就是,通过OuterField的静态方法,返回this$0的OuterField。

总的来讲,内部类访问外部类的私有成员的原理,是通过编译器分别给外部类自动生成访问私有成员的静态方法access$000及给内部类自动生成外部类的final引用、外部类初始化的构造函数及修改调用外部类私有成员的代码为调用外部类包可见的access$000实现的。同理,匿名内部类、静态内部类都可以通过这种方法分析实现原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值