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

在学习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.x指向基类的x而不是Child类? 如果this.x指向基类的x ,为什么this.b()调用子类的b() ? 字段和方法的行为是否不同?

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

好吧,假设上述情况属实, this应该是一个public final (一个空白的决赛),每次构造对象并实例化其字段时,它都会被实例化。 这意味着它是一个实例变量而不是静态变量。

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

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

其他答案和注释解释了字段不是多态的,以及如何根据实例引用的编译时类型解析字段访问表达式。 下面,我解释字节代码如何处理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 ++术语。)这样的调用是使用invokevirtual指令实现的,该指令将运算时常量池条目的索引作为其参数,给出对象类类型的二进制名称的内部forms。 ,要调用的方法的名称,以及该方法的描述符(第4.3.3节)。 要调用之前定义为实例方法的addTwo方法,我们可能会写:

 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() 

通过首先将对当前实例的引用推送到操作数堆栈来设置调用。 然后推送方法调用的参数, int值12和13。 创建addTwo方法的框架时,传递给方法的参数将成为新框架局部变量的初始值。 也就是说,由调用者推送到操作数堆栈的this和两个参数的引用将成为调用方法的局部变量0,1和2的初始值。

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

因为Java中的字段不是多态的。 字段绑定在编译时解析。 如果你想使用递增作为多态,你可以用一个方法来做。 要正确执行,您需要在父级和子级中定义它。

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

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

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

字段和方法的行为是否不同?

如你所见。

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

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

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

我们可以看到这个例子:

 class TestBase { private int x; public void a() { this.x++; } public int getX() { return x; } } public class Test extends TestBase{ private int x; public int getX() { return this.x; } } 

并生成字节码:

 public class Test extends TestBase{ public Test(); Code: 0: aload_0 1: invokespecial #1; //Method TestBase."":()V 4: return public int getX(); Code: 0: aload_0 1: getfield #2; //Field x:I 4: ireturn public void a(); Code: 0: aload_0 1: invokespecial #3; //Method TestBase.a:()V 4: return } 

在那里, Test extends TestBase并将方法a编译到Test类中,它将调用它的父亲1: invokespecial #3; //Method TestBase.a:()V 1: invokespecial #3; //Method TestBase.a:()V

TestgetX方法将调用1: getfield #2; //Field x:I 1: getfield #2; //Field x:I来自它自己的constant pool table , http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

TestBase类字节码:

 class TestBase extends java.lang.Object{ TestBase(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return public void a(); Code: 0: aload_0 1: dup 2: getfield #2; //Field x:I 5: iconst_1 6: iadd 7: putfield #2; //Field x:I 10: return public int getX(); Code: 0: aload_0 1: getfield #2; //Field x:I 4: ireturn } 

方法a()也将通过getfield #2; //Field x:I从它自己的常量池中获取x getfield #2; //Field x:I getfield #2; //Field x:I

所以还有另一件事:Java的gettersetter是邪恶的。

事实上,JAVA编程语言中的多态属性只能应用于具有足够资格成为多态成员的方法。 您不应该将Fields视为具有上述属性的成员。因此,您不会再对此类问题感到困惑。

[编辑答案]我做了一些研究,得到了以下信息,以进一步回答你的问题。 我们确实可以通过使用逆向工程工具将字节码转换回java源来validationthis是字节码的一部分。

为什么我们会在字节码中找到它?

因为java是多遍编译器,并且由于字节码可以在任何其他平台和任何其他机器上运行,所以所有信息都必须是字节码,足够的信息能够将字节码反向工程为源代码。 但是,由于源代码必须与字节码的原始源相同,所以包括变量和字段的确切名称在内的所有内容都必须“以某种方式”与字节码中的所有信息保持良好的组织。 而与使用单通道编译器的java不同,C ++或pascal通常不会保留字段的确切名称,并且由于这些语言输出必须准备好运行的最终“可执行”文件,因此可能不在乎维护名称(指令不必通过另一个“通行证”)。 如果任何人反向设计可执行文件(C ++或Pascal),则变量名称将不是人类可读的。 因此在字节码中,“this”可以表示为非人类可读格式,但是可以将其反向工程化为“this”。 单通道编译器也是如此。

多通道编译器

类方法不能直接访问实例变量或实例方法 – 它们必须使用对象引用。 此外,类方法不能使用this关键字,因为没有要引用的实例。

现在第一个问题是:为什么x和this.x指向基类的x而不是Child类?

这是因为多态行为不适用于字段,因此结果来自基类。

为什么this.b()调用子类的b()? 字段和方法的行为是否不同?

使用此行:BasicInheritanceTest3 bit2 = new BasicInheritanceTest3(); 堆中唯一的对象(就base和子类而言)是BasicInheritanceTest3类型的对象。 因此,无论如何,该调用将应用于子类方法。 bit2在堆中引用其自己的层次结构(inheritance)。

现在 – 编译器如何处理它与jdk处理任何其他键/保留字的方式相同。 在类方法的上下文中不允许这样做类方法不能直接访问实例变量或实例方法 – 它们必须使用对象引用。 此外,类方法不能使用this关键字,因为没有要引用的实例。 事实上,一个有趣的问题让OP对这个问题进行了投票。

我阅读的更多有用的信息是:标识符和保留关键字是标记,如+的单个字符和!=等字符序列。

我想请求社区保留这个主题。 我没有探讨jdk(编译器和运行时)如何处理关键字和保留字。

Java Api Docs:这个