学习Java编程的时候,无论是谁,当学到面向对象这部分内容时都会遇到一个关键字:this。很多初学者对这个关键字的都会感觉到理解不透,不明白这个神秘的”this”到底表示什么意思。按照官方正规的解释,this关键字的意义被解释为“指向当前对象的引用”。这个解释非常准确并且很精炼,但它太过学术化了,导致很多初学者有点读不懂,更谈不上深入理解它的意义。本文将用大白话的形式帮助初学Java的小伙伴来深入理解this关键字的意义,并且梳理它的各种用法。
其实,我们只要把this理解成”本对象自己的...”就可以了。看到这个解释,很多人可能更懵了,什么叫”本对象自己的...”?不要着急,让我们首先来看一段代码。
在这段代码中,定义了一个表示”人”的类Person,在Person类中,有3个属性name、age和height,分别来表示姓名、年龄和身高。在类当中定义了一个构造方法,构造方法中对3个属性进行了初始化操作。最后定义了一个introduce方法,这个方法能够打印一个Person对象的姓名和年龄。现在,我们在main方法当中创建一个Person类的对象,并且调用这个对象的introduce方法,代码如下:
愉快的运行一下程序,出来的运行结果是这样的:
通过程序的运行结果我们可以看出,在创建对象的时候,对象的属性被赋予了正确初始值。这个程序本身非常的简单,谁都可以理解,但是大家请注意,我们在定义构造方法的时候,把表示姓名、年龄和身高的参数分别命名为:n、a和h,这种命名的可读性有点差,为了提高可读性,我们把构造方法的参数名称修改为name、age和height,如下图所示:
修改之后,再次运行main方法,得到的运行结果变成了这个样子:
为什么这一次的运行结果出现了问题呢?就是因为,修改了构造方法之后,当我们调用构造方法创建对象时,给构造方法所传递的3个参数值“张三”、20和178.5最终并没有赋值到对象的3个属性中。那么,既然参数值没有被赋值到对象的属性中,它们去了哪里呢?修改代码后,构造方法的参数与类所定义的属性同名,根据”同名情况下,局部变量的优先级更高”原则,在构造方法执行的过程中,虚拟机会把参数值赋给”参数本身”,而不是赋值给对象的属性!具体来说,就是我们给构造方法的name参数传递的值是”张三”,而这个”张三”在构造方法执行的过程中,当运行到”name=name;”这条语句时,并没有把”张三”赋值给对象的name属性,而是又重新赋值给了name参数自身。就是因为”张三”最终没有被赋值到对象的name属性中,才导致introduce方法中打印出的name属性是null。当然,age和height这两个参数也是同样的赋值效果。
为了能够让虚拟机明白我们所期望的是:把”张三”这个字符串赋值给对象的name属性,而不是”再一次”把它赋值给构造方法的参数,就需要把构造方法中的赋值语句做出如下修改:
大家来看,这一次,我们在构造方法中,给”=”左边的属性前面都加上了this关键字,经过修改之后,重新运行main方法,就恢复了正常的运行效果。好,现在我们就来探究一下,加了this关键字之后,为什么程序能够”恢复正常”。刚才我们说过,”this”可以被解释为”本对象自己的...”,按照这个逻辑,”this.name”就可以被解释为”本对象自己的name属性”,所以在执行”this.name=name;”这条语句的时候,虚拟机就会把name参数的值”张三”赋值给对象的name属性。也就是说在这条赋值语句中,”=”左边的”this.name”表示对象的name属性,而”=”右边的name表示方法的name参数。
讲到这里,有的小伙伴可能会问:”this.name”为什么不能被解释为”本对象自己的name参数”呢?因为”参数”这个概念是就某个方法而言的,它相当于某个方法的”局部变量”,只是这个”局部变量”比起在方法中定义的真正的局部变量来讲有点特殊,它能够接收从主调方法中传递过来的值。因此,当我们说到”参数”这个概念的时候,都是相对于一个”方法”而不是一个”对象”而言的,所以也就不会有”某个对象的参数”这一说法。因此,”this.name”只能被虚拟机认定为本对象自己的name”属性”,绝不会被当作name”参数”。
通过这个例子,希望大家能够理解this关键字的意义。在程序中,所出现的”this.属性名”是this关键字最常见的一种用法,也是我们给大家总结的this关键字的第一种用法。既然”this.属性名”可以被解释为”本对象自己的XX属性”,那么会不会有”this.方法名”这种用法呢?当然有这种用法,当程序中出现了”this.方法名”这种写法,就可以被解释为调用”本对象自己的XX方法”。来看下面的代码:
这一次,我们给Person类增加了一个”打招呼”的方法叫做greet。在introduce方法当中,就可以通过”this.方法名”的方式来调用这个方法,表示调用的是”本对象自己的greet”方法。这是this关键字的第二种用法。当然,在introduce方法中并没有出现其他对象,所以方法名前面的this关键字也可以省略不写。
既然this关键字表示的是本对象自己,所以在代码中可以直接用this来代表对象自身。比如说,在Ojbect类当中定义了一个equals()方法,这个方法用来比较自身对象与其他对象是不是相等,就直接可以用this来与其他对象做比较。
上面这段代码就是Object类的equals()方法的源代码,有兴趣的小伙伴可以去看看。在代码中直接用this关键字代表对象本身,并且和另一个对象obj做了比较。(关于==这个运算符到底比的什么,大家可以看我的另一篇博文《Java千问09:你真的掌握了Java语言的==吗?我看未必!》)
其实,this关键字还有另外一种很重要的用法,那就是在this关键字的后面加上小括号,这样就表示调用了某个类自身的构造方法,为了讲解这种用法我们再来修改一下Person类。
这一次,我们给Person类又增加了一个构造方法。这个构造方法只有2个参数,并且只初始化2个属性。为了讲述方便,我们把上面的3个参数的构造方法称之为”构造方法①”,把下面的2个参数的构造方法称之为”构造方法②”。通过观察不难发现,这两个构造方法当中前2行代码是相互重复的,为了避免这种重复性的代码出现,我们可以在”构造方法①”当中调用”构造方法②”。调用的方式如下:
在”构造方法①”中,通过”this(参数);”的方式调用了”构造方法②”。这就是this关键字的又一种用法。很多小伙伴可能不理解,为什么要通过这种方式来调用构造方法呢?我们难道不能直接写一个”Person(name,age);”来调用吗?这里必须做出解释:在Java语言中,一个类的构造方法与类名相同。但是,一个类当中也可以定义一个与类名相同的”普通方法”,换句话说就是:并不是只有构造方法与类名相同,”普通方法”也可以取和类相同的名称(只不过全世界的程序员都不会这么干)。那么,在这种情况下,编译器如何区分这个方法是”普通方法”还是”构造方法”呢?很简单,”普通方法”的名称前面必须定义返回值类型,而”构造方法”的名称前面则没有返回值类型的定义。这样,编译器就能够分得清哪个是”构造方法”,哪个是”和类同名的普通方法”。
定义的时候分得清,但是在调用的时候,都是通过方法名来调用的,这时如何分得清代码中哪一句调用的是”构造方法”,哪一句调用的是”和类同名的普通方法”呢?为了解决这个问题,Java语言规定,在本类中调用构造方法的时候,需要通过”this(参数)”的方式来调用。除此之外,Java语言还规定了这种调用方式所必须遵守的规则。首先,这种”this(参数)”的方式只能在”其他构造方法中”使用,不能在普通方法中用。如果在普通方法中按这种方式使用,将被视为语法错误,如下图所示
可以看到,在普通方法中按这样的方式调用构造方法会出问题。
其次,在一个构造方法中,用”this(参数)”的形式调用构造方法,”this(参数)”必须写在主调方法的第一行。第三,不能出现相互循环嵌套调用,也就是说,不能在构造方法①中调用构造方法②,又同时在构造方法②中调用构造方法①,如下图所示
这种相互调用的写法是绝对不允许的。
接下来我们再来思考两个问题:首先,在main()方法中执行到”new Person("张三",20,178.5);”这句代码时,实际上是调用了构造方法①,而构造方法①中又调用了构造方法②,两个构造方法都被调用到了,那么在内存中会不会创建两个Person对象呢?答案是不会,为什么呢?原因很简单:构造方法①中调用了构造方法②,并没有出现new关键字,调用构造方法②仅仅是完成了name和age这两个属性的初始化,并不会创建出两个对象。
我们要思考的第二个问题是:既然Java语言允许”普通方法”的名称与类名相同,而构造方法也与类名相同,那么在Person以外的类当中如果写上了”Person(参数)”这样的代码,虚拟机如何判断所调用的是普通方法还是构造方法呢?答案也很简单,如果”Person(参数)”的前面出现了new关键字,这就说明调用的是构造方法,否则说明调用的是普通方法。
讲到这里,我们就把this关键字最常用的几种用法讲完了,其实,this关键字在我们编写内部类代码的时候,还有一种用途,那就是区分属性或方法的具体归属。我们来看下面的代码:
在这段代码中,定义了外部类Outter,Outter有一个属性a,并且Outter中又定义了内部类,在内部类的printA()方法中调用了外部类的a属性。我们都知道,一个内部类可以直接访问它所在外部类的属性和方法。这个特性在我们的上面这段代码中得到了体现。但是,如果内部类中出现了与外部类同名的属性或方法时,该如何区分调用的到底是哪个属性或方法呢?比如说,在Inner类中也出现了a属性,那么输出语句中的a到底是指哪个a呢?很简单,如果输出语句中直接写a,那么调用的是内部类的a属性。为了强调它是内部类的a属性,我们也可以在a的前面加this关键字。如果我们希望调用的是外部类的a属性,可以用”外部类名.this.a”的方式来调用,如下图所示:
以上我们就把this关键字的意义和它的各种用法讲完了,简单总结一下:
- this:表示自身对象,也就是本对象自己
- this.属性名:表示本对象自己的属性
- this.方法名:表示本对象自己的方法
- this(参数)表示本对象自身的构造方法(注:”构造方法”这个概念是相对于”类”而言的,但具体到this(参数)这种用法时,表示”我这个对象自己的构造方法”)
- 外部类名.this.属性:表示在内部类中调用的是外部类的某个属性(调用外部类方法亦同)
希望本文能够帮助初学者深入理解this关键字的作用。