引言

本文结合一个例子来说明继承实现的基本原理。

基类Base代码如下所示:

public class Base {
public static int s;
private int a;
static {
System.out.println("基类静态代码块,s:"+s);
s=1;
}
{
System.out.println("基类实例代码块,a:"+a);
a=1;
}
public Base(){
System.out.println("基类构造方法,a:"+a);
a=2;
}
protected void step(){
System.out.println("base s:"+s+",a:"+a);
}
public void action(){
System.out.println("start");
step();
System.out.println("end");
}
}

注意: Base包含一个静态变量s,一个实例变量a,一段静态初始化代码块,一段实例初始化代码块,一个构造方法,两个方法step和action。

子类Child代码如下所示:

public class Child extends Base{
public static int s;
private int a;
static {
System.out.println("子类静态代码块,s:"+s);
s=10;
}
{
System.out.println("子类实例代码块,a:"+a);
a=10;
}
public Child(){
System.out.println("子类构造方法,a:"+a);
a=20;
} @Override
protected void step() {
System.out.println("child s:"+s+",a:"+a);
}
}

注意: 子类Child继承了Base,也定义了和基类同名的静态变量s和实例变量a并且重写了方法step。

测试的main方法代码如下所示:

public static void main(String[] args) {
System.out.println("------------- new Child()");
Child c=new Child();
System.out.println("\n-- c.action");
c.action();
Base b=c;
System.out.println("\n --- b.action()");
System.out.println("\n --- b.s:"+b.s);
System.out.println("\n --- c.s:"+c.s);
}

执行结果如下:

下边我们逐过程来解释下其背后发生了什么,并解释我们所提出的问题。

类的加载过程

在java中,所谓的类加载指的是将类的相关信息加载到内存中。在java中类是动态加载的,当第一次使用这个类的时候才会加载,而且在加载一个类的时候会查看其父类是否被加载,如果没有则会加载其父类。

一个类的信息主要包含以下几个部分:

类的加载过程顺序如下:

  1. 分配内存保存类的信息

  2. 给类变量赋默认值

    注意: 数字类变量默认值都是0,boolean默认值是false,char是\u0000,引用型变量默认值都是null

  3. 加载父类

  4. 设置父子关系

  5. 执行类的初始化代码

以我们的例子来说,我们这里有三份类信息,分别是ChildBaseObject,内存布局如下图所示:

对象的创建过程

在类加载之后,new Child()就会创建Child对象,创建Child对象过程包括:

  1. 分配内存
  2. 对所有实例变量赋默认值
  3. 执行实例初始化代码

在该部分分配的内存包括本类和所有父类的实例变量,不包含任何静态变量(因为类加载过程中这部分变量的内存已经分配完成)。实例的初始化代码执行先从父类开始,父类执行完成之后再执行子类。但需要注意的是在任何类执行初始化代码之前,任何实例变量都会被赋默认值。

但需要注意的是,每一个对象除了保存类的实例变量之外,还保存着实际类信息的引用。

方法调用的过程

在该部分我们分析前边所提出的问题,首先我们先来看c.action(),这句代码的执行过程如下:

  1. 查看c的对象类型,找到Child类型,在Child类中找action方法,发现没有然后到父类中寻找。
  2. 在父类Base中找到了方法action,开始执行action方法;
  3. action先输出start,然后发现需要调用step()方法,就从Child类型中去寻找step()方法;
  4. 在Child类型中找到了Child方法,执行Child类中的step()方法,输出Child类中的两个变量s(=10),a(=20)的值;
  5. 继续执行action方法输出end

在此处我们可以发现,寻找要执行的实例方法的时候,是从对象的实际类型信息开始查找的,找不到的时候在查找父类的类型信息

接着我们来看b.action()的执行过程,这句话实际上输出的结果和c.aciton()方法输出的结果是一样的,这我们称之为动态绑定,而动态绑定实现的机制就是根据对象的实际类型信息查找要执行的方法,子类型中查找不到才会查找父类。这里因为b和c执行的是相同的对象,所有执行的结果是一样的。

而对于变量部分我们发现,其访问过程是静态绑定的,即无论对于类变量还是实例变量访问的时候,访问的实际变量都和其对象类的类型绑定,如b.s和c.s分别访问的是Base.s和Child.s。例子中实例变量都是private类型的,如果是public我们会发现b.a访问的是Base类定义的实例变量a,而c.a访问的是对象中Child类定义的实例变量a。

小结

在该部分我们分析的类和对象加载的过程,以及在过程中方法和代码块的执行顺序,我们可以得到以下结论。

方法和代码的执行顺序:

  1. 父类的类初始化代码块(static代码块)
  2. 子类的类代码初始化块
  3. 父类的实例初始化代码块
  4. 父类的构造方法
  5. 子类的实例初始化代码块
  6. 子类的构造方法

重载方法的执行逻辑:

