【第五章】Java中的继承与多态

【第五章】Java------继承、抽象类与多态

  • 一、为什么要学习继承、抽象类与多态
    • [1. 单纯使用类和对象还不够](#1. 单纯使用类和对象还不够)
    • [2. 代码复用的需求非常强烈](#2. 代码复用的需求非常强烈)
    • [3. 现实世界中天然存在"is-a"关系](#3. 现实世界中天然存在“is-a”关系)
    • [4. 有些父类只想拿来约束,不想直接创建对象](#4. 有些父类只想拿来约束,不想直接创建对象)
    • [5. 程序不仅要复用,还要可扩展](#5. 程序不仅要复用,还要可扩展)
  • 二、什么是继承
    • [1. 继承的基本概念](#1. 继承的基本概念)
    • [2. 继承的本质](#2. 继承的本质)
    • [3. 继承的意义](#3. 继承的意义)
  • 三、继承的基本语法
    • [1. 语法格式](#1. 语法格式)
    • [2. 示例](#2. 示例)
  • 四、子类到底继承了父类什么
    • [1. 子类会继承父类的非私有成员](#1. 子类会继承父类的非私有成员)
    • [2. 构造方法不能被继承](#2. 构造方法不能被继承)
    • [3. 私有成员不能被子类直接访问](#3. 私有成员不能被子类直接访问)
    • [4. 静态成员会被继承吗](#4. 静态成员会被继承吗)
  • 五、继承中的成员访问特点
    • [1. 成员变量的访问](#1. 成员变量的访问)
    • [2. 如果想访问父类同名成员变量怎么办](#2. 如果想访问父类同名成员变量怎么办)
    • [3. 方法访问也遵循查找机制](#3. 方法访问也遵循查找机制)
  • [六、`super` 关键字详解](#六、super 关键字详解)
  • 七、子类构造方法与父类构造方法
    • [1. 子类对象创建时,一定会先调用父类构造方法](#1. 子类对象创建时,一定会先调用父类构造方法)
    • [2. 默认会隐含调用 `super()`](#2. 默认会隐含调用 super())
    • [3. 如果父类没有无参构造怎么办](#3. 如果父类没有无参构造怎么办)
    • [4. `super(...)` 必须写在构造方法第一行](#4. super(...) 必须写在构造方法第一行)
    • [5. `this(...)` 与 `super(...)` 不能同时出现在同一构造方法第一行](#5. this(...)super(...) 不能同时出现在同一构造方法第一行)
  • 八、方法重写
    • [1. 什么是方法重写](#1. 什么是方法重写)
    • [2. 为什么要重写](#2. 为什么要重写)
    • [3. 方法重写的条件](#3. 方法重写的条件)
    • [4. `@Override` 注解](#4. @Override 注解)
    • [5. 方法重写示例](#5. 方法重写示例)
  • 九、方法重写与方法重载的区别
    • [1. 方法重写](#1. 方法重写)
    • [2. 方法重载](#2. 方法重载)
    • [3. 对比总结](#3. 对比总结)
    • [4. 示例对比](#4. 示例对比)
  • 十、什么是抽象类
    • [1. 抽象类的基本概念](#1. 抽象类的基本概念)
    • [2. 抽象类的语法](#2. 抽象类的语法)
    • [3. 抽象类不能直接创建对象](#3. 抽象类不能直接创建对象)
    • [4. 抽象类里可以有普通成员](#4. 抽象类里可以有普通成员)
    • [5. 什么是抽象方法](#5. 什么是抽象方法)
    • [6. 抽象类中可以有抽象方法,也可以没有](#6. 抽象类中可以有抽象方法,也可以没有)
  • 十一、抽象类和继承、多态之间是什么关系
    • [1. 抽象类本质上还是父类](#1. 抽象类本质上还是父类)
    • [2. 子类继承抽象类后,要么重写全部抽象方法,要么自己也声明为抽象类](#2. 子类继承抽象类后,要么重写全部抽象方法,要么自己也声明为抽象类)
    • [3. 抽象方法经常是在"强制子类重写"](#3. 抽象方法经常是在“强制子类重写”)
    • [4. 抽象类常常是多态最自然的父类型](#4. 抽象类常常是多态最自然的父类型)
  • 十二、什么是多态
    • [1. 多态的基本概念](#1. 多态的基本概念)
    • [2. 生活中的类比](#2. 生活中的类比)
    • [3. Java 中的多态表现](#3. Java 中的多态表现)
  • 十三、多态成立的条件
    • [1. 要有继承或实现关系](#1. 要有继承或实现关系)
    • [2. 要有方法重写](#2. 要有方法重写)
    • [3. 要有父类引用指向子类对象](#3. 要有父类引用指向子类对象)
    • [4. 示例](#4. 示例)
  • 十四、父类引用指向子类对象怎么理解
    • [1. 看起来很奇怪,实际上很合理](#1. 看起来很奇怪,实际上很合理)
    • [2. 为什么父类能接收子类对象](#2. 为什么父类能接收子类对象)
    • [3. 这种设计有什么好处](#3. 这种设计有什么好处)
  • 十五、多态下访问成员变量和成员方法的规律
    • [1. 成员变量访问规律](#1. 成员变量访问规律)
    • [2. 成员方法访问规律](#2. 成员方法访问规律)
    • [3. 经典总结](#3. 经典总结)
    • [4. 口诀总结](#4. 口诀总结)
  • 十六、多态的好处
    • [1. 提高程序扩展性](#1. 提高程序扩展性)
    • [2. 降低代码耦合度](#2. 降低代码耦合度)
    • [3. 让代码更通用](#3. 让代码更通用)
    • [4. 符合开闭原则思想](#4. 符合开闭原则思想)
  • 十七、多态中的向上转型
    • [1. 什么是向上转型](#1. 什么是向上转型)
    • [2. 为什么叫"向上"](#2. 为什么叫“向上”)
    • [3. 向上转型是否需要强制转换](#3. 向上转型是否需要强制转换)
    • [4. 向上转型后的特点](#4. 向上转型后的特点)
  • 十八、多态中的向下转型
    • [1. 什么是向下转型](#1. 什么是向下转型)
    • [2. 为什么需要向下转型](#2. 为什么需要向下转型)
    • [3. 向下转型需要强制类型转换](#3. 向下转型需要强制类型转换)
    • [4. 向下转型有风险](#4. 向下转型有风险)
    • [5. 安全转型要配合 `instanceof`](#5. 安全转型要配合 instanceof)
  • [十九、`instanceof` 关键字详解](#十九、instanceof 关键字详解)
    • [1. 作用](#1. 作用)
    • [2. 示例](#2. 示例)
    • [3. 为什么它很重要](#3. 为什么它很重要)
  • 二十、一个完整的继承、抽象类与多态案例
  • 二十一、继承的优点与局限
    • [1. 优点](#1. 优点)
    • [2. 局限](#2. 局限)
    • [3. 使用继承的原则](#3. 使用继承的原则)
  • 二十二、多态的应用思想
    • [1. 面向父类编程](#1. 面向父类编程)
    • [2. 统一调用,不同实现](#2. 统一调用,不同实现)
    • [3. 方便后续扩展](#3. 方便后续扩展)
  • 二十三、开发中常见错误与误区
    • [1. 误以为继承可以继承父类一切内容](#1. 误以为继承可以继承父类一切内容)
    • [2. 误以为子类对象创建时只执行子类构造](#2. 误以为子类对象创建时只执行子类构造)
    • [3. 误以为方法重写和重载是一回事](#3. 误以为方法重写和重载是一回事)
    • [4. 误以为多态下变量和方法都看右边](#4. 误以为多态下变量和方法都看右边)
    • [5. 误以为父类引用能直接调用子类特有方法](#5. 误以为父类引用能直接调用子类特有方法)
    • [6. 不加判断就强制向下转型](#6. 不加判断就强制向下转型)
    • [7. 滥用继承](#7. 滥用继承)
  • 二十四、学习继承、抽象类与多态的建议
  • 二十五、本章知识总结
    • [1. 继承总结](#1. 继承总结)
    • [2. 抽象类总结](#2. 抽象类总结)
    • [3. 重写总结](#3. 重写总结)
    • [4. 多态总结](#4. 多态总结)
    • [5. 成员访问规律总结](#5. 成员访问规律总结)
    • [6. 类型转换总结](#6. 类型转换总结)
  • 二十六、结语
  • 二十七、附:常见语法速查表
    • [1. 继承语法](#1. 继承语法)
    • [2. 抽象类与抽象方法](#2. 抽象类与抽象方法)
    • [3. `super` 常见写法](#3. super 常见写法)
    • [4. 方法重写写法](#4. 方法重写写法)
    • [5. 多态写法](#5. 多态写法)
    • [6. 向下转型写法](#6. 向下转型写法)
    • [7. `instanceof` 写法](#7. instanceof 写法)

前言

在 Java 面向对象编程中,继承抽象类多态 是一组联系非常紧的知识点。前面学类和对象时,更多是在做"抽象";到了这一章,重点就变成了:

  • 类与类之间怎样建立层次关系
  • 代码如何实现复用
  • 有些父类为什么只适合拿来"约束",不适合直接创建对象
  • 相同操作为什么会表现出不同结果
  • 为什么父类引用可以指向子类对象
  • 为什么同样是调用一个方法,运行结果却和对象真实类型有关

很多初学者在刚学到这里时,会觉得概念很多、关系很绕,常见困惑包括:

  • 什么叫"子类继承父类"
  • 子类到底继承了父类的什么内容
  • 构造方法能不能被继承
  • superthis 有什么区别
  • 抽象类到底"抽象"在哪里
  • 抽象类和普通父类有什么区别
  • 抽象方法为什么没有方法体
  • 方法重写和方法重载到底哪里不一样
  • 父类引用指向子类对象是什么意思
  • 编译看左边、运行看右边到底怎么理解
  • 为什么会有向上转型和向下转型
  • 为什么多态能提高程序扩展性

这些问题,基本都集中在"继承、抽象类与多态"这一章里。

可以这样理解:

继承负责抽取共性,抽象类负责把共性规范再往前推进一步,多态负责让同一种调用在不同对象上表现出不同结果。

这三者不是割裂开的。

很多时候,先有继承关系,再把父类设计成抽象类,最后通过父类引用指向子类对象来体现多态,这才是比较完整的一条线。

本章会按"继承 -> 抽象类 -> 多态"这条主线来讲,尽量把语法、运行逻辑、常见坑和代码案例串在一起,避免只背概念却不会分析代码。

通过本章学习,可以掌握:

  • 什么是继承
  • 继承的基本语法
  • 子类能够继承父类哪些成员
  • extends 的作用
  • super 关键字的使用方法
  • 什么是抽象类
  • 抽象类和普通类的区别
  • 抽象方法的定义方式
  • 子类继承抽象类后的规则
  • 方法重写的概念、条件和注意事项
  • 继承中成员访问的查找顺序
  • 什么是多态
  • 多态成立的条件
  • 父类引用指向子类对象的本质
  • 多态下成员变量和成员方法的访问规律
  • 向上转型与向下转型
  • instanceof 的作用
  • 继承与多态在实际开发中的意义

一、为什么要学习继承、抽象类与多态

1. 单纯使用类和对象还不够

在前面的面向对象学习中,我们已经知道:

  • 类用于描述一类事物
  • 对象是类的具体实例

例如:

  • Student 类表示学生
  • Teacher 类表示老师
  • Dog 类表示狗
  • Cat 类表示猫

但是继续往下写程序时,就会发现一个问题:

很多类之间并不是完全独立的,它们往往存在共同特征

例如:

  • 学生和老师都属于"人"
  • 狗和猫都属于"动物"
  • 圆形和矩形都属于"图形"
  • 支付宝支付和微信支付都属于"支付方式"

如果每个类都从零开始写,往往会产生大量重复代码。

2. 代码复用的需求非常强烈

例如:

  • 学生有姓名、年龄、性别
  • 老师也有姓名、年龄、性别

如果 StudentTeacher 中都重复定义这些属性和方法,就会造成:

  • 代码冗余
  • 维护困难
  • 修改麻烦

这时候就会自然想到:

能不能把它们共同的内容抽取到一个更通用的类中,然后让其他类复用?

这正是继承要解决的问题。

3. 现实世界中天然存在"is-a"关系

继承最适合表达一种关系:

子类 is a 父类

例如:

  • 学生是一个人
  • 老师是一个人
  • 狗是一种动物
  • 汽车是一种交通工具

只要这种"是一个"的关系成立,就往往可以考虑使用继承。

4. 有些父类只想拿来约束,不想直接创建对象

实际开发里,经常会碰到这样一种类:

  • 它确实适合作为父类
  • 它里面确实写了一些共性成员
  • 但这个类本身又不够具体,不应该直接 new

例如:

  • Animal 可以作为狗、猫、鸟的父类
  • 但"动物"这个概念太泛,很多时候不适合直接创建一个"纯动物对象"
  • Shape 可以作为圆形、矩形、三角形的父类
  • 但"图形"往往只是一个抽象总类

这时候,普通继承就还差一点意思。

因为普通父类既能被继承,也能被直接实例化。

如果你不希望别人直接拿它创建对象,就需要引入抽象类

5. 程序不仅要复用,还要可扩展

除了复用代码,程序还要有良好的扩展能力。例如写一个"动物叫"的程序:

  • 狗会汪汪叫
  • 猫会喵喵叫
  • 鸟会啾啾叫

如果每增加一种动物,都要修改一大堆旧代码,那么程序就很僵硬。

这时就需要多态,让程序在面对不同对象时,自动表现出不同的行为。


二、什么是继承

1. 继承的基本概念

继承是面向对象的三大特征之一。在 Java 中,继承表示:

让一个类获得另一个类中已有的属性和方法。

被继承的类称为:

  • 父类
  • 基类
  • 超类

继承别人的类称为:

  • 子类
  • 派生类

例如:

java 复制代码
class Person {
    String name;
    int age;

    public void eat() {
        System.out.println("人会吃饭");
    }
}

class Student extends Person {
    public void study() {
        System.out.println("学生会学习");
    }
}

这里:

  • Person 是父类
  • Student 是子类
  • Student 通过 extends 继承了 Person

因此 Student 不仅有自己的 study() 方法,还能使用从 Person 继承来的:

  • name
  • age
  • eat()

2. 继承的本质

继承的本质并不是"复制粘贴"父类代码,而是建立一种类与类之间的层次关系。

这种关系使得子类拥有父类非私有的成员访问能力,并可以在此基础上扩展自己的新功能。

3. 继承的意义

继承的主要意义有:

  • 提高代码复用性
  • 减少重复代码
  • 建立类之间的层次结构
  • 为多态提供前提条件

三、继承的基本语法

1. 语法格式

Java 中使用 extends 关键字表示继承。

java 复制代码
class 子类名 extends 父类名 {
    // 子类自己的成员
}

2. 示例

java 复制代码
class Animal {
    String name;

    public void eat() {
        System.out.println("动物会吃东西");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("狗会汪汪叫");
    }
}

使用时:

java 复制代码
public class Test {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.name = "旺财";
        d.eat();
        d.bark();
    }
}

输出结果:

text 复制代码
动物会吃东西
狗会汪汪叫

这说明:

  • Dog 自己新增了 bark()
  • 同时还能使用 Animal 的成员

四、子类到底继承了父类什么

这是非常重要的问题。

1. 子类会继承父类的非私有成员

通常可以先记住一个结论:

子类会继承父类中可访问的成员变量和成员方法。

这些成员包括:

  • 成员变量
  • 成员方法

但这里有一个前提:访问权限要允许。

2. 构造方法不能被继承

很多初学者容易误以为"子类把父类的一切都继承了"。

实际上,构造方法不能被继承

原因很简单:

  • 构造方法的名字必须和类名一致
  • 父类构造方法名字是父类名
  • 子类构造方法名字必须是子类名

因此从语法本质上,它就不能被直接继承。

不过要注意:

虽然构造方法不能被继承,但子类对象创建时会调用父类构造方法。

这一点后面会详细讲。

3. 私有成员不能被子类直接访问

如果父类成员使用 private 修饰,那么子类不能直接访问。

例如:

java 复制代码
class Father {
    private int age = 50;
}

class Son extends Father {
    public void test() {
        // System.out.println(age);  // 这里会报错
    }
}

这并不意味着父类私有成员"完全不存在",而是:

  • 它们属于父类对象的一部分
  • 但子类不能直接使用名字访问它们

如果父类提供了公共的 get / set 方法,子类仍可间接操作。

4. 静态成员会被继承吗

从语法使用上看,子类可以访问父类的静态成员。

但静态成员本质上属于类,而不属于某一个对象。

因此更准确地说:

  • 静态成员会被子类"继承可访问性"
  • 但它并不是对象实例层面的普通成员

初学阶段可以先记住:

  • 父类的静态成员,子类可以使用
  • 但最好通过"类名.成员"访问,而不是通过对象访问

五、继承中的成员访问特点

1. 成员变量的访问

当子类和父类中出现同名成员变量时,访问会遵循就近原则。

示例:

java 复制代码
class Father {
    int num = 10;
}

class Son extends Father {
    int num = 20;

    public void show() {
        System.out.println(num);
    }
}

输出的是:

text 复制代码
20

因为在 show() 方法内部,优先访问的是当前类中的成员变量。

2. 如果想访问父类同名成员变量怎么办

这时候就需要使用 super

java 复制代码
class Father {
    int num = 10;
}

class Son extends Father {
    int num = 20;

    public void show() {
        System.out.println(num);       // 20
        System.out.println(super.num); // 10
    }
}

这里:

  • num 表示当前对象中优先从子类查找的成员
  • super.num 表示访问父类中的成员变量

3. 方法访问也遵循查找机制

如果子类没有该方法,就向父类找。

如果父类也没有,再继续向更上层找。

如果一直找不到,就编译报错。

例如:

java 复制代码
class Father {
    public void eat() {
        System.out.println("父类吃饭");
    }
}

class Son extends Father {
    public void study() {
        System.out.println("子类学习");
    }
}

Son 对象既可以调用:

  • study()
  • eat()

因为 eat() 能在父类中找到。


六、super 关键字详解

super 是继承中必须熟练掌握的关键字。

1. super 的含义

super 可以理解为:

当前对象中的父类部分。

它主要用于在子类中访问父类成员。

2. super 的常见用法

(1)访问父类成员变量

java 复制代码
super.成员变量名

(2)访问父类成员方法

java 复制代码
super.成员方法名();

(3)调用父类构造方法

java 复制代码
super();
super(参数列表);

3. super 访问父类方法示例

java 复制代码
class Father {
    public void show() {
        System.out.println("这是父类的 show 方法");
    }
}

class Son extends Father {
    public void show() {
        System.out.println("这是子类的 show 方法");
    }

    public void test() {
        show();
        super.show();
    }
}

输出顺序为:

  • 调用当前类重写后的方法
  • 调用父类原始方法

4. thissuper 的区别

这是常见考点。

this

  • 表示当前对象
  • 优先访问当前类成员

super

  • 表示当前对象中的父类部分
  • 用于访问父类成员

例如:

java 复制代码
class Father {
    int num = 10;
}

class Son extends Father {
    int num = 20;

    public void show() {
        System.out.println(this.num);   // 20
        System.out.println(super.num);  // 10
    }
}

七、子类构造方法与父类构造方法

这是继承中的重点,也是很多人最容易出错的地方。

1. 子类对象创建时,一定会先调用父类构造方法

为什么?

因为子类对象中不仅包含子类自己的成员,还包含从父类继承来的那一部分。

既然父类部分也属于整个对象的一部分,那么创建子类对象时,就必须先把父类部分初始化好。

2. 默认会隐含调用 super()

看下面代码:

java 复制代码
class Father {
    public Father() {
        System.out.println("父类无参构造方法");
    }
}

class Son extends Father {
    public Son() {
        System.out.println("子类无参构造方法");
    }
}

创建对象:

java 复制代码
Son s = new Son();

输出结果:

text 复制代码
父类无参构造方法
子类无参构造方法

原因是:

  • 子类构造方法第一行默认隐藏着 super();
  • 它会先调用父类无参构造

3. 如果父类没有无参构造怎么办

这时候子类必须显式调用父类有参构造,否则会报错。

示例:

java 复制代码
class Father {
    public Father(String name) {
        System.out.println("父类有参构造:" + name);
    }
}

class Son extends Father {
    public Son() {
        super("张三");
        System.out.println("子类无参构造");
    }
}

如果你不写 super("张三");,编译就会报错。

因为编译器默认想加 super();,但父类没有无参构造。

4. super(...) 必须写在构造方法第一行

这是语法规定。

例如下面写法是错误的:

java 复制代码
class Son extends Father {
    public Son() {
        System.out.println("hello");
        super();
    }
}

因为 super() 必须放在第一行。

5. this(...)super(...) 不能同时出现在同一构造方法第一行

因为构造方法第一行只能有一条语句来决定"先调谁"。

所以:

  • this(...)
  • super(...)

二者只能二选一。


八、方法重写

继承讲到一定程度,就必须理解方法重写,因为多态几乎建立在重写之上。

1. 什么是方法重写

方法重写也叫覆盖,指的是:

子类中出现了和父类同名、同参数列表、同返回值类型的方法,用来重新定义父类原有行为。

例如:

java 复制代码
class Animal {
    public void speak() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("狗汪汪叫");
    }
}

这里 Dog 中的 speak() 就重写了 Animal 中的 speak()

2. 为什么要重写

因为父类提供的是"共性行为",而子类往往要提供"更具体的实现"。

例如:

  • 动物都会叫
  • 但狗的叫声和猫的叫声不同

因此父类只给出一个通用定义,子类再根据自己特点进行重写。

3. 方法重写的条件

通常可记为:

  • 方法名相同
  • 参数列表相同
  • 返回值类型兼容
  • 子类权限不能小于父类权限
  • 子类重写方法不能抛出比父类更大的检查异常

在初学阶段,最核心的三个条件先记住:

  • 同名
  • 同参数
  • 有继承关系

4. @Override 注解

重写时推荐写上:

java 复制代码
@Override

作用:

  • 告诉编译器这是一个重写方法
  • 如果方法签名写错,编译器会帮你报错

这可以有效避免低级错误。

5. 方法重写示例

java 复制代码
class Person {
    public void work() {
        System.out.println("人都需要工作");
    }
}

class Teacher extends Person {
    @Override
    public void work() {
        System.out.println("老师的工作是教书");
    }
}

class Doctor extends Person {
    @Override
    public void work() {
        System.out.println("医生的工作是治病救人");
    }
}

这里 work() 在不同子类中有不同表现,这正是在为多态做准备。


九、方法重写与方法重载的区别

这是面试和考试中非常常见的比较题。

1. 方法重写

方法重写发生在:

  • 父类和子类之间

特点:

  • 方法名相同
  • 参数列表相同
  • 返回值类型兼容

目的:

  • 子类重新定义父类行为

2. 方法重载

方法重载发生在:

  • 同一个类中
  • 也可以发生在父子类中,但本质判断仍看参数列表变化

特点:

  • 方法名相同
  • 参数列表不同

目的:

  • 让同一个方法名适应不同参数情况

3. 对比总结

重写

  • 看继承关系
  • 看方法是否"完全对应"

重载

  • 看参数列表是否不同
  • 和返回值无关

4. 示例对比

重载

java 复制代码
class MathTool {
    public int add(int a, int b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

重写

java 复制代码
class Animal {
    public void move() {
        System.out.println("动物会移动");
    }
}

class Bird extends Animal {
    @Override
    public void move() {
        System.out.println("鸟会飞");
    }
}

十、什么是抽象类

1. 抽象类的基本概念

抽象类,顾名思义,就是"抽象出来的类"。

它往往用于描述一组子类共同具备的特征,但这个父类本身又不够具体,不适合直接创建对象。

例如:

  • 动物都能吃东西,也都能发出声音
  • 但不同动物发出声音的方式不一样
  • "动物"这个类适合作为父类
  • 但很多时候我们并不需要直接创建 Animal 对象

这时就可以把 Animal 设计成抽象类。

2. 抽象类的语法

Java 中使用 abstract 关键字修饰类:

java 复制代码
abstract class 类名 {
}

例如:

java 复制代码
abstract class Animal {
}

这就表示 Animal 是一个抽象类。

3. 抽象类不能直接创建对象

这是抽象类最重要的特点之一。

例如:

java 复制代码
abstract class Animal {
}

public class Test {
    public static void main(String[] args) {
        // Animal a = new Animal();  // 报错
    }
}

原因很简单:

  • 抽象类本身不够具体
  • 它更像是一个"父类模板"
  • 它的意义主要在于被继承,而不是直接实例化

4. 抽象类里可以有普通成员

很多同学一看到"抽象类",就误以为它里面只能写抽象方法。

其实不是。

抽象类中可以有:

  • 普通成员变量
  • 普通成员方法
  • 构造方法
  • 抽象方法

也就是说,抽象类并不是"什么都没有的空壳子",它完全可以把共性代码提前写好。

例如:

java 复制代码
abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + "正在吃东西");
    }
}

这里虽然 Animal 是抽象类,但它照样可以有:

  • 属性 name
  • 构造方法
  • 普通方法 eat()

5. 什么是抽象方法

抽象方法就是:

只有方法声明,没有方法体的方法。

语法格式:

java 复制代码
abstract 返回值类型 方法名(参数列表);

例如:

java 复制代码
abstract class Animal {
    public abstract void speak();
}

这里的 speak() 只有声明,没有具体实现。它的意思其实很明确:

  • 所有动物都应该会"发出声音"
  • 但具体怎么叫,父类不写死
  • 交给各个子类自己去实现

6. 抽象类中可以有抽象方法,也可以没有

这是一个容易忽略的点。

情况一:抽象类中有抽象方法

这最常见,也最符合大家对抽象类的直觉。

情况二:抽象类中没有抽象方法

这也是合法的。此时把类声明为抽象类,主要目的是:

  • 不让外界直接创建对象
  • 强调这个类只是给子类继承使用

十一、抽象类和继承、多态之间是什么关系

1. 抽象类本质上还是父类

抽象类首先是一个类,所以它照样能被继承。

例如:

java 复制代码
abstract class Animal {
    public abstract void speak();
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("狗汪汪叫");
    }
}

这里:

  • Animal 是抽象父类
  • Dog 是它的子类

所以抽象类和继承并不冲突,反而常常是配套出现的。

2. 子类继承抽象类后,要么重写全部抽象方法,要么自己也声明为抽象类

这是语法规则。

例如:

java 复制代码
abstract class Animal {
    public abstract void speak();
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("狗汪汪叫");
    }
}

这里 Dog 重写了 speak(),所以 Dog 可以是普通类。

但如果不重写:

java 复制代码
abstract class Animal {
    public abstract void speak();
}

abstract class Dog extends Animal {
}

那么 Dog 也必须继续声明为抽象类。

3. 抽象方法经常是在"强制子类重写"

普通父类也能写方法重写,但它偏向于"我先给你一个默认实现,你有需要再改"。抽象方法更像是在说:

这个行为对子类来说是必须有的,但父类这里不负责具体实现。

所以抽象类在设计上比普通父类更进一步,它不仅抽取共性,还能规定子类必须完成哪些行为。

4. 抽象类常常是多态最自然的父类型

看一个典型例子:

java 复制代码
abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + "正在吃东西");
    }

    public abstract void speak();
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(name + "汪汪叫");
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(name + "喵喵叫");
    }
}

这时候就可以写:

java 复制代码
Animal a1 = new Dog("旺财");
Animal a2 = new Cat("咪咪");

a1.speak();
a2.speak();

这里:

  • Animal 作为抽象父类,负责抽取共性
  • DogCat 负责给出具体实现
  • 最终通过父类引用体现多态

所以把继承、抽象类、多态连起来看,逻辑会很顺:

  • 先有继承关系
  • 再用抽象类把父类设计得更合理
  • 最后用多态统一调用不同子类的行为

十二、什么是多态

1. 多态的基本概念

多态也是面向对象三大特征之一。所谓多态,可以直白地理解为:

同一种行为,由不同对象去完成时,会表现出不同的形态。

例如:

  • 同样是调用 speak()
  • 狗对象会输出"汪汪"
  • 猫对象会输出"喵喵"

这就是多态最直观的表现。

2. 生活中的类比

例如"按下开关"这个动作:

  • 电灯响应后会发光
  • 风扇响应后会转动
  • 空调响应后会制冷

动作看起来一样,但具体结果不同。

这就是"同一操作,不同表现"。

3. Java 中的多态表现

在 Java 中,多态最常见的形式是:

父类引用指向子类对象。

例如:

java 复制代码
Animal a = new Dog();

这里:

  • Animal 是父类引用类型
  • new Dog() 是子类对象

这种写法就是多态的典型形式。


十三、多态成立的条件

想要形成多态,通常需要满足以下条件:

1. 要有继承或实现关系

例如:

  • Dog extends Animal
  • Cat extends Animal

或者接口实现关系也可以形成多态,但本章先以类继承为主。

2. 要有方法重写

父类中定义通用方法,子类中进行重写。

如果没有重写,多态的动态效果就不明显。

3. 要有父类引用指向子类对象

例如:

java 复制代码
Animal a = new Dog();

只有当引用类型和对象实际类型不一致时,多态特征才会真正体现出来。

4. 示例

java 复制代码
class Animal {
    public void speak() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void speak() {
        System.out.println("狗汪汪叫");
    }
}

class Cat extends Animal {
    @Override
    public void speak() {
        System.out.println("猫喵喵叫");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a1 = new Dog();
        Animal a2 = new Cat();

        a1.speak();
        a2.speak();
    }
}

输出:

text 复制代码
狗汪汪叫
猫喵喵叫

这就是多态。


十四、父类引用指向子类对象怎么理解

这句话必须真正理解透。

1. 看起来很奇怪,实际上很合理

例如:

java 复制代码
Animal a = new Dog();

很多初学者会问:

  • a 到底是 Animal 还是 Dog

正确理解是:

  • a引用类型Animal
  • 它实际指向的对象类型是 Dog

也就是说:

"左边看类型,右边看对象。"

2. 为什么父类能接收子类对象

因为子类对象本质上也是一个父类对象。

例如"狗是动物",那么把狗当成动物来看待,是完全合理的。

这就像现实中:

  • 你可以说"旺财是一条狗"
  • 也可以说"旺财是一只动物"

第二种说法虽然更宽泛,但并没有错。

3. 这种设计有什么好处

好处非常大:

  • 代码更通用
  • 方法参数更灵活
  • 更容易扩展新子类

例如你只写一个:

java 复制代码
public void makeAnimalSpeak(Animal a) {
    a.speak();
}

那么以后传入:

  • Dog
  • Cat
  • Bird

都可以使用,而不需要为每个具体子类单独写方法。


十五、多态下访问成员变量和成员方法的规律

这是最经典、也是最容易混淆的考点。

1. 成员变量访问规律

先看代码:

java 复制代码
class Father {
    int num = 10;
}

class Son extends Father {
    int num = 20;
}

public class Test {
    public static void main(String[] args) {
        Father f = new Son();
        System.out.println(f.num);
    }
}

输出是:

text 复制代码
10

为什么?

因为:

成员变量没有多态性,访问看编译时左边引用类型。

也就是说:

  • f 左边是 Father
  • 所以访问 num 时,看的是 Father 中定义的 num

2. 成员方法访问规律

再看代码:

java 复制代码
class Father {
    public void show() {
        System.out.println("父类 show");
    }
}

class Son extends Father {
    @Override
    public void show() {
        System.out.println("子类 show");
    }
}

public class Test {
    public static void main(String[] args) {
        Father f = new Son();
        f.show();
    }
}

输出是:

text 复制代码
子类 show

因为:

成员方法存在多态,运行时看右边对象真实类型。

3. 经典总结

这就是大家常说的:

编译看左边,运行看右边。

更准确地说:

  • 编译阶段,看引用类型中是否有这个成员
  • 运行阶段,方法调用看对象真实类型

但注意:

  • 这个结论主要针对成员方法
  • 成员变量不体现运行时重写机制

4. 口诀总结

可以这样记:

  • 变量看左边
  • 方法看右边

前提是这里讨论的是"多态访问"场景。


十六、多态的好处

1. 提高程序扩展性

这是多态最大的优势。

例如有一个动物表演程序:

java 复制代码
public void perform(Animal a) {
    a.speak();
}

以后新增:

  • Dog
  • Cat
  • Tiger
  • Bird

只要它们都继承 Animal 并重写 speak(),原方法几乎不用改。

这就叫"面向抽象编程"。

2. 降低代码耦合度

如果一个方法只依赖父类或接口,而不依赖某个具体子类,那么这个方法就不容易因为新增子类而频繁改动。

3. 让代码更通用

例如:

java 复制代码
public void feed(Animal a) { ... }

就比:

java 复制代码
public void feedDog(Dog d) { ... }
public void feedCat(Cat c) { ... }

更通用。

4. 符合开闭原则思想

也就是:

  • 对扩展开放
  • 对修改关闭

新增功能时,尽量通过新增子类来扩展,而不是频繁修改旧代码。


十七、多态中的向上转型

1. 什么是向上转型

向上转型指的是:

把子类对象赋值给父类引用。

例如:

java 复制代码
Animal a = new Dog();

这就是向上转型。

2. 为什么叫"向上"

因为在继承层次结构图中:

  • 父类通常画在上面
  • 子类画在下面

子类转为父类,就像往上走,所以叫向上转型。

3. 向上转型是否需要强制转换

不需要。

因为这是安全的。

子类本来就是父类的一种。

4. 向上转型后的特点

向上转型后,引用变量能访问的成员范围,取决于父类中定义的内容。

例如:

java 复制代码
class Animal {
    public void eat() {
        System.out.println("动物吃");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("狗叫");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.eat();
        // a.bark();  // 报错
    }
}

为什么 a.bark() 报错?

因为编译器只知道 aAnimal 类型引用。

Animal 类中没有 bark() 方法。


十八、多态中的向下转型

1. 什么是向下转型

向下转型指的是:

把父类引用重新转换为子类类型。

例如:

java 复制代码
Animal a = new Dog();
Dog d = (Dog) a;

这样就能重新使用子类特有成员。

2. 为什么需要向下转型

因为向上转型后,父类引用无法直接访问子类特有方法。

如果确实需要调用子类独有行为,就要向下转型。

例如:

java 复制代码
d.bark();

3. 向下转型需要强制类型转换

必须写:

java 复制代码
(Dog) a

因为并不是所有 Animal 引用都一定指向 Dog 对象。

4. 向下转型有风险

例如:

java 复制代码
Animal a = new Cat();
Dog d = (Dog) a;

这会在运行时抛出:

text 复制代码
ClassCastException

因为 a 实际上指向的是 Cat 对象,不是 Dog 对象。

5. 安全转型要配合 instanceof

在向下转型前,一般先判断:

java 复制代码
if (a instanceof Dog) {
    Dog d = (Dog) a;
    d.bark();
}

这样可以避免类型转换异常。


十九、instanceof 关键字详解

1. 作用

instanceof 用于判断某个对象是否属于某种类型。

语法:

java 复制代码
对象引用 instanceof 类名

返回值是:

  • true
  • false

2. 示例

java 复制代码
Animal a = new Dog();

System.out.println(a instanceof Animal); // true
System.out.println(a instanceof Dog);    // true
System.out.println(a instanceof Cat);    // false

3. 为什么它很重要

因为多态下,引用类型和对象真实类型可能不一致。

在做向下转型前,必须尽量先确认真实类型,instanceof 就是最常用的工具。


二十、一个完整的继承、抽象类与多态案例

下面通过一个较完整的案例,把继承、抽象类、重写、多态、向上转型、向下转型串起来。

java 复制代码
abstract class Animal {
    String name;

    public Animal(String name) {
        this.name = name;
    }

    public void eat() {
        System.out.println(name + "正在吃东西");
    }

    public abstract void speak();
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(name + "汪汪叫");
    }

    public void guardHome() {
        System.out.println(name + "正在看家");
    }
}

class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println(name + "喵喵叫");
    }

    public void catchMouse() {
        System.out.println(name + "正在抓老鼠");
    }
}

public class Test {
    public static void main(String[] args) {
        Animal a1 = new Dog("旺财");
        Animal a2 = new Cat("咪咪");

        a1.eat();
        a1.speak();

        a2.eat();
        a2.speak();

        if (a1 instanceof Dog) {
            Dog d = (Dog) a1;
            d.guardHome();
        }

        if (a2 instanceof Cat) {
            Cat c = (Cat) a2;
            c.catchMouse();
        }
    }
}

运行结果会体现出:

  • 父类构造被调用
  • 子类重写方法生效
  • 多态调用方法表现不同
  • 向下转型后可以访问子类特有方法

这是一个非常典型、也非常适合练习的综合案例。


二十一、继承的优点与局限

1. 优点

继承的优点主要有:

  • 提高代码复用性
  • 减少重复定义
  • 有利于建立清晰的类层次结构
  • 为多态提供基础

2. 局限

继承不是越多越好。如果继承关系设计不合理,会导致:

  • 类之间耦合过强
  • 层次过深,理解困难
  • 父类改动影响多个子类

3. 使用继承的原则

继承适合表达真正的"is-a"关系。

如果两个类之间只是"有一个""使用一个",就不一定适合继承,可能更适合组合。

例如:

  • 人有一辆车

这里"车"不是"人"的一种,所以不能写成:

java 复制代码
class Car extends Person

这显然不合理。

因此:

只有存在明确的"是一个"关系时,才考虑继承。


二十二、多态的应用思想

1. 面向父类编程

多态的核心思想之一,是尽量让代码依赖:

  • 父类
  • 抽象类
  • 接口

而不是过度依赖某个具体实现类。

例如:

java 复制代码
public void useAnimal(Animal a) {
    a.speak();
}

这种写法比只接收 Dog 更有扩展性。

2. 统一调用,不同实现

在多态设计下,调用者不一定关心具体对象到底是哪一个子类,只要知道:

  • 它具备某种共同行为

就可以统一调用。

3. 方便后续扩展

以后新增子类时:

  • 不必大规模修改调用端
  • 只需新增新的子类并重写对应行为

这正是优秀设计的体现。


二十三、开发中常见错误与误区

1. 误以为继承可以继承父类一切内容

错误。

构造方法不能被继承,私有成员也不能被子类直接访问。

2. 误以为子类对象创建时只执行子类构造

错误。

创建子类对象时,一定会先执行父类构造。

3. 误以为方法重写和重载是一回事

错误。

重写强调继承关系和相同参数列表;重载强调同名但参数不同。

4. 误以为多态下变量和方法都看右边

错误。多态下:

  • 成员变量看左边
  • 成员方法看右边

5. 误以为父类引用能直接调用子类特有方法

错误。

父类引用只能访问父类中声明过的成员。

想调用子类特有方法,通常要先向下转型。

6. 不加判断就强制向下转型

这很危险,可能导致 ClassCastException

应优先使用 instanceof 进行判断。

7. 滥用继承

并不是所有类之间都适合继承。

只因为"代码长得像"就强行继承,往往会带来设计问题。


二十四、学习继承、抽象类与多态的建议

1. 先把继承关系画出来

学习时建议先把父类、子类关系画成树状图,例如:

text 复制代码
Animal
├── Dog
├── Cat
└── Bird

这样更容易理解谁是谁的父类,谁能调用什么。

2. 多画内存和引用关系

例如:

java 复制代码
Animal a = new Dog();

你要在脑中清楚:

  • 左边是引用变量类型
  • 右边是真实创建对象类型

这对理解多态非常关键。

3. 多写重写案例

建议练习下面这些主题:

  • 动物叫
  • 图形面积计算
  • 员工工作内容
  • 交通工具运行方式

这些都很适合练习继承和多态。

4. 遇到题目先问自己四个问题

第一问:谁是父类,谁是子类

先判断继承关系。

第二问:有没有方法重写

看子类是否重新定义了父类方法。

第三问:引用左边是什么类型

决定编译时能访问哪些成员。

第四问:右边真实对象是什么类型

决定运行时调用哪个重写方法。

只要把这四个问题理清,绝大多数继承与多态题目都能分析出来。


二十五、本章知识总结

1. 继承总结

继承表示子类获得父类中已有成员的能力,通过 extends 实现。

语法:

java 复制代码
class 子类 extends 父类 {
}

继承的主要作用:

  • 提高代码复用性
  • 抽取共性
  • 建立类层次结构

注意事项:

  • Java 中类只能单继承
  • 构造方法不能被继承
  • 私有成员不能被子类直接访问
  • 子类对象创建时会先调用父类构造方法

2. 抽象类总结

抽象类使用 abstract 修饰,主要用于:

  • 提取共性
  • 限制父类不能直接实例化
  • 约束子类必须实现某些行为

特点:

  • 抽象类不能直接 new
  • 抽象类中可以有普通成员,也可以有抽象方法
  • 子类继承抽象类后,要么重写全部抽象方法,要么自己继续声明为抽象类

3. 重写总结

方法重写表示子类重新定义父类已有方法。

特点:

  • 方法名相同
  • 参数列表相同
  • 有继承关系

作用:

  • 实现子类特有行为
  • 为多态提供基础

4. 多态总结

多态表示同一种行为在不同对象上有不同表现。

常见形式:

java 复制代码
父类类型 引用名 = new 子类对象();

多态成立条件:

  • 有继承关系
  • 有方法重写
  • 有父类引用指向子类对象

5. 成员访问规律总结

多态下:

  • 成员变量看左边
  • 成员方法看右边

6. 类型转换总结

  • 向上转型:自动完成,安全
  • 向下转型:强制转换,可能有风险
  • 向下转型前建议使用 instanceof

二十六、结语

继承、抽象类与多态,是 Java 面向对象部分最关键的一组知识。

它们不是三个各讲各的零散点,而是一条很完整的线:先通过继承建立类之间的关系,再用抽象类把父类设计得更合理,最后通过多态把调用写得更灵活。

真正掌握这一章,不只是记住几个语法,而是要慢慢形成一种面向对象的写法:

  • 共性的内容放到父类中
  • 不够具体、但适合统一规范的父类,考虑写成抽象类
  • 个性的内容放到子类中
  • 调用时尽量面向父类或抽象类型编程
  • 让程序在扩展新类型时更自然、更灵活

这一章难,不是因为代码多,而是因为脑中没有把"父类和子类""普通类和抽象类""引用类型和对象类型""编译时和运行时"这些概念分开。

一旦这些关系理顺,继承与多态其实非常有逻辑,也非常优雅。

因此学习本章时,建议不要只停留在"会写几个例子",而要反复练习去回答这些问题:

  • 这个类为什么适合继承
  • 这个父类是不是更适合写成抽象类
  • 哪个方法应该重写
  • 这个引用到底能访问什么
  • 这个方法运行时到底调用谁
  • 这里为什么需要转型

当这些问题你都能解释清楚时,说明你已经真正掌握了 Java 中的继承与多态。


二十七、附:常见语法速查表

1. 继承语法

java 复制代码
class 子类 extends 父类 {
}

2. 抽象类与抽象方法

java 复制代码
abstract class Animal {
    public abstract void speak();
}

3. super 常见写法

java 复制代码
super.成员变量;
super.成员方法();
super(参数列表);

4. 方法重写写法

java 复制代码
@Override
public 返回值类型 方法名(参数列表) {
    // 方法体
}

5. 多态写法

java 复制代码
父类类型 引用名 = new 子类对象();

6. 向下转型写法

java 复制代码
子类类型 变量名 = (子类类型) 父类引用;

7. instanceof 写法

java 复制代码
if (对象引用 instanceof 子类类型) {
    子类类型 变量名 = (子类类型) 对象引用;
}

相关推荐
神州世通2 小时前
解密企业通信安全防线:Avaya Aura 三层安全架构深度解析
开发语言·php
AC赳赳老秦3 小时前
企业工商公开信息采集分析:OpenClaw 批量查询企业工商信息,生成企业画像报告
大数据·开发语言·python·自动化·php·deepseek·openclaw
AI多Agent协作实战派3 小时前
AI多Agent协作系统实战(十七):凌晨4点,我的AI系统在“假装工作“——3个bug同时爆炸的5小时
java·前端·bug
gaolei_eit3 小时前
Java+Ai+vue
java·spring·maven
qq_2518364573 小时前
基于java Web 动漫视频网站毕业论文
java·开发语言·前端
野生风长3 小时前
C++入门基础:从命名空间到引用与指针的全面解析
开发语言·c++
LayZhangStrive3 小时前
JUC相关的函数、注解、变量杂记
java·面试·多线程·juc
未秃头的程序猿3 小时前
给公司做了个AI客服Agent,用的Spring AI 1.0,3天上线领导拍板了
java·后端·ai编程
曹牧3 小时前
Eclipse 批量文本替换
java·ide·eclipse