Java学习 -- 继承性

2021/9/21 1:27:10

本文主要是介绍Java学习 -- 继承性,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

文章目录

    • 继承概述
      • 共性抽取
      • 继承的特点
      • 继承的好处
    • 继承的格式
    • 属性的访问特点
      • 如何区分三种变量重名时
    • 方法的访问特点
    • 方法的重写
      • 重写与重载的区别
      • 覆盖重写的特点
      • 覆盖重写的应用
      • 覆盖重写注意事项(重要)
    • 构造器的访问特点
    • 两个关键字
      • super
      • this
    • Java继承中的三个特点
    • 子类对象实例化的过程
      • 从结果上来看(继承性)
      • 从过程上来看

继承概述

继承是面向对象的三大特性之一,继承从字面可以理解为继承了某种事物或者能力。而在Java中继承发生在子父类(或子父接口)的关系中,当子类继承父类后子类具有了父类所有的属性和方法。

共性抽取

继承主要解决的问题就是:共性抽取

我们之前学过类和对象的内容,关于类我们可以看作是对一类具备相关属性和行为的事物的描述。而当多个类之间具有相同性同时也具有相异性,显然无法将其归为一类。此时我们可以抽取这些类之间的相同属性和行为,得到一个具备了这些相同属性和行为的类。那么其它的类就无需在本类中定义这些相同的属性和行为,只需要继承那一个类即可。

比如下图:
在这里插入图片描述

兔子可以归为一类,绵羊也可以归为一类,它们都是吃草,那么通过这个共同的特性可以将它们同时归为食草动物一类。同样狮子和豹子也可以同时被归为食肉动物一类。而食草动物和食肉动物也具备相同的属性和行为,比如:进食、活动、交配…,那么就可以将食草动物和食肉动物归为一类。这种一级一级的关系就像是继承,兔子继承了食草动物的特性,食草动物继承了动物的特性。

回到Java中,多个具备相同属性和行为的类被称作子类派生类,而通过抽取这些属性和行为得到的类被称为父类超类或者基类。继承描述的是事物之间的所属关系,这种关系是: is-a 的关系

继承的特点

子类继承父类后,就继承父类了所有的属性和方法。使得子类可以直接访问父类中的非私有的属性和方法。

  1. 子类可以拥有父类的“内容”
  2. 子类还可以拥有自己专有的内容,实现功能的拓展。
  3. 父类中私有的属性和方法,子类也会继承,但是不能直接进行访问,可以通过继承父类中公共的方法来访问父类的私有属性或方法。

继承的好处

  1. 减少了代码的冗余,提高代码的复用性。
  2. 便于功能的扩展。
  3. 类与类之间产生了关系,是多态的前提

继承的格式

在继承的关系中,子类就是一个父类。也就是说,子类可以被当作父类看待(这很重要,多态前提)。

例如:父类是员工,子类是讲师,那么讲师就是一个员工。

关系:is-a

//  定义父类的格式 (一个普通的类定义)
public class 父类名称{
    // ...
}

// 定义子类的格式
public class 子类名称 extends 父类名称{
    // ...
}

例如:创建一个员工的父类,和它的一些子类。

// 定义一个父类,员工
public class Employee{
    
    public void method() {
        System.out.println("方法执行");
    }
}

// 定义一个员工的子类,讲师
public class Teacher extends Employee {
    // 子类会继承父类的方法
}

// 定义一个员工的子类,助教
public class Assistant extends Employee {
    // 子类会继承父类的方法
}

public class Demo01Extends {
    public static void main(String[] args) {
        // 创建子类讲师的对象
        Teacher teacher = new Teacher();
        
        // 子类讲师调用父类的方法
        teacher.method();
        
        // 创建子类助教的对象
        Assistant assistant = new Assistant();
        
        // 子类助教调用父类的方法
    	assistant.method();
    }
}

// 通过继承的方法,可以起到代码复用的作用

在这里插入图片描述

属性的访问特点

在父子类的继承关系当中,如果子类和父类中的成员变量重名,则创建子类对象时,访问有两种方式:

  1. 直接通过子类对象访问成员变量。

    等号左边是谁(对象引用),就优先用谁,没有则向上找

  2. 间接通过成员方法访问成员变量。

    该方法属于谁,就用优先用谁的成员变量,没有则向上找。

