半深入理解Java属性继承

前几天在面试的时候又被问到了一个问题,“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. 父类子类有相同属性时,是在父类基础上添加而非覆盖。
  2. 方法和属性调用时,是从当前类开始一直向上查找。找到就停止。
  3. 调用的是谁的方法,查找的起点就是谁。所以,准确判断是哪个类的方法很重要。

问题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);
}
}
时间: 2024-10-24 14:51:08

半深入理解Java属性继承的相关文章

步步理解 JAVA 泛型编程

步步理解 JAVA 泛型编程 转载自: 隔叶黄莺 Unmi Blog------步步理解 JAVA 泛型编程(一) 隔叶黄莺 Unmi Blog------步步理解 JAVA 泛型编程(二) 隔叶黄莺 Unmi Blog------步步理解 JAVA 泛型编程(三) 步步理解 JAVA 泛型编程(一) JDK 1.5 相对于 JDK 1.4 来说变化最大的部分就是泛型,甚至可以说自 Java 1.0 发布以来也是最大的一次语言变化,因为要涉及到编译器的大改动.很早的时候大家对泛型的呼声很大,正如

【深入理解Java虚拟机】垃圾回收机制

本文内容来源于<深入理解Java虚拟机>一书,非常推荐大家去看一下这本书. 本系列其他文章: [深入理解Java虚拟机]Java内存区域模型.对象创建过程.常见OOM 1.垃圾回收要解决的问题 垃圾收集(Garbage Collection,GC),要设计一个GC,需要考虑解决下面三件事情: (1)哪些内存需要回收? (2)什么时候回收? (3)如何回收? 哪些内存需要回收? 根据<Java内存区域模型.对象创建过程.常见OOM>中介绍的java内存模型,其中,程序计数器.虚拟机栈

深入理解Java虚拟机之垃圾收集一

"生存还是死亡" 如何来判定对象是否存活?针对这个问题书中给出了两种算法,分别是引用计数算法和可达性分析算法 引用计数算法 该算法的思路简单并且易于实现.我们给对象中添加一个引用计数器,当有一个地方引用它时,引用计数器就加一,当引用失效时,计数器减一,当计数器为0时就说明该对象不可能再被引用. 客观的评价,该算法判定效率很高,在很多情况下都是一种不错的算法,但是,至少主流的Java虚拟机并没有采用采用这种算法.原因是该算法无法解决对象之间的循环引用问题. 什么是循环引用呢?笔者认为就是

jvm--深入理解java虚拟机 精华总结(面试)(转)

深入理解java虚拟机 精华总结(面试)(转) 一.运行时数据区域 3 1.1 程序计数器 3 1.2 Java虚拟机栈 3 1.3 本地方法栈 3 1.4 Java堆 3 1.5 方法区 3 1.6 运行时常量池 4 二. hotspot虚拟机对象 4 2.1 对象的创建 4 检查 4 分配内存 4 Init 4 2.2 对象的内存布局 4 2.3 对象的访问定位 4 使用句柄访问 4 使用直接指针访问 5 三. OutOfMemoryError 异常 5 3.1 Java堆溢出 5 3.2

全面理解Java内存模型(JMM)及volatile关键字(转)

原文地址:全面理解Java内存模型(JMM)及volatile关键字 关联文章: 深入理解Java类型信息(Class对象)与反射机制 深入理解Java枚举类型(enum) 深入理解Java注解类型(@Annotation) 深入理解Java类加载器(ClassLoader) 深入理解Java并发之synchronized实现原理 Java并发编程-无锁CAS与Unsafe类及其并发包Atomic 深入理解Java内存模型(JMM)及volatile关键字 剖析基于并发AQS的重入锁(Reetr

深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

周志明的<深入理解Java虚拟机>很好很强大,阅读起来颇有点费劲,尤其是当你跟随作者的思路一直探究下去,开始会让你弄不清方向,难免有些你说的啥子的感觉.但知识不得不学,于是天天看,反复看,就慢慢的理解了.我其实不想说这种硬磨的方法有多好,我甚至不推荐,我建议大家阅读这本书时,由浅入深,有舍有得,先从宏观去理解去阅读,再慢慢深入,有条不紊的看下去.具体来说,当你看书的某一部分时,先看这部分的章节名,了解这部分这一章在讲什么,然后再看某一章,我拿"类文件结构"这一章来说,我必须

读深入理解Java中的String(包括JVM)一文总结和提升

读深入理解Java中的String(包括JVM)一文总结和提升 摘要:String作为Java语言中的字符串模拟类,无论是实际的编程工作还是笔试面试过程,都需要我们都String类非常熟悉,对于String类的大部分字符串操作方法,都必须达到熟练运用的程度才行.但是,笔试和面试过程中,面试官往往喜欢问一些String特性相关的题目,来考察面试者对于String基础知识的掌握是否牢固.(本人尚未研读深入理解JVM这本书,分析JVM都是查看网上资料来分析的,若在接下来的内容有分析不到位的地方请见谅和

深入理解Java中的Garbage Collection

前提 最近由于系统业务量比较大,从生产的GC日志(结合Pinpoint)来看,需要对部分系统进行GC调优.但是鉴于以往不是专门做这一块,但是一直都有零散的积累,这里做一个相对全面的总结.本文只针对HotSpot VM也就是Oracle Hotspot VM或者OpenJDK Hotspot VM,版本为Java8,其他VM不一定适用. 什么是GC(Garbage Collection) Garbage Collection可以翻译为"垃圾收集" -- 一般主观上会认为做法是:找到垃圾,

《深入理解Java集合框架》系列文章

Introduction 关于C++标准模板库(Standard Template Library, STL)的书籍和资料有很多,关于Java集合框架(Java Collections Framework, JCF)的资料却很少,甚至很难找到一本专门介绍它的书籍,这给Java学习者们带来不小的麻烦.我深深的不解其中的原因.虽然JCF设计参考了STL,但其定位不是Java版的STL,而是要实现一个精简紧凑的容器框架,对STL的介绍自然不能替代对JCF的介绍. 本系列文章主要从数据结构和算法层面分析