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
// 1. 基础类 - 巫师(封装演示)
class Wizard {
    private String name;  // 封装:私有属性
    private String house;
    private static final String DEFAULT_WAND = "11英寸 冬青木 凤凰羽毛";

    public Wizard(String name, String house) {
        this.name = name;
        this.house = house;
    }

    // 封装的getter方法
    public String getHouse() {
        return house;
    }

    public void castSpell(String spell) {
        System.out.println(name + "挥动魔杖:" + spell + "!");
    }
}

// 2. 继承类 - 黑巫师
class DarkWizard extends Wizard {
    public DarkWizard(String name, String house) {
        super(name, house);  // 调用父类构造器
    }

    @Override  // 方法重写
    public void castSpell(String spell) {
        super.castSpell(spell);
        System.out.println("一道绿光闪过...");
    }
}

// 3. 接口实现多态(抽象演示)
interface MagicCreature {
    void castSpell();  // 抽象方法
}

class HouseElf implements MagicCreature {
    @Override
    public void castSpell() {
        System.out.println("弹指间完成魔法,不需要魔杖");
    }
}

// 4. 组合关系演示
class MagicWorld {
    public static void main(String[] args) {
        // 多态演示
        Wizard harry = new Wizard("哈利·波特", "格兰芬多");
        DarkWizard voldemort = new DarkWizard("汤姆·里德尔", "斯莱特林");
        MagicCreature dobby = new HouseElf();

        // 对象协作
        harry.castSpell("除你武器");
        voldemort.castSpell("阿瓦达索命");
        dobby.castSpell();

        // 封装访问
        System.out.println("哈利的学院:" + harry.getHouse());
        
        // 类型转换检查
        if(dobby instanceof HouseElf) {
            System.out.println("发现一只家养小精灵!");
        }
    }
}

通过这个魔法世界模型,我们可以看到面向对象的核心概念

  • 类定义角色模板
  • 对象是具体的角色实例
  • 继承建立层次关系
  • 接口定义行为契约
  • 多态实现统一操作

2. 面向对象的核心特性

1. 封装

封装是将数据和操作数据的方法封装在一起,形成一个独立的单元。通过访问修饰符(private、public等)控制数据的访问权限,提高代码的安全性和可维护性。

核心要点

  • Java的访问控制是基于类而不是实例的
  • 即使在A类中创建B类对象,A类也无法访问B的private变量
  • 提供公共的getter和setter方法来访问和修改私有属性

2. 继承

继承允许子类继承父类的属性和方法,减少代码重复,提高代码的可扩展性。

继承规则

  • 子类会继承父类中所有非私有(private)的成员变量
  • 子类会继承父类中所有非私有(private)、非 final 的方法
  • 继承的属性是否有值取决于属性的初始化情况和父类构造方法的调用
  • final 方法不能被子类重写,但可以被继承使用
  • 创建对象时,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
// 父类 A
class A {
    // 未显式初始化的属性
    int num1;
    // 显式初始化的属性
    int num2 = 20;
    // 私有属性
    private int privateNum = 30;

    public A() {
        // 通过构造方法初始化属性
        num1 = 10;
    }

    public void printInfo() {
        System.out.println("num1: " + num1 + ", num2: " + num2 + ", privateNum: " + privateNum);
    }
}

// 子类 B 继承父类 A
class B extends A {
    public void testInheritance() {
        // 子类可以访问继承的非私有属性
        System.out.println("继承的 num1: " + num1);
        System.out.println("继承的 num2: " + num2);
        // 子类无法直接访问父类的私有属性
        // System.out.println("继承的 privateNum: " + privateNum); // 编译错误
    }
}

public class Main {
    public static void main(String[] args) {
        B b = new B(); // 子类在创建对象时,会先调用父类的构造方法
        b.testInheritance();
        b.printInfo();
    }
}

3. 多态

多态是面向对象编程中极为重要的特性,它允许我们以统一的方式处理不同类型的对象。

多态的实现方式

  • 继承实现多态:基于"父类引用指向子类对象"的运行时多态,如 Animal animal = new Cat()
  • 接口实现多态:通过接口定义行为契约,不同实现类提供不同实现
  • 方法重载:同一个方法名,不同参数列表,编译时多态
  • 参数传递:使用父类或接口作为方法参数,接收不同的子类对象
多态示例(统一听指挥)
 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
// 交通工具抽象类
abstract class Vehicle {
    public abstract void transport();
}