// 定义父类
public class Father {
    int numF = 10;
   // 与子类成员变量名相同
    int num = 100;
    public void methodF()
    {
        // 此方法内部需要一个num变量,优先用本类的变量
        System.out.println(num);
    }
}

// 定义子类
public class Son extends  Father{
    int numS = 20;
    // 与父类成员变量名相同
    int num = 200;

    public void methodZ()
    {
        // 此方法内部需要一个num变量,优先用本类的变量
        System.out.println(num);
    }
}

// 创建对象
public class Demo01ExtendsField {
    public static void main(String[] args) {
        // 创建父类对象
        Father fa = new Father();

        // 父类只能使用父类的成员变量和方法
        System.out.println(fa.numF);   // 10

        // 创建子类对象
        Son son = new Son();
		
        // 子类可以使用父类和子类的成员变量和方法
        System.out.println(son.numF);  // 10
        System.out.println(son.numS);  // 20
		
        // 变量名重名,直接方法
        // 【等号左边是谁,就有用谁】
        System.out.println(son.num);   // 200

        // 变量名重名,间接方法
        // 【调用方法,方法属于谁,优先用谁的】
        son.methodF();                // 100
        son.methodZ();                // 200

    }
}

如何区分三种变量重名时

三种变量:父成员变量,子成员变量,局部变量

  1. 局部变量:直接在作用域的{}中写变量名,根据就近原则会优先使用该局部变量。

  2. 子类成员变量:使用this.变量名来调用子类成员变量,this可以理解为当前对象的引用。

  3. 父类成员变量:使用super.变量名来调用父类成员变量,super可以理解为当前对象的父类引用。

// 局部变量     直接写变量名
// 子类成员变量  this.变量名
// 父类成员变量  super.变量名

// 父类
public class Father {
    int num = 10;
}
// 子类
public class Son extends Father {

    int num = 20;

    public void method(){
        int num = 30;
        System.out.println(num);       // 30,访问局部变量
        System.out.println(this.num);  // 20,访问本类成员变量
        System.out.println(super.num); // 10,访问父类成员变量
    }
}

// 使用
public class Demo01ExtendsField {
    public static void main(String[] args) {
        Son son = new Son();
        // 调用子类的成员方法,查看输出结果
        son.method();
    }
}

在这里插入图片描述

方法的访问特点

在父子类的继承关系当中,创建子类对象,访问成员方法的规则是:

创建的对象是谁,就优先用谁,如果没有则向上找。

单以继承举例,子类对象在调用方法时,会优先执行本类中相应的方法,如果子类中没有该方法则会执行父类相应的方法。

注意事项

无论是成员方法还是成员变量,如果没有都是向上找父类绝不会向下找子类

// 父类
public class Fu {
    public void methodFu(){
        System.out.println("父类方法执行!");
    }


    public  void method()
    {
        System.out.println("父类重名方法执行了");
    }
}

// 子类
public class Zi extends Fu{
    public void methodZi(){
        System.out.println("子类方法执行!");
    }

    public  void method()
    {
        System.out.println("子类重名方法执行了");
    }
}

// 使用
public class Demo01ExtendsMethod {
    public static void main(String[] args) {
        Zi zi = new Zi();

        zi.methodFu();
        zi.methodZi();

        // 调用重名成员方法,查看输出结果
        zi.method();
    }
}

在这里插入图片描述

方法的重写

如果子类父类中出现重名的成员方法,这时的访问是一种特殊情况,叫做方法重写 (Override)。

方法重写 :子类中出现与父类一模一样的方法时(返回值类型,方法名和参数列表都相同),会出现覆盖效果,也称为重写或者复写。声明不变,重新实现

class Fu {
    public void show() {
        System.out.println("Fu show");
    }
} class Zi extends Fu {
    //子类重写了父类的show方法
    @Override
    public void show() {
        System.out.println("Zi show");
    }
} public class ExtendsDemo05{
    public static void main(String[] args) {
        Zi z = new Zi();
        // 子类中有show方法,只执行重写后的show方法
        z.show(); // Zi show
    }
}

重写与重载的区别

重写(Override):在继承关系当中,方法的名称一样参数列表一样

