1.引子
public class WhoIsCalled {
public static void main(String[] args) {
A a1 = new A();
A a2 = new A();
a1.func();
a2.func();
}
}
class A {
public void func() {
System.out.println("just for test , caller is : "+ this);
}
}
just for test , caller is : A@2a139a55
just for test , caller is : A@15db9742
针对同一个变量或者同一个方法(如上例中的A.func()),在同一个类型的两个不同对象(分别为a1和a2)去调用该方法时,怎么知道该方法此时是被a1对象调用还是被a2对象调用呢?此时就引出了this(java语言提供的一个关键字)来解决该问题,当然这么说是片面的,其实是由于编译器做了一些工作,它悄悄把"调用当前方法的引用对象"作为第一个参数传递给了方法func()(对于用户而言是透明的,不需要也不能这样显示的操作),即实际上调用的时候是这样的:
A.func(a1);
a.func(a2);
2.使用场景
(1)常用于有多个可变入参的构造器中。即重叠构造器模式(telescoping constructors)。有时候需要在一个构造器中调用另一个构造器以达到代码重用,可以使用this关键字。举一个hive源码中的例子:
package org.apache.hadoop.hive.serde2;
import java.util.ArrayList;
/**
* ColumnSet.
*
*/
public class ColumnSet {
public ArrayList<String> col;
public ColumnSet() {
}
public ColumnSet(ArrayList<String> col) {
this();
this.col = col;
}
@Override
public String toString() {
return col.toString();
}
}
此例简短能够说明场景意思,一看便知。需要注意的是这种情况下使用this关键字,必须将它置于方法的首行。这种构造器对于可变入参较少的场景比较适合,但当构造当前对象的可变参数很多时,就要考虑使用JavaBean方式或者Builder模式了。
(2)需要明确地在非静态方法的内部获得对当前对象的引用。这种常用的比如作为方法的返回值return。具体举例,比如常用的Builder模式——链式编程模型,这种模式的许多类似setter的方法的返回值都必须是this,用于确保对象在构造过程中的一致性。常用的典型的比如Jdk源码中的StringBuilder和StringBuffer,比如Hive源码中的org.apache.hive.http.HttpServer.java,再复杂一些用法比如org.apache.hadoop.hive.ql.optimizer.calcite.translator.ASTBuilder等等。有时候多看优秀的开源代码对自己是一种启迪。
注:
①this关键字仅使用在非静态方法内部;②this(params)这种方式仅适用于构造方法,且不能用于递归调用如下面的调用是不允许的:
class C {
public C(){
this(p);
}
public C(String p2) {
this();
}
}
3.this关键字与类的绑定关系
绑定是指将一个方法调用同一个方法主体关联起来。如果在程序之前之前进行绑定称作前期绑定(静态绑定),在程序运行时根据对象的类型进行绑定称作运行时绑定(动态绑定)。在java中,除了static方法和final(private方法属于final方法)之外,其他所有方法都是运行时绑定。—— 《java编程思想-多态》
在继承关系中,this关键字有时候容易混淆,在了解了上一段解释的基础上,可以通过下面一个例子进行分析:
import java.util.Random;
public class Parent {
private String parentName = null;
private static final String DEFAULT_PARENT_NAME = "default";
private int luckyNumber = 0;
private final String CONSTANT = "parent constant";
public Parent() {
this(0);
}
public Parent(int randInt) {
this(randInt, DEFAULT_PARENT_NAME);
}
public Parent(int randInt, String name) {
luckyNumber = randInt;
parentName = name;
}
public void printNum() {
System.out.println("parent method: printNum");
printNum_1();
this.printNum_1();
this.printNum_2();
printNum_2();
}
private void printNum_1() {
System.out.println("parent method: printNum_1");
}
protected void printNum_2() {
System.out.println("parent method: printNum_2");
}
public void printConstant() {
System.out.println(this.CONSTANT);
}
}
class Child extends Parent {
private final String CONSTANT = "child constant";
public Child(int randInt) {
super(randInt);
}
public void printNum_1() {
System.out.println("child method: printNum_1");
}
protected void printNum_2() {
System.out.println("child method: printNum_2");
}
public void printConstant() {
super.printConstant();
System.out.println(this.CONSTANT);
}
}
class TestThis {
public static void main(String[] args) {
Child childObj = new Child(new Random(47).nextInt(20));
childObj.printNum();
childObj.printConstant();
}
}
parent method: printNum
parent method: printNum_1
parent method: printNum_1
child method: printNum_2
child method: printNum_2
parent constant
child constant
从打印结果可以看出,对于"this.变量",该变量如果是private类型,则"this.变量"写在哪个类里面,就是哪个类对象的成员变量;对于"this.方法",要分权限看待:
1)当该方法是private类型时,像本例这种调用方式就是调用的父类的方法;
2)如果该方法是protected或者public,则调用的是子类的方法。如果在同一个类中的一个方法中调用另一个方法,不需要使用this。
本例的测试结果对应的验证了这一小节前面说的话。即在Java中, final,static,private和构造方法与类的绑定关系在编译期确定;对于其他protected/public实例方法,则需要在运行时根据对象类型确定。本例也可以从另一个角度分析,当对一个方法设置为private(private方法可以看做是隐去了final关键字),就表示对该方法关闭了“动态绑定”,那么该方法所属的对象就在JAVAC编译时确定,而根据final的定义,当编译期确定了一个方法所属的对象之后,就不能再在后期运行时更改该方法的所属对象(不过该对象自身是可以被修改的)。
通过"javap -verbose Parent"可以查看到编译期上例中父类的printNum()方法的字节码:
public void printNum();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #40 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #46 // String parent method: printNum
5: invokevirtual #48 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: aload_0
9: invokespecial #54 // Method printNum_1:()V
12: aload_0
13: invokespecial #54 // Method printNum_1:()V
16: aload_0
17: invokevirtual #57 // Method printNum_2:()V
20: aload_0
21: invokevirtual #57 // Method printNum_2:()V
24: return
再一次验证了在java语言中符合"编译期可知,运行期不可变"要求的方法类型:static方法和private(final)方法。
“invokespecial指静态绑定后,由JVM产生调用的方法。如super(),以及super.someMethod(),都属于invokespecial。而invokevirtual指动态绑定后由JVM产生调用的方法,如obj.someMethod(),属于invokevirtual。正是由于这两种绑定的不同,在子类覆盖超类的方法、并向上转型引用后,才产生了多态以及其他特殊的调用结果。运行时,invokespecial选择方法基于引用的类型,而不是对象所属的类。但invokevirtual则选择当前引用的对象。”——《Java编程艺术》
此处对“运行时,invokespecial选择方法基于引用的类型,而不是对象所属的类。但invokevirtual则选择当前引用的对象。”再做解释:
(1)invokespecial: 调用实例构造器的<init>方法,私有方法和父类方法(super的方式)。调用的是当前类(父类)的实例化方法
(2)invokevirtual:调用所有的虚方法,即运行时调用基于class对象的方法
4.super和this的区别
“(1) 需要在子类中访问父类的一个非静态非private成员变量或者成员方法的时候要用到super关键字,但是super并不是对象的引用。当子类中覆盖了父类的某个成员变量,或者重写了父类的某个成员方法时还能够访问到父类的成员变量和成员方法;
(2)如果子类中没有重写父类的成员变量和成员方法,则子类会继承父类的所有非private的成员变量和成员方法。这时在子类中无论通过this来访问成员和通过super来访问成员,结果都是一样的。”
——引用自Java this 关键字用法
public class Parent2 {
private Parent2 parentSelf;
public Parent2() {
parentSelf = this;
}
public void show() {
System.out.println("this.getClass().getName():\t"+this.getClass().getName());
System.out.println("super.getClass().getName():\t"+super.getClass().getName());
System.out.println("parentSelf.getClass().getName():\t"+parentSelf.getClass().getName());
}
}
class Child2 extends Parent2 {
public Child2() {}
public void show() {
System.out.println("this.getClass().getName():\t"+this.getClass().getName());
System.out.println("super.getClass().getName():\t"+super.getClass().getName());
System.out.println("==================below call parent.show()=========================");
super.show();
}
}
class TestThis2 {
public static void main(String[] args) {
Child2 childObj = new Child2();
childObj.show();
}
}
this.getClass().getName(): Child2
super.getClass().getName(): Child2
==================below call parent.show()=========================
this.getClass().getName(): Child2
super.getClass().getName(): Child2
parentSelf.getClass().getName(): Child2
由此例也可以知道,对象的权限类型是"private"类型和“public final”类型,在this调用方式中是有区别的:
①this.private方法:中的this是编译时确定的当前类对应的对象自身
②this.public-final方法:中的this是动态运行时绑定的对象
也就是说private方法是属于final方法的,但是反之不成立。
5.小结
this在使用中常见于多个构造器中——重叠构造器模式,以及Builder模式中。不能用于静态变量或者方法,在构造器中使用时必须置于构造器第一行的位置。
referenced article
【1】java编程思想
【2】effective java中文版
【3】深入理解java虚拟机
【4】java编程艺术