今天在浏览知乎的时候,看到了这个问题,感觉很多人说的不清楚。问题链接:Java 类名.class与类名.this 的区别?

话说它有什么区别呢?从API层面上来说,"类.class"返回该类所对应的class对象,而"类.this"得到的是该类的对象,这两者的区别大着呢!前者是描述该类的Class对象,后者是该类的实例对象,两者没有可比性。API层面上有些说不清,还是从字节码层面上来说吧!话不多说,先上车。

类.class

看“类.class”

public class Demo62 {
public static void main(String[] args) {
Class clz = Demo62.class;
}
}

执行反编译:

"D:\Program Files\Java\jdk1.8.0_77\bin\javap.exe" -v -p com.bigdata.java.Demo62
Classfile /D:/Project/JvmDemo/target/classes/com/bigdata/java/Demo62.class
Last modified 2020-2-17; size 438 bytes
MD5 checksum 19a4a4e0310212eaabf8f8a5c18d82fc
Compiled from "Demo62.java"
public class com.bigdata.java.Demo62
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#19 // java/lang/Object."<init>":()V
#2 = Class #20 // com/bigdata/java/Demo62
#3 = Class #21 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/bigdata/java/Demo62;
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 args
#14 = Utf8 [Ljava/lang/String;
#15 = Utf8 clz
#16 = Utf8 Ljava/lang/Class;
#17 = Utf8 SourceFile
#18 = Utf8 Demo62.java
#19 = NameAndType #4:#5 // "<init>":()V
#20 = Utf8 com/bigdata/java/Demo62
#21 = Utf8 java/lang/Object
{
public com.bigdata.java.Demo62();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/bigdata/java/Demo62; public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=2, args_size=1
0: ldc #2 // class com/bigdata/java/Demo62
2: astore_1
3: return
LineNumberTable:
line 5: 0
line 6: 3
LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
3 1 1 clz Ljava/lang/Class;
}
SourceFile: "Demo62.java" Process finished with exit code 0

这一小段代码编译后这么多,别被吓到,有用的没几条,着重看这些:

Code:
stack=1, locals=2, args_size=1
0: ldc #2 // class com/bigdata/java/Demo62
2: astore_1
3: return

“ 0: ldc #2 // class com/bigdata/java/Demo62”,表示将常量池中索引2位置上的元素,压入到操作数的栈顶。通过查阅常量池,我们可以看到它就是“com/bigdata/java/Demo62”,只是它是一个符号引用,在类的解析阶段被转换为了直接引用,因此这里压入到操作数栈顶的是解析后的直接引用(也即该类所对应的Class对象的地址或指针),

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.ldc

“astore_1”,弹出操作数栈顶的值,然后放到局部变量表的slot1中。

所以最终看到的就是局部变量表的slot1中有一个class对象