重载(OverLoad):方法的名称一样,参数列表不一样

覆盖重写的特点

创建的是子类对象,则优先用子类方法

覆盖重写的应用

子类可以根据需要,定义特定于自己的行为。既沿袭了父类的功能名称,又根据子类的需要重新实现父类方法,从而进行扩展增强。比如新的手机增加来电显示头像的功能,代码如下:

// 老手机
public class Phone {
    public void call() {
        System.out.println("打电话");
    }
    public void send() {
        System.out.println("发短信");
    }
    public void show() {
        System.out.println("显示号码");
    }
}

// 定义一个新手机,使用老手机作为父类
public class NewPhone extends Phone {
    @Override
    public void show() {
        super.show(); // 把父类的show方法拿过来使用

        // 自己再增加新功能
        System.out.println("显示姓名");
        System.out.println("显示头像");
    }
}

public class Demo01Phone {
    public static void main(String[] args) {
        Phone phone = new Phone();

        // 父类手机
        phone.call();
        phone.send();
        phone.show();

        System.out.println("==========");

        NewPhone newPhone = new NewPhone();

        newPhone.call();
        newPhone.send();
        newPhone.show();
    }
}

在这里插入图片描述

覆盖重写注意事项(重要)

  1. 必须保证父子类之间方法的名称相同参数列表也相同

    @Override 写在方法前,用来检测是不是有效的正确覆盖重写。

@Override  // 如果不是有效覆盖会报错
public void method()
{
    // ... 
}
  1. 子类方法的返回值类型必须小于等于父类方法的返回值范围。

    例如:javajava.lang.Object类是所有类的公共最高父类,如果父类的返回值是String,子类的返回值是Object,这是错误写法会编译报错!

在这里插入图片描述
在这里插入图片描述

  1. 父类被重写方法的返回值类型是void,则子类重写的方法返回值类型也必须是void。
    在这里插入图片描述

  2. 子类方法的权限修饰符必须大于等于父类方法的权限修饰符。

    权限修饰符:public > protected > (default) > private

    备注:(default) 不是关键字default,而是什么都不写,留空。

在这里插入图片描述
在这里插入图片描述
4. 子类不能重写父类中声明为private权限的方法
在这里插入图片描述

  1. 子类方法抛出的异常不能大于父类被重写方法的异常

  2. 子类和父类中的同名同参数的方法,要么声明为非static的(重写),要么都声明为static的(不是重写)。

构造器的访问特点

  1. 子类构造器当中,有一个默认隐含的super()调用,子类在创建对象时会默认先执行父类构造器,再执行子类构造器
// 父类
public class Fu {

    public Fu(){
        System.out.println("父类构造器!");
    }
}

// 子类
public class Zi extends Fu{

    public Zi(){
        // super(); 默认隐含调用无参父类构造,不写也会有
        System.out.println("子类构造器!");
    }
}

public class Demo01Constructor {
    public static void main(String[] args) {
        Zi zi = new Zi();
    }
}

在这里插入图片描述
2. 可以通过**super**关键字,调用父类重载的构造器

// 父类
public class Fu {

    public Fu(){
        System.out.println("父类无参构造器!");
    }
    
    public Fu(int num){
        System.out.println("父类有参构造器!");
    }
    
}

// 子类
public class Zi extends Fu{

    public Zi(){
        super(10); // 调用父类有参构造器
        System.out.println("子类构造器!");
    }
}

public class Demo01Constructor {
    public static void main(String[] args) {
        Zi zi = new Zi();  // 此时会调用父类有参构造
    }
}

在这里插入图片描述

  1. 子类的构造器中必须要调用父类的构造器,如果父类只定义了一个有参构造器,且子类没有在构造器中调用父类的有参构造器,编译会报错。
public class Fu{
	public Fu(int param){
		System.out.println(param);
    }
        
}
public class Zi{
    public Zi(){  // 编译不通过,父类没有空参构造器,子类必须调用父类的有参构造器
        
    }
}
// 修改方式如下:
// 1. 在子类构造器中调用父类的带参构造器
public class Zi{
    public Zi(){
        super(123);
    }
}

// 2. 给父类提供有参构造器
public class Fu{
    public Fu(){
        
    }
    public Fu(int param){
        System.out.println(param);
    }
}
  1. super的父类调用,必须是子类构造器的第一个语句。

