“this”关键字:Java中的工作机制

2022-09-04 04:06:51

在学习了Java一段时间之后,这是第一次使用关键字让我如此困惑。this

这是我感到困惑的原因。我写了下面的代码:

class BasicInheritanceTest3Base{
    private int x = 0;
    public int y;

    public void a() {
        x++;
        this.x++;
        System.out.println("BasicInheritanceTest3Base.a()");
        b();
        this.b();
        System.out.println(x);
        System.out.println(y);
    }

    public void b(){
        System.out.println("BasicInheritanceTest3Base.b()");
    }
}

public class BasicInheritanceTest3 extends BasicInheritanceTest3Base {
    private int x = 3;
    public int y = 2;

    public void b() {
        System.out.println("BasicInheritanceTest3.b()");
    }

    public static void main(String[] args){
        BasicInheritanceTest3 bit2 = new BasicInheritanceTest3();
        bit2.a();
    }
}

我得到了以下输出:

BasicInheritanceTest3Base.a()
BasicInheritanceTest3.b()
BasicInheritanceTest3.b()
2
0

现在这里的第一个问题是:为什么并指向基类而不是子类?如果指向基类的,为什么叫子类呢?对于字段和方法,此行为是否不同?xthis.xxthis.xxthis.b()b()

但是,主要关注的是关键字的机制。我的意思是,你知道,指向(指)当前对象。如果你仔细想想,这不是一个神奇的行为。在某个地方必须有田野。例如,类的文字不可见,但存在于发出的字节码中。同样,此引用应存在于字节码中。thisthisthis.class

好吧,假设上面是正确的,应该是一个(空白的 final),每次构造对象及其字段时都会实例化它。这意味着它是一个实例变量,而不是一个静态变量。thispublic final

现在,如果将其实例化为当前对象的引用(仅是特定对象),那么对于字段和方法,为什么上面的使用是不同的?那么总而言之,背后的机制是什么?该机制是否也适用于关键字?thisthissuper

编辑:每个人都在阅读问题,然后是评论,我想问一下,编译器声明的字段在哪里,它的限定符是什么。由此产生的行为如何在幕后发生?this


答案 1

其他答案和注释解释了字段如何不是多态的,以及如何根据实例引用的编译时类型解析字段访问表达式。下面,我将解释字节码如何处理引用。this

在关于接收参数的章节中,Java 虚拟机规范指出

如果将 n 个参数传递给实例方法,则按照约定,这些参数将在为新方法调用创建的帧的编号为 1 到 n 的局部变量中接收。参数按其传递顺序接收。例如:

int addTwo(int i, int j) {
    return i + j;
}

编译为:

Method int addTwo(int,int)
0   iload_1        // Push value of local variable 1 (i)
1   iload_2        // Push value of local variable 2 (j)
2   iadd           // Add; leave int result on operand stack
3   ireturn        // Return int result

按照约定,实例方法在局部变量 0 中传递对其实例的引用。在 Java 编程语言中,可以通过 this 关键字访问实例。

类(静态)方法没有实例,因此对于它们来说,这种局部变量 0 的使用是不必要的。类方法开始使用索引 0 处的局部变量。如果 addTwo 方法是类方法,则其参数的传递方式与第一个版本类似:

static int addTwoStatic(int i, int j) {
    return i + j;
}

编译为:

Method int addTwoStatic(int,int)
0   iload_0
1   iload_1
2   iadd
3   ireturn

唯一的区别是方法参数从局部变量 0 开始出现,而不是从 1 开始。

换言之,您可以查看为未在任何地方声明,也可以将其视为声明为每个实例方法的第一个参数。将为每个实例方法创建一个局部变量表条目,并在每次调用时填充。this

关于调用方法的章节指出

实例方法的常规方法调用根据对象的运行时类型进行调度。(从C++的角度来看,它们是虚拟的。这种调用是使用指令实现的,该指令将运行时常量池条目的索引作为其参数,该条目给出了对象类类型的二进制名称的内部形式,要调用的方法的名称以及该方法的描述符(§4.3.3)。要调用前面定义为实例方法的方法,我们可以这样写:invokevirtualaddTwo

int add12and13() {
    return addTwo(12, 13);
}

这将编译为:

Method int add12and13()
0   aload_0             // Push local variable 0 (this)
1   bipush 12           // Push int constant 12
3   bipush 13           // Push int constant 13
5   invokevirtual #4    // Method Example.addtwo(II)I
8   ireturn             // Return int on top of operand stack;
                        // it is the int result of addTwo()

通过首先将对当前实例的引用(this)推送到操作数堆栈来设置调用。然后推送方法调用的参数值 12 和 13。创建方法的帧时,传递给该方法的参数将成为新帧的局部变量的初始值。也就是说,对 this 和两参数的引用(由调用程序推送到操作数堆栈上)将成为被调用方法的局部变量 0、1 和 2 的初始值。intaddTwo


答案 2

为什么 x 和 this.x 指向基类的 x 而不是子类?

因为 Java 中的字段不是多态的。字段绑定在编译时解析。如果要将递增用作多态性,则可以使用方法进行操作。要正确执行,您需要在父项和子项中定义它。

public void increment(){
    x++; //this.x++; would do the same;
}

如果 this.x 指向基类的 x,为什么 this.b() 调用子类的 b()?

另一方面,因为方法是多态的,这意味着它们的绑定在运行时解析,这就是为什么 this.b() 从子类调用方法的原因,在你的例子中,这是 BasicInheritanceTest3 的实例,并调用了相应的方法。

对于字段和方法,此行为是否不同?

正如你所看到的。

Super 是对基类的引用,因此您可以在例如需要调用重写的方法或/和隐藏字段时访问它。

编辑回复:这是一个引用,这意味着它只是对象的地址以及JVM内存中的所有数据,JVM如何处理这个关键字并不真正已知或重要,它可能是在实例化时声明的。但最终你需要知道的是,这是对对象本身实例的引用。