// 汽车类
class Car extends Vehicle {
    @Override
    public void transport() {
        System.out.println("汽车在公路上行驶,将你送到目的地");
    }
}

// 火车类
class Train extends Vehicle {
    @Override
    public void transport() {
        System.out.println("火车在铁轨上行驶,将你送到目的地");
    }
}

// 飞机类
class Plane extends Vehicle {
    @Override
    public void transport() {
        System.out.println("飞机在天空中飞行,快速将你送到目的地");
    }
}

// 出行服务类
class TravelService {
    public void travel(Vehicle vehicle) {
        vehicle.transport();
    }
}

public class Travel {
    public static void main(String[] args) {
        TravelService service = new TravelService();

        Vehicle car = new Car();
        Vehicle train = new Train();
        Vehicle plane = new Plane();

        service.travel(car);
        service.travel(train);
        service.travel(plane);
    }
}

4. 抽象

抽象是将一组类的共同特征提取出来,形成抽象类或接口。抽象类不能实例化,只能作为父类被继承。

抽象类的特点

  • 抽象类不能实例化
  • 抽象类可以包含抽象方法、普通方法、构造方法、静态方法、静态变量、内部类
  • 抽象类可以实现接口
  • 抽象类为子类提供公共的属性和方法
抽象示例(一件事一群人干)
 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
// 抽象类 Animal
abstract class Animal {
    protected String name;

    // 抽象类的构造方法
    public Animal(String name) {
        this.name = name;
    }

    // 抽象方法
    public abstract void makeSound();

    // 非抽象方法
    public void introduce() {
        System.out.println("我是一只名叫 " + name + " 的动物。");
    }
}

// 子类 Dog
class Dog extends Animal {
    public Dog(String name) {
        // 调用父类的构造方法初始化 name 属性
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println("汪汪汪");
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("旺财");
        dog.introduce();
        dog.makeSound();
    }
}

5. 接口

接口是一种特殊的抽象类型,它只定义方法签名,不提供实现。接口用于定义类的行为契约,实现接口的类必须实现接口中定义的所有方法。

接口的特点

  • 接口中的方法默认是 public abstract 的
  • 接口中的变量默认是 public static final 的
  • 一个类可以实现多个接口
  • 接口可以继承多个接口
接口示例(一群人干一件事)
 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
// 支付接口
interface Payment {
    void pay(double amount);
}

// 现金支付类
class CashPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用现金支付了 " + amount + " 元");
    }
}

// 银行卡支付类
class CardPayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用银行卡支付了 " + amount + " 元");
    }
}

// 手机支付类
class MobilePayment implements Payment {
    @Override
    public void pay(double amount) {
        System.out.println("使用手机支付了 " + amount + " 元");
    }
}

3. 构造函数

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

  2. new 做的事:
    在内存里开辟空间,把对象造出来
    但这时候字段都是默认值:0 / null / false

  3. 构造函数做的事:
    给这块空间里的字段赋值、做初始化
    对这个已经存在的对象进行初始化,让对象从“无数据”变成“有数据”

生活场景

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

Java 永远不变的规律

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

二、面向对象的设计原则

面向对象设计原则是软件开发中用于指导设计高质量、可维护、可扩展软件系统的重要准则。以下是几个常见的面向对象设计原则:

1. 单一职责原则(Single Responsibility Principle)

定义:一个类应该仅有一个引起它变化的原因。简单来说,一个类只负责一项职责。

示例:在一个图书管理系统中,Book 类只负责存储图书的基本信息,而图书的借阅、归还等操作则由 BorrowService 类来负责。这样当图书信息的存储方式发生变化时,只需要修改 Book 类;当借阅规则发生变化时,只需要修改 BorrowService 类。

2. 开闭原则(Open-Closed Principle)

定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即当有新需求时,应该通过扩展现有代码来实现,而不是修改已有的代码。

示例:在一个图形绘制系统中,有一个抽象的 Shape 类,以及具体的 CircleRectangle 等子类。当需要添加新的图形(如三角形)时,只需要创建一个新的 Triangle 类继承自 Shape 类,而不需要修改 Shape 类和其他已有的子类。

3. 里氏替换原则(Liskov Substitution Principle)

定义:子类可以替换其父类并且出现在父类能够出现的任何地方,而不会影响系统的正确性。也就是说,子类应该能够完全替代父类的功能。

