Java SE知识点五:面向对象之:继承

1. 什么是继承

1.1 继承的概念

Java中的类是对事物的抽象描述,但有时候一个类与其他类有许多的共性,比如猫和狗都是动物,那么猫和狗这两个类就难免会有许多相似之处:

class Dog{
    public String name;
    public int age;
    public String color;

    public void eat(){
    
    }

    public void sleep(){

    }
}

class Cat{
    public String name;
    public int age;
    public String color;

    public void eat(){
    
    }

    public void sleep(){

    }
}

可以看到,这两个类的成员变量和方法都可以有许多相似之处。

那么,是不是可以将这些共性的东西提取出来,形成一个动物类,让猫和狗之间使用呢?这就要说到Java中的继承了。所以,可以定义一个动物类,然后让猫类和狗类继承这些共性。

1.2 继承的语法

继承使用的是 extends 关键字:

class Animal{
    String name;
    int age;
    public void eat(){
        System.out.println(name + "eating");
    }
    public void sleep(){
        System.out.println(name + "sleeping");
    }
}


class Dog extends Animal{
    //继承后,狗类就可以使用动物类的成员变量和方法
    void bark(){
        System.out.println(name + "barking");
    }
}

public class TestExtend {
    public static void main(String[] args) {
        Dog dog = new Dog();
        // name和age属性肯定是从父类Animal中继承下来的
        System.out.println(dog.name);
        System.out.println(dog.age);
        // 方法也是从Animal中继承下来的
        dog.eat();
        dog.sleep();
        dog.bark(); //子类自己的方法
    }
}

我们称被继承的类为父类或基类,继承者称为子类。

子类可以在自己的类中添加特有的方法和变量,以区分共性和特性。

2. 父类与子类

2.1 子类访问父类成员变量

当子类中的成员变量与父类当中的成员变量不同时,子类可以直接访问父类的成员变量。

class Base {
    int a;
    int b;
}

class Derived extends Base{
    int c;
    public void method(){
        a = 10; // 访问从父类中继承下来的a
        b = 20; // 访问从父类中继承下来的b
        c = 30; // 访问子类自己的c
    }
}

因为子类继承了父类的成员变量,所以当变量名称不同时,可以区分出父类和子类的成员变量。

而当子类中的成员变量与父类中的成员变量名称相同时呢?

class Base {
    int a;
    int b;
}

class Derived extends Base{
    int c;
    int a; //子类中有同名变量

    public void method(){
        a = 10; // 访问的是谁的 a ?
        b = 20; // 访问从父类中继承下来的b
        c = 30; // 访问子类自己的c
    }
}

这个情况下,会访问父类的变量 a 还是子类的呢? 

答案是会访问子类的成员变量,因为遵循以下规律:

  • 如果访问的成员变量子类中有,优先访问子类自己的;
  • 如果访问的成员变量子类中没有,则访问从父类中继承下来的,如果父类也没有,则报错;
  • 如果访问的成员变量子类和父类中都有且同名,则优先访问子类自己的。

2.2 子类访问父类成员方法

访问成员方法的情况与成员变量相同,如果成员方法名称不同,则访问父类继承的。

class Base {
    public void methodA(){
        System.out.println("Base中方法");
    }
}


class Derived extends Base{
    public void methodB(){
        System.out.println("Derived中的方法");
    }
    public void methodC(){
        methodB(); // 访问子类自己的methodB()
        methodA(); // 访问父类继承的methodA()
    }
}

那么当子类中的成员方法和父类中的相同时,怎么访问呢?

class Base {
    public void methodA(){
        System.out.println("Base中的A方法");
    }
    public void methodB(){
        System.out.println("Base中的B方法");
    }
}

class Derived extends Base{
    public void methodA(int a) {
        System.out.println("Derived中的int A方法");
    }
    public void methodB(){
        System.out.println("Derived中的B方法");
    }

    public void methodC(){
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)
        methodB(); // 直接访问,则永远访问到的都是子类中的methodB(),基类的无法访问到
    }
}

这里也会遵循以下规则:

  • 通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
  • 通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同(重载),根据调用方法适传递的参数选择合适的方法访问,如果没有则报错

那么就会引出一个问题,当子类中的成员名称和父类中的相同时,怎么访问父类的成员呢?

