JAVA语言-类的继承详解

目录

1 为什么需要继承

2 继承概念

3 继承的语法

3.1 extends关键字

3.2 父类成员访问

3.2.1 子类中访问父类的成员变量

1. 子类和父类不存在同名成员变量

2. 子类和父类成员变量同名

3.2.2 子类中访问父类的成员方法

1. 成员方法名字不同

2. 成员方法名字相同

4 super关键字

5 子类构造方法

6 super和this

7 再谈初始化

8 protected 关键字

9 继承方式

10 fifinal 关键字

11 继承与组合


为什么需要继承

设想一下,我们要写一个狗类,我们会这样写:

public class Dog{
	public String name;
	public String color;
	public int age;
	
	public void eat() {
		System.out.println(name+"正在吃狗粮...");
	}
	
	public void bark() {
		System.out.println(name+"正在汪汪叫~");
	}
}

如果再让我们写一个猫类呢,我们也许会这样写:

public class Cat {
	public String name;
	public String color;
	public int age;
	
	public void eat() {
		System.out.println(name+"正在吃猫粮...");
	}
	
	public void mew() {
		System.out.println(name+"正在喵喵叫~");
	}
}

但是这里有一个问题,狗类和猫类的name、color、age、eat这些成员重复了,这样会显得代码

冗余


只写一个狗类和猫类可能不是很明显,那么如果再写一个鸟类、鱼类....等等。

每写一个这样的类我们就需要重复写这样一段代码,那么有没有办法能够省略呢?

有!这就需要用到我今天讲的继承

继承的意义即:达到代码的复用。

继承概念

继承 (inheritance) 机制 :是面向对象程序设计使代码可以复用的最重要的手段,它允许程序员在保持原有类特性的基础上进行扩展,增加新功能 ,这样产生新的类,称 派生类 。继承呈现了面向对象程序设计的层次结构, 体现了由简单到复杂的认知过程。继承主要解决的问题是:共性的抽取,实现代码复用

继承的类被称为子类,或者派生类。

被继承的类被称为父类/超类 或者基类。

子类会继承父类的特性,也能够派生出父类没有的特性。

例如:

上述图示中, DogCat都继承了Animal ,其中: Animal 类称为父类 / 基类或超类, Dog Cat 可以称为 Animal 的子类/ 派生类,继承之后,子类可以复用父类中成员,子类在实现时只需关心自己新增加的成员即可。
从继承概念中可以看出继承最大的作用就是:实现代码复用,还有就是来实现多态 ( 后序讲 )

继承的语法

3.1 extends关键字

Java中如果要表示类之间的继承关系,需要借助extends关键字,具体如下:

修饰符 class 子类 extends 父类 {
// ...
}

用上面的动物类和猫类狗类为例的话,就是这样:

// Animal.java
public class Animal {
String name ;
String color;
int age ;
public void eat (){
System . out . println ( name + " 正在吃饭 " );
}
public void sleep (){
System . out . println ( name + " 正在睡觉 " );
}
}
// Dog.java
public class Dog extends Animal {
void bark (){
System . out . println ( name + " 汪汪汪 ~~~" );
}
}
// Cat.Java
public class Cat extends Animal {
void mew (){
System . out . println ( name + " 喵喵喵 ~~~" );
}
}
将猫类和狗类实例化测试一下是否真的继承了父类的属性:
public class Test {
    public static void main(String[] args) {
        Dog dog=new Dog();
        dog.name="大黄";
        dog.color="黄色";
        dog.age=3;
        dog.bark();

        Cat cat=new Cat();
        cat.name="小黑";
        cat.color="黑色";
        cat.age=2;
        cat.mew();
    }
}

运行结果:

Cat类和Dog类中并未定义name、color、age等变量,但是实例化后的对象却可以访问到这些成员

说明子类Cat和子类Dog成功继承了父类Animal的属性。

注意事项

1. 子类会将父类中的成员变量或者成员方法继承到子类中了
2. 子类继承父类之后,必须要新添加自己特有的成员,体现出与基类的不同,否则就没有必      要继承

3.2 父类成员访问

3.2.1 子类中访问父类的成员变量

