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;加新代码是安全的。

多态的实现前提

  1. 继承/接口:必须有 is-a 关系
  2. 方法重写:子类重写父类方法
  3. 父类引用指向子类对象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.OnClickListenerRecyclerView.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 个类——MyClickListener1MyClickListener2……

匿名内部类就是为了消灭这种"一次性类"而生的。

一句话理解

匿名内部类 = 现场创建对象 + 现场写好方法,不给类起名字

就像一次性筷子——当场拆开用,用完就不管了,不需要给它起名字放进碗柜。

渐进式理解

第一步:先看常规写法(你已知的)

// 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()  // "周一"

关键记忆点

基础规则

  1. new 干了三件事:分配内存 → 初始化默认值 → 返回引用
  2. 构造方法:无返回值类型、名=类名、this() 调用同类构造必须在第一行
  3. 封装private 属性 + public getter/setter,目的是控制访问 + 方便后续修改
  4. Java 单继承:一个类只能 extends 一个父类,但可以 implements 多个接口
  5. 多态核心:父类引用指向子类对象 → 运行时决定调用谁的方法
  6. 抽象 vs 接口abstract class 是 “is-a”(共性提取);interface 是 “can-do”(能力契约)
  7. 匿名内部类 编译后产生 $1.class,持有外部类引用时要小心内存泄漏
  8. 子类构造必须第一行super()this()

设计思维

  1. 继承解决的是"代码重复":DRY 原则,公共逻辑收归父类,改一处全局生效
  2. 多态解决的是"分支泛滥":用 父类引用.方法() 替代 if-else 判断类型,新增子类调用方零改动(开闭原则)
  3. 抽象类解决的是"父类不知道怎么写":不写空方法体糊弄,编译期强制子类实现(错误左移)
  4. 接口解决的是"单继承不够用 + 高层不该依赖低层":能力标签 + 依赖倒置,换实现不改调用方