Java 面向对象进阶
Java 面向对象进阶:继承、多态、抽象与接口,OOP 的核心拼图
学习日期:2026-06-04 难度:⭐⭐⭐⭐ 进阶
一、继承(Inheritance):站在巨人的肩膀上
1.1 什么是继承?
子类继承父类,直接获得父类的属性和方法,还能扩展自己的新能力。
// 父类
class Animal {
String name;
int age;
void eat() {
System.out.println(name + "在吃东西");
}
}
// 子类:用 extends 继承
class Dog extends Animal {
void bark() {
System.out.println(name + ":汪汪汪!");
}
}
// 使用
Dog dog = new Dog();
dog.name = "旺财";
dog.eat(); // 旺财在吃东西(继承来的)
dog.bark(); // 旺财:汪汪汪!(自己的)
1.2 继承的本质
plaintext
Animal
/ \
Dog Cat
| |
GuideDog Persian
- 子类拥有父类所有非 private 的成员
- 子类可以新增自己的成员
- 子类可以重写父类的方法
- Java 只支持单继承(一个类只能有一个直接父类)
1.3 方法重写(Override)
子类对父类的方法"重新实现":
java
class Animal {
void speak() {
System.out.println("动物发出声音");
}
}
class Dog extends Animal {
@Override // 注解:标记这是重写,拼写错误时编译器会提醒
void speak() {
System.out.println("汪汪汪!");
}
}
class Cat extends Animal {
@Override
void speak() {
System.out.println("喵喵喵!");
}
}
重写规则:
- ✅ 方法名、参数列表必须完全一致
- ✅ 返回值类型相同或是父类返回值的子类
- ✅ 访问权限不能比父类更严格(父类 public → 子类不能改 protected)
- ❌ 不能重写 private 方法(子类根本看不到)
- ❌ 不能重写 static 方法(那叫"隐藏",不是重写)
⚠️ 重写 vs 重载,别搞混!
表格
重写 Override 重载 Overload 发生在 父子类之间 同一个类中 方法名 相同 相同 参数列表 相同 不同 返回值 相同或子类 无关 关注点 行为不同实现 同名不同参数
1.4 super 关键字
super 指向父类,this 指向当前对象:
java
class Animal {
String name;
Animal(String name) {
this.name = name;
}
}
class Dog extends Animal {
String breed;
Dog(String name, String breed) {
super(name); // 调用父类构造方法,必须放第一行!
this.breed = breed;
}
void info() {
System.out.println("名字:" + super.name); // 访问父类属性
System.out.println("品种:" + this.breed); // 访问自己的属性
}
}
构造方法的调用顺序:
class A {
A() { System.out.println("A的构造方法"); }
}
class B extends A {
B() { System.out.println("B的构造方法"); }
}
class C extends B {
C() { System.out.println("C的构造方法"); }
}
new C();
// 输出:A的构造方法 → B的构造方法 → C的构造方法
💡 创建子类对象时,一定先调用父类构造方法。如果不显式写
super(...),编译器自动加super()(即调用父类无参构造)。
1.5 final 关键字
表格
| 用法 | 含义 |
|---|---|
final 变量 |
常量,赋值后不能改 |
final 方法 |
不能被重写 |
final 类 |
不能被继承 |
java
final class MathUtils { // 不能被继承
static final double PI = 3.14159; // 常量
final double calc() { // 不能被重写
return 0;
}
}
二、多态(Polymorphism):同一指令,不同表现
2.1 核心概念
编译看左边,运行看右边——这是理解多态的关键口诀。
Animal animal = new Dog(); // 父类引用指向子类对象
animal.speak(); // 编译时看 Animal 有没有 speak(),运行时执行 Dog 的 speak()
// 输出:汪汪汪!
多态的三个前提:
- 有继承关系
- 有方法重写
- 父类引用指向子类对象
2.2 多态的威力
不使用多态——每个类型单独处理:
void feed(Dog d) { d.eat(); }
void feed(Cat c) { c.eat(); }
void feed(Bird b) { b.eat(); }
// 每增加一种动物,就要加一个方法!
使用多态——一个方法搞定所有:
void feed(Animal a) {
a.eat(); // 传什么动物,就调谁的 eat()
}
feed(new Dog()); // Dog 的 eat()
feed(new Cat()); // Cat 的 eat()
feed(new Bird()); // Bird 的 eat()
2.3 类型转换
向上转型(自动,安全):
Animal a = new Dog(); // 子类 → 父类,自动
a.eat(); // ✅ 可以调
// a.bark(); // ❌ 编译报错!父类引用看不到子类特有方法
向下转型(需要强转,有风险):
Animal a = new Dog();
Dog d = (Dog) a; // 父类 → 子类,强转
d.bark(); // ✅ 现在可以调了
// 危险!类型不对会报 ClassCastException
Animal b = new Cat();
Dog x = (Dog) b; // ❌ 运行时报错!Cat 不能转成 Dog
安全做法:先判断再转
Animal a = new Dog();
if (a instanceof Dog) {
Dog d = (Dog) a;
d.bark();
}
// Java 16+ 更简洁的写法:
if (a instanceof Dog d) { // 模式匹配,一步到位
d.bark();
}
三、抽象类(Abstract Class):只定规矩,不干实事
3.1 为什么需要抽象类?
有些方法在父类中没法给出具体实现,比如 Animal.speak()——每种动物叫法不同,写什么都没意义。
abstract class Animal {
String name;
// 抽象方法:只有声明,没有实现,留给子类去重写
abstract void speak();
// 抽象类也可以有普通方法
void eat() {
System.out.println(name + "在吃东西");
}
}
3.2 抽象类的规则
- 有抽象方法的类必须声明为抽象类
- 抽象类不能实例化(不能
new Animal()) - 子类必须重写所有抽象方法,否则子类也得是抽象类
class Dog extends Animal {
Dog(String name) {
this.name = name;
}
@Override
void speak() { // 必须重写,否则编译报错
System.out.println("汪汪汪!");
}
}
3.3 抽象类 vs 普通类
表格
| 抽象类 | 普通类 | |
|---|---|---|
| 实例化 | ❌ 不能 | ✅ 可以 |
| 抽象方法 | ✅ 可以有 | ❌ 不能 |
| 普通方法 | ✅ 可以有 | ✅ 可以 |
| 构造方法 | ✅ 可以有 | ✅ 可以 |
| 作用 | 定义模板,强制子类实现 | 直接使用 |
四、接口(Interface):行为的契约
4.1 什么是接口?
接口是一组行为规范的集合,定义"能做什么",不管"怎么做"。
interface Swimable {
void swim(); // 默认 public abstract,不需要写修饰符
}
interface Flyable {
void fly();
}
4.2 实现接口
class Duck extends Animal implements Swimable, Flyable {
Duck(String name) {
this.name = name;
}
@Override
void speak() {
System.out.println("嘎嘎嘎!");
}
@Override
public void swim() {
System.out.println(name + "在游泳");
}
@Override
public void fly() {
System.out.println(name + "在飞");
}
}
💡 一个类只能继承一个父类,但可以实现多个接口——这就是 Java 实现"多继承"效果的方式。
4.3 接口的新特性(Java 8+)
默认方法——接口可以有方法体了:
interface Swimable {
void swim();
// 默认方法:有实现,子类可以选择重写或直接用
default void rest() {
System.out.println("上岸休息一会儿");
}
}
class Duck implements Swimable {
@Override
public void swim() {
System.out.println("鸭子游泳");
}
// rest() 不用重写也能用
}
new Duck().rest(); // 上岸休息一会儿
静态方法:
interface Swimable {
static void checkWater() {
System.out.println("检查水质...");
}
}
Swimable.checkWater(); // 通过接口名调用
常量——接口中的变量默认是 public static final:
interface Config {
int MAX_SIZE = 100; // 等同于 public static final int MAX_SIZE = 100
}
4.4 接口的继承
接口可以继承多个接口:
interface A {
void methodA();
}
interface B {
void methodB();
}
interface C extends A, B { // 接口多继承
void methodC();
}
class Impl implements C {
public void methodA() { }
public void methodB() { }
public void methodC() { }
}
4.5 抽象类 vs 接口
表格
| 抽象类 | 接口 | |
|---|---|---|
| 关键字 | abstract class | interface |
| 构造方法 | ✅ 可以有 | ❌ 没有 |
| 成员变量 | 任意 | 只能是常量(public static final) |
| 方法 | 抽象 + 普通都行 | 默认抽象;Java 8+ 可有 default/static |
| 多继承 | 单继承 | 可多实现 |
| 设计意图 | is-a 关系(是什么) | can-do 关系(能做什么) |
🎯 选择原则:如果是一组有层级关系的类,用抽象类(Animal → Dog/Cat);如果是一种能力/行为,用接口(Swimable、Flyable)。
五、内部类
5.1 成员内部类
定义在类内部,可以访问外部类的所有成员:
class Outer {
private String msg = "外部类的私有数据";
class Inner {
void show() {
System.out.println(msg); // 能访问外部类私有成员!
}
}
}
// 创建内部类对象需要先有外部类对象
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.show(); // 外部类的私有数据
5.2 静态内部类
加 static 的内部类,不依赖外部类对象:
java
class Outer {
static String msg = "静态数据";
static class StaticInner {
void show() {
System.out.println(msg); // 只能访问外部类的静态成员
}
}
}
Outer.StaticInner inner = new Outer.StaticInner(); // 不需要外部类对象
inner.show();
5.3 匿名内部类
没有名字的内部类,一次性使用,常见于接口/抽象类的快速实现:
java
interface Greeting {
void sayHello();
}
// 传统方式:写一个类实现接口
// 匿名内部类:当场实现
Greeting g = new Greeting() {
@Override
public void sayHello() {
System.out.println("你好!");
}
};
g.sayHello(); // 你好!
💡 匿名内部类在事件监听、回调、线程创建等场景非常常见,后续学 GUI 和多线程时会大量用到。
5.4 局部内部类
定义在方法内部的类,作用域仅限于该方法:
class Outer {
void method() {
class Local {
void show() {
System.out.println("我是局部内部类");
}
}
Local l = new Local();
l.show();
}
}
六、Object 类:所有类的祖宗
Java 中每个类都直接或间接继承 Object,它提供几个重要方法:
6.1 toString()
默认打印类名+哈希值,建议重写:
class Student {
String name;
int age;
Student(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Student{name='" + name + "', age=" + age + "}";
}
}
System.out.println(new Student("张三", 20));
// 不重写:Student@15db9742
// 重写后:Student{name='张三', age=20}
6.2 equals()
默认比较引用(地址),建议重写为比较内容:
class Student {
String name;
int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 同一个对象
if (obj == null) return false; // null 不等于任何
if (getClass() != obj.getClass()) return false; // 类型不同
Student other = (Student) obj;
return age == other.age && name.equals(other.name);
}
}
new Student("张三", 20).equals(new Student("张三", 20)); // true
⚠️
==比较引用(地址),equals()比较内容(但前提是你重写了它)。
6.3 hashCode()
重写 equals 时必须同时重写 hashCode,否则 HashSet/HashMap 等会出 bug:
rride
public int hashCode() {
return Objects.hash(name, age);
}
七、实战练习
练习1:多态版动物合唱团
abstract class Animal {
String name;
Animal(String name) { this.name = name; }
abstract void speak();
}
class Dog extends Animal {
Dog(String name) { super(name); }
void speak() { System.out.println(name + ":汪!"); }
}
class Cat extends Animal {
Cat(String name) { super(name); }
void speak() { System.out.println(name + ":喵!"); }
}
class Cow extends Animal {
Cow(String name) { super(name); }
void speak() { System.out.println(name + ":哞!"); }
}
// 合唱:多态的体现
class Chorus {
static void perform(Animal[] animals) {
for (Animal a : animals) {
a.speak(); // 运行时自动调用各自的实现
}
}
}
Animal[] band = { new Dog("旺财"), new Cat("咪咪"), new Cow("大黄") };
Chorus.perform(band);
// 旺财:汪!
// 咪咪:喵!
// 大黄:哞!
练习2:接口实现多重能力
interface Chargeable {
void charge();
}
interface Playable {
void play();
}
class Phone implements Chargeable, Playable {
public void charge() { System.out.println("手机充电中..."); }
public void play() { System.out.println("刷视频~"); }
}
class Car implements Chargeable {
public void charge() { System.out.println("电车充电中..."); }
}
Chargeable[] devices = { new Phone(), new Car() };
for (Chargeable d : devices) {
d.charge(); // 同一个接口,不同实现
}
八、总结速查表
表格
| 概念 | 核心要点 |
|---|---|
| 继承 | extends;单继承;子类获得父类非 private 成员 |
| 方法重写 | @Override;方法签名一致;运行时执行子类版本 |
| super | 调用父类构造(必须第一行)、父类成员 |
| final | 修饰变量→常量,修饰方法→不可重写,修饰类→不可继承 |
| 多态 | 父类引用指向子类对象;编译看左,运行看右 |
| 向上/向下转型 | 向上自动、向下强转;instanceof 先判断再转 |
| 抽象类 | abstract;不能实例化;可含抽象方法+普通方法 |
| 接口 | interface/implements;可多实现;Java 8+ 支持 default/static 方法 |
| 抽象类 vs 接口 | is-a 用抽象类,can-do 用接口 |
| 内部类 | 成员内部类、静态内部类、匿名内部类、局部内部类 |
| Object | toString/equals/hashCode 建议重写 |
继承让你复用,多态让你灵活,抽象类定模板,接口定契约——这四块拼图合在一起,才是完整的 OOP。从流程控制走到这里,你已经拿下了 Java 面向对象的全景图。下一步:异常处理、集合框架,冲!💪
更多推荐
所有评论(0)