Java面向对象编程进阶

前言

本文是《Java 后端从小白到大神》修仙系列第一篇,在上一篇文章中我们介绍了面向对象的基础概念,本文将深入探讨Java面向对象的核心思想、设计原则以及类之间的交互方式,帮助你构建更健壮、可维护的Java应用。现正式进入Java后端世界,本篇文章主要聊Java基础。若想详细学习请点击首篇博文,我们开始吧。

一、Java的面向对象思想

1. 用"对象"模拟现实世界

在 Java 中,面向对象的核心思想非常朴素:用代码描述现实世界中的事物
现实里有什么、长什么样、能做什么,代码就原样表达出来,不虚构、不抽象过度。

  • :是对现实中某一类事物的模板定义,描述这类事物“有什么特征”和“能做什么行为”。
  • 对象:是类创建出来的具体实例,是模板对应的真实个体

简单理解:
类 = 事物的分类与模板
对象 = 模板造出来的具体一个东西

举个最生活化的例子(外卖场景):

  • :外卖订单(所有订单都有订单号、金额,都能支付)
  • 对象:你今天中午点的那一份具体订单

示例代码:外卖订单模型

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
 * 面向对象核心思想:用对象还原现实世界
 * 场景:外卖订单
 */

// 1. 父类:订单模板(描述“订单”这类事物)
class Order implements Payable {
    private String orderId;
    private double money;

    public Order(String orderId, double money) {
        this.orderId = orderId;
        this.money = money;
    }

    // 实现接口方法
    @Override
    public void pay() {
        System.out.println("订单【" + orderId + "】已支付:" + money + " 元");
    }

    public String getOrderId() { return orderId; }
    public double getMoney() { return money; }
}

// 2. 子类:外卖订单(继承订单,扩展外卖专属行为)
class TakeawayOrder extends Order {

    public TakeawayOrder(String orderId, double money) {
        super(orderId, money);
    }

    // 重写支付逻辑
    @Override
    public void pay() {
        super.pay();
        System.out.println("→ 商家已接单,准备餐品中...");
    }

    // 外卖专属行为:配送
    public void deliver() {
        System.out.println("→ 骑手正在配送订单【" + getOrderId() + "】");
    }
}

// 3. 接口:支付契约(定义“能支付”的统一标准)
interface Payable {
    void pay();
}

// 4. 多态实现:优惠券支付
class CouponPay implements Payable {
    @Override
    public void pay() {
        System.out.println("使用优惠券完成抵扣支付");
    }
}

// 5. 组合:整个外卖系统(多对象协作完成业务)
class TakeawaySystem {
    public static void main(String[] args) {
        // 创建一个具体订单(对象 = 真实的一份订单)
        TakeawayOrder myOrder = new TakeawayOrder("TK202604170088", 42.5);
        
        // 调用对象行为
        myOrder.pay();
        myOrder.deliver();

        System.out.println("");

        // 多态:统一处理“能支付”的事物
        Payable p1 = myOrder;
        Payable p2 = new CouponPay();
        
        p1.pay();
        p2.pay();
    }
}

通过外卖场景可以清晰看到面向对象的本质:

  1. 是现实事物的模板,对象是具体实例;
  2. 属性描述事物特征,方法描述事物能力;
  3. 封装保护内部信息,只对外暴露安全行为;
  4. 继承复用共性,扩展专属特性;
  5. 接口定义统一行为标准;
  6. 多态让同一行为有不同实现;
  7. 组合让多个对象协作,完成真实业务。

2. 面向对象6大底层思维模型

(1)6 大核心模型
这是面向对象最底层、最通用的 6 个思维模型,所有 Java 代码、框架底层都是它们的组合:

  1. 方法调用模型:对象.方法(参数),结果只返回/自留/外发
  2. 状态行为模型:对象 = 状态(数据/属性) + 行为(方法/能力)
  3. 消息传递模型:对象之间只调用方法,不直接操作内部数据
  4. 封装模型:内部隐藏,外部只看接口
  5. 抽象模型:关注能力,不关注实现
  6. 职责模型:谁该干什么,就干什么,不越界

(2)模型 1 详解:方法调用模型(对象.方法(参数))
看到格式:对象.方法(参数);
固定思考逻辑:

  • 对象:干活的主体,拥有自己的数据和能力
  • 方法:对象能做的某一件具体事情(动作/功能)
  • 参数:完成这件事需要用到的材料/条件

