"这" keyword:Java中的工作机制

时间:2015-04-06 13:25:05

标签: java this

在学习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指向(引用)当前对象。如果你想一想,它不是一个神奇的行为。某处必须有this字段。例如,类的.class字面值是不可见的,但存在于发出的字节码中。同样,此引用应存在于字节码中。

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

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

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

5 个答案:

答案 0 :(得分:3)

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

在关于接收参数的章节中,Java Virtual Machine Specification个州

  

如果将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是不必要的。类方法开始使用local   索引为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视为未在任何地方声明,也可以将其声明为每个实例方法的第一个参数。为每个实例方法创建一个局部变量表条目,并在每次调用时填充。

关于Invoking methods州的章节

  

实例方法的常规方法调用调度   对象的运行时类型。 (它们是虚拟的,用C ++术语表示。)这样的   使用invokevirtual指令实现调用   将参数作为运行时常量池条目的索引   给出类的类型的二进制名称的内部形式   object,要调用的方法的名称以及该方法的描述符   (§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()
     

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

答案 1 :(得分:1)

为什么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本身的实例的引用。

答案 2 :(得分:1)

<强> 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."<init>":()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}}。

Test getX方法将从1: getfield #2; //Field x:Ihttp://en.wikipedia.org/wiki/Java_bytecode_instruction_listings

中调用constant pool table

TestBase类字节码:

class TestBase extends java.lang.Object{
TestBase();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()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()也会xgetfield #2; //Field x:I获得getter自己的常量池。

所以还有另外一件事:Java的setter和{{1}}是邪恶的。

答案 3 :(得分:1)

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

答案 4 :(得分:0)

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

  

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

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

Multi Pass Compiler

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

  

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

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

  

为什么this.b()调用子类的b()?是这样的行为   不同的领域和方法?

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

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

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

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

Java Api Docs: this