5.4介绍的是this关键字。关于this,在此之前只是一走一过,没有深挖过,这两天忙完公众号支付,翻到这里,觉得这部分的介绍收获不少,现将阅读时的思考和理解记录下来。
书中用一个有趣的思想引入关键字this,这点是我从没想过的,老样子,只写关键代码:
Banana a = new Banana(),
b = new Banana();
(多嘴一句,以前经常 int i = 1, j = 2 ; 这样写。虽然无伤大雅,但是现在看来,很多科班的东西,确实值得野路子好好思考)
a.peel(1);
b.peel(2);
那么问题来了:编译器如何知道是哪个对象调用的peel()方法呢? 在此之前我真不知道… 书中给出的解释是:
Banana.peel(a,1);
Banana.peel(b,2);
编译器暗自将“所操作对象的引用”作为方法的第一个参数进行传递,形成了这样一种类似于静态方法调用的内部表现形式。
由此,引入了指代当前对象引用的this关键字。
书中主要介绍了this的四种应用场景,现按照书中序,归纳解释如下:
0.只在必要处使用this。这条不计数,因为讲道理,应该没谁会在一个方法中调用本类另一方法时多此一举使用this来调用,执意如此,必是真爱…
1 . 返回当前对象的引用。这条很实用,借书中例:
Leaf increment(){
i++;
return this;
}
new Leaf().increment().increment().increment();
一句话:链式编程的福音!我个人很喜欢链式编程,也很喜欢用匿名对象,很多时候,明明匿名对象能满足的单次调用,非要费尽心思想个名字,实在浪费时间。(于我,会对每个声明的变量负责。如果你刚入行,希望你也能这样要求自己,真的没有坏处。老鸟应该不会点开这篇文章,当然,就算看到应该也不会改变他们无所谓的态度。噢,可以尝试Alt+Ctrl+L,第一次见到这个重构快捷键的效果,我是被惊艳到的。)性能上,省略一个没有必要的栈中空间,虽然提升微乎其微,但至少是一种态度。同样的,明明一行链式编程能实现的逻辑,非要拆分成一整段?别人会一行一行读?还是不加注释强迫别人一行一行读?一行链式编程,一个阐述这一串代码逻辑的简要注释,足矣。我一激动就容易话多…
2 . 将当前对象传递给其他方法。对于此种应用我想补充我自己的想法,现将书中例完整引用(一般我不这样做),仅调整代码顺序,这样你可以自上而下只读一遍,不用读到最后再回头找…:
public class PassingThis{
public static void main(String[] args){
new Person().eat(new Apple());
}
}
class Person{
public void eat(Apple apple){
Apple peeled = apple.getPeeled();
System.out.println("Yummy"); //每日一词:yummy 好吃的。讲道理不削皮更有营养,当然,洗干净...
}
}
class Apple{
Apple getPeeled(){
return Peeler.peel(this);
}
}
class Peeler{
static Apple peel(Apple apple){
//... remove peel 削皮
return apple;
}
}
/* Output: Yummy */
书中的写法如上。我个人的想法啊,这个例子有点为了用this而用this之嫌,完全可以这样:
public class PassingThis{
public static void main(String[] args){
new Person().eat(new Apple());
}
}
class Person{
public void eat(Apple apple){
Apple peeled = Peeler.peel(apple);
System.out.println("Yummy");
}
}
class Apple{
}
class Peeler{
static Apple peel(Apple apple){
//... remove peel 削皮
return apple;
}
}
/* Output: Yummy */
eat()方法直接调用工具类Peeler的静态方法peel(),传递apple对象即可,干掉Apple类中的getPeeled()方法。
这是我的第一感觉。
转念一想,如果Apple类中集中了大量的操作苹果的方法,那么,书中的写法应该是更便于管理的。
此外,书中例子下的解释提到“Apple需要调用外部工具类Peeler的peel()方法,这个方法由于某种原因必须放在Apple类外部,也许是因为该外部方法要应用于许多不同的类,而你不想重复”。可能是我的理解有障碍,也可能是翻译的失了真,这段解释我来来回回琢磨了好一阵。我的理解是:这个Peeler.peel()方法不仅能削苹果,还能削梨,还能“削”你,所以放在Apple类中不合适,所以例中使用this来指代当前调用方法的对象。但是这个“不重复”我没有理解,如果削梨,一样要重复上述代码,我能想到的,多态能去重,如:
public class Fruit {
public Fruit getPeeled() {
System.out.println(this);
return Peller.peel(this);
}
public static void main(String[] args) {
new Apple().getPeeled();
new Pear().getPeeled();
}
}
class Apple extends Fruit{
}
class Pear extends Fruit{
}
class Peller {
public static Fruit peel(Fruit fruit) {
System.out.println("削皮");
//... remove peel 削皮
return fruit;
}
}
但是说到底,我个人还是倾向于:
class Person{
public void eat(Fruit fruit){
Fruit peeled = Peeler.peel(fruit);
}
}
这是第二个应用,我道行浅,真没这么写过… 另外有个小细节,我会这样写:
class Peeler{
static Apple peel(Apple apple){
//... remove peel 削皮
return peeledApple;
}
}
3 . 在构造器中调用构造器。也就是 this(…) 的使用(括号中“…”代表参数列表,下同)。 对此,书中的解释是:在构造器中,如为this添加了参数列表(需指出,无参形式的 this() 亦包含在内),这将产生对符合此参数列表的某个构造器的明确调用(显式调用)。这里我个人有一个疑问,在这个定义之前,也就是引语中,有这样一句:“有时可能想在一个构造器中调用另一个构造器,以避免重复代码”,不知道这里“避免重复代码”是指在一个构造方法中调用另一构造方法时,省略拷贝后者中的代码进前者,还是除了this(…)/super(…)之外还有其他显式调用其他构造器的方法?我印象里是没有。当然,这是题外话了,另外讲道理,这块儿结合类和对象的初始化一起说可能效果更好,等之后腾开时间再说吧,此处就不涉及了。此处只合并this(…)/super(…)二者加以说明。
关于this(…)/super(…)是什么就不再赘述了。书中例挺繁琐的,我简化一下(额,可能不止一下…),主要解释结论。
Class A{
public static void main(String[] args){
A a = new A();
}
A(){
this("A");
//this("A",1); //不能调用两次
}
A(String s){
}
A(String s,int i){
}
void 这是一个普通方法(){
//this("A",1); //不能在除构造方法之外的任何方法中调用另一构造方法
}
}
我省略了输出,由于this(…)/super(…)的必须置于首行的特性,书中例整体上 自下向上执行,自上而下输出,可能从某种程度上费点儿眼神…
主要解释注释:
其一,this(…)可以实现在一个构造器中调用另一个构造器,但不能调用两个。
其二,必须将 构造器调用 置于最起始处(首行),见下例
A(){
System.out.println("只是为了占用首行");
// this("A");
// Constructor call must be the first statement in a constructor 构造器调用必须是一个构造器中的首条语句
}
这两条结论的解释是:保证对象能够正确进行初始化(关于首行),且这种初始化仅进行一次(关于不能调用两个)。
有意思的是(或者说让人困惑的),
A(){
this("A");
// this("A",1);
// 它的报错信息同样是:
// Constructor call must be the first statement in a constructor
}
看起来就像:“因为必须置于首行,所以才不能调用两个”。实际上这种因果关系是牵强的。记结论吧,这地方容易花式绕进去(比如“因为不能在首行外的其他行调用第二个,所以第二次调用行报错,只能置于首行”…)。
其三,不能在除构造方法外的其他任何方法中,使用this(…)/super(…)调用其他构造方法。没什么解释的。
关于super(…)补充说明如下:
其一,用数学语言,this(…)和super(…)互斥。
其二,当没有显式使用this(…)或super(…)调用指定构造,编译器默认补充无参形式super(),调用其基类或公共基类Object的无参构造,以保证初始化正确进行。
这第三条终于说完了…
4 . this.参数 代表成员变量。第四条就简单了,局部变量名称和成员变量冲突,使用 this.同名参数 指代成员变量,通常用于将局部变量赋值给同名成员变量,对,就是自动生成的 set() 方法,自己写就不要给自己挖坑找麻烦了。
最后,书中关于static的补充说明也十分有意思,特别是“在静态方法内部能够调用非静态方法”的“打破真理一般”的表述,面试加分项,我觉得。