对象处理完后,结果只有 3 种去向:
① 返回结果(给调用者)例:int len = str.length();
② 修改自身状态(自己藏起来)例:list.add(item);
③ 输出/转发到外部(发送、打印、写入文件)例:网络发送、控制台打印

(3)模型 2 详解:状态行为模型(对象的本质)
核心概念统一

  • 状态 = 属性 = 数据:对象“记住”的东西(存在内部)例:ByteArrayOutputStream 内部的 byte[] 缓冲区
  • 行为 = 方法 = 能力:对象“能干”的事情(对外提供的功能)例:baos.write()baos.toByteArray()

终极公式
对象 = 状态(数据/属性) + 行为(方法/能力)

(4)模型 3 详解:消息传递模型
固定思考逻辑:

  • 对象之间不直接访问、修改对方内部数据
  • 只能通过调用方法的方式传递指令(发消息)
  • 彼此保持独立、低耦合、更安全

交互规则:
① A 对象想要 B 对象做事 → 调用 B 的方法
② 不直接读写 B 的私有属性
③ 结果由 B 自己处理(返回/自留/外发)

例:clientChannel.read(buffer, handler)
服务端不操作客户端内部数据,只发送“读”的消息。

(5)模型 4 详解:封装模型
固定思考逻辑:

  • 内部数据隐藏(私有化)
  • 外部只能通过公开方法访问
  • 调用者无需关心内部实现,只关心能用做什么

核心规则:
① 内部细节藏起来 → 安全、易维护
② 对外暴露稳定方法 → 易用、易扩展
③ 内部修改不影响外部调用

例:ArrayList 不用关心数组扩容,只用 add()/get()

(6)模型 5 详解:抽象模型
固定思考逻辑:

  • 只关注“能做什么”,不关注“怎么做”
  • 面向接口/抽象类编程
  • 依赖能力,不依赖具体实现

核心规则:
① 提取共同能力,定义统一接口
② 不同实现可以互相替换
③ 代码更灵活、易扩展、易升级

例:List list = new ArrayList();
面向 List 接口,不依赖具体实现类。

(7)模型 6 详解:职责模型
固定思考逻辑:

  • 一个对象只负责自己职责范围内的事
  • 不越界、不包揽、不混乱
  • 各司其职,结构清晰

核心规则:
① 数据存储 → 自己负责
② 功能处理 → 专属对象负责
③ 连接/通道/IO/回调 → 各自分工

例:Channel 负责连接、Buffer 负责数据、Handler 负责回调、BAOS 负责拼接。

3. 面向对象的核心特性

下面这 5 大特性,是面向对象的灵魂。它们不是孤立知识点,而是一套 从“保护数据”→“复用代码”→“灵活扩展”→“抽象设计”→“定义契约” 的完整思想体系。

3.1 封装(保护自己)

一句话本质: 把数据藏起来,只暴露安全的方法,不让外部乱改。

  • 核心思想:数据私有化,行为公开化
  • 怎么做:private 修饰变量,public 提供方法
  • 为什么:安全、易维护、内部修改不影响外部

封装的特点

  • Java的访问控制是基于类而不是实例的
  • 即使在A类中创建B类对象,A类也无法访问B的private变量
  • 可以提供公共的getter和setter方法来访问和修改私有属性
1
2
3
4
5
6
7
8
9
class User {
    // 数据隐藏
    private String name;
    private int age;

    // 公开方法访问
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

3.2 继承(复用父亲)

一句话本质: 子类继承父类,直接拥有父类的功能,再扩展自己的新功能。

  • 核心思想:is-a 关系
  • 怎么做:extends 关键字
  • 为什么:减少重复代码,快速扩展能力

继承的特点

