- 按照官方正规的解释,this关键字的意义被解释为“指向当前对象的引用”。
- 其实,我们只要把this理解成”本对象自己的…”就可以了。
首先来看一段代码。
public class Person {
String name;
int age;
double height;
public Person(String n,int a,double h) {
name=n;
age=a;
height=h;
}
public void introduce() {
System.out.println("我叫"+name+",今年"+age+"岁");
}
}
在这段代码中,定义了一个表示”人”的类Person,在Person类中,有3个属性name、age和height,分别来表示姓名、年龄和身高。在类当中定义了一个构造方法,构造方法中对3个属性进行了初始化操作。最后定义了一个introduce方法,这个方法能够打印一个Person对象的姓名和年龄。
现在,我们在main方法当中创建一个Person类的对象,并且调用这个对象的introduce方法,代码如下:
public static void main(String[] args) {
Person p=new Person("张三",20,178.5);
p.introduce();
}
运行结果是这样的:
大家请注意,我们在定义构造方法的时候,把表示姓名、年龄和身高的参数分别命名为:n、a和h,这种命名的可读性有点差,为了提高可读性,我们把构造方法的参数名称修改为name、age和height。
public Person(String name,int age,double height) {
name=name;
age=age;
height=height;
}
修改之后,再次运行main方法,得到的运行结果:
- 为什么这一次的运行结果出现了问题呢?就是因为,修改了构造方法之后,当我们调用构造方法创建对象时,给构造方法所传递的3个参数值“张三”、20和178.5最终并没有赋值到对象的3个属性中。
- 既然参数值没有被赋值到对象的属性中,它们去了哪里呢?修改代码后,构造方法的参数与类所定义的属性同名,根据”同名情况下,局部变量的优先级更高”原则,在构造方法执行的过程中,虚拟机会把参数值赋给”参数本身”,而不是赋值给对象的属性!具体来说,就是我们给构造方法的name参数传递的值是”张三”,而这个”张三”在构造方法执行的过程中,当运行到”name=name;”这条语句时,并没有把”张三”赋值给对象的name属性,而是又重新赋值给了name参数自身。就是因为”张三”最终没有被赋值到对象的name属性中,才导致introduce方法中打印出的name属性是null。当然,age和height这两个参数也是同样的赋值效果。
为了能够让虚拟机明白我们所期望的是:把”张三”这个字符串赋值给对象的name属性,而不是”再一次”把它赋值给构造方法的参数,就需要把构造方法中的赋值语句做出如下修改:
public Person(String name,int age,double height) {
this.name=name;
this.age=age;
this.height=height;
}
这一次,我们在构造方法中,给”=”左边的属性前面都加上了this关键字,经过修改之后,重新运行main方法,就恢复了正常的运行效果。
- 加了this关键字之后,为什么程序能够”恢复正常”。刚才我们说过,”this”可以被解释为” 本对象自己的…”,按照这个逻辑,”this.name”就可以被解释为”本对象自己的name属性”,所以在执行”this.name=name;”这条语句的时候,虚拟机就会把name参数的值”张三”赋值给对象的name属性。也就是说在这条赋值语句中,”=”左边的”this.name”表示对象的name属性,而”=”右边的name表示方法的name参数。
this关键字的用法
1.this.属性名
- 有参构造方法中如果参数名和类属性名同名,则用this.属性名区分类属性名和构造方法的参数名,如上面所举例的Person类的有参构造;
- “this.属性名”可以被解释为”本对象自己的XX属性”;
public Person(String name,int age,double height) {
this.name=name;
this.age=age;
this.height=height;
}
2.this.方法名
public void greet() {
System.out.println("大家好!");
}
public void introduce() {
this.greet();
System.out.println("我叫"+name+",今年"+age+"岁");
}
- 我们给Person类增加了一个”打招呼”的方法叫做greet。在introduce方法当中,就可以通过”this.方法名”的方式来调用这个方法,表示调用的是”本对象自己的greet”方法;
- 在introduce方法中并没有出现其他对象,所以方法名前面的this关键字也可以省略不写。
- ”this.方法名”这种写法,就可以被解释为调用”本对象自己的XX方法”;
3.this表示对象自身
- 既然this关键字表示的是本对象自己,所以在代码中可以直接用this来代表对象自身。
- 比如说,在Ojbect类当中定义了一个equals()方法,这个方法用来比较自身对象与其他对象是不是相等,就直接可以用this来与其他对象做比较。
public boolean equals(Object obj) {
return (this == obj);
}
4.this(参数),如this(name,age)
我们给Person类又增加了一个构造方法。这个构造方法只有2个参数,并且只初始化2个属性。为了讲述方便,我们把上面的3个参数的构造方法称之为”构造方法①”,把下面的2个参数的构造方法称之为”构造方法②”。
//构造方法 ①
public Person(String name,int age,double height) {
this.name=name;
this.age=age;
this.height=height;
}
//构造方法 ②
public Person(String name,int age) {
this.name=name;
this.age=age;
}
通过观察不难发现,这两个构造方法当中前2行代码是相互重复的,为了避免这种重复性的代码出现,我们可以在”构造方法①”当中调用”构造方法②”。调用的方式如下:
public Person(String name,int age,double height) {
this(name,age);//调用构造方法 ②
this.height=height;
}
public Person(String name,int age) {
this.name=name;
this.age=age;
}
- 为什么要通过这种方式来调用构造方法呢?我们难道不能直接写一个”Person(name,age);”来调用吗?这里必须做出解释:在Java语言中,一个类的构造方法与类名相同。但是,一个类当中也可以定义一个与类名相同的”普通方法”,换句话说就是:并不是只有构造方法与类名相同,”普通方法”也可以取和类相同的名称(只不过全世界的程序员都不会这么干)。那么,在这种情况下,编译器如何区分这个方法是”普通方法”还是”构造方法”呢?很简单,”普通方法”的名称前面必须定义返回值类型,而”构造方法”的名称前面则没有返回值类型的定义。这样,编译器就能够分得清哪个是”构造方法”,哪个是”和类同名的普通方法”。
- 定义的时候分得清,但是在调用的时候,都是通过方法名来调用的,这时如何分得清代码中哪一句调用的是”构造方法”, 哪一句调用的是”和类同名的普通方法”呢?为了解决这个问题,Java语言规定,在本类中调用构造方法的时候,需要通过”this(参数)”的方式来调用。除此之外,Java语言还规定了这种调用方式所必须遵守的规则:首先,这种”this(参数)”的方式只能在”其他构造方法中”使用,不能在普通方法中用。如果在普通方法中按这种方式使用,将被视为语法错误。
其次,在一个构造方法中,用”this(参数)”的形式调用构造方法,”this(参数)”必须写在主调方法的第一行。第三,不能出现相互循环嵌套调用,也就是说,不能在构造方法①中调用构造方法②,又同时在构造方法②中调用构造方法①,如下所示:
- 注:构造方法②中也可以调用构造方法①
public class Person {
String name;
int age;
double height;
public Person(String name,int age,double height) {
// this(name,age);//构造方法①中调用构造方法②
this.name=name;
this.age=age;
this.height=height;
}
public Person(String name,int age) {
this(name,age,179);//构造方法②中调用构造方法①
}
public void introduce() {
System.out.println("我叫"+name+",今年"+age+"岁"+",身高"+height+"cm");
}
public static void main(String[] args) {
Person p=new Person("张三",20);
p.introduce();
}
}
运行结果如下:
- 在main()方法中执行到”new Person(“张三”,20,178.5);”这句代码时,实际上是调用了构造方法①,而构造方法①中又调用了构造方法②,两个构造方法都被调用到了,那么在内存中会不会创建两个Person对象呢?答案是不会,为什么呢?原因很简单:构造方法①中调用了构造方法②,并没有出现new关键字,调用构造方法②仅仅是完成了name和age这两个属性的初始化,并不会创建出两个对象。
- 既然Java语言允许”普通方法”的名称与类名相同,而构造方法也与类名相同,那么在Person以外的类当中如果写上了”Person(参数)”这样的代码,虚拟机如何判断所调用的是普通方法还是构造方法呢?答案也很简单,如果”Person(参数)”的前面出现了new关键字,这就说明调用的是构造方法,否则说明调用的是普通方法。就算是同类名的普通方法也是在new了一个对象之后才能调用。
5.this区分属性或方法的具体归属(内部类和外部类有同名属性或同名方法,用来区分两者)
public class Outter {
private int a=1;
class Inner{
void printA() {
System.out.println(a);//内部类直接调用外部类的属性
}
}
public static void main(String[] args) {
Outter.Inner o=new Outter().new Inner();
o.printA();
}
}
- 在上面一段代码中,定义了外部类Outter,Outter有一个属性a,并且Outter中又定义了内部类,在内部类的printA()方法中调用了外部类的a属性。我们都知道,一个内部类可以直接访问它所在外部类的属性和方法。这个特性在我们的上面这段代码中得到了体现。
- 如果内部类中出现了与外部类同名的属性或方法时,该如何区分调用的到底是哪个属性或方法呢?比如说,在Inner类中也出现了a属性,那么输出语句中的a到底是指哪个a呢?很简单,如果输出语句中直接写a,那么调用的是内部类的a属性。为了强调它是内部类的a属性,我们也可以在a的前面加this关键字。如果我们希望调用的是外部类的a属性,可以用”外部类名.this.a”的方式来调用,如下所示:
public class Outter {
private int a=1;
class Inner{
private int a=10;
void printA() {
System.out.println(a);//调用内部类的a属性,输出10
System.out.println(this.a);//调用内部类的a属性,输出10
System.out.println(Outter.this.a);//调用外部类的a属性,输出1
}
}
public static void main(String[] args) {
Outter.Inner o=new Outter().new Inner();
o.printA();
}
}