错误写法:

public void method(){
    super(); // 错误写法!只有子类构造器,才能调用父类构造器
}
public class Zi extends Fu{

    public Zi(){
        super();
        super(10); // 错误写法!只能调用一个父类构造
        System.out.println("子类构造器!");
    }
}
public class Zi extends Fu{

    public Zi(){
        System.out.println("子类构造器!");
        super(10); // 错误写法!super() 必须是第一个语句
    }
}

在这里插入图片描述

两个关键字

super

super关键字用来访问父类内容,代表父类的内存空间的标识。

父类的成员变量不会被覆盖重写,如果父类和子类中有同名的成员变变量,各自归属于不同的类,如果想在子类中调用父类的成员变量则也需要使用super关键字。

super关键字的用法有三种:

  1. 在子类的成员方法中,访问父类的成员变量。
  2. 在子类的成员方法中,访问父类的成员方法。
  3. 在子类的构造器中,调用父类的构造器。

注意:

  1. 子类中使用super([形参列表])的方式调用父类构造器,必须声明在子类构造器中的首行
  2. 子类的构造器中会默认隐含一个super() ,如果再定义了一个super([参数列表]),则不会在默认隐含。
  3. 在类的多个构造器中,至少有一个类的构造器中使用了super([形参列表]),调用父类的构造器(没有直接父类,那么还有Object接盘)。
  4. super的追溯不仅限于直接父类。
// 父类
public class Fu {
    int num = 10; // 父类私有成员变量

    public Fu(int num){
        System.out.println("父类构造");
    }

    public void method() {
        System.out.println("父类方法!");
    }
}
// 子类
public class Zi extends Fu{
    int num = 20;
    public Zi()  {
        super(10);        // 调用父类构造器
    }

    public void methodZi() {
        System.out.println(super.num);  // 父类的num
    }

    public void method() {
        super.method();    // 访问父类中的method
        System.out.println("子类方法!");
    }
}

this

this关键字用来访问本类内容

this关键字的三种用法:

  1. 在本类的成员方法中,访问本类的成员变量

  2. 在本类的成员方法中,访问本类的另一个成员方法

  3. 在本类的构造器中,访问本类的另一个构造器

    this(...) 调用也必须是构造器的第一个语句,唯一一个。

注意

super(...)this(...)两种构造调用,不能同时使用,因为this(…) 和 super(…) 都必须是构造器中的第一个语句

// 父类
public class Fu {
    int num = 30;
}

// 子类
public class Zi extends Fu{

    int num = 20; 
    
    // 无参构造
    public Zi() {
        this(10);  // 本类无参构造调用本类有参构造
                   // 必须是构造器的第一条(唯一)语句
    }
    
    // 有参构造
    public Zi(int n) {  
        
    }
    
    public void showNum(){
        int num = 10; 
        System.out.println(num); // 局部变量
        System.out.println(this.num);  // 本类成员变量
        System.out.println(super.num); // 父类中的成员变量
    }

    public void methodA() {
        System.out.println("AAA");
    }

    public void methodB(){
        methodA();
        this.methodA();  // 两种效果系相同,使用this关键字强调本类方法
        System.out.println("BBB");
    }
}

Java继承中的三个特点

  1. Java语言是单继承的,一个类的直接父类只有唯一一个

  2. Java语言可以多级继承,即可以有父亲,爷爷,祖宗…。

  3. 子类的直接父类是唯一的,但是父类可以拥有多个子类

在这里插入图片描述

子类对象实例化的过程

从结果上来看(继承性)

子类继承父类以后,就获取了父类中声明的属性或方法。

创建子类的对象,在堆空间中,就会加载所有父类中声明的属性(不是创造父类对象)。

在这里插入图片描述

从过程上来看

通过子类的构造器创建子类对象时,一定会直接或间接的调用父的构造器,进而调用父类的父类的构造器,直到调用了java.lang.Object类中的空参构造器。这也就是为什么子类会继承所有父类的属性或方法。

在这里插入图片描述
注意:虽然创建子类对象的过程中,调用了父类的构造器,但是自始至终只创建了一个对象,即为new的子类对象。



这篇关于Java学习 -- 继承性的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程