  • 子类会继承父类所有成员变量包括私有(private),但子类无法直接访问父类的 private 成员变量
  • 子类会继承父类中所有非私有(private)、非 final 的方法
  • 继承的属性是否有值取决于属性的初始化情况和父类构造方法的调用
  • final 方法不能被子类重写,但可以被继承使用
  • 创建对象时,Java 会先调用父类的构造方法,再调用子类的构造方法
  • 子类可以继承父类的内部类,作为自己的内部类
  • 父类实现的所有接口
1
2
3
4
5
6
7
8
9
// 父类
class Animal {
    public void eat() { System.out.println("吃"); }
}

// 子类继承
class Dog extends Animal {
    public void bark() { System.out.println("汪"); }
}

3.3 多态(灵活替换)

一句话本质: 同一个行为,不同对象有不同实现;统一接口,灵活替换。

  • 核心思想:一个接口,多种实现
  • 怎么做:父类引用指向子类对象
  • 为什么:代码可插拔、易扩展、更灵活

多态的特点

  • 继承实现多态:基于「父类引用指向子类对象」的运行时多态,如 Animal animal = new Cat()
  • 接口实现多态:通过接口定义行为契约,不同实现类提供不同实现
  • 方法重载:同一个方法名,不同参数列表,编译时多态
  • 参数传递:使用父类或接口作为方法参数,接收不同的子类对象

无论外部直接调用、还是父类自身方法内部,调用被重写 / 抽象方法,
只要:父类引用 指向 子类对象 + 子类重写了方法
一律跑子类的实现,规则是统一的,不是两种逻辑。

1
2
3
// 常规多态
Animal animal = new Dog();
animal.eat(); // 执行Dog的行为
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// 多态,一律跑子类的实现
abstract class PhoneFactory {
    // 工厂方法:造手机(由子类决定造哪种手机)
    public abstract Phone createPhone();
    
    // 业务方法:使用手机(不用管手机是谁造的,拿到就能用)
    public void usePhone() {
        Phone phone = createPhone(); // 工厂造手机
        phone.makeCall(); // 使用手机
    }
}

class HuaweiFactory extends PhoneFactory {
    @Override
    public Phone createPhone() {
        return new HuaweiPhone(); // 华为工厂只造华为手机
    }
}

PhoneFactory huaweiFactory = new HuaweiFactory();
huaweiFactory.usePhone();  // 输出 "用华为手机打电话"

3.4 抽象(提取共性)

一句话本质: 只保留共同特征,不关心具体细节;定义标准,不做实现。

  • 核心思想:抽取共性,模糊个性
  • 怎么做:abstract class 抽象类
  • 为什么:统一规范、强制子类实现

抽象类的特点

  • 抽象类不能实例化
  • 抽象类可以包含抽象方法、普通方法、构造方法、静态方法、静态变量、内部类
  • 抽象类可以实现接口
  • 抽象类为子类提供公共的属性和方法
1
2
3
4
5
6
7
8
abstract class Animal {
    public abstract void sound(); // 只定义,不实现
}

class Cat extends Animal {
    @Override
    public void sound() { System.out.println("喵"); }
}

3.5 接口(定义契约)

一句话本质: 定义一套行为规则,谁实现谁负责;不关心你是谁,只关心你能做什么。

  • 核心思想:行为契约、能力定义
  • 怎么做:interface
  • 为什么:解耦、灵活、支持多实现

接口的特点

  • 接口中的方法默认是 public abstract 的
  • 接口中的变量默认是 public static final 的
  • 一个类可以实现多个接口
  • 接口可以继承多个接口
1
2
3
4
5
6
7
8
interface Run {
    void run();
}

class Person implements Run {
    @Override
    public void run() { System.out.println("人跑"); }
}

4. 构造函数

4.1 构造函数的基本作用

对象 = 一块内存空间
里面放着一堆字段(成员变量),这些字段就是对象要保存的数据。

  • new 做的事
    在内存中开辟空间,把对象创建出来。
    此时所有字段都是默认值:0 / null / false

  • 构造函数做的事
    对这块已经存在的内存空间赋值、初始化
    让对象从“无数据状态”变成“有数据状态”。

生活场景理解

  • new = 先造出一个空杯子
  • 构造方法 = 往杯子里倒水、放茶叶

Java 永远不变的规律

  1. new 负责创建对象
  2. 构造方法负责初始化字段
  3. 先有对象,再初始化
  4. 先 new,后构造

4.2 继承关系下的构造函数

当存在父类 + 子类时,规则会变得更严谨,也是最容易理解不透彻的地方:

核心规则 1:子类对象包含父类数据