这就要引出 super 关键字了。

3. super关键字

3.1 super 的用法

有时候,会出现子类和父类存在相同名称的成员,那么这种情况下如何访问父类的成员呢,Java中的 super 关键字可以在子类中访问父类成员。

class Base {
    int a;
    int b;
    public void methodA(){
        System.out.println("Base中的A方法");
    }
    public void methodB(){
        System.out.println("Base中的B方法");
    }
}

class Derived extends Base{
    int a; // 与父类中成员变量同名且类型相同
    char b; // 与父类中成员变量同名但类型不同

    // 与父类中methodA()构成重载
    public void methodA(int a) {
        System.out.println("Derived中的int A方法");
    }

    // 与基类中methodB()构成重写(即原型一致)
    public void methodB(){
        System.out.println("Derived中的B方法");
    }

    public void methodC(){
        // 对于同名的成员变量,直接访问时,默认访问的都是子类的
        a = 100; // 等价于: this.a = 100;
        b = 101; // 等价于: this.b = 101;
        // 注意:this是当前对象的引用

        // 访问父类的成员变量时,需要借助super关键字
        // super是获取到子类对象中从父类继承下来的部分
        super.a = 200;
        super.b = 201;

        // 父类和子类中构成重载的方法,直接可以通过参数列表区分清访问父类还是子类方法
        methodA(); // 没有传参,访问父类中的methodA()
        methodA(20); // 传递int参数,访问子类中的methodA(int)

        // 如果在子类中要访问重写的基类方法,则需要借助super关键字
        super.methodB(); // 访问父类的methodB()
    }
}

从上述代码中可以总结一下:

  • 当成员变量同名时,在子类中,this. 访问子类自己的成员,super. 访问父类的;
  • 当成员方法同名时(不是方法的重载),构成方法的重写(即返回值相同,参数相同,名称相同),这时,使用 super. 访问父类的成员方法;

需要注意的是:1. this访问的时候,不仅可以访问子类的,也可以访问父类的
                         2. 当使用this访问父类和子类同名的成员变量时,优先访问子类
                         3. super只能访问从父类继承过来的成员变量

3.2 子类的构造方法

构造方法前面也提到过,和成员方法的区别是,成员方法有 void ,而构造方法没有。

当父类中存在构造方法时,无论有无参数,子类在继承时,都要和父类一样存在构造方法且参数相同。而且,在执行子类中的构造方法时,会先执行父类的构造方法。

使用 IntelliJ IDEA 写代码时,可以鼠标右键--生成,一键生成构造方法,还是很方便的。

class Animals{
    String name;
    int age;

    public Animals(String name) { //一个参数的构造方法
        this.name = name;
    }

    public void eat(){  //子类重写父类的成员方法
        System.out.println(name + "正在吃饭");
    }
    public void sleep(){
        System.out.println(name + "正在睡觉");
    }
}

//子类dog
class Dog extends Animals{

    public Dog(String name) { //子类中可以创造匹配super的构造方法
        super(name);
    }
    public void eat(){
        System.out.println(name + "正在吃狗粮");
    }

    public void bark(){
        System.out.println(name + "汪汪汪~~~");
    }
}

需要注意的是:

  1. 若父类显式定义无参或者默认的构造方法,在子类构造方法第一行默认有隐含的super()调用,即调用基类构 造方法
  2. 如果父类构造方法是带有参数的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的 父类构造方法调用,否则编译失败。
  3. 在子类构造方法中,super(...)调用父类构造时,必须是子类构造函数中第一条语句。
  4. super(...)只能在子类构造方法中出现一次,并且不能和 this 同时出现,因为都只能在第一行

3.3 super 与 this

super和this都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?

相同点:

  1. 都是Java中的关键字
  2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
  3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在


不同点:

  1. this是当前对象的引用,当前对象即调用实例方法的对象,super相当于是子类对象中从父类继承下来部分成员的引用
  2. 在非静态成员方法中,this用来访问本类的方法和属性,super用来访问父类继承下来的方法和属性
  3. 在构造方法中,this(...) 用于调用本类构造方法,super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现
  4. 构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加,但是this(...)用户不写则没有

3.4 继承当中的初始化

