前几天在面试的时候又被问到了一个问题,“Java重写和重载有什么区别?”。这个问题在Java领域是一个老生常谈的问题了,事实上我认为这两个东东除了中文名长得很像以外(英文名好像也很像),基本就没半毛钱关系了。我们很难找出他们的共性,却一直要尝试找出他们之间的区别,呵呵。
然而本文的主题并非重写和重载,而是重写的的孪生兄弟,属性继承。
故事的开始,我们先看一段代码
1 public class Parent{ 2 public String color; 3 public Parent(){ 4 this.color="green"; 5 } 6 public void printColor(){ 7 System.out.println(color); 8 } 9 public static void main(String args[]){ 10 new Child().printColor(); 11 } 12 } 13 class Child extends Parent{ 14 public String color; 15 public Child(){ 16 } 17 public void printColor(){ 18 System.out.println(color); 19 } 20 }
这段代码很简单,我们先写了一个类Parent,给他添加了一个属性color,并在构造函数中初始化为“green”,同时提供一个打印函数将color的值输出。
然后我们编写了一个Child类继承Parent,重写color属性,重写打印方法。
我们在main函数里面创建一个Child对象,并调用printColor方法。
那么,输出的结果是什么呢?
答案是null。
很多人会问,你Child类构造函数都没有初始化color,怎么可能有值。
慢着慢着,我们把这段代码改改。
public class Parent{ public String color; public Parent(){ this.color="green"; } public void printColor(){ System.out.println(color); } public static void main(String args[]){ new Child().printColor(); } } class Child extends Parent{ public String color; public Child(){ } }
这段代码里面我们只是删掉了子类的printColor方法。运行,结果是green。 事情好像就不那么简单了。 事实上,事情的关键点有三个。
- 父类的构造方法是否在子类对象创建时调用?
- 父类和子类拥有相同属性时,是覆盖还是共存?
- 父类和子类printColor方法中的color究竟是谁的?父类的还是子类的?
我们一个一个来,首先是继承时构造方法的调用顺序。
是这样的,在几乎所有语言里面,如果想要正确运行一段代码的,前提条件是这段代码的所有依赖都能正常运行。所以,在搞定一段代码之前,都要先搞定他的依赖关系。例如我们加载一个类的时候,要先加载完成类中所有引用到的其他类。对象的创建也是一样,当我们创建一个对象时,首要任务就是“创建父类对象”,父类的构造方法也就是在这个时候调用的。
当我们“创建父类对象”之后明显会碰到一个问题,父类对象创建并初始化所有属性后,子类又打算再创建一个相同的属性,此时会怎样呢?
答案是:而是再创建一个该属性。
于是一个子类对象里面就有了两个同名的属性,一个是父类创建并且初始化为green的color,一个是还没有被初始化的子类的color。那么在printColor方法里面的color到底是哪一个呢?
这里要先说两个规则:
1.“逐级查找,找到则停”。属性和方法都可以适用这条规则。他的大意是,当我们需要调用一个方法或者属性时,我们先看看自己有没有,没有的话,就去上级(父类)找找,直到找到为止。这也可以很好地解释方法重写是如何实现的。不过属性和方法有一点区别,当子类对象向上转型为父类对象后,调用同名方法调用的只可以是子类方法,调用属性则调用父类属性。
2.调用的是谁的方法,属性查找的起点就是谁。这条规则的重点是在于判断调用的究竟是谁的方法。当一个子类重写父类方法时,调用的必然是子类方法,反之则调用的必然是父类的方法。
以上两点看似非常容易理解,现在我们来结合前面的代码理解一下。
第一段代码,当我们调用printColor方法时。
- 1. 先在child对象中查找printColor方法,很容易就在Child类中找到了,于是调用的方法必然是Child类中的方法。
- 2. 查找color属性,因为Child类中已经有color属性,于是该属性率先被查找到,并停止继续查找,打印出来,为null。
第二段代码
- 1.先找到printColor方法,发现找不到,于是去父类找。
- 2.在父类中找到了printColor方法,停止查找。
- 3.找到color属性,由于调用的是父类的方法,所有属性查找起点为父类对象,在父类对象中成功找到初始化为green的color
- 4.打印出green
如果以上两个流程你都理解了,那么属性继承你基本上也就掌握了。总结一下关键点。
- 父类子类有相同属性时,是在父类基础上添加而非覆盖。
- 方法和属性调用时,是从当前类开始一直向上查找。找到就停止。
- 调用的是谁的方法,查找的起点就是谁。所以,准确判断是哪个类的方法很重要。
问题1:当我们处在在第一段代码中的情况,并且想调用父类的color属性打印green方法怎么办呢?
使用super.color就可以访问
同样,使用super.printColor()可以调用父类的printColor()方法。
问题2:如果层级Parent还有一个父类PerParent也有相同属性,我们在Child该怎么才能调用到?
super.super.color?
明显不对。
此时我们只要向上转型即可。(Perparent)child.color;
不过这里要注意的是,方法重写后,使用(Parent)child.printColor()是无法访问到父类方法的。
文章的结尾,我留下两个习题,如果你看一眼能正确给出答案的话,我只能为你默默刷66个6666。
题1:
public class Parent{ public String color; public Parent(){ this.color="green"; } public void printColor(){ System.out.println(color); } public static void main(String args[]){ new Child().printColor(); } } class Child extends Parent{ //我们在这里删掉color属性 //public String color; public Child(){ } public void printColor(){ System.out.println(color); } }
题2:
public class Parent{ public String color; public Parent(){ this.color="green"; } //在这里删掉父类的printColor方法 /* public void printColor(){ System.out.println(color); }*/ public static void main(String args[]){ new Child().printColor(); } } class Child extends Parent{ //我们在这里删掉color属性 //public String color; public Child(){ } public void printColor(){ System.out.println(color); } }