1. 子类和父类不存在同名成员变量
public class Base {
int a ;
int b ;
}
public class Derived extends Base {
int c ;
public void method (){
a = 10 ; // 访问从父类中继承下来的 a
b = 20 ; // 访问从父类中继承下来的 b
c = 30 ; // 访问子类自己的 c
}
}
2. 子类和父类成员变量同名

这是一个父类:

public class Base {
    public int a=100;
    public int b=200;
}
这是一个它的子类:
public class Derived extends Base{
    public int a=1000;
    public char b = 'A';

    public void method(){
        System.out.println(a);
        System.out.println(b);
    }

    public static void main(String[] args) {
        Derived derived=new Derived();
        derived.method();
    }
}

当子类和父类存在相同的变量名的时候,子类访问的是子类的成员变量还是父类的成员变量呢?
运行结果:
调用子类成员方法method,输出的是1000和A,而不是100和200。说明变量名相同时,子类会优先调用子类的成员变量。
【注意事项】
在子类方法中 或者 通过子类对象访问成员时
如果访问的成员变量子类中有,优先访问自己的成员变量。
如果访问的成员变量子类中无,则访问父类继承下来的,如果父类也没有定义,则编译报错。
如果访问的成员变量与父类中成员变量同名,则优先访问自己的。
成员变量访问遵循就近原则,自己有优先自己的,如果没有则向父类中找

3.2.2 子类中访问父类的成员方法

1. 成员方法名字不同
public class Base {
public void methodA (){
System . out . println ( "Base 中的 methodA()" );
}
}
public class Derived extends Base {
public void methodB (){
System . out . println ( "Derived 中的 methodB() 方法 " );
}
public void methodC (){
methodB (); // 访问子类自己的 methodB()
methodA (); // 访问父类继承的 methodA()
// methodD(); // 编译失败,在整个继承体系中没有发现方法 methodD()
}
}
总结:成员方法没有同名时,在子类方法中或者通过子类对象访问方法时,则优先访问自己的,自己没有时再到父类中找,如果父类中也没有则报错。
2. 成员方法名字相同
public class Base {
public void methodA (){
System . out . println ( "Base 中的 methodA()" );
}
public void methodB (){
System . out . println ( "Base 中的 methodB()" );
}
}
public class Derived extends Base {
public void methodA ( int a ) {
System . out . println ( "Derived 中的 method(int) 方法 " );
}
public void methodB (){
System . out . println ( "Derived 中的 methodB() 方法 " );
}
public void methodC (){
methodA (); // 没有传参,访问父类中的 methodA()
methodA ( 20 ); // 传递 int 参数,访问子类中的 methodA(int)
methodB (); // 直接访问,则永远访问到的都是子类中的 methodB() ,基类的无法访问到
}
}
【说明】
通过子类对象访问父类与子类中不同名方法时,优先在子类中找,找到则访问,否则在父类中找,找到则访问,否则编译报错。
通过派生类对象访问父类与子类同名方法时,如果父类和子类同名方法的参数列表不同 ( 重载 ) ,根据调用方法适传递的参数选择合适的方法访问,如果没有则报错;
问题:如果子类中存在与父类中相同的成员时,那如何在子类中访问父类相同名称的成员呢?
答   :这就需要用到接下来要讲的  super关键字

4 super关键字

由于设计不好,或者因场景需要,子类和父类中可能会存在相同名称的成员,如果要在子类方法中访问父类同名成员时,该如何操作?直接访问是无法做到的,Java 提供了 super 关键字,该关键字主要作用:在子类方法中访问父 类的成员

对于以下的基类和派生类:

public class Base {
    public int a=100;
    public int b=200;
    public void methodA(){
        System.out.println("这是Base中的methodA");
    }
}
public class Derived extends Base{
    public int a=1000;//变量名和类型名相同
    public char b = 'A';//变量名相同 类型名不同

    public void methodA(){
        System.out.println("这是Derived中的methodA");
    }
    public void method(){
        System.out.println(a);
        System.out.println(b);
        methodA();
    }

    public static void main(String[] args) {
        Derived derived=new Derived();
        derived.method();
    }
}

根据我们上面学的知识,当我调用Derived类中的method方法时,输出的应当是Derived中的属性

