java继承实现原理,快来get!

  Java 继承(inheritance)是 Java 面向对象的三大重要特性之一(封装-encapsulation,   继承-inheritance,  多态-polymorphsim)   

Java 继承很好的管理了具有相似特征的类之间的关系(主要集中在成员变量、方法),  使程序可扩展、易修改,并且成为java多态的基础。下面将介绍Java继承的基本语法以及特性:

继承的意义:继承是面向对象最显著的一个特性。继承是从已有的类中派生出新的类,新的类能吸收已有类的数据属性和行为,并能扩展新的能力。

语法:

class 子类 extends 父类 {}

子类又被称为派生类; 父类又被称为超类(Super Class)。

子类即使不扩充父类,也能维持父类的操作。

思考:子类从外表看是扩充了父类的功能, 子类还有一个特点:子类实际上是将父类定义的更加的具体化的一种手段。父类表示的范围大,而子类表示的范围小

 

 

1.  使用extends关键字实现类与类之间的继承

public class parent {
 
}
 
public class child extends parent {
 
}

在这段代码中,我们首先声明了一个名叫parent的类。然后声明了一个child类extends parent类作为parent的子类,这时候parent类就被用作了父类(super class)。

事实上,任何类都可以被用作父类,只要我们为其声明子类,不管它是否是抽象类(abstract class)。 在我们不重写(overwrite) 或重载(override)父类中的方法、

不增添新的方法、不更改原始的成员变量或增加新的变量的情况下,子类将默认保留父类的特性。
 

2.  关于抽象类的继承

    对于一个抽象类,如果里面包含有未实现的抽象方法并且我们想创造一个子类继承这个抽象类,这个子类不必重写(overwrite)抽象类中所有未实现的抽象方法,

    但是必须被定义为抽象类。同理,如果一个类实现一个接口(Interface),那么它要么重写接口里的所有抽象方法,要么被定义为抽象类。
 

3. 关于父类与子类构造器的调用

  3.1 父类与子类均为默认构造器:

public class parent {
 
}
 
public class child extends parent {
 
}

在如上代码中,父类与子类均调用super class Object的构造器(constructor)。 需要注意的是,当我们使用“new”关键字新建子类child对象时,同时会调用父类parent的构造器并新建一个父类对象(parent对象)

 

  3.2 父类与子类均不为默认构造器:

public class parent{
    public parent (int m) {
    }
}
 
public class child extends parent {
    public child (int m, String a) {
        super(m);
    }
}

在这种情况下,子类必须重写(overwrite)或重载(override)父类的构造函数,同时必须调用父类的构造器(constructor),因为父类不再有默认的构造器可供调用。

 

4. 限制一:Java单继承的特性

在java中,一个父类被多个子类继承,但一个子类只能继承一个父类。与接口不同的是,一个类可以实现(implement)多个接口。
错误的写法:

class A {}
class B {}
class C extends A,B {}          // 一个子类继承了两个父类

以上操作称为多重继承,实际上以上的做法就是希望一个子类,可以同时继承多个类的功能,但是以上的语法不支持而已,但是可以换种方式完成同样的操作。

正确的写法:

class A {}
class B extends A {}
class C extends B {}

结论:Java之中只允许多层继承,不允许多重继承,Java存在单继承局限。

 

5、限制二:在一个子类继承的时候,实际上会继承父类之中的所有操作(属性、方法),但是需要注意的是,对于所有的非私有(no private)操作属于显式继承(可以直接利用对象操作),而所有的私有操作属于隐式继承(间接完成)。

package com.zz.extendsdemo;

class A {
    private String msg;

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return this.msg;
    }
}

class B extends A {
    public void print() {
        //System.out.println(msg); // 错误: msg定义为private,不可见
    }
}

public class TestDemo {
    public static void main(String args[]) {
        B b = new B();
        b.setMsg("张三");
        System.out.println(b.getMsg());
    }
}

此时对于A类之中的msg这个私有属性发现无法直接进行访问,但是却发现可以通过setter、getter方法间接的进行操作。

 