  • 子类对象在内存中 包含父类的全部字段
  • 即使父类字段是 private,子类看不见、访问不到
  • 但这些字段依然存在于子类对象中,占用空间、有值

核心规则 2:子类不能初始化父类私有字段

父类的 private 成员变量,子类无权直接赋值

1
2
3
4
// 父类
class Order {
    private String orderId; // 子类看不见、不能直接赋值
}

子类不能写:

1
this.orderId = orderId; // 编译报错

核心规则 3:子类必须调用 super(...) 让父类自己初始化

因为子类不能操作父类私有变量,
必须调用父类的构造函数,让父类自己给自己的字段赋值

1
2
3
4
5
6
class TakeawayOrder extends Order {
    public TakeawayOrder(String orderId, double money) {
        // 子类无法初始化父类字段 → 必须调用 super
        super(orderId, money);
    }
}

核心规则 4:什么时候只需要写 super?

  • 子类没有新增字段
  • 父类构造已经能完成所有字段初始化 此时子类构造只需要转发参数,不用写任何额外逻辑。

核心规则 5:什么时候子类需要自己写逻辑?

当子类有自己的新字段时:

1
2
3
4
5
6
7
8
class TakeawayOrder extends Order {
    private String address; // 子类新增字段

    public TakeawayOrder(String orderId, double money, String address) {
        super(orderId, money);   // 父类部分 → 交给父类
        this.address = address; // 子类部分 → 自己赋值
    }
}

4.3 继承构造终极总结

  1. 子类对象包含父类所有字段,包括 private
  2. 子类不能访问、不能直接赋值父类 private 字段
  3. 子类构造必须第一行调用 super(…),让父类完成自己的初始化
  4. 父类够⽤ → 子类只写 super
  5. 父类不够用 → 子类处理自己新增字段

一句话口诀: 父类造父类的,子类造子类的;父类私有不让碰,只能 super 请它动。

二、面向对象的设计原则

1. 单一职责原则(SRP)

一句话本质:一个类只干一件事。

  • 干得多,错得多;职责越纯,越容易维护。
  • 一个类只有一个引起它变化的原因。
1
2
3
4
5
// 好的设计:Book 只存数据
class Book { String name; }

// 好的设计:BookService 只处理业务
class BookService { void borrow(Book b) {} }

2. 开闭原则(OCP)

一句话本质:对扩展开放,对修改关闭。

  • 新功能 → 加代码,不改老代码。
  • 老代码稳定不动,系统才不崩。
1
2
3
4
5
abstract class Shape { abstract void draw(); }

class Circle extends Shape { void draw() {} }
class Square extends Shape { void draw() {} }
// 加新图形只新增,不改原有类

3. 里氏替换原则(LSP)

一句话本质:子类必须能完全替代父类。

  • 子类不能破坏父类的规则。
  • 鸵鸟不能继承“会飞的鸟”。
1
2
3
4
5
class Bird { void fly() {} }
class Sparrow extends Bird { } // 能飞

// 坏的设计:鸵鸟不能飞,破坏父类约定
// class Ostrich extends Bird { void fly() { throw error; } }

4. 接口隔离原则(ISP)

一句话本质:接口要小,不要强迫实现无用方法。

  • 大接口拆小,按需实现。
  • 不用的方法,不应该强迫实现。
1
2
3
4
5
6
7
// 坏的设计:接口太大
interface Animal { eat(); sleep(); fly(); }

// 好的设计:拆小接口
interface Eatable { void eat(); }
interface Sleepable { void sleep(); }
interface Flyable { void fly(); }

5. 依赖倒置原则(DIP)

一句话本质:依赖抽象,不依赖具体。

  • 高层 ← 抽象 → 低层
  • 高层和低层谁也不直接依赖谁,两者都靠 “抽象 / 接口” 来连接
  • 换实现不影响业务,这就是“面向接口编程”。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
interface OrderDao { void save(); }

class MySqlDao implements OrderDao { }
class OracleDao implements OrderDao { }

// Service 依赖接口,不依赖具体库
class OrderService {
    OrderDao dao;
    OrderService(OrderDao dao) { this.dao = dao; }
}

6. 迪米特法则(LOD)

一句话本质:只和直接朋友说话,少跟陌生人打交道。