寻找要执行的实例方法的时候,是从对象的实际类型信息开始查找的,找不到的时候在查找父类的类型信息。

关于java继承的哪些事的更多相关文章

  1. 【C#】OOP之继承那点事

    前言: 继承这点事,说多不多,说少不少,这里只描述了一些我认为的基础篇,望各位大神指教.本节参照了C#高级编程和Think in java对继承的描述,我个人认为OOP只是思想,故看明白一个就说通的, ...

  2. Java继承之再谈构造器

    目录 Java继承之再谈构造器 初始化基类 默认构造器 带参数的构造器 子类调用父类构造器 Java继承之再谈构造器 初始化基类 前面提到,继承是子类对父类的拓展.<Thinking in Ja ...

  3. Java继承与组合

    Java继承与组合 继承 java 中使用extends关键字表示继承关系,当创建一个类时,如果没有明确指出要继承的类,则是隐式地从根类Object进行继承. 子类继承父类的成员变量 子类能够继承父类 ...

  4. JAVA继承时构造函数的问题

    今天看到java继承部分时,关于构造函数是否继承以及如何使用时遇到了点问题,后来查找相关资料解决了. 下面是我个人的总结: 先创建一个父类,里面有两个构造函数: public class Jisuan ...

  5. Java继承和接口

    接口最关键的作用,也是使用接口最重要的一个原因:能上溯造型至多个基础类.使用接口的第二个原因与使用抽象基础类的原因是一样的:防止客户程序员制作这个类的一个对象,以及规定它仅仅是一个接口.这样便带来了一 ...

  6. Java继承的初始化

    /** * Created by xfyou on 2016/11/2. * Java继承的初始化 */ public class Beetle extends Insect { int k = pr ...

  7. Java—继承、封装、抽象、多态

    类.对象和包 1) 面向对象编程(Object Oriented Programming ,简称 OOP):20世纪70年代以后开始流行. 2) 结构化编程与面向对象编程的区别: A. 在结构化编程中 ...

  8. java继承关系中成员变量,构造方法,成员方法的关系

    Java继承中的成员关系 A:成员变量 a:子类的成员变量名称和父类中的成员变量名称不一样,这个太简单写那个名字就访问那个名字! b:子类的成员变量名称和父类中的成员变量名称一样,这个怎么访问呢? 子 ...

  9. JAVA继承时this和super关键字

    JAVA继承时this和super关键字 本文主要讨论在方法前使用this或super关键字时,编译器在什么地方查找对应的函数. 在子类中指定this关键字.首先在本类中查找,如果本类中找不到,再在父 ...

随机推荐

  1. hdu 1561【树形dp+01背包】

    http://acm.hdu.edu.cn/showproblem.php?pid=1561 很容易想到如果是要攻克v城需要先攻克u城的话,可以建u到v的边.但是如果能够直接攻克u城呢?无边可建,这样 ...

  2. hdu 3234 Exclusive-OR (并查集)

    Problem - 3234 题意不难理解,就是给出一些断言,以及一些查询,回答查询或者在找到断言矛盾以后沉默不做任何事. 这题其实就是一个并查集的距离存储问题,只要记录并查集元素的相对值以及绝对值就 ...

  3. 箭头函数表达式和声名式函数表达式的区别以及 Function.prototype的bind, apply,call方法

    箭头函数不能用做构造函数 箭头函数没有arguments参数 箭头函数没有自己的this,是从作用域链上取this,是与箭头函数定义的位置有关的,与执行时谁调用无关,所以用call,apply,bin ...

  4. html--多媒体文件

    添加多媒体文件 1.添加多媒体文件标记 <embed src="" width="" height=""></embed& ...

  5. H3C Inverse ARP

  6. 高级教程: 作出动态决策和 Bi-LSTM CRF 重点

    动态 VS 静态深度学习工具集 Pytorch 是一个 动态 神经网络工具包. 另一个动态工具包的例子是 Dynet (我之所以提这个是因为使用 Pytorch 和 Dynet 是十分类似的. 如果你 ...

  7. java数组简介

    数组(Array)是Java 语言中内置的一种基本数据存储结构,通俗的理解,就是一组数的集合,目的是用来一次存储多个数据.数组是程序中实现很多算法的基础,可以在一定程度上简化代码的书写. 备注: 数组 ...

  8. vue调用兄弟组件的方法使用vueBus调用$emit、$on(只需触发方法即可,不需要考虑传值或参数的问题)

    触发方: vueBus.$emit('queryAll') 被触发方: created() { vueBus.$on('queryAll', () => { this.getList() // ...

  9. UVA 11996 Jewel Magic —— splay、序列的分裂与合并、LCP的哈希算法

    #include <cstdio> #include <cstdlib> #include <iostream> #include <algorithm> ...

  10. CCPC2018 桂林 D "Bits Reverse"

    传送门 题目描述 Now given two integers x and y, you can reverse every consecutive three bits ,,) means chan ...