半深入理解Java属性继承

来源:互联网 发布:centos输入法下载安装 编辑:程序博客网 时间:2024/06/05 19:39

半深入理解Java属性继承


文/Levi

前几天在面试的时候又被问到了一个问题,“java重写和重载有什么区别?”。这个问题在Java领域是一个老生常谈的问题了,事实上我认为这两个东东除了中文名长得很像以外(英文名好像也很像),基本就没半毛钱关系了。我们很难找出他们的共性,却一直要尝试找出他们之间的区别,呵呵。
然而本文的主题并非重写和重载,而是重写的的孪生兄弟,属性继承。
故事的开始,我们先来撸一段代码

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(){    }    public void printColor(){        System.out.println(color);    }}

这段代码很简单,我们先写了一个类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.父类子类有相同属性时,是在父类基础上添加而非覆盖。
  • 2.方法和属性调用时,是从当前类开始一直向上查找。找到就停止。
  • 3.调用的是谁的方法,查找的起点就是谁。所以,准确判断是哪个类的方法很重要。

那么还有一个问题。当我们处在在第一段代码中的情况,并且想调用父类的color属性打印green方法怎么办呢?

使用super.color就可以访问

同样,使用super.printColor()可以调用父类的printColor()方法。

那么还有一个问题。如果层级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);    }}
0 0