示例:有一个 Bird 类,其中有一个 fly 方法。Sparrow 类继承自 Bird 类,并且可以正常飞行。但是如果有一个 Ostrich(鸵鸟)类也继承自 Bird 类,鸵鸟不能飞行,此时 Ostrich 类就不应该重写 fly 方法。可以将 Bird 类拆分为 FlyingBirdNonFlyingBird 两个类,让 Sparrow 继承自 FlyingBirdOstrich 继承自 NonFlyingBird

4. 接口隔离原则(Interface Segregation Principle)

定义:客户端不应该依赖它不需要的接口。一个类对另一个类的依赖应该建立在最小的接口上。

示例:在一个系统中,有一个 Animal 接口,包含 eatsleepfly 等方法。但是对于一些不会飞的动物(如猫、狗),它们并不需要实现 fly 方法。此时可以将 Animal 接口拆分为多个更小的接口,如 EatableSleepableFlyable 等,让不同的动物类根据自身需求实现相应的接口。

5. 依赖倒置原则(Dependency Inversion Principle)

定义:高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。即要面向接口编程,而不是面向实现编程。

示例:在一个电商系统中,高层的 OrderService 类不应该直接依赖低层的 MySQLOrderDao 类,而是应该依赖一个抽象的 OrderDao 接口。当需要更换数据库时,只需要创建一个实现 OrderDao 接口的新类,而不需要修改 OrderService 类。

6. 迪米特法则(Law of Demeter)

定义:一个对象应该对其他对象有最少的了解。一个类应该只和它的直接朋友通信,而不与陌生的类通信。

示例:在一个学校管理系统中,Student 类只需要和 Teacher 类(直接朋友)进行交互,而不需要和学校的 FinanceDepartment 类(陌生类)直接交互。如果 Student 需要缴纳学费,可以通过 Teacher 作为中间媒介来完成。

背诵口诀

单一不改子继承
细口高低不陌生

三、类之间如何交互

类之间的交互方式多种多样,每种方式都有其适用场景。以下是常见的类交互方式:

交互方式对比

交互方式 耦合度 扩展性 性能 典型场景
直接方法调用 工具类内部协作
接口参数传递 插件式架构
继承 框架模板方法
组合/聚合 模块化系统
观察者模式 事件驱动系统
依赖注入 企业级应用
消息队列 极低 极高 分布式系统

1. 直接方法调用(基础交互)

特点

  • 强耦合,适合简单流程
  • 违反依赖倒置原则,难以扩展
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class OrderService {
    public void createOrder(Cart cart) {
        PaymentProcessor processor = new PaymentProcessor();
        processor.charge(cart.getTotal()); // 直接调用其他类的方法
    }
}

class PaymentProcessor {
    public void charge(double amount) {
        System.out.println("扣款:" + amount);
    }
}

2. 参数传递(解耦起点)

优势

  • 依赖接口而非实现
  • 符合开闭原则,易扩展新数据源
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class ReportGenerator {
    // 通过接口参数传递依赖
    public void generateReport(DataFetcher fetcher) {
        String data = fetcher.fetch();
        System.out.println("生成报告:" + data);
    }
}

interface DataFetcher {
    String fetch();
}

class DatabaseFetcher implements DataFetcher {
    public String fetch() { return "数据库数据"; }
}

3. 继承(is-a 关系)

适用场景

  • 需要复用父类模板方法
  • 需要多态特性
  • 继承也有风险,不要过度继承导致类爆炸
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
abstract class Logger {
    protected abstract void writeLog(String message);
    
    public final void log(String message) { // 模板方法
        String formatted = format(message);
        writeLog(formatted);
    }
    
    private String format(String msg) {
        return "[LOG] " + msg;
    }
}

class FileLogger extends Logger {
    protected void writeLog(String message) {
        // 写入文件的实现
        System.out.println("写入文件:" + message);
    }
}

4. 组合/聚合(has-a 关系)

优势

  • 灵活替换组件
  • 符合合成复用原则
 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
import java.util.ArrayList;
import java.util.List;

class Computer {
    private CPU cpu;         // 组合:生命周期一致
    private List<RAM> rams; // 聚合:独立生命周期
    
    public Computer(CPU cpu, List<RAM> rams) {
        this.cpu = cpu;
        this.rams = new ArrayList<>(rams); // 防御性拷贝
    }
    
    public void start() {
        cpu.run();
        for (RAM ram : rams) {
            ram.initialize();
        }
        System.out.println("电脑启动完成");
    }
}

class CPU {
    public void run() {
        System.out.println("CPU运行中");
    }
}

