Java 面向对象编程
参考指导书 2.2 面向对象编程(OOP)
学习清单
- 类与对象
- 构造方法
- 封装(private, default, protected, public)
- 继承(extends)
- 多态(方法重载、方法重写)
- 抽象类(abstract class)
- 接口(interface)
- 内部类(成员内部类、静态内部类、匿名内部类、局部内部类)
- 枚举(enum)
1. 类与对象
核心概念
- 类(Class):对象的模板/蓝图,定义了属性和行为
- 对象(Object):类的具体实例,通过
new关键字创建 - 属性(Field):类中的变量,描述对象的状态
- 方法(Method):类中的函数,描述对象的行为
最小示例
public class Person {
// 属性(成员变量)
String name;
int age;
// 方法
void introduce() {
System.out.println("I'm " + name + ", " + age + " years old.");
}
}
// 使用
Person p = new Person(); // 创建对象
p.name = "Zhang San"; // 给属性赋值
p.age = 20;
p.introduce(); // 调用方法
内存模型
Person p = new Person();
↑ ↑
栈上的引用 堆上的对象(实际数据)
p 是栈上的一个引用变量(4/8 字节),指向堆中真正的 Person 对象。new 干了三件事:堆上分配内存 → 初始化默认值 → 返回引用地址。
2. 构造方法
什么是构造方法
- 方法名与类名完全相同
- 没有返回值类型(连 void 都不写)
- 通过
new调用,在对象创建时自动执行 - 如果没写,编译器会自动生成无参构造(默认构造)
构造方法重载
public class Person {
String name;
int age;
// 无参构造
public Person() {
this("unknown", 0); // 调用下面的有参构造
}
// 有参构造
public Person(String name, int age) {
this.name = name; // this.name 是属性,name 是参数
this.age = age;
}
}
Person p1 = new Person(); // 调无参
Person p2 = new Person("Li Si", 25); // 调有参
this 关键字
this.属性:区分成员变量和局部变量(同名时)this():在构造方法中调用另一个构造方法(必须写在第一行)
3. 封装
核心思想
把属性设为 private,对外提供 public 的 getter/setter 控制访问,可以在 setter 中做校验。
四种访问修饰符
| 修饰符 | 本类 | 同包 | 子类 | 任意 |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ |
default(不写) |
✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ |
public |
✅ | ✅ | ✅ | ✅ |
最小示例
public class Person {
private String name; // 外部不能直接访问
private int age;
// getter —— 读
public String getName() { return name; }
public int getAge() { return age; }
// setter —— 写,可以加校验
public void setName(String name) { this.name = name; }
public void setAge(int age) {
if (age >= 0) this.age = age; // 防止非法值
}
}
为什么封装? 直接暴露成员变量 (public String name) 会导致调用方随意修改,无法控制数据有效性。封装让类的内部实现可以自由变化,不影响外部调用方。
4. 继承
核心概念
- 用
extends关键字 - 子类拥有父类所有非 private 的属性和方法
- Java 是单继承:一个类只能继承一个直接父类
- 所有类的最终祖先都是
Object - 子类不会继承父类的构造方法
public class Student extends Person {
private String school;
public Student(String name, int age, String school) {
super(name, age); // 必须调用父类构造,且必须在第一行
this.school = school;
}
// 子类独有方法
public void study() {
System.out.println(getName() + " is studying...");
}
}
super 关键字
super(参数):调用父类构造,必须写在子类构造的第一行super.方法():调用父类中被重写的方法
子类对象的创建顺序
父类静态代码块 → 子类静态代码块 → 父类构造 → 子类构造
为什么需要继承?—— 消除重复代码
没有继承的世界:假设你有 3 种用户——学生、老师、管理员,每种都要写 name、age、login、logout。
// 反模式:每个类各自为战,大量重复
class Student {
String name; int age; // ← 重复
void login() { /* ... */ } // ← 重复
void logout() { /* ... */ } // ← 重复
void submitHomework() { /* 特有 */ }
}
class Teacher {
String name; int age; // ← 又重复一遍
void login() { /* ... */ } // ← 又重复一遍
void logout() { /* ... */ } // ← 又重复一遍
void gradePaper() { /* 特有 */ }
}
class Admin {
String name; int age; // ← 第三遍
void login() { /* ... */ } // ← 第三遍
void logout() { /* ... */ } // ← 第三遍
void manageSystem() { /* 特有 */ }
}
问题:想给 login 加个验证码功能 → 3 个类各改一遍 → 漏改一个就是 bug。
用继承解决:
class User { // 父类:抽取共性
String name; int age;
void login() { /* 统一实现 */ }
void logout() { /* 统一实现 */ }
}
class Student extends User { // 子类:只写自己特有的
void submitHomework() { /* ... */ }
}
class Teacher extends User {
void gradePaper() { /* ... */ }
}
class Admin extends User {
void manageSystem() { /* ... */ }
}
效果:login 加验证码 → 只改 User 一处 → 所有子类自动生效。
继承的设计原则:DRY(Don’t Repeat Yourself)。当你发现多个类有相同的属性和行为,说明该抽取父类了。反过来,如果两个类只有一两处相似、大部分不同,就不该强行继承——继承不是万能胶。
5. 多态
两个维度
| 方向 | 技术 | 发生时机 | 目标 |
|---|---|---|---|
| 编译时多态 | 方法重载 | 同一个类中 | 方法名相同,参数列表不同 |
| 运行时多态 | 方法重写 | 父子类之间 | 子类覆盖父类同名方法 |
方法重载(Overload)
public class Calculator {
public int add(int a, int b) { return a + b; }
public double add(double a, double b) { return a + b; } // 参数类型不同
public int add(int a, int b, int c) { return a + b + c; } // 参数个数不同
}
重载规则:方法名相同,参数列表必须不同(类型/个数/顺序),跟返回值无关。
方法重写(Override)
public class Student extends Person {
@Override // 注解:编译器帮你检查是否真的重写了
public void introduce() { // 子类重新实现父类方法
super.introduce(); // 也可以先调父类
System.out.println("I study at " + school);
}
}
重写规则:
- 方法签名(方法名 + 参数列表)必须完全相同
- 返回值类型可以是被重写方法返回值的子类型(协变返回)
- 访问权限不能比父类更严格(父 public → 子 public,不能变 private)
static方法不能被重写(只能隐藏)final方法不能被重写
多态的核心用法
Animal dog = new Dog(); // 父类引用 指向 子类对象
dog.makeSound(); // 实际调用的是 Dog 的方法(运行时决定)
// 参数用父类型 → 可以传入任何子类型对象
public void feed(Animal a) { ... }
feed(new Dog());
feed(new Cat());
instanceof 与向下转型
if (animal instanceof Dog) {
Dog dog = (Dog) animal; // 向下转型,调用 Dog 独有的方法
dog.play();
}
为什么需要多态?—— 写出通用代码
核心问题:如何让一段代码处理不同类型,而不需要为每种类型写一个版本?
没有多态的世界:
// 喂动物的方法 —— 每多一种动物,就要多写一个方法
public void feedDog(Dog dog) { dog.eat(); }
public void feedCat(Cat cat) { cat.eat(); }
public void feedBird(Bird bird) { bird.eat(); }
// 调用方必须知道具体类型
if (type == "Dog") feedDog(dog);
else if (type == "Cat") feedCat(cat);
else if (type == "Bird") feedBird(bird);
问题:加一个新动物(Fish)→ 新增 feedFish 方法 → 调用方加 else if → 每加一个动物要改两处。真正的噩梦是这样的代码散落在项目各处,改漏一处就是运行时 bug。
用多态解决:
// 父类:普通动物类
class Animal {
public void eat() {
System.out.println("动物在吃东西");
}
}
// 子类:狗
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗正在啃骨头");
}
}
// 子类:猫
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫正在吃鱼");
}
}
// 饲养员类
class Feeder {
public void feed(Animal a) {
a.eat(); // 运行时动态绑定,调用实际对象的方法
}
}
为什么叫"多态":a.eat() 这一行代码,根据 a 实际指向的对象类型表现出不同行为——同一个方法调用,多种形态。这个决策不是编译期写死的,而是运行时 JVM 根据实际对象类型动态分派的(动态绑定 / 虚方法调用)。
多态的设计原则:开闭原则(Open-Closed Principle)——对扩展开放,对修改关闭。加新功能是"新增代码"而不是"改旧代码"。改旧代码有风险,可能引入 bug;加新代码是安全的。
多态的实现前提
- 继承/接口:必须有 is-a 关系
- 方法重写:子类重写父类方法
- 父类引用指向子类对象:
Animal a = new Dog();
三者缺一不可。只写 Dog d = new Dog() 然后用 d.makeSound(),这不叫多态,叫普通方法调用——你根本没有利用"用父类型处理子类型"的灵活性。
6. 抽象类
核心概念
- 用
abstract修饰 - 不能实例化(
new Animal()编译报错) - 可以有抽象方法(没有方法体),也可以有普通方法
- 子类必须实现所有抽象方法,否则自己也要声明为抽象类
// 定义抽象动物类,声明统一的抽象方法
abstract class Animal {
public abstract void eat();
}
// 狗类
class Dog extends Animal {
@Override
public void eat() {
System.out.println("狗正在啃骨头");
}
}
// 猫类
class Cat extends Animal {
@Override
public void eat() {
System.out.println("猫正在吃鱼");
}
}
// 饲养员类
class Feeder {
// 一个方法通吃所有动物,体现多态
public void feed(Animal a) {
a.eat(); // 运行时自动调用对应的 eat()
}
}
// 测试类
public class AnimalTest {
public static void main(String[] args) {
Feeder feeder = new Feeder();
// 传什么具体的动物,就执行什么动物具体的吃法
feeder.feed(new Dog());
feeder.feed(new Cat()); // 新增 Cat 时,饲养员调用方零改动
}
}
抽象类 vs 普通类
| 普通类 | 抽象类 | |
|---|---|---|
| 实例化 | 可以 | 不可以 |
| 抽象方法 | 不能有 | 可以有 |
| 构造方法 | 可以有 | 可以有(给子类用) |
| 意义 | 具体事物 | 抽取子类共性 |
为什么需要抽象类?—— 强制子类遵守契约
问题:普通父类的方法给了默认实现,但有些场景下父类自己根本不知道该怎么实现。
// 普通父类:makeSound() 写什么?
class Animal {
void makeSound() {
// ??? 每个动物叫声都不一样,这里写什么都不对
System.out.println("???");
}
}
如果你在父类写一个空方法体 { },子类忘了重写,程序静默执行空方法——不会有任何错误提示,但功能缺失。这种 bug 极难发现(它不崩,只是不干活)。
抽向类解决:
abstract class Animal {
public abstract void makeSound(); // 不写方法体 → 语法上强制子类实现
}
class Dog extends Animal {
// 如果不写 makeSound(),编译直接报错!
@Override
public void makeSound() { System.out.println("Woof!"); }
}
效果:子类忘记实现 → 编译期就报错,不可能带到运行时。这就是"把错误左移"——bug 发现得越早,修复成本越低(编译期 < 测试阶段 < 线上事故)。
抽象类的另一个经典用法:模板方法模式
父类定义一个算法的骨架,把可变步骤交给子类实现:
abstract class DataExporter {
// 模板方法(final:不让子类改流程)
public final void export() {
connect();
writeData();
close();
}
private void connect() { System.out.println("Connected"); } // 固定步骤
private void close() { System.out.println("Closed"); } // 固定步骤
abstract void writeData(); // 可变步骤 → 子类实现
}
class PdfExporter extends DataExporter {
void writeData() { System.out.println("Writing PDF..."); }
}
class ExcelExporter extends DataExporter {
void writeData() { System.out.println("Writing Excel..."); }
}
调用方只需要 exporter.export(),不用关心具体格式。新增导出格式只需写一个新子类——流程固定,细节可变。这是 Android 中 BaseActivity 的常见模式。
7. 接口
核心概念
- 用
interface声明,用implements实现 - 定义行为的契约:实现类必须提供所有声明的方法
- 一个类可以实现多个接口(打破单继承限制)
- 接口中所有方法默认是
public abstract - Java 8+ 可以有
default方法(有默认实现)
interface Pet {
void play(); // 默认 public abstract
default void cuddle() { // Java 8+: 默认方法,可选重写
System.out.println("Being cuddled...");
}
}
class Dog extends Animal implements Pet {
public Dog(String name) { super(name); }
@Override
public void makeSound() { System.out.println(name + ": Woof!"); }
@Override
public void play() { // 必须实现 Pet 的 play
System.out.println(name + " plays fetch.");
}
}
接口 vs 抽象类
| 抽象类 | 接口 | |
|---|---|---|
| 关键字 | abstract class |
interface |
| 关系 | extends(单继承) |
implements(多实现) |
| 成员变量 | 可以有任意变量 | 只能有常量(public static final) |
| 构造方法 | 有 | 没有 |
| 方法 | 抽象方法 + 普通方法 | 抽象方法 + default 方法(Java 8+) |
| 设计意图 | “is-a”(是什么) | “can-do”(能做什么) |
Android 场景:View.OnClickListener 就是接口——任何类实现了它就能响应点击。
为什么需要接口?—— 三层递进理解
接口有两个作用,我们由浅入深来讲。
第一层:避免单继承的限制(最直观)
Java 规定一个类只能有一个亲爹(单继承)。但现实中的东西往往有多个身份:
// 狗既是一种动物,又可以当宠物
// 但 extends 只能写一个!
class Dog extends Animal { } // 只能继承一个
接口就是"干爹"——你可以认无数个:
class Dog extends Animal implements Pet, Serializable, Comparable {
// 亲爹 ↑ 干爹1 干爹2 干爹3
}
继承回答"我是什么",接口回答"我能干什么"。
第二层:统一不同类的行为(多态的基础)
想象你有一堆完全不相关的设备,但它们都有一个共同的操作:开机。
// 这三个类没有任何继承关系
class Phone {
void powerOn() { System.out.println("Phone booting..."); }
}
class TV {
void powerOn() { System.out.println("TV turning on..."); }
}
class Computer {
void powerOn() { System.out.println("Computer starting..."); }
}
现在你想写一个"一键开机所有设备"的功能。没有接口,你只能这样:
// 每加一种设备就要加一行 —— 烦!
phone.powerOn();
tv.powerOn();
computer.powerOn();
接口登场——给它们贴一个共同的"标签":
// 第一步:定义接口(贴标签)
interface Powerable {
void powerOn(); // 有这个标签的,必须会 powerOn
}
// 第二步:各设备贴上标签
class Phone implements Powerable {
public void powerOn() { System.out.println("Phone booting..."); }
}
class TV implements Powerable {
public void powerOn() { System.out.println("TV turning on..."); }
}
class Computer implements Powerable {
public void powerOn() { System.out.println("Computer starting..."); }
}
// 第三步:一段代码处理所有设备
Powerable[] devices = { new Phone(), new TV(), new Computer() };
for (Powerable d : devices) {
d.powerOn(); // 不关心具体是什么设备,只管"能开机"这件事
}
接口在这里的本质:给互不相关的类贴一个共同的标签,让它们可以被同一段代码处理。这跟多态是一样的思想——Powerable d = new Phone(),运行时自动调用 Phone 的 powerOn。
生活类比:遥控器上的"开关"按钮。遥控器不需要知道你是什么牌子的电视——只要你的电视"实现了"被遥控的接口,同一个按钮就能关掉你。
第三层:换零件不改代码(解耦)
这是接口真正强大的地方,也是最容易被初学者忽略的地方。
生活场景:你的手机没电了。
有接口的世界(现实): 没有接口的世界(假设):
┌──────────┐ ┌──────────┐
│ 手机 │ │ 手机 │
│ 充电口是 │── 随便插哪个充电器 │ 只能插 │── 只能用 X 牌充电器
│ USB-C 接口│ │ X 牌充电器 │
└──────────┘ └──────────┘
USB-C 就是一个接口标准。手机只认"USB-C 接口",不认具体是哪个牌子的充电器。所以你换充电器不需要换手机,换手机也不需要扔充电器。
翻译成代码——没有接口的世界:
class Phone {
private XiaomiCharger charger = new XiaomiCharger(); // 焊死小米充电器
void charge() {
charger.supplyPower(); // 只能用小米的
}
}
如果充电器坏了,你只能买小米的。想用华为的快充?对不起,Phone 类的代码里写死了 XiaomiCharger,想换就得改 Phone 的源码。
有接口的世界:
// 第一步:定义充电接口标准
interface Charger {
void supplyPower();
}
// 第二步:各品牌充电器实现这个标准
class XiaomiCharger implements Charger {
public void supplyPower() { System.out.println("Xiaomi: 18W charging"); }
}
class HuaweiCharger implements Charger {
public void supplyPower() { System.out.println("Huawei: 66W fast charge"); }
}
// 第三步:手机只依赖接口,不依赖具体品牌
class Phone {
private Charger charger; // 只要是符合接口标准的充电器就行
public Phone(Charger charger) { // 插什么充电器你说了算
this.charger = charger;
}
void charge() {
charger.supplyPower(); // 管它小米华为,能充电就行
}
}
// 使用 —— 想用哪个就用哪个
Phone p1 = new Phone(new XiaomiCharger());
Phone p2 = new Phone(new HuaweiCharger());
关键对比:
| 没有接口 | 有接口 | |
|---|---|---|
| 换充电器 | 改 Phone 源码 | 不关 Phone 的事 |
| 测试 | 必须插真充电器 | 可以插一个"假的"模拟充电器 |
| 加新品牌 | 改 Phone 源码 | 写一个新的 implements Charger 即可 |
总结:接口让你把"我要什么能力"和"谁来实现这个能力"分开。Phone 说"我要一个能供电的东西",但不关心谁来供电——这就叫解耦。
Android 中你见到的
View.OnClickListener、RecyclerView.Adapter都是这个道理——系统不知道你要怎么处理点击、显示什么数据,所以定义接口让你自己实现。
接口 vs 抽象类:怎么选(来自实践)
需要一个"默认骨架" → 抽象类(可以包含成员变量、构造方法、已实现的方法)
需要多个"能力标签" → 接口(一个类可以实现多个接口)
"这两个东西本质上是一种东西" → 抽象类(Animal → Dog, Cat)
"这两个东西不是一种东西,但能做同样的事" → 接口(Dog, Robot 都实现了 Playable)
实际开发中,接口的使用频率远高于抽象类。现代 Android 开发(依赖注入、Repository 模式、回调监听)大量使用接口来解耦。
8. 内部类
概述
定义在另一个类内部的类,一共四种。
内部类
├── 成员内部类 普通 inner class
├── 静态内部类 static class
├── 局部内部类 定义在方法里
└── 匿名内部类 用最多的,无类名
8.1 成员内部类
public class Outer {
private String msg = "Hello";
class Inner { // 不加 static
public void print() {
System.out.println(msg); // 可以直接访问外部类私有成员
}
}
}
Outer out = new Outer();
Outer.Inner in = out.new Inner(); // 必须通过外部类对象创建
in.print();
特点:持有外部类引用,能无限制访问外部类成员。容易造成内存泄漏。
8.2 静态内部类
public class Outer {
private static String msg = "Hello";
static class Inner { // 加了 static
public void print() {
System.out.println(msg); // 只能访问外部类静态成员
}
}
}
Outer.Inner in = new Outer.Inner(); // 不需要外部类对象
in.print();
特点:不持有外部类引用,不能直接访问外部类的非静态成员。比成员内部类更安全,常用于 ViewHolder 模式。
8.3 局部内部类
public class Outer {
public void method() {
final int localVar = 10; // JDK 8+ 可以省略 final,但本质要求 final
class LocalInner { // 定义在方法体内部
public void print() {
System.out.println(localVar); // 只能访问 final 局部变量
}
}
LocalInner li = new LocalInner(); // 只能在方法内使用
li.print();
}
}
特点:作用域仅限当前方法,用得很少。
8.4 匿名内部类
痛点:为了一行代码要单独建一个文件
假设你有一个按钮,点击后弹出一句话。按常规做法,你得:
// 步骤 1:写一个类实现点击接口
class MyClickListener implements View.OnClickListener {
@Override
public void onClick(View v) {
System.out.println("按钮被点了!");
}
}
// 步骤 2:在另一个地方创建对象并设置
button.setOnClickListener(new MyClickListener());
就为了实现一行 System.out.println("按钮被点了!"),你要新建一个类、给它起个名字、写一堆样板代码。如果页面上有 5 个按钮,每个按钮的点击行为不同,你就要建 5 个类——MyClickListener1、MyClickListener2……
匿名内部类就是为了消灭这种"一次性类"而生的。
一句话理解
匿名内部类 = 现场创建对象 + 现场写好方法,不给类起名字
就像一次性筷子——当场拆开用,用完就不管了,不需要给它起名字放进碗柜。
渐进式理解
第一步:先看常规写法(你已知的)
// 1. 定义一个类
class Dog implements Pet {
public void play() {
System.out.println("Dog plays fetch.");
}
}
// 2. 创建这个类的对象
Pet pet = new Dog();
pet.play();
第二步:匿名内部类的写法——把上面两步合并成一步
Pet pet = new Pet() { // ← 注意:这里是 new Pet(),不是 new Dog()
public void play() { // ← 现场直接把方法写好
System.out.println("Dog plays fetch.");
}
}; // ← 分号!这是一个完整的语句
pet.play();
关键疑问:new Pet()?接口不是不能 new 吗?
Pet pet = new Pet() { ... };
↑ ↑
不是真的在 这个大括号里的内容
new 接口 定义了一个"无名类"
new Pet() { ... } 的意思是:创建一个没有名字的类,这个类实现了 Pet 接口,然后立即创建它的对象。语法糖,只是看起来像在 new 接口。
不是 new 接口,是 new (一个实现了接口的匿名类)。Pet 只是告诉你这个匿名类遵守什么协议。
第三步:对比一目了然
常规做法: 匿名类做法:
┌─────────────────────┐ ┌─────────────────────┐
│ 文件 Dog.java: │ │ 一行搞定: │
│ class Dog impl Pet {│ │ Pet p = new Pet() { │
│ void play() {...} │ │ void play() {...} │
│ } │ │ }; │
│ │ └─────────────────────┘
│ 调用处: │ 不建文件
│ Pet p = new Dog(); │ 不起类名
└─────────────────────┘ 用完即弃
需要单独的 .java 文件
完整示例:按钮点击
// 场景:页面上有两个按钮,点击后做不同的事
// 按钮 1:弹出"收藏成功"
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("收藏成功!");
}
});
// 按钮 2:弹出"已取消"
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
System.out.println("已取消!");
}
});
不用建两个类文件,行为就写在设置监听的地方——逻辑紧凑、一目了然。
什么时候用?
一个接口/抽象类的实现 ──▶ 只用到一次 ──▶ 匿名内部类 ✓
──▶ 会在多处复用 ──▶ 老老实实建一个普通类
匿名内部类适合"一次性使用"的场景。如果同一个实现要在 3 个地方用到,就别用匿名——老老实实定义一个类,避免代码重复。
Lambda 简化(预告)
上面的按钮代码在 Android Studio 里会被提示简化为一行:
button.setOnClickListener(v -> System.out.println("收藏成功!"));
这行代码做的事情跟匿名内部类完全一样,只是语法更短。后面学 Lambda 的时候会详细讲,现在你只需要知道:Lambda 是匿名内部类的"极简版"语法。
记住本质:
new Xxx() { 方法实现 }= 现场创建了一个实现了Xxx的无名类对象。编译后确实会生成Outer$1.class这样的文件,只是你不用手动管理它。
9. 枚举
基本用法
enum WeekDay {
MON, TUE, WED, THU, FRI, SAT, SUN
}
WeekDay today = WeekDay.MON; // 类型安全:不会出现非法值
// 遍历
for (WeekDay d : WeekDay.values()) {
System.out.println(d); // 输出 MON TUE ...
}
// switch
switch (today) {
case MON: System.out.println("Monday"); break;
case TUE: System.out.println("Tuesday"); break;
}
带属性的枚举
enum WeekDay {
MON("周一"), TUE("周二"), WED("周三"),
THU("周四"), FRI("周五"), SAT("周六"), SUN("周日");
private String chinese;
WeekDay(String ch) { this.chinese = ch; } // 构造方法默认 private
public String getChinese() { return chinese; }
}
WeekDay.MON.getChinese() // "周一"
关键记忆点
基础规则
new干了三件事:分配内存 → 初始化默认值 → 返回引用- 构造方法:无返回值类型、名=类名、
this()调用同类构造必须在第一行 - 封装:
private属性 +publicgetter/setter,目的是控制访问 + 方便后续修改 - Java 单继承:一个类只能
extends一个父类,但可以implements多个接口 - 多态核心:父类引用指向子类对象 → 运行时决定调用谁的方法
- 抽象 vs 接口:
abstract class是 “is-a”(共性提取);interface是 “can-do”(能力契约) - 匿名内部类 编译后产生
$1.class,持有外部类引用时要小心内存泄漏 - 子类构造必须第一行调
super()或this()
设计思维
- 继承解决的是"代码重复":DRY 原则,公共逻辑收归父类,改一处全局生效
- 多态解决的是"分支泛滥":用
父类引用.方法()替代 if-else 判断类型,新增子类调用方零改动(开闭原则) - 抽象类解决的是"父类不知道怎么写":不写空方法体糊弄,编译期强制子类实现(错误左移)
- 接口解决的是"单继承不够用 + 高层不该依赖低层":能力标签 + 依赖倒置,换实现不改调用方