和方法,运行结果如下:

如果要让method输出父类的属性和methodA方法,就要用到super关键字,就像下面这样:

    public void method(){
        System.out.println(super.a);
        System.out.println(super.b);
        super.methodA();
    }

总结:在子类方法中,如果想要明确访问父类中成员时,借助super关键字即可。

【注意】:只能在非静态方法中使用

子类构造方法

父子父子,先有父再有子,即:子类对象构造时,需要先调用基类构造方法,然后执行子类的构造方法。

以刚才的父类Base和子类Derived为例:

public class Base {
    public int a=100;
    public int b=200;
    public Base(){
        System.out.println("这是Base的构造方法");
    }
}
public class Derived extends Base{
    public int a=1000;//变量名和类型名相同
    public char b = 'A';//变量名相同 类型名不同

    public Derived(){
        // super(); // 注意子类构造方法中默认会调用基类的无参构造方法:super(), 
        // 用户没有写时,编译器会自动添加,而且super()必须是子类构造方法中第一条语句, 
        // 并且只能出现一次
        System.out.println("这是Derived的构造方法");
    }

    public static void main(String[] args) {
        Derived derived=new Derived();
    }
}

运行结果如下:

在子类构造方法中,并没有写任何关于基类构造的代码,但是在构造子类对象时,先执行基类的构造方法,然后执行子类的构造方法,因为:子类对象中成员是有两部分组成的,基类继承下来的以及子类新增加的部分 。父子父子 肯定是先有父再有子,所以在构造子类对象时候 ,先要调用基类的构造方法,将从基类继承下来的成员构造完整 ,然后再调用子类自己的构造方法,将子类自己新增加的成员初始化完整。
【注意事项】
1. 父类显式定义 无参 或者默认的构造方法,在子类构造方法第一行默认有隐含的 super() 调用,即调用基类构造方法。
2. 如果父类构造方法是带 有参 的,此时需要用户为子类显式定义构造方法,并在子类构造方法中选择合适的父类构造方法调用,否则编译失败。
3. 在子类构造方法中, super(...) 调用父类构造时,必须是子类构造函数中第一条语句
4. super(...) 只能在子类构造方法中出现一次,并且不能和this同时出现

6 superthis

super this 都可以在成员方法中用来访问:成员变量和调用其他的成员函数,都可以作为构造方法的第一条语句,那他们之间有什么区别呢?
相同点
1. 都是 Java 中的关键字
2. 只能在类的非静态方法中使用,用来访问非静态成员方法和字段
3. 在构造方法中调用时,必须是构造方法中的第一条语句,并且不能同时存在
不同点
1. this 是当前对象的引用,当前对象即调用实例方法的对象, super 相当于是子类对象中从父类继承下来部分成员的引用
2. 在非静态成员方法中, this 用来访问本类的方法和属性, super 用来访问父类继承下来的方法和属性
3. 在构造方法中: this(...) 用于调用本类构造方法, super(...) 用于调用父类构造方法,两种调用不能同时在构造方法中出现
4. 构造方法中一定会存在 super(...) 的调用,用户没有写编译器也会增加,但是 this(...) 用户不写则没有

再谈初始化

在讲接下来的内容之前,我们先回顾一下之前的代码块相关的知识。

构造代码块,静态代码块,以及构造方法的执行顺序...来看以下代码:

public class Derived {
    public Derived(int data) {
        System.out.println("这是Derived的构造方法");
    }

    {
        System.out.println("这是Derived的实例代码块");
    }
    static{
        System.out.println("这是Derived的静态代码块");
    }
}

运行结果:

结论:

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

上述的内容是在没有继承关系的情况下的结论。

如果存在继承关系的执行顺序呢?

public class Base {
    public int Data;
    public Base(int data) {
        Data = data;
        System.out.println("这是Base的构造方法");
    }
    {
        System.out.println("这是Base的实例代码块");
    }
    static{
        System.out.println("这是Base的静态代码块");
    }
}
public class Derived extends Base{
    public Derived(int data) {
        super(data);
        System.out.println("这是Derived的构造方法");
    }

    {
        System.out.println("这是Derived的实例代码块");
    }
    static{
        System.out.println("这是Derived的静态代码块");
    }
}