  • 类之间知道得越少,耦合越低。
  • 朋友越少,关系越简单,代码耦合越低,越不容易崩
  • 朋友:当前类、成员对象、方法入参、子类。
1
2
3
4
5
6
class Student {
    void payFee(Teacher teacher) {
        teacher.helpPay(); // 朋友
        // 不直接调用 FinanceDepartment.fxx() 陌生人
    }
}

三、类之间如何交互

1. 直接依赖(最简单,但耦合高)

一句话本质:我直接 new 你,直接调用你。

  • 简单、直接、粗暴
  • 高耦合,换实现必须改代码
  • 小工具、简单流程可用
1
2
3
4
5
6
class OrderService {
    public void pay() {
        Payment payment = new Payment();  // 直接创建
        payment.doPay();                 // 直接调用
    }
}

2. 接口调用(解耦核心)

一句话本质:不依赖具体类,只依赖能力(接口)。

  • 最常用、最标准、最优雅
  • 符合依赖倒置原则
  • 换实现不用改代码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
interface Payable {
    void pay();
}

class OrderService {
    private Payable payable;  // 依赖接口

    public OrderService(Payable payable) {
        this.payable = payable;
    }
}

3. 组合(has - a,最推荐的复用)

一句话本质:我包含你,我们一起工作。

  • 电脑包含CPU、内存
  • 订单包含商品
  • 灵活、低耦合、推荐优先使用
1
2
3
4
5
6
7
8
9
class Computer {
    private CPU cpu;    // 组合
    private Memory mem; // 组合

    public Computer(CPU cpu, Memory mem) {
        this.cpu = cpu;
        this.mem = mem;
    }
}

4. 继承(is - a,父子关系)

一句话本质:我是你的特例,拥有你的全部能力。

  • 复用父类代码
  • 实现多态
  • 不要滥用,优先使用组合
1
2
class Animal { void eat() {} }
class Dog extends Animal { void bark() {} }

5. 观察者(事件通知:一对多)

一句话本质:我变化了,自动通知所有关注我的人。

  • 事件驱动、消息通知
  • Observer = 观察者 = 粉丝 / 订阅者 / 接收消息的人
  • Subject = 主题 = 你自己 / 被关注的人 / 发通知的人
  • UI、消息系统、Spring 事件都用它
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 观察者 = 粉丝 = 接收消息的人
interface Observer {
    void update(String msg); // 收到通知时要做的事
}

// 主题 = 大V = 发消息的人
class Subject {
    // 关注我的粉丝列表
    List<Observer> list = new ArrayList<>();

    // 我变了 → 通知所有粉丝
    void notifyAll() {
        for (Observer o : list) {
            o.update("你关注的大V更新了!");
        }
    }
}

6. 依赖注入(DI,框架底层)

一句话本质:我不自己创建依赖,别人传给我。

  • 彻底解耦
  • 便于测试
  • Spring 核心原理
1
2
3
4
5
6
7
8
class UserService {
    private UserRepository repo;

    // 依赖从外部注入
    public UserService(UserRepository repo) {
        this.repo = repo;
    }
}

7. 消息队列(跨服务异步交互)

一句话本质:不直接调用,发个消息让对方自己处理。

  • 微服务、分布式系统常用
  • 异步、解耦、流量削峰
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class OrderService {
    void create() {
        MQ.send("order.created", order); // 发消息
    }
}

class StockService {
    @Listener
    void consume() {
        // 减库存
    }
}

实际开发中建议:优先使用组合+接口的方式,关键模块采用依赖注入,异步场景使用消息机制,慎用继承关系。这些交互方式共同构成了面向对象系统的"神经网络"。

总结

本文深入探讨了Java面向对象编程的核心思想、设计原则以及类之间的交互方式。通过学习这些内容,你将能够:

  1. 理解面向对象的核心特性:封装、继承、多态、抽象和接口
  2. 掌握面向对象的设计原则:单一职责、开闭原则、里氏替换、接口隔离、依赖倒置和迪米特法则
  3. 选择合适的类交互方式:根据不同场景选择直接调用、参数传递、继承、组合/聚合、观察者模式、依赖注入或消息队列

这些知识将帮助你设计出更加模块化、可维护、可扩展的Java应用程序。在后续的文章中,我们将进一步探讨面向对象编程的高级应用和设计模式。