6、限制三:在继承关系之中,如果要实例化子类对象(new 对象),会默认先调用父类构造,为父类之中的属性初始化,之后再调用子类构造,为子类之中的属性初始化,即:默认情况下,子类会找到父类之中的无参构造方法。

package com.zz.extendsdemo;

class A {
    public A() {         // 父类无参构造
              System.out.println("*************************") ;
    }
}
class B extends A {
    public B() {         // 子类构造
              System.out.println("#########################");
    }
}
public class TestDemo {
    public static void main(String args[]) {
              B b = new B() ;   // 实例化子类对象
    }
}

运行结果:

*************************
#########################

这个时候虽然实例化的是子类对象,但是发现它会默认先执行父类构造,调用父类构造的方法体执行,而后再实例化子类对象,调用子类的构造方法。而这个时候,对于子类的构造而言,就相当于隐含了一个super()的形式:

class B extends A {
    public B() { // 子类构造
        super(); // 调用父类构造
        System.out.println("#########################");
    }
}

在默认调用的是无参构造,而如果这个时候父类没有无参构造,则子类必须通过super()调用指定参数的构造方法:

package com.zz.extendsdemo;

class A {
    public A(String msg) { // 父类构造
        System.out.println("*************************");
    }
}

class B extends A {
    public B() { // 子类构造
        super("Hello"); // 调用父类构造
        System.out.println("#########################");
    }
}

public class TestDemo {
    public static void main(String args[]) {
        B b = new B(); // 实例化子类对象
    }
}

在任何的情况下,子类都逃不出父类构造的调用,很明显,super调用父类构造,这个语法和this()很相似super调用父类构造时,一定要放在构造方法的首行上。

 

7. 创建子类或父类的对象

我们看下面这段代码:

public class demo {
    public static void main (String[] args) {
        parent p = new parent();
        child c = new child();
        parent pc = new child();
    }
}
 
public class parent {
 
}
 
public class child extends parent {
 
}

 

对于main方法中的前两行,我们可以很容易的看出它新建了一个子类的对象和一个父类的对象,对于第三行这种,叫做父类的引用指向子类的对象,我们下边分别来分析对于三种声明的成员变量和方法的调用。


实例方法(instance method)和实例变量(instance method)的调用:

    针对p和c,这个类里面有什么实例方法和实例变量,那么就允许调用什么

    针对pc,编译器首先会在父类中进行搜索,如果搜索到,则不会报错,此时再去看子类中有没有重写此方法或改变此变量。如果有,则调用子类的。如果被调用的方法或变量只在子类中有而在父类中没有,那么编译器会报错。

 

静态方法(static method)和静态变量(static variable)的调用:   

    针对p和c,这个类里面有什么实例方法和实例变量,那么就允许调用什么

    针对pc,同理,只有在父类中存在,编译器才不会报错。但是,即使子类重写了父类的静态方法,调用时依然会使用父类的静态方法(在理论上,不建议通过objectName.method或objectName.variable的方式调用静态方法或变量,最好使用className.method或className.variable的方法调用)
 

 

具体想知道类的继承原理,我们需要从类的加载开始。

类加载过程包括:
加载:根据类名称获取二进制字节流,转化成方法区数据结构,并在堆中生成类class对象
验证:验证是否安全
准备:为静态变量在方法区分配内存
解析:把符号引用转化成直接引用
初始化:一般在new对象的时候会触发初始化,并为静态变量附上真实的值。

上面也说明了:类加载后会在方法区存放该类的信息,创建对象时,创建出一个对象存放到方法区,并在栈中有指向该对象的引用,堆中每个对象除了保存类的实例变量之外,还保存着实际类信息的引用 

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

如果继承的层次比较深,那么如果调用的存在最底层的父类,则调用的效率是比较低的,所以系统使用一种称为虚方法表的方法来优化调动的效率。
所谓虚方法表,就是在类加载的时候,为每个类创建一个表,这个表包括该类的对象所有动态绑定的方法及其地址,包括父类的方法,但一个方法只有一条记录,子类重写了父类方法后只会保留子类的。
 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值