对于上面继承了父类的子类,我们另外用一个Test类来运行:

public class Test {
    public static void main(String[] args) {
        Derived derived=new Derived(100);
    }
}

运行结果如下:

通过分析结果,得出以下结论:

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

8 protected 关键字

​​​​​​
在类和对象章节中,为了实现封装特性, Java 中引入了访问限定符,主要限定:类或者类中成员能否在类外或者其他包中被访问。

那父类中不同访问权限的成员,在子类中的可见性又是什么样子的呢?

来看一下代码:

//父类
public class B {
    private int a;
    protected int b;
    public int c;
    int d;
}
//同一包内的子类
public class D extends B{
    public void method(){
        //super.a=10;//编译报错,父类中private成员在不同包子类中不可见
        super.b=20;
        super.c=30;
        super.d=40;
    }
}
//不同包的子类
public class C extends B {
    public void method(){
        //super.a=10;//编译报错,父类中private成员在不同包子类中不可见
        super.b=20;
        super.c=30;
        //super.d=40;//编译报错 父类中默认访问权限修饰的成员在不同包子类中不能直接访问
    }
}
//不同包中的类
public class TestC {
    public static void main(String[] args) {
        C c=new C();
        c.a=10;// 编译报错,父类中private成员在不同包其他类中不可见
        c.b=20;// 父类中protected成员在不同包其他类中不能直接访问
        c.c=30;// 父类中public成员在不同包其他类中可以直接访问
        c.d=40;// 父类中默认访问权限修饰的成员在不同包其他类中不能直接访问
    }
}
【注意】:父类中private成员变量虽然在子类中不能直接访问,但是也继承到子类中了
什么时候下用哪一种呢?
我们希望类要尽量做到 "封装", 即隐藏内部实现细节, 只暴露出 必要 的信息给类的调用者.
因此我们在使用的时候应该尽可能的使用 比较严格 的访问权限. 例如如果一个方法能用 private, 就尽量不要用 public.
另外, 还有一种 简单粗暴 的做法: 将所有的字段设为 private, 将所有的方法设为 public. 不过这种方式属于是对访问权限的滥用, 还是更希望同学们能写代码的时候认真思考, 该类提供的字段方法到底给 "谁" 使用(是类内部自己用, 还是类的调用者使用, 还是子类使用)

继承方式

Java中只支持以下几种继承方式:

注意: Java 中不支持多继承

10 final 关键字

final关键可以用来修饰变量、成员方法以及类。

1. 修饰变量或字段,表示常量(即不能修改)

final int a = 10 ;
a = 20 ; // 编译出错

2. 修饰类:表示此类不能被继承

final public class Animal {
...
}
public class Bird extends Animal {
...
}
// 编译出错
Error :( 3 , 27 ) java : 无法从最终 com . bit . Animal 进行继承

3. 修饰方法:表示该方法不能被重写

11 继承与组合

和继承类似 , 组合也是一种表达类之间关系的方式 , 也是能够达到代码重用的效果。组合并没有涉及到特殊的语法 (诸如 extends 这样的关键字 ), 仅仅是将一个类的实例作为另外一个类的字段。
继承表示对象之间是 is-a 的关系 ,比如:狗是动物,猫是动物
组合表示对象之间是 has-a的关系,比如:汽车(有轮子,发动机,车载系统...等等)
// 轮胎类
class Tire {
// ...
}
// 发动机类
class Engine {
// ...
}
// 车载系统类
class VehicleSystem {
// ...
}
class Car {
private Tire tire ; // 可以复用轮胎中的属性和方法
private Engine engine ; // 可以复用发动机中的属性和方法
private VehicleSystem vs ; // 可以复用车载系统中的属性和方法
// ...
}
// 奔驰是汽车
class Benz extend Car {
// 将汽车中包含的:轮胎、发送机、车载系统全部继承下来
}
组合和继承都可以实现代码复用,应该使用继承还是组合,需要根据应用场景来选择,一般建议:能用组合尽量用组合。

看完了觉得有用的姥爷们求求您们点个免费的赞和关注,这对我真的很重要!非常感谢大家!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值