LocalVariableTable:
Start Length Slot Name Signature
0 4 0 args [Ljava/lang/String;
3 1 1 clz Ljava/lang/Class;

小结:所以“类.class”返回的实际上就是该类所对应的class对象。但是底层实际上是通过常量池找到对应的class对象的。

类.this

再看“类.this”

public class Demo62 {
public Demo62 method1(){
return Demo62.this;
}
}

执行反编译:

"D:\Program Files\Java\jdk1.8.0_77\bin\javap.exe" -v -p com.bigdata.java.Demo62
Classfile /D:/Project/JvmDemo/target/classes/com/bigdata/java/Demo62.class
Last modified 2020-2-17; size 375 bytes
MD5 checksum 0ca3c6df6b1bc2a64c91aa755eba16fe
Compiled from "Demo62.java"
public class com.bigdata.java.Demo62
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #3.#15 // java/lang/Object."<init>":()V
#2 = Class #16 // com/bigdata/java/Demo62
#3 = Class #17 // java/lang/Object
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/bigdata/java/Demo62;
#11 = Utf8 method1
#12 = Utf8 ()Lcom/bigdata/java/Demo62;
#13 = Utf8 SourceFile
#14 = Utf8 Demo62.java
#15 = NameAndType #4:#5 // "<init>":()V
#16 = Utf8 com/bigdata/java/Demo62
#17 = Utf8 java/lang/Object
{
public com.bigdata.java.Demo62();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/bigdata/java/Demo62; public com.bigdata.java.Demo62 method1();
descriptor: ()Lcom/bigdata/java/Demo62;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: areturn
LineNumberTable:
line 5: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/bigdata/java/Demo62;
}
SourceFile: "Demo62.java" Process finished with exit code 0

重点关注这段代码

Code:
stack=1, locals=1, args_size=1
0: aload_0
1: areturn
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/bigdata/java/Demo62;

"aload_0",表示将加载局部变量表slot0中的元素到操作数栈顶,而这个slot0中存储的就是this。这里先说明一下,每个方法(非静态方法)都隐含有一个“this”参数,这也是为什么我们能够在方法中使用this的原因。

“areturn”,表示从方法返回引用。这里它将弹出操作数栈弹出的元素并返回。

小结:“类.this”得到的是当前类的实例。底层实际上就是得到方法的隐含参数this。

最后,

public class Demo62 {
public static void main(String[] args) {
Class clz = Demo62.class;
Demo62 instance1 = new Demo62();
Demo62 instance2 = instance1.method1();
System.out.println(instance1 == instance2);//true
System.out.println(instance1.getClass()==clz);//true
}
public Demo62 method1(){
return Demo62.this;
}
} 通过上面的分析,得到这样的执行结果是毫不意外的。

再看另外热议的问题

class Foo {
class Bar {
Foo getFoo() {
return Foo.this;
}
}
}

在Foo.Bar类中的getFoo()方法中,如果直接写“this”的话所指的是这个Foo.Bar类的实例,而如果要指定外围的Foo类的this实例的话,这里就得写成Foo.this。

特别的,如果在上例的getFoo()方法中写Bar.this的话,作用就跟直接写this一样,指定的是当前的Foo.Bar类实例。

作者:RednaxelaFX

链接:https://www.zhihu.com/question/55565290/answer/145355951

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

下面我们尝试从字节码的角度来解读,话不多说,上码:

...
Constant pool:
#1 = Fieldref #3.#20 // com/bigdata/java/other/Foo$Bar.this$0:Lcom/bigdata/java/other/Foo;
#2 = Methodref #4.#21 // java/lang/Object."<init>":()V
#3 = Class #23 // com/bigdata/java/other/Foo$Bar
#4 = Class #24 // java/lang/Object
#5 = Utf8 this$0
#6 = Utf8 Lcom/bigdata/java/other/Foo;
#7 = Utf8 <init>
#8 = Utf8 (Lcom/bigdata/java/other/Foo;)V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Bar
#14 = Utf8 InnerClasses
#15 = Utf8 Lcom/bigdata/java/other/Foo$Bar;
#16 = Utf8 getFoo
#17 = Utf8 ()Lcom/bigdata/java/other/Foo;
#18 = Utf8 SourceFile
#19 = Utf8 Foo.java
#20 = NameAndType #5:#6 // this$0:Lcom/bigdata/java/other/Foo;
#21 = NameAndType #7:#25 // "<init>":()V
#22 = Class #26 // com/bigdata/java/other/Foo
#23 = Utf8 com/bigdata/java/other/Foo$Bar
#24 = Utf8 java/lang/Object
#25 = Utf8 ()V
#26 = Utf8 com/bigdata/java/other/Foo
...
{
final com.bigdata.java.other.Foo this$0;
descriptor: Lcom/bigdata/java/other/Foo;
flags: ACC_FINAL, ACC_SYNTHETIC com.bigdata.java.other.Foo$Bar(com.bigdata.java.other.Foo);
descriptor: (Lcom/bigdata/java/other/Foo;)V
flags:
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:Lcom/bigdata/java/other/Foo;
5: aload_0
6: invokespecial #2 // Method java/lang/Object."<init>":()V
9: return
LineNumberTable:
line 4: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/bigdata/java/other/Foo$Bar;
0 10 1 this$0 Lcom/bigdata/java/other/Foo; com.bigdata.java.other.Foo getFoo();
descriptor: ()Lcom/bigdata/java/other/Foo;
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field this$0:Lcom/bigdata/java/other/Foo;
4: areturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/bigdata/java/other/Foo$Bar;
}
...

重点看“ getFoo()”方法

aload_0:表示将本地变量表中的slot0元素加载到操作数栈顶,这里也就是将this(com/bigdata/java/other/Foo$Bar)压入到操作数栈顶。

getfield:表示获取字段。弹出操作数栈顶的com/bigdata/java/other/Foo$Bar,然后获取该对象的#1索引所对应的常量池的值,也即“this$0”,它的类型是“Foo”,然后将它压入到操作数的栈顶

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.getfield

areturn:弹出操作数栈顶的值并返回,也即返回“this.$0”,它的类型是Foo。

小结:通过这个实例,印证了Foo.this返回的就是Foo。

下面我们在分别看Bar.this和this

脑洞大开,写成这种形式:

class Foo {
class Bar {
Foo getFoo() {
return this;//编译器报错,提示类型不兼容
}
}
}

这里的类型不兼容也不难理解,由于getFoo中的this,指的是“Foo$Bar”,而返回值是“Foo”。

修改为return this

下面将返回值修改为this

class Foo {
class Bar {
Bar getFoo() {
return this;
}
}
}

反编译:

...
com.bigdata.java.other.Foo$Bar getFoo();
descriptor: ()Lcom/bigdata/java/other/Foo$Bar;
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: areturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/bigdata/java/other/Foo$Bar;
...

可以发现,它返回了本地变量表slot0的元素,也即"Foo$Bar"

修改为Bar.this

class Foo {
class Bar {
Bar getFoo() {
return Bar.this;
}
}
}

反编译:

...
com.bigdata.java.other.Foo$Bar getFoo();
descriptor: ()Lcom/bigdata/java/other/Foo$Bar;
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: areturn
LineNumberTable:
line 6: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 this Lcom/bigdata/java/other/Foo$Bar;
...

可以发现字节码和return this是一样的。返回值也是一样的。从而印证了“getFoo()方法中写Bar.this的话,作用就跟直接写this一样,指定的是当前的Foo.Bar类实例”的结论。

关于“类.class”和“类.this”的更多相关文章

  1. JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式

    相关链接: JS面向对象(1) -- 简介,入门,系统常用类,自定义类,constructor,typeof,instanceof,对象在内存中的表现形式 JS面向对象(2) -- this的使用,对 ...

  2. 【转】UML类图与类的关系详解

    UML类图与类的关系详解   2011-04-21 来源:网络   在画类图的时候,理清类和类之间的关系是重点.类的关系有泛化(Generalization).实现(Realization).依赖(D ...

  3. Java如何解决脆弱基类(基类被冻结)问题

    概述  大多数好的设计者象躲避瘟疫一样来避免使用实现继承(extends 关系).实际上80%的代码应该完全用interfaces写,而不是通过extends.“JAVA设计模式”一书详细阐述了怎样用 ...

  4. 类A have-a 类B,类B访问类A public 成员

    需求是类A中包含类B,而类B又需要访问类A的public属性的成员. 首先类B中要访问类A的属性,那么对于类B而言,我们必须要知道有类A这个类,所以在类B的具体实现之前我们需要前向声明类A. 对于类A ...

  5. UML(一) 类图及类间关系

    原创文章,同步发自作者个人博客,http://www.jasongj.com/uml/class_diagram/ UML类图 UML类图介绍 在UML 2.*的13种图形中,类图是使用频率最高的UM ...

  6. JAVA正则表达式:Pattern类与Matcher类详解(转)

    java.util.regex是一个用正则表达式所订制的模式来对字符串进行匹配工作的类库包.它包括两个类:Pattern和Matcher Pattern 一个Pattern是一个正则表达式经编译后的表 ...

  7. C# 根据类名称创建类示例

    //获得类所在的程序集名称(此处我选择当前程序集) string bllName = System.IO.Path.GetFileNameWithoutExtension(System.Reflect ...

  8. 使用date类和format类对系统当前时间进行格式化显示

    一:Date------------String 代码1:(代码二对显示出来的时间格式进行优化) package DateDemo; import java.text.SimpleDateFormat ...

  9. java中的 FileWriter类 和 FileReader类的一些基本用法

    1,FileWriter类(字符输出流类) |--用来写入字符文件的便捷类.此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的.要自己指定这些值,可以先在 FileOutputStream ...

  10. 使用 Date 和 SimpleDateFormat 类表示时间、Calendar类和Math类

    一. Date 和 SimpleDateFormat类表示时间 在程序开发中,经常需要处理日期和时间的相关数据,此时我们可以使用 java.util 包中的 Date 类.这个类最主要的作用就是获取当 ...

随机推荐

  1. 服务质量分析:腾讯会议&腾讯云Elasticsearch玩出了怎样的新操作?

    导语 | 腾讯会议于2019年12月底上线,两个月内日活突破1000万,被广泛应用于疫情防控会议.远程办公.师生远程授课等场景,为疫情期间的复工复产提供了重要的远程沟通工具.上线100天内,腾讯会议快 ...

  2. Java继承之面向对象

    面向对象与面向过程: 面向对象(OOP)与面向过程 二者都是一种思想,面向对象是相对于面向过程而言的. 面向过程,强调的是功能行为.面向对象,将功能封装进对象,强调具备了功能的对象. 面向对象更加强调 ...

  3. 04爬取拉勾网Python岗位分析报告

    # 导入需要的包import requestsimport time,randomfrom openpyxl import Workbookimport pymysql.cursors#@ 连接数据库 ...

  4. PHP date_add() 函数

    ------------恢复内容开始------------ 实例 添加 40 天到 2013 年 3 月 15 日: <?php$date=date_create("2013-03- ...

  5. PHP is_bool() 函数

    is_bool() 函数用于检测变量是否是布尔型.高佣联盟 www.cgewang.com PHP 版本要求:PHP 4, PHP 5, PHP 7 语法 bool is_bool ( mixed $ ...

  6. 5073 [Lydsy1710月赛]小A的咒语

    LINK:[Lydsy1710月赛]小A的咒语 每次给定两个串 要求从a串中选出x段拼成B串 能否做到.T组数据. \(n\leq 100000,m\leq 100000,T\leq 10,x\leq ...

  7. java多线程的问题

    1.多线程有什么用 (1) 发挥多核CPU的优势 单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程" ...

  8. JDK8的Optional用法

    参考资料:https://www.baeldung.com/java-optional https://mp.weixin.qq.com/s/P2kb4fswb4MHfb0Vut_kZg 1. 描述 ...

  9. spring security 简介+实战

    过滤器链: 依赖: security 功能列表: 一.登录验证.权限验证 1.1 httpbasic验证 1.2form验证 建立数据需要遵循RBAC模型 用户表要参考UserDetail创建 实例类 ...

  10. Docker-Compose介绍,安装和使用

    Docker-Compose 介绍 有时候运行一个镜像需要大量的参数,可以通过Docker-Compose编写这些参数.而且Docker-Compose可以版主我们批量管理容器,这些信息值需要通过一个 ...