Java SE 基础学习笔记(一):面向对象、继承多态、抽象接口与异常(JDK 8)
适用版本:JDK 8
学习目标:把"类、对象、封装、继承、多态、抽象、接口、内部类、异常"串成一个完整的知识框架。
阅读建议:代码按知识点拆分;为展示继承、接口等关系,部分片段会把相关类型放在一起。实际运行时,请将每个
public class放在同名的.java文件中,并补齐片段依赖的类型。
一、学习路线与核心问题
这一节的核心不是背概念,而是回答四个问题:
- 如何用类和对象描述现实或业务中的事物?
- 如何隐藏实现细节,并让代码容易修改和扩展?
- 为什么"父类引用指向子类对象"能调用子类重写的方法?
- 程序出现异常时,应该在哪里发现、在哪里处理、如何避免资源泄漏?
text
类(抽象的设计图) ── new ──> 对象(具体实例)
│ │
├─ 封装:保护数据 ├─ 状态:字段 / 属性
├─ 继承:复用共性 └─ 行为:方法
└─ 多态:统一调用、按实际对象执行
异常机制:发现异常 → 抛出 / 传播 → 捕获处理 → 资源收尾
二、面向对象编程(OOP)
2.1 什么是面向对象
面向对象编程(Object-Oriented Programming,OOP)是一种组织程序的思路:用类 描述一类事物的共同特征,再用对象表示一个个具体实例。对象把自己的数据和可执行的行为组织在一起。
以学生为例:
| 概念 | 示例 |
|---|---|
| 类(Class) | Student,描述"学生"这一类事物 |
| 属性 / 字段(Field) | name、age |
| 方法(Method) | study()、introduce() |
| 对象(Object) | new Student("Mingyu", 20) |
"字段 + 方法 = 类"可以帮助初学理解,但并不严谨。一个类还可以包含构造器、初始化块、内部类等;而某些类可能只有字段或只有工具方法。
2.2 面向过程与面向对象
| 思路 | 关注点 | 适合的表达方式 |
|---|---|---|
| 面向过程 | 先做什么、后做什么 | 把任务拆成步骤和函数 |
| 面向对象 | 谁拥有数据、谁负责行为 | 把状态和行为交给相应对象 |
例如"完成一次付款":
text
面向过程:校验金额 → 选择渠道 → 扣款 → 记录订单 → 通知用户
面向对象:Order 负责订单状态;
Payment 负责付款能力;
AlipayPayment / WeChatPayment 负责各自渠道的具体实现。
两种思路并不对立。Java 的方法内部仍然常常以步骤执行;面向对象主要帮助我们在程序规模变大时划分职责。
2.3 面向对象的核心思想
初学阶段常把封装、继承、多态 称为 Java 面向对象的三大特性。除此之外,抽象也是非常重要的思想:从众多具体事物中提炼共同特征和规则。
三、类、对象与引用
3.1 创建对象
java
public class Student {
String name;
int age;
public void study() {
System.out.println(name + " 正在学习 Java");
}
}
创建对象时:
java
Student student = new Student();
student.name = "Mingyu";
student.age = 20;
student.study();
这行代码可以拆开理解:
text
Student :引用变量的声明类型
student :引用变量名
new Student() :创建一个 Student 对象
= :把对象的引用赋给变量
student 保存的是对象的引用,不是把整个对象复制进变量中。
3.2 类与对象的关系
text
Student 类(模板)
├─ name
├─ age
└─ study()
new Student() ──> 一个具体 Student 对象
student ─────────> 指向该对象的引用
3.3 命名规范
这不是语法强制,但建议从一开始遵守:
| 项目 | 建议 | 示例 |
|---|---|---|
| 类、接口、枚举 | 大驼峰(UpperCamelCase) | Student、TestException |
| 方法、变量 | 小驼峰(lowerCamelCase) | calculateTotal()、userName |
| 常量 | 全大写,下划线分隔 | MAX_AGE |
| 包名 | 全小写,通常使用域名倒写 | com.example.demo |
四、静态成员与实例成员
4.1 静态字段和静态方法
用 static 修饰的成员属于类本身,由该类的所有对象共享。推荐使用"类名.成员名"访问。
java
public class Counter {
static int total = 0;
public Counter() {
total++;
}
public static void printTotal() {
System.out.println("对象总数:" + total);
}
}
java
new Counter();
new Counter();
Counter.printTotal(); // 对象总数:2
4.2 实例字段和实例方法
没有 static 的成员属于对象,每个对象都有自己的实例状态。
java
public class Person {
String name;
public void sayHello() {
System.out.println("你好,我是 " + name);
}
}
java
Person p1 = new Person();
p1.name = "Alice";
Person p2 = new Person();
p2.name = "Bob";
p1.sayHello(); // 你好,我是 Alice
p2.sayHello(); // 你好,我是 Bob
4.3 静态方法为什么不能直接调用实例方法
静态方法可以在没有创建任何对象时被调用,因此它没有隐式的 this 对象;而实例字段、实例方法必须依附于某个具体对象。
java
public class Demo {
int age = 18;
public void instanceMethod() {
System.out.println(age);
}
public static void staticMethod() {
// System.out.println(age); // 编译错误:没有具体对象
// instanceMethod(); // 编译错误:没有具体对象
Demo demo = new Demo();
System.out.println(demo.age);
demo.instanceMethod();
}
}
反过来,实例方法可以直接访问同类的静态成员:
java
public void instanceMethod() {
System.out.println(Counter.total);
Counter.printTotal();
}
4.4 main 为什么是静态方法
JVM 启动程序时,需要先找到入口:
java
public static void main(String[] args)
此时 JVM 还没有帮我们创建任何对象,所以入口方法必须是静态方法。一个项目中可以存在多个带 main 的练习类或启动类;每次运行配置只会选择其中一个作为当前入口。
五、形参与实参:Java 只有值传递
5.1 基本数据类型
java
public class PassByValueDemo {
public static void main(String[] args) {
int number = 10;
change(number);
System.out.println(number); // 10
}
static void change(int value) {
value = 99;
}
}
调用 change(number) 时,number 的值 10 被复制给了形参 value。修改 value 不会影响原变量。
5.2 对象引用
java
public class PassByValueObjectDemo {
public static void main(String[] args) {
Student student = new Student();
student.name = "Mingyu";
rename(student);
System.out.println(student.name); // Ray
}
static void rename(Student value) {
value.name = "Ray";
}
}
这里看起来像"引用传递",但更准确的说法是:
**Java 始终只有值传递。**传入的是对象引用的副本;两个引用都指向同一个对象,因此可以通过该副本修改对象的状态。
如果在方法里让形参指向一个新对象,调用方的引用不会改变:
java
static void replace(Student value) {
value = new Student();
value.name = "New Name";
}
六、构造器与 this
6.1 构造器的作用
构造器用于初始化新对象。它的规则:
- 名称必须与类名相同;
- 没有返回类型,连
void也不能写; - 创建对象时由
new自动调用; - 只有当类中完全没有声明任何构造器时,编译器才会自动提供无参构造器。
java
public class User {
private String name;
private int age;
public User() {
this("未命名用户", 0);
}
public User(String name, int age) {
this.name = name;
this.age = age;
}
}
6.2 this 的含义
this 是"当前对象"的引用,常用于区分成员变量和形参:
java
public User(String name, int age) {
this.name = name;
this.age = age;
}
左边的 this.name 是对象字段,右边的 name 是传入的形参。this 不能出现在静态方法中,因为静态方法没有当前对象。
七、对象在内存中的概念模型
下面是帮助理解的简化模型;JVM 的具体实现会随版本和虚拟机不同而变化。
text
方法调用栈(栈帧) 堆(Heap)
┌────────────────┐ ┌──────────────────────┐
│ student 引用 ────────────────> │ Student 对象 │
│ │ name = "Mingyu" │
│ │ age = 20 │
└────────────────┘ └──────────────────────┘
类的元数据、方法信息等由 JVM 管理(Java 8 中常称 Metaspace / 元空间)。
记住三个结论即可:
- 局部变量和方法调用相关的数据可以理解为与栈帧相关;
- 用
new创建的普通对象通常位于堆中; - 变量保存的是对象引用,多个引用可以指向同一个对象。
八、封装:保护数据,而不是机械地生成 Getter / Setter
8.1 为什么需要封装
如果字段全部公开,任何代码都可以写出不合理的数据:
java
// 不推荐
public int age;
更好的做法是隐藏字段,并只暴露必要的操作:
java
public class Account {
private double balance;
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("存款金额必须大于 0");
}
balance += amount;
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) {
return false;
}
balance -= amount;
return true;
}
}
这里外部代码不能任意修改 balance,只能通过 deposit()、withdraw() 改变余额,从而保证规则得到执行。
8.2 高内聚、低耦合
text
高内聚:一个类专注完成自己的职责。
低耦合:类之间只通过少量、清晰的接口合作,不依赖彼此内部细节。
封装并不等于"每个字段都必须生成 getter/setter"。如果某个属性不应被外部修改,就不应该提供 setter。
九、方法重载(Overload)
方法重载指在同一个类中,方法名相同、参数列表不同(参数数量、类型或顺序不同)。
java
public class PrintUtil {
public void print(int value) {
System.out.println(value);
}
public void print(String value) {
System.out.println(value);
}
public void print(String value, int times) {
for (int i = 0; i < times; i++) {
System.out.println(value);
}
}
}
以下情况不能构成重载:
java
// 错误示例:仅返回类型不同,参数列表相同
// public int add(int a, int b) { return a + b; }
// public double add(int a, int b) { return a + b; }
println 就是常见的重载例子:它可以接收字符串、整数、布尔值、对象等不同类型的参数。
十、继承与访问修饰符
10.1 继承
当两个类是明确的 "is-a(是一个)" 关系时,可以使用继承复用共性。
java
public class Animal {
protected String name;
public Animal(String name) {
this.name = name;
}
public void eat() {
System.out.println(name + " 正在进食");
}
}
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
public void bark() {
System.out.println(name + ":汪汪");
}
}
text
Dog is an Animal(狗是一种动物) → 适合继承
Dog has an Owner(狗拥有主人) → 更适合组合,而非继承
除 Object 本身外,每个 Java 类都直接或间接继承 Object。Java 的类只支持单继承,但一个类可以实现多个接口。
10.2 访问修饰符
| 修饰符 | 同一个类 | 同一个包 | 不同包的子类 | 其他位置 |
|---|---|---|---|---|
private |
可以 | 不可以 | 不可以 | 不可以 |
| 不写修饰符(default / package-private) | 可以 | 可以 | 不可以 | 不可以 |
protected |
可以 | 可以 | 可以,但跨包时通常只能通过继承关系访问 | 不可以 |
public |
可以 | 可以 | 可以 | 可以 |
父类的 private 成员不能被子类直接访问,也不能被子类重写;需要时应通过父类提供的受保护或公开方法访问。
10.3 super 与构造器调用
super 用于访问可访问的父类成员,或调用父类构造器:
java
public Dog(String name) {
super(name); // 必须位于构造器的第一行
}
如果子类构造器没有显式写 super(...) 或 this(...),编译器会尝试在第一行插入 super()。如果父类没有可访问的无参构造器,子类必须显式选择一个可访问的父类构造器。
十一、方法重写(Override)
子类可以为继承到的实例方法提供自己的实现,这叫方法重写。
java
public class Animal {
public void speak() {
System.out.println("动物发出声音");
}
}
public class Dog extends Animal {
@Override
public void speak() {
System.out.println("汪汪");
}
}
重写需要满足:
- 有继承关系;
- 方法名和参数列表相同;
- 返回类型相同,或是父类返回类型的子类型(协变返回);
- 子类方法的访问权限不能更严格;
- 不能抛出比父类方法更宽泛的受检异常;
- 建议始终加上
@Override,让编译器帮助检查。
以下成员不参与普通的实例方法重写:
| 成员 | 说明 |
|---|---|
static 方法 |
属于类;子类同名方法属于隐藏(method hiding),不是重写 |
final 实例方法 |
不允许被重写 |
private 方法 |
子类不可见,不能重写 |
| 构造器 | 不会被继承,因此不存在重写 |
十二、多态:父类引用,子类对象
12.1 最核心的写法
java
Animal animal = new Dog();
animal.speak(); // 汪汪
这行代码中:
text
Animal animal :引用变量的声明类型(编译期类型)
new Dog() :对象的实际类型(运行期类型)
调用被子类重写的实例方法 时,JVM 会根据对象的实际类型进行动态绑定,因此执行 Dog.speak()。
12.2 编译期与运行期分别决定什么
这是多态中最容易混淆的一点:
text
编译期:看引用变量的声明类型,决定"能否调用这个成员"。
运行期:对被重写的实例方法,看对象实际类型,决定"执行哪一版实现"。
java
Animal animal = new Dog();
animal.speak(); // 可以调用:Animal 中声明了 speak()
// animal.bark(); // 编译错误:Animal 类型中没有 bark()
12.3 多态的价值
java
public static void makeAnimalSpeak(Animal animal) {
animal.speak();
}
makeAnimalSpeak(new Dog()); // 汪汪
makeAnimalSpeak(new Cat()); // 喵喵
调用方只依赖 Animal 的共同能力,不必为每一种动物写一套调用逻辑。之后加入 Bird,通常不需要修改 makeAnimalSpeak()。
常见业务场景包括付款方式、消息通知、员工薪资计算、文件导出等。
12.4 多态的边界
- 多态主要体现为实例方法的重写,字段没有运行时多态;
- 静态方法按引用的声明类型绑定,不是动态分派;
- 父类引用不能直接访问子类特有方法;
- 向上转型自动且安全;向下转型需要显式强转并检查实际类型。
java
Animal animal = new Dog(); // 向上转型,安全
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型
dog.bark();
}
数值强转可能发生精度损失;引用强转不存在"丢失精度"。向上转型只是让子类特有成员暂时不能通过父类引用直接访问。
十三、static、代码块与 final
13.1 初始化顺序
java
public class InitDemo {
static {
System.out.println("静态代码块:类首次初始化时执行一次");
}
{
System.out.println("实例初始化块:每次创建对象时执行");
}
public InitDemo() {
System.out.println("构造器:每次创建对象时执行");
}
}
首次 new InitDemo() 时,简化理解为:
text
静态字段 / 静态代码块(按源码顺序,且只执行一次)
→ 实例字段 / 实例初始化块(每次对象创建)
→ 构造器体(每次对象创建)
13.2 静态导入
import static 导入的是某个类的静态成员,不是"导入包"。
java
import static java.lang.Math.PI;
import static java.lang.Math.max;
double radius = 2;
double perimeter = 2 * PI * radius;
int bigger = max(3, 5);
静态导入过多会降低代码可读性,通常只在常用常量、测试断言等场景谨慎使用。
13.3 final
| 用法 | 含义 |
|---|---|
final 变量 |
变量只能赋值一次;若是对象引用,不能重新指向其他对象,但对象内部状态仍可能变化 |
final 方法 |
不能被子类重写 |
final 类 |
不能被继承 |
十四、抽象类
14.1 为什么需要抽象类
当父类只提供共同状态和部分共同实现,但某个行为必须由具体子类决定时,可以使用抽象类。
java
public abstract class Shape {
private String color;
public Shape(String color) {
this.color = color;
}
public String getColor() {
return color;
}
public abstract double area();
}
public class Circle extends Shape {
private double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
结论:
- 抽象类不能直接
new; - 抽象类可以有字段、构造器、普通方法、静态方法和抽象方法;
- 抽象方法没有方法体;
- 具体子类必须实现所有继承到的抽象方法,否则子类也必须声明为
abstract; - 抽象类的构造器可以存在;创建具体子类对象时,父类构造器仍会执行。
十五、接口:定义能力与约定
接口更强调"能做什么",常用于定义多个类共同遵守的能力或规范。
java
public interface Flyable {
void fly();
}
public interface Swimmable {
void swim();
}
public class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子飞行");
}
@Override
public void swim() {
System.out.println("鸭子游泳");
}
}
在 JDK 8 中,接口可以包含:
| 成员 | 说明 |
|---|---|
| 常量 | 默认是 public static final |
| 抽象方法 | 默认是 public abstract |
| 默认方法 | 使用 default,可以有方法体 |
| 静态方法 | 使用 static,可以有方法体 |
接口不能被实例化,也不能声明构造器。类可以实现多个接口;接口本身也可以继承多个接口:
java
public interface Amphibious extends Flyable, Swimmable {
}
15.1 面向接口编程
java
public interface Payment {
void pay(double amount);
}
public class AlipayPayment implements Payment {
@Override
public void pay(double amount) {
System.out.println("支付宝付款:" + amount);
}
}
public class PaymentService {
public void checkout(Payment payment, double amount) {
payment.pay(amount);
}
}
PaymentService 依赖的是 Payment 这个约定,而不是某个具体渠道。以后替换为微信支付、银行卡支付时,调用方通常不需要改变。
15.2 抽象类与接口如何选择
| 场景 | 更适合 |
|---|---|
| 多个类有共同字段、构造逻辑、部分通用实现 | 抽象类 |
| 只需要描述共同能力或规范,且可能由不相关的类实现 | 接口 |
| 需要让一个类具备多种能力 | 多个接口 |
十六、内部类
内部类是定义在另一个类内部的类。常见形式如下:
| 类型 | 特点 |
|---|---|
| 成员内部类 | 定义在外部类成员位置,通常依赖外部类对象 |
| 静态内部类 | 用 static 修饰,不依赖外部类对象 |
| 局部内部类 | 定义在方法、构造器或代码块内部 |
| 匿名内部类 | 没有类名,在创建对象时同时给出实现 |
匿名内部类示例:
java
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("执行任务");
}
};
task.run();
注意:
java
new TestException().a();
这只是"创建一个对象后立刻调用 a()",不是 匿名内部类。匿名内部类必须有紧跟在 new 类型() 后面的类体:
java
new Runnable() {
@Override
public void run() {
}
};
十七、异常机制
17.1 异常体系
text
Throwable
├─ Error
│ ├─ OutOfMemoryError
│ └─ StackOverflowError
└─ Exception
├─ RuntimeException(非受检异常)
│ ├─ NullPointerException
│ ├─ ArithmeticException
│ └─ ArrayIndexOutOfBoundsException
└─ 其他 Exception(受检异常)
└─ IOException
| 类型 | 说明 | 编译器是否强制处理 |
|---|---|---|
Error |
通常表示严重问题,如内存不足、栈溢出;一般不建议业务代码试图恢复 | 否 |
| 运行时异常 | 常见于空指针、除零、下标越界、非法参数等 | 否 |
| 受检异常 | 常见于文件、网络、数据库等外部操作失败 | 是:必须捕获或声明 throws |
不要简单地把
Error理解成"JVM 抛出"、把Exception理解成"程序逻辑错误"。两者的真实边界更复杂;初学阶段重点是区分"是否可恢复、是否要求编译期处理"。
17.2 try-catch-finally
java
public class DivideDemo {
public static void main(String[] args) {
try {
int result = 10 / 0;
System.out.println(result);
} catch (ArithmeticException e) {
System.out.println("除数不能为 0");
e.printStackTrace();
} finally {
System.out.println("任务结束");
}
}
}
执行过程:
text
进入 try
→ 发生 ArithmeticException
→ 跳过 try 中剩余语句
→ 进入匹配的 catch
→ 执行 finally(通常会执行)
→ 继续执行 finally 之后的代码
finally 常用于收尾,但不是绝对保证执行:例如调用 System.exit()、进程被强制终止或 JVM 崩溃时,它可能不会执行。
多个 catch 时,必须先写具体的子类异常,再写一般的父类异常:
java
try (java.io.FileReader reader = new java.io.FileReader("note.txt")) {
System.out.println(reader.read());
} catch (java.io.FileNotFoundException e) {
// 更具体的异常
} catch (java.io.IOException e) {
// 更一般的异常
}
17.3 throw 与 throws
| 关键字 | 位置 | 作用 |
|---|---|---|
throw |
方法体、构造器或代码块内部 | 真正创建并抛出一个异常对象 |
throws |
方法声明处 | 声明该方法可能向调用方传播的异常,主要用于受检异常 |
java
public int divide(int a, int b) {
if (b == 0) {
throw new ArithmeticException("除数不能为 0");
}
return a / b;
}
ArithmeticException 是运行时异常,因此上述方法即使不写 throws ArithmeticException 也可以编译和传播异常。真正使异常离开 divide() 的是 throw new ...。
受检异常的例子:
java
import java.io.FileReader;
import java.io.IOException;
public class FileDemo {
public static void readFile() throws IOException {
FileReader reader = new FileReader("note.txt");
reader.close();
}
}
调用 readFile() 的代码必须捕获 IOException,或继续用 throws IOException 向上声明。
17.4 更推荐的资源处理方式:try-with-resources
文件流、网络连接、数据库连接等资源应及时关闭。JDK 7 及以后优先使用 try-with-resources:
java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class ReadFileDemo {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("note.txt"))) {
System.out.println(reader.readLine());
} catch (IOException e) {
System.out.println("读取文件失败:" + e.getMessage());
}
}
}
资源会在 try 结束时自动关闭,通常比手写 finally 更安全、简洁。
17.5 自定义异常
当业务规则需要表达明确错误时,可以定义自定义异常:
java
public class AgeOutOfRangeException extends Exception {
public AgeOutOfRangeException(String message) {
super(message);
}
}
java
public class AgeValidator {
public static void validate(int age) throws AgeOutOfRangeException {
if (age < 0 || age > 150) {
throw new AgeOutOfRangeException("年龄必须在 0 到 150 之间");
}
}
public static void main(String[] args) {
try {
validate(200);
} catch (AgeOutOfRangeException e) {
System.out.println(e.getMessage());
}
}
}
17.6 异常处理建议
- 能在入口校验的预期问题,优先用清晰的
if判断; - 当前方法不知道如何恢复时,再抛异常交给上层决定;
- 不要随意捕获
Throwable; - 不要吞掉异常:空的
catch会让问题更难排查; printStackTrace()适合学习和临时排查;生产代码应结合日志和业务提示;- 异常信息要包含上下文,例如"订单号、文件路径、输入参数范围"等;
- 不要把异常当作普通流程控制工具。
十八、常见易错点速查
| 易错点 | 正确理解 |
|---|---|
| Java 有"引用传递" | Java 只有值传递;对象引用的副本被传入方法 |
| 静态方法绝不能调用普通方法 | 静态方法不能直接调用实例方法;可以先创建对象再调用 |
所有类只能有一个 main |
一个项目可有多个入口类;一次运行选择一个入口 |
static 方法能被重写 |
静态方法是隐藏,不是实例方法重写 |
| 父类引用调用方法只看左边类型 | 编译期看左边决定可调用成员;运行期重写方法看实际对象 |
| 强制向下转型会"丢失方法" | 向上转型会隐藏子类特有成员;向下转型恢复可见性,需先判断 instanceof |
| 抽象类只有抽象方法 | 抽象类也可以有字段、构造器和普通方法 |
throws 就是在抛异常 |
throw 才真正抛出;throws 是声明或继续传播责任 |
finally 一定执行 |
通常执行,但 System.exit() 等情况是例外 |
十九、复习清单
学完后,应该能不看资料回答:
- 类、对象、字段、方法、引用分别是什么?
- 静态成员和实例成员的差别是什么?为什么
main是静态的? - Java 参数传递为什么只有值传递?
- 构造器、
this、super分别解决什么问题? - 封装为什么不是"无脑生成 getter/setter"?
- 方法重载和重写有什么区别?
- 继承、多态、向上转型、向下转型之间有什么关系?
- 抽象类与接口各适用于什么场景?
- 受检异常、运行时异常、
Error的区别是什么? -
throw、throws、try-catch-finally如何配合?
二十、练习建议
- 创建
Student类:私有化姓名和年龄,为年龄设置合法范围; - 创建
Animal、Dog、Cat,用父类引用数组依次调用speak(); - 创建
Payment接口,实现两个支付类,并让一个统一服务调用它们; - 编写一个
divide(a, b)方法:除数为 0 时给出清晰错误信息; - 使用 try-with-resources 读取一个文本文件,并分别处理"文件不存在"和"读取失败"。
结语
这一节的主线可以概括为:
用类抽象事物,用对象保存状态;用封装保护规则,用继承复用共性,用多态降低耦合;在异常发生时,让合适的层级决定如何处理。
后续学习集合、IO、多线程、数据库和框架时,今天的类、接口、多态和异常都会反复出现。先把这些基础概念理解准确,比记住更多语法更重要。