zoukankan      html  css  js  c++  java
  • 关于“类.class”和“类.this”

    今天在浏览知乎的时候,看到了这个问题,感觉很多人说的不清楚。问题链接: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 FilesJavajdk1.8.0_77injavap.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 FilesJavajdk1.8.0_77injavap.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类实例”的结论。

  • 相关阅读:
    布局重用 include merge ViewStub
    AS 常用插件 MD
    AS 2.0新功能 Instant Run
    AS .ignore插件 忽略文件
    AS Gradle构建工具与Android plugin插件【大全】
    如何开通www国际域名个人网站
    倒计时实现方案总结 Timer Handler
    AS 进行单元测试
    RxJava 设计理念 观察者模式 Observable lambdas MD
    retrofit okhttp RxJava bk Gson Lambda 综合示例【配置】
  • 原文地址:https://www.cnblogs.com/cosmos-wong/p/12323090.html
Copyright © 2011-2022 走看看