java基础之this、super详解
一、this关键字
this关键字相信大家都很熟悉,在实际使用中主要有以下三个用途:
1.调用类中的成员变量;
2.调用类中的其他方法;
3.构造方法中调用类的其他构造方法(注意只能调用一个且必须位于首行)。
当我们平时开发中经常使用this之后,你有没有想过this是从哪里来的?this的数据类型是什么?为什么在static方法和static块中无法使用this?
接下来,我将对这些问题一个一个进行剖析,本节主要通过class来分析this的原理。
下面的代码用到了三种this的用途:
public class ThisStu {
private String name;
private Integer age;
public ThisStu(String name) {
this.name = name;
}
public ThisStu(String name, Integer age) {
this(name);
this.age = age;
}
public String getName() {
this.getStaticNme("");
return this.name;
}
public Integer getAge() {
return age;
}
public static String getStaticNme(String name){
return name;
}
}
我们通过javap反编译ThisStu.class,可以看到第一个构造方法生成的如下代码:
public com.xr.basic.ThisStu(java.lang.String); //第一个构造方法
descriptor: (Ljava/lang/String;)V
flags: ACC_PUBLIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #2 // Field name:Ljava/lang/String;
9: return
LineNumberTable:
line 6: 0
line 7: 4
line 8: 9
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/xr/basic/ThisStu;
0 10 1 name Ljava/lang/String;
MethodParameters:
Name Flags
name
其中可以看到LocalVariableTable属性,该属性是位于Code属性的属性表中的可选变长属性,调试器在执行方法的过程中,可以用它来确定某个局部变量的值。对比第一个构造方法,我们可以看到第一个构造方法中只有一个参数,而args_size=2,LocalVariableTable的第一个局部变量就是this,其类型为ThisStu!所以当我们调用该构造方法的时候,第一个(序号为0)局部变量是用来存储该类对象的引用(this),后续的其他参数将会传递至局部变量表中从1开始的连续位置上。
同样地,代码中getName()编译出来的代码如下:
public java.lang.String getName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #2 // Field name:Ljava/lang/String;
4: areturn
LineNumberTable:
line 14: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/xr/basic/ThisStu;
我们可以发现,构造方法、实例方法在LocalVariableTable的第一个参数都是this,这是由编译器自动添加的,其中aload_0指令将this引用推至栈顶,即压入栈。而且this的数据类型就是this所在方法的所属类。
在反编译ThisStu.class的代码中,我们还可以看到如下代码:
public static java.lang.String getStaticNme(java.lang.String);
descriptor: (Ljava/lang/String;)Ljava/lang/String;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: areturn
LineNumberTable:
line 20: 0
LocalVariableTable:
Start Length Slot Name Signature
0 2 0 name Ljava/lang/String;
MethodParameters:
Name Flags
name
该代码是类中的静态方法反编译出来的,我们可以看到args_size=1和LocalVariableTable中只有一个局部变量(String name),所以我们可以得出结论:类的静态方法中,并没有保存this的局部变量,所以我们也就无法在类的静态方法中使用this关键字。
二、super关键字
super 可以理解为是指向离自己最近的一个父(基)类对象的一个指针。
super也有三种用法:
1.直接引用父类的成员;
2.子类和父类中成员同名时候用来调用父类的成员;
3.引用父类的某个构造函数。
super的原理是在创建子类对象的时候,会生成一个指向父类的对象的引用super。以下面的代码为例:
class A{
public void sayName(String name) {
System.out.println(name);
}
}
class B extends A{
}
通过javap反编译B.class可以得到如下代码:
Classfile /E:/java-projects/inspection-task/target/classes/com/xr/basic/B.class
Last modified 2021-3-15; size 259 bytes
MD5 checksum 9482d914c1f65de35fd45d02273b7366
Compiled from "SuperStu.java"
class com.xr.basic.B extends com.xr.basic.A
minor version: 0
major version: 52
flags: ACC_SUPER
Constant pool:
#1 = Methodref #3.#13 // com/xr/basic/A."<init>":()V
#2 = Class #14 // com/xr/basic/B
#3 = Class #15 // com/xr/basic/A
#4 = Utf8 <init>
#5 = Utf8 ()V
#6 = Utf8 Code
#7 = Utf8 LineNumberTable
#8 = Utf8 LocalVariableTable
#9 = Utf8 this
#10 = Utf8 Lcom/xr/basic/B;
#11 = Utf8 SourceFile
#12 = Utf8 SuperStu.java
#13 = NameAndType #4:#5 // "<init>":()V
#14 = Utf8 com/xr/basic/B
#15 = Utf8 com/xr/basic/A
{
com.xr.basic.B();
descriptor: ()V
flags:
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method com/xr/basic/A."<init>":()V
4: return
LineNumberTable:
line 12: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lcom/xr/basic/B;
}
SourceFile: "SuperStu.java"
首先可以看到access flag记录了flags: ACC_SUPER。ACC_SUPER是用来表示如何调用父类的方法,在jdk1.1之前,jvm使用一种invokenonvirtual的指令,调用父类方法。这个方法就是现在的invokespecial 前身,他们的实现差距比较大。invokenonvirtual是不会进行虚函数查找的,也就是总是静态绑定。为了兼容性,通过设置ACC_SUPER标记表示使用新语义。
其次我们还可以看到:
1:invokespecial #1 // Method com/xr/basic/A."<init>":()V
而当我们通过javap反编译A.class依然可以看到:
1: invokespecial #1 // Method java/lang/Object."<init>":()V
在A和B类中,此处调用了父类的init方法,
注意上面的 invokespecial指令。super 关键字的底层原理就是靠 invokespecial指令。invokespecial是一个普通的调用指令,调用< init >方法、私有及父类方法,解析阶段确定唯一方法版本。
invokespecial在使用上,只能在三种情况下使用:
- the instance initialization method, < init>
- a private method of this
- a method in a superclass of this