上一章提到了使用代码块进行初始化,实例代码块 和 静态代码块 的执行顺序是:静态>实例。

而出现构造方法后,它们的执行顺序会有什么变化:

  1. 静态代码块先执行,并且只执行一次,在类加载阶段执行
  2. 当有对象创建时,才会执行实例代码块,实例代码块执行完成后,最后构造方法执行

所以,它们三个点执行顺序是:静态代码块(只执行一次) > 实例代码块 > 构造方法。

以上是在非继承情况下,那么在继承情况下,它们的顺序呢:

  1. 父类静态代码块优先于子类静态代码块执行,且是最早执行
  2. 父类实例代码块和父类构造方法紧接着执行
  3. 子类的实例代码块和子类构造方法紧接着再执行 
  4. 第二次实例化子类对象时,所有静态代码块都不执行

结论是,在继承环境下,首先:父类 > 子类,其次:静态 > 实例 > 构造。

4. protected关键字

我们前面提到的几个限定词:public,private,default,protected,其中protected提到了可以在不同包的子类中访问,这里,提到了子类,那父类中不同访问权限的成员,在子类中的可见性又是什么样子的呢?

// extend01包中
class B {
    private int a; 
    protected int b; 
    public int c;
    int d;
}

// extend01包中
// 同一个包中的子类 
class D extends B{
    public void method(){
        // super.a = 10;// 编译报错,父类private成员在相同包子类中不可见 
        super.b = 20; // 父类中protected成员在相同包子类中可以直接访问
        super.c = 30; // 父类中public成员在相同包子类中可以直接访问
        super.d = 40; // 父类中默认访问权限修饰的成员在相同包子类中可以直接访问
    } 
}

// extend02包中
// 不同包中的子类
class C extends B {
    public void method(){
        // super.a = 10; // 编译报错,父类中private成员在不同包子类中不可见
        super.b = 20; // 父类中protected修饰的成员在不同包子类中可以直接访问
        super.c = 30; // 父类中public修饰的成员在不同包子类中可以直接访问
        //super.d = 40; // 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
    }
}

上述代码解释了protected限定词在不同包的子类中可以访问的情况。

很明显,如果在不同包的非子类,即其他类中则不能访问。

5. 继承的多种方式

继承的方式有很多种,像刚才提到的狗继承动物类,是单继承

还有多层继承 和 不同类继承同一类。

多层继承:C 继承 B,B 继承 A。因此 C 也间接继承了 A。

不同类继承同一类:C 和 B 同时继承了 A,但 C 和 B直接没有继承关系。

需要注意,Java中不支持一个类继承多个类,但这种思想可以通过后面会提到的接口来实现。

6. final 关键字

final 关键字也是用来修饰成员变量和成员方法的:

修饰变量的时候就会把变量变成常量,不能修改:

final int a = 10;
a = 20; //error

修饰方法的时候也是同理。表示该方法不能被重写。

另外,final 关键字还可以用来修饰类,被 final 修饰的类表示不能被继承。我们平时使用的 String 字符串类就是被 final 修饰的,所以不能被修改(可以使用 StringBuilder)。

7. 继承与组合

看到这里,大家可能会对继承有了比较深的了解,就像字面意思中的关系:狗是动物,猫是动物。继承者与被继承者之间是 is - a 的关系。

而还有一种思想是组合,就像是汽车与部件之间的关系:汽车有轮胎。 是一种 has - a 的关系。

下面举一个例子:

// 轮胎类 
class Tire{
    // ...
}
// 发动机类 
class Engine{
    // ...
}
// 车载系统类
class VehicleSystem{
    // ...
}

class Car{
    private Tire tire; // 可以复用轮胎中的属性和方法
    private Engine engine; // 可以复用发动机中的属性和方法
    private VehicleSystem vs; // 可以复用车载系统中的属性和方法
    // ...
}

上述代码就是一个组合的过程,组合没有什么特别的语法结构,是一种思想。

当然,组合可以和继承串联起来,上述的汽车类,就可以具体化一个宝马汽车,创建一个宝马汽车,然后继承汽车类。

小结:

这一章主要讲了Java面向对象的关键部分:继承,后面还提到了组合的思想,都是日常中经常用到的。下面就是又一个关键部分:多态。

  • 28
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值