🎯 本章你会学到:extends / implements、@Override 与运行时多态:同一段代码处理不同对象。建议边读边敲,每节练习先自己做,再展开答案对照。
多态解决的问题是:**同一段代码,处理不同的具体类型**。在 Java 里这件事比 C++ 更省心——方法默认就是动态绑定的,不需要记得写 virtual。
接口 + record:最简多态
接口与多态
import java.util.ArrayList;
import java.util.List;
public class Main {
interface Shape { // 接口:只有约定,没有状态
double area();
String name();
}
record Circle(double r) implements Shape { // record:一行写完不可变数据类
public double area() { return Math.PI * r * r; }
public String name() { return "circle"; }
}
record Square(double side) implements Shape {
public double area() { return side * side; }
public String name() { return "square"; }
}
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle(1));
shapes.add(new Square(2));
for (Shape s : shapes) { // 这里不关心具体是圆还是方
System.out.printf("%s %.2f%n", s.name(), s.area());
}
}
}运行结果
circle 3.14
square 4.00
抽象类:需要共享状态或部分实现时
抽象类与方法重写
public class Main {
static abstract class Animal {
private final String name;
Animal(String name) { this.name = name; }
abstract String sound(); // 子类必须实现
String describe() { // 共有逻辑写在父类
return name + " says " + sound(); // 运行期按实际类型调用
}
}
static class Dog extends Animal {
Dog(String name) { super(name); }
String sound() { return "woof"; }
}
static class Cat extends Animal {
Cat(String name) { super(name); }
String sound() { return "meow"; }
}
public static void main(String[] args) {
Animal[] zoo = { new Dog("Wangcai"), new Cat("Mimi") };
for (Animal a : zoo) {
System.out.println(a.describe());
}
}
}运行结果
Wangcai says woof
Mimi says meow
| 概念 | 作用 | 什么时候用 |
|---|---|---|
| extends | 继承父类(单继承) | 确实存在“是一种”关系,且要复用实现 |
| implements | 实现接口(可多个) | 只约定能力,最推荐的方式 |
| abstract | 抽象类 / 抽象方法 | 需要共享字段或部分实现时 |
| @Override | 声明这是重写 | 强烈建议都写,编译器帮你查签名 |
| record | 不可变数据类 | 只装数据、不需要继承时 |
⚠️ 易错点 父类构造器要用
super(...) 显式调用(不写时编译器会插入无参 super(),父类没有无参构造器就报错)。⚠️ 易错点 接口里的方法默认是
public,实现时不能漏写 public,否则编译报“试图分配更低的访问权限”。✍️ 本节练习
5.1必做抽象类与两个子类
定义抽象类 Shape(抽象方法 area()),派生 Circle 和 Rect。读入半径、宽、高,输出两个面积(2 位小数)。
输入 一行三个实数:半径 宽 高。
输出 两行:circle=、rect=(2 位小数)。
样例输入
1 2 3样例输出
circle=3.14
rect=6.00💡 提示 Shape[] shapes = { new Circle(r), new Rect(w, h) }; 然后循环调用 area()。
✅ 查看参考答案与解析
import java.util.Scanner;
public class Main {
static abstract class Shape {
abstract double area();
}
static class Circle extends Shape {
private final double r;
Circle(double r) { this.r = r; }
double area() { return Math.PI * r * r; }
}
static class Rect extends Shape {
private final double w, h;
Rect(double w, double h) { this.w = w; this.h = h; }
double area() { return w * h; }
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
double r = sc.nextDouble();
double w = sc.nextDouble();
double h = sc.nextDouble();
Shape[] shapes = { new Circle(r), new Rect(w, h) };
System.out.printf("circle=%.2f%n", shapes[0].area());
System.out.printf("rect=%.2f%n", shapes[1].area());
}
}解析 数组元素类型写成父类,实际对象是子类——调用 area() 时 JVM 按实际类型分派,这就是多态。
5.2挑战多形面积求和
定义接口 Shape(area()),实现圆与正方形。读入 n,随后 n 行每行是 C r(圆)或 S a(正方形),输出总面积(2 位小数)。
输入 第一行 n;接下来 n 行 C r 或 S a。
输出 一行 total=总面积(2 位小数)。
样例输入
3
C 1
S 2
C 2样例输出
total=19.71💡 提示 用 List<Shape> 收集对象,最后遍历求和;C/S 用 String 比较。
✅ 查看参考答案与解析
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Main {
interface Shape {
double area();
}
record Circle(double r) implements Shape {
public double area() { return Math.PI * r * r; }
}
record Square(double side) implements Shape {
public double area() { return side * side; }
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
List<Shape> shapes = new ArrayList<>();
for (int i = 0; i < n; i++) {
String kind = sc.next();
double v = sc.nextDouble();
shapes.add(kind.equals("C") ? new Circle(v) : new Square(v));
}
double total = 0;
for (Shape s : shapes) {
total += s.area();
}
System.out.printf("total=%.2f%n", total);
}
}解析 把所有对象装进 List<Shape> 再统一处理,是面向对象里最常用的组织方式;kind.equals("C") 而不是 kind == "C"。
📚 本文概念都在知识大全:
JVM 与字节码 · JDK · String 与 StringBuilder · 集合框架 · 泛型 · 受检异常 · 继承与接口 · record · Stream API