背景

本文是《Java 后端从小白到大神》修仙系列第一篇,正式进入Java后端世界,我想先来聊聊Java后端的基础。本篇文章主要聊Java面向对象思想

文章概览

  1. Java的面向对象思想
  2. 类的设计原则
  3. 类之间如何交互

一、Java的面向对象思想

1. 核心思想:用"对象"模拟现实世界

在Java中, 是对象的蓝图,而 对象 是类的实例。类定义了对象的属性和方法。对象 = 数据(属性) + 行为(方法)。类不是"图纸",而是"角色说明书" 把类想象成戏剧中的角色设定表。比如《哈利波特》中:

  • 示例:Wizard 巫师类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 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 + "!");
    }
}
  • 示例:DarkWizard 黑巫师类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 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("一道绿光闪过...");
    }
}
  • 示例:MagicCreature 魔法生物类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 3. 接口实现多态(抽象演示)
interface MagicCreature {
    void castSpell();  // 抽象方法
}

class HouseElf implements MagicCreature {
    @Override
    public void castSpell() {
        System.out.println("弹指间完成魔法,不需要魔杖");
    }
}
  • 示例:MagicWorld 魔法世界类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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. 封装

封装就是A类的对象,不能直接访问或修改B类对象的属性,只能通过调用B类自己暴露出来的方法完成,比如getXXX或者updateXXX等,这就是封装。

  • Java的访问控制是基于类而不是实例的,既在A类中创建B类对象,但A类仍然没有办法访问B的private变量。

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();)。
  • 接口实现多态(如Animal animal = new Cat(); animal.sound())。
  • 方法重载(Overloading)也可以看作是多态的一种形式,如 int add(int a, int b) {}add(double a, double b) {}
  • 在参数传递和返回值中时,letAnimalMakeSound(Animal animal);letAnimalMakeSound(dog);
多态(统一听指挥)
 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. 接口

当你只需要定义一组规范,让不同的类去实现这些规范,并且这些类之间没有太多的公共代码和状态时,使用接口。例如,不同的支付方式、不同的数据库驱动等。

接口(一群人干一件事)
 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 + " 元");
    }
}

二、 面向对象的设计原则

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

1. 单一职责原则

  • 定义:一个类应该仅有一个引起它变化的原因。简单来说,一个类只负责一项职责。
  • 示例:在一个图书管理系统中,有一个 Book 类,它只负责存储图书的基本信息,如书名、作者、ISBN 等,而图书的借阅、归还等操作则由另外的 BorrowService 类来负责。这样当图书信息的存储方式发生变化时,只需要修改 Book 类;当借阅规则发生变化时,只需要修改 BorrowService 类。

2. 开闭原则

  • 定义:软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。即当有新需求时,应该通过扩展现有代码来实现,而不是修改已有的代码。
  • 示例:在一个图形绘制系统中,有一个抽象的 Shape 类,以及具体的 CircleRectangle 等子类。当需要添加新的图形(如三角形)时,只需要创建一个新的 Triangle 类继承自 Shape 类,并实现相应的绘制方法,而不需要修改 Shape 类和其他已有的子类。

3. 里氏替换原则

  • 定义:子类可以替换其父类并且出现在父类能够出现的任何地方,而不会影响系统的正确性。也就是说,子类应该能够完全替代父类的功能。
  • 示例:有一个 Bird 类,其中有一个 fly 方法。Sparrow 类继承自 Bird 类,并且可以正常飞行。但是如果有一个 Ostrich(鸵鸟)类也继承自 Bird 类,鸵鸟不能飞行,此时 Ostrich 类就不应该重写 fly 方法,否则会违反里氏替换原则。可以将 Bird 类拆分为 FlyingBirdNonFlyingBird 两个类,让 Sparrow 继承自 FlyingBirdOstrich 继承自 NonFlyingBird

4. 接口隔离原则

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

5. 依赖倒置原则

  • 定义:高层模块不应该依赖低层模块,二者都应该依赖抽象;抽象不应该依赖细节,细节应该依赖抽象。即要面向接口编程,而不是面向实现编程。
  • 示例:在一个电商系统中,高层的 OrderService 类不应该直接依赖低层的 MySQLOrderDao 类(用于操作 MySQL 数据库),而是应该依赖一个抽象的 OrderDao 接口。当需要更换数据库(如从 MySQL 切换到 Oracle)时,只需要创建一个实现 OrderDao 接口的 OracleOrderDao 类,而不需要修改 OrderService 类。

6. 迪米特法则

  • 定义:一个对象应该对其他对象有最少的了解。一个类应该只和它的直接朋友通信,而不与陌生的类通信。
  • 示例:在一个学校管理系统中,Student 类只需要和 Teacher 类(直接朋友)进行交互,而不需要和学校的 FinanceDepartment 类(陌生类)直接交互。如果 Student 需要缴纳学费,可以通过 Teacher 作为中间媒介来完成,而不是直接和 FinanceDepartment 通信。

三、 类之间如何交互

类之间的交互如同城市交通网络,有的像直达高速公路(直接调用),有的像立交桥(接口解耦),有的像地铁系统(消息队列)。优秀的系统设计需要根据场景选择合适的「交通方式」,既要保证效率,又要避免拥堵和事故。掌握这些交互方式的本质区别,才能设计出如瑞士钟表般精密可靠的软件架构,交互方式选择矩阵。

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

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
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) {
        // 写入文件的实现
    }
}

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

优势

  • 灵活替换组件
  • 符合合成复用原则
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Computer {
    private CPU cpu;         // 组合:生命周期一致
    private List<RAM> rams; // 聚合:独立生命周期
    
    public Computer(CPU cpu, List<RAM> rams) {
        this.cpu = cpu;
        this.rams = new ArrayList<>(rams); // 防御性拷贝
    }
}

class CPU { /* 处理器实现 */ }
class RAM { /* 内存实现 */ }

5. 观察者模式

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

1. 观察者模式的核心组件
  • 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 类,具体观察者类,实现 Observer 接口,定义如何响应主题状态的变化
class ConcreteObserver implements Observer {
    private String name;

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

    @Override
    public void update(String message) {
        System.out.println(name + " received message: " + 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
        ConcreteSubject subject = new ConcreteSubject();

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

        // 注册观察者,将观察者注册到主题中
        subject.registerObserver(observer1);
        subject.registerObserver(observer2);
        subject.registerObserver(observer3);

        // 改变主题状态,改变主题状态为 "State 1",所有观察者都会收到通知
        subject.setState("State 1");

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

        // 再次改变主题状态,再次改变主题状态为 "State 2",只有 observer1 和 observer3 会收到通知
        subject.setState("State 2");
    }
}

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

优势

  • 解耦组件依赖
  • 提升可测试性
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class UserService {
    private final UserRepository repository;
    
    // 通过构造函数注入
    public UserService(UserRepository repo) {
        this.repository = repo;
    }
}

interface UserRepository {
    void save(User user);
}

// Spring 等框架自动装配
@Bean
public UserRepository userRepo() {
    return new JpaUserRepository();
}

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

适用场景

  • 跨系统/微服务通信
  • 异步处理解耦
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 订单服务(生产者)
class OrderProducer {
    public void createOrder(Order order) {
        MessageQueue.send("order_created", order);
    }
}

// 库存服务(消费者)
class InventoryConsumer {
    @MessageListener(topic = "order_created")
    public void handleOrder(Order order) {
        deductStock(order.getItems());
    }
}

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

总结

本文重点掌握面向对象思想,理解类、实例概念以及核心特性。