class RAM {
    public void initialize() {
        System.out.println("内存初始化");
    }
}

5. 观察者模式

观察者模式是一种行为设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象的状态发生变化时,它会通知所有观察者对象,使它们能够自动更新。

核心组件

  • Subject(主题):维护一个观察者列表,提供添加、删除和通知观察者的方法
  • Observer(观察者):定义一个更新接口,当主题状态发生变化时,观察者会调用这个接口
  • ConcreteSubject(具体主题):具体的主题类,包含具体的状态和通知逻辑
  • ConcreteObserver(具体观察者):具体的观察者类,实现更新接口,定义如何响应主题状态的变化
 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
79
80
81
82
83
84
85
86
87
import java.util.ArrayList;
import java.util.List;

// Observer 接口,定义观察者需要实现的更新方法
interface Observer {
    void update(String message);
}

// ConcreteObserver 类,具体观察者类
class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    @Override
    public void update(String message) {
        System.out.println(name + " 收到消息: " + message);
    }
}

// Subject 接口,定义主题需要实现的方法
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers(String message);
}

// ConcreteSubject 类,具体主题类
class ConcreteSubject implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private String state;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }

    public void setState(String state) {
        this.state = state;
        notifyObservers(state);
    }

    public String getState() {
        return state;
    }
}

// 测试代码
public class ObserverPatternExample {
    public static void main(String[] args) {
        // 创建具体主题
        ConcreteSubject subject = new ConcreteSubject();

        // 创建具体观察者
        Observer observer1 = new ConcreteObserver("观察者1");
        Observer observer2 = new ConcreteObserver("观察者2");
        Observer observer3 = new ConcreteObserver("观察者3");

        // 注册观察者
        subject.registerObserver(observer1);
        subject.registerObserver(observer2);
        subject.registerObserver(observer3);

        // 改变主题状态
        subject.setState("状态1");

        // 移除一个观察者
        subject.removeObserver(observer2);

        // 再次改变主题状态
        subject.setState("状态2");
    }
}

6. 依赖注入(现代框架基石)

优势

  • 解耦组件依赖
  • 提升可测试性
 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
class UserService {
    private final UserRepository repository;
    
    // 通过构造函数注入
    public UserService(UserRepository repo) {
        this.repository = repo;
    }
    
    public void saveUser(User user) {
        repository.save(user);
    }
}

interface UserRepository {
    void save(User user);
}

class JpaUserRepository implements UserRepository {
    @Override
    public void save(User user) {
        System.out.println("保存用户到JPA数据库:" + user.getName());
    }
}

class User {
    private String name;
    
    public User(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
}

// 测试代码
public class DependencyInjectionExample {
    public static void main(String[] args) {
        // 手动依赖注入
        UserRepository repo = new JpaUserRepository();
        UserService service = new UserService(repo);
        
        User user = new User("张三");
        service.saveUser(user);
    }
}

7. 消息队列(分布式交互)

适用场景

  • 跨系统/微服务通信
  • 异步处理解耦
 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
// 订单服务(生产者)
class OrderProducer {
    public void createOrder(Order order) {
        MessageQueue.send("order_created", order);
        System.out.println("订单已发送到消息队列:" + order.getOrderId());
    }
}

// 库存服务(消费者)
class InventoryConsumer {
    @MessageListener(topic = "order_created")
    public void handleOrder(Order order) {
        deductStock(order.getItems());
        System.out.println("已处理订单:" + order.getOrderId());
    }
    
    private void deductStock(List<Item> items) {
        // 扣减库存逻辑
        System.out.println("扣减库存:" + items.size() + " 件商品");
    }
}

// 订单类
class Order {
    private String orderId;
    private List<Item> items;
    
    public Order(String orderId, List<Item> items) {
        this.orderId = orderId;
        this.items = items;
    }
    
    public String getOrderId() {
        return orderId;
    }
    
    public List<Item> getItems() {
        return items;
    }
}

// 商品类
class Item {
    private String itemId;
    private int quantity;
    
    public Item(String itemId, int quantity) {
        this.itemId = itemId;
        this.quantity = quantity;
    }
}

// 模拟消息队列
class MessageQueue {
    public static void send(String topic, Object message) {
        // 发送消息到队列的逻辑
        System.out.println("发送消息到主题 " + topic);
    }
}

// 定义消息监听器注解
@interface MessageListener {
    String topic();
}

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

总结

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

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

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