Skip to content

Java 面向对象编程 (OOP)

面向对象编程(Object-Oriented Programming, OOP)是 Java 的核心思想。通过将数据和操作数据的方法封装在对象中,Java 提供了强大的模块化编程能力。

与面向过程编程不同,OOP 让我们用"对象"来描述现实世界的事物,使代码更贴近人类的思维方式,也更容易维护和扩展。


核心三大特性

1. 封装 (Encapsulation)

将对象的属性隐藏在对象内部,不允许外部直接访问,而是通过暴露的方法(如 Getter/Setter)来操作。这提高了数据的安全性和代码的可维护性。

代码示例

java
/**
 * 用户类 - 演示封装
 * 将属性设为 private,通过 public 方法访问
 */
public class User {
    // 私有属性,外部无法直接访问
    private String name;
    private int age;
    private String password;

    // Getter 方法 - 获取属性值
    public String getName() {
        return name;
    }

    // Setter 方法 - 设置属性值(带数据校验)
    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄不合法: " + age);
        }
        this.age = age;
    }

    public int getAge() {
        return age;
    }

    // 密码不提供 Getter,只提供验证方法
    public boolean checkPassword(String input) {
        return this.password.equals(input);
    }

    public void setPassword(String password) {
        if (password == null || password.length() < 6) {
            throw new IllegalArgumentException("密码长度不能少于6位");
        }
        this.password = password;
    }
}

实际应用场景

  • 数据校验:在 Setter 中对输入进行合法性检查,防止脏数据进入系统
  • 权限控制:只暴露必要的操作接口,隐藏内部实现细节
  • 日志记录:在 Getter/Setter 中添加日志,方便排查问题
  • 延迟加载:在 Getter 中实现懒加载,提升性能

2. 继承 (Inheritance)

子类可以继承父类的属性和方法,避免代码重复。Java 支持单继承,即一个类只能有一个直接父类,但可以通过接口实现多重继承的效果。

代码示例

java
/**
 * 动物基类
 */
public class Animal {
    protected String name;
    protected int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 通用方法
    public void eat() {
        System.out.println(name + "正在吃东西");
    }

    public void sleep() {
        System.out.println(name + "正在睡觉");
    }
}

/**
 * 狗类 - 继承 Animal
 */
public class Dog extends Animal {
    private String breed; // 品种

    public Dog(String name, int age, String breed) {
        super(name, age); // 调用父类构造方法
        this.breed = breed;
    }

    // 子类特有方法
    public void bark() {
        System.out.println(name + "汪汪叫");
    }

    // 重写父类方法
    @Override
    public void eat() {
        System.out.println(name + "正在吃狗粮,品种是" + breed);
    }
}

/**
 * 猫类 - 继承 Animal
 */
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    // 子类特有方法
    public void meow() {
        System.out.println(name + "喵喵叫");
    }
}

实际应用场景

  • 代码复用:多个类有共同的属性和方法时,提取到父类中
  • 建立层次结构:如 Animal -> Dog -> GuideDog 形成清晰的类层次
  • 框架扩展:继承框架提供的基类,重写特定方法来定制行为

3. 多态 (Polymorphism)

同一个接口或父类引用,可以指向不同的子类实例,并根据实际指向的对象类型调用对应的方法。这是实现设计模式中"开闭原则"的基础。

多态的实现依赖两个关键机制:

  • 编译时多态:方法重载(Overloading),同一个类中方法名相同但参数不同
  • 运行时多态:方法重写(Overriding),子类重写父类方法,运行时根据实际类型调用

代码示例

java
/**
 * 运行时多态示例
 */
public class PolymorphismDemo {
    public static void main(String[] args) {
        // 父类引用指向子类对象
        Animal animal1 = new Dog("旺财", 3, "金毛");
        Animal animal2 = new Cat("咪咪", 2);

        // 同一个方法调用,不同的行为表现
        animal1.eat(); // 输出:旺财正在吃狗粮,品种是金毛
        animal2.eat(); // 输出:咪咪正在吃东西

        // 实际类型是 Dog,所以可以向下转型
        if (animal1 instanceof Dog) {
            Dog dog = (Dog) animal1;
            dog.bark(); // 输出:旺财汪汪叫
        }
    }
}

/**
 * 方法重载 - 编译时多态
 */
public class Calculator {
    // 两个 int 相加
    public int add(int a, int b) {
        return a + b;
    }

    // 三个 int 相加(参数个数不同)
    public int add(int a, int b, int c) {
        return a + b + c;
    }

    // 两个 double 相加(参数类型不同)
    public double add(double a, double b) {
        return a + b;
    }
}

实际应用场景

  • 策略模式:定义一组算法,通过多态实现运行时切换
  • 工厂模式:工厂方法返回父类类型,实际返回不同的子类实例
  • 统一接口处理:如 List<Animal> 中存放不同的动物,统一调用 eat() 方法

抽象类 vs 接口

这是初学者最容易混淆的两个概念。它们都可以定义"规范",但适用场景不同。

对比表

特性抽象类 (abstract class)接口 (interface)
关键字abstract classinterface
构造方法可以有不能有
成员变量可以有普通字段只能有 public static final 常量
方法实现可以有具体方法实现Java 8+ 可以有 default 方法
继承/实现单继承 (extends)多实现 (implements)
访问修饰符方法可以是任意修饰符方法默认 public
设计意图"is-a" 关系,提供公共基础实现"has-a" 能力,定义行为规范

代码示例

java
/**
 * 抽象类 - 表示"是什么"
 * 所有形状都有面积,但计算方式不同
 */
public abstract class Shape {
    protected String color;

    public Shape(String color) {
        this.color = color;
    }

    // 抽象方法 - 子类必须实现
    public abstract double area();

    // 具体方法 - 提供公共实现
    public void display() {
        System.out.println("这是一个" + color + "的形状,面积是" + area());
    }
}

public class Circle extends Shape {
    private double radius;

    public Circle(String color, double radius) {
        super(color);
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

/**
 * 接口 - 表示"能做什么"
 * 定义可序列化、可比较的行为能力
 */
public interface Serializable {
    byte[] serialize();
    void deserialize(byte[] data);
}

public interface Comparable<T> {
    int compareTo(T other);
}

/**
 * 一个类可以实现多个接口
 */
public class UserDTO implements Serializable, Comparable<UserDTO> {
    private String name;
    private int age;

    @Override
    public byte[] serialize() {
        // 序列化实现
        return (name + ":" + age).getBytes();
    }

    @Override
    public void deserialize(byte[] data) {
        // 反序列化实现
    }

    @Override
    public int compareTo(UserDTO other) {
        return Integer.compare(this.age, other.age);
    }
}

如何选择?

  • 用抽象类:当多个类有共同的状态(字段)和行为(方法),且存在"is-a"关系时。例如 Dog is an Animal
  • 用接口:当需要定义一组行为规范,不关心具体实现时。例如 Dog can swimBird can fly
  • 组合使用:类继承抽象类获得基础实现,同时实现接口获得额外能力

常见误区

初学者在学习 OOP 时容易踩坑,以下是浮浮酱总结的常见问题喵~ ( ̄^ ̄)

误区 1:滥用继承(Inheritance Abuse)

java
// 错误示范:继承用于代码复用,而不是建立"is-a"关系
public class ArrayList extends Database {
    // ArrayList 和 Database 没有"is-a"关系!
    // 只是因为想复用 Database 的一些方法
}

// 正确做法:使用组合
public class ArrayList {
    private Database db; // 通过组合使用 Database 的功能
}

原则:优先使用组合(Composition),而非继承(Inheritance)。继承是耦合度最高的关系。

误区 2:上帝类(God Class)

java
// 错误示范:一个类做了太多事情
public class UserService {
    public void createUser() { /* ... */ }
    public void sendEmail() { /* ... */ }
    public void generateReport() { /* ... */ }
    public void validateData() { /* ... */ }
    public void logActivity() { /* ... */ }
    // ... 几千行代码
}

// 正确做法:单一职责原则
public class UserService {
    private EmailService emailService;
    private ReportService reportService;
    private ValidationService validationService;

    public void createUser(User user) {
        validationService.validate(user);
        // 创建用户逻辑
        emailService.sendWelcomeEmail(user);
    }
}

原则:一个类应该只有一个引起它变化的原因(单一职责原则 SRP)。

误区 3:过度使用 getter/setter

java
// 错误示范:把所有字段都暴露为 getter/setter,等于没有封装
public class Order {
    private BigDecimal amount;
    private String status;

    public BigDecimal getAmount() { return amount; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
    public String getStatus() { return status; }
    public void setStatus(String status) { this.status = status; }
}

// 正确做法:让对象自己管理状态
public class Order {
    private BigDecimal amount;
    private String status;

    // 提供有意义的行为方法
    public void pay() {
        if (!"PENDING".equals(status)) {
            throw new IllegalStateException("只有待支付的订单才能支付");
        }
        this.status = "PAID";
    }

    public void cancel() {
        if ("PAID".equals(status)) {
            // 退款逻辑
        }
        this.status = "CANCELLED";
    }
}

原则:Tell, Don't Ask(告诉对象做什么,而不是询问它的状态然后替它做决定)。

误区 4:忽略 equals/hashCode

java
// 错误示范:自定义类作为 HashMap 的 key 时没有重写 equals 和 hashCode
public class UserId {
    private String id;
    // 没有重写 equals 和 hashCode
}

Map<UserId, String> map = new HashMap<>();
map.put(new UserId("1001"), "张三");
map.get(new UserId("1001")); // 返回 null!因为是不同的对象

// 正确做法:重写 equals 和 hashCode
@Override
public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;
    UserId userId = (UserId) o;
    return Objects.equals(id, userId.id);
}

@Override
public int hashCode() {
    return Objects.hash(id);
}

最佳实践

1. 面向接口编程

java
// 好的做法:依赖接口
public class OrderService {
    private final PaymentGateway paymentGateway; // 接口类型

    // 通过构造函数注入具体实现
    public OrderService(PaymentGateway paymentGateway) {
        this.paymentGateway = paymentGateway;
    }
}

// 可以轻松替换实现
public interface PaymentGateway {
    boolean pay(BigDecimal amount);
}

public class AlipayGateway implements PaymentGateway { /* ... */ }
public class WechatGateway implements PaymentGateway { /* ... */ }

2. 使用组合代替继承

java
// 组合优于继承
public class Logger {
    private final LogWriter writer; // 组合
    private final LogFormatter formatter; // 组合

    public Logger(LogWriter writer, LogFormatter formatter) {
        this.writer = writer;
        this.formatter = formatter;
    }

    public void log(String message) {
        writer.write(formatter.format(message));
    }
}

3. 遵循 SOLID 原则

原则含义实践建议
S - 单一职责一个类只做一件事类名应该是名词,方法应该是动词,如果需要"和"来描述类的职责,就该拆分
O - 开闭原则对扩展开放,对修改关闭使用接口和抽象类定义扩展点
L - 里氏替换子类可以替换父类不要重写父类方法来改变其预期行为
I - 接口隔离接口要小而专一宁可多个小接口,不要一个大接口
D - 依赖倒置依赖抽象而非具体高层模块不应依赖低层模块,都应依赖抽象

4. 善用设计模式

设计模式是 OOP 的精华应用,以下是初学者应该优先掌握的模式:

  • 单例模式:确保一个类只有一个实例(如数据库连接池)
  • 工厂模式:将对象创建逻辑集中管理
  • 策略模式:定义一组算法,运行时可以互相替换
  • 观察者模式:实现事件驱动的松耦合通信
  • 装饰器模式:动态地给对象添加功能

总结

特性核心思想关键字使用场景
封装隐藏实现,暴露接口privatepublic数据校验、权限控制
继承代码复用,建立层次extends"is-a" 关系
多态统一接口,不同实现@Override策略切换、工厂模式

掌握 OOP 不仅仅是记住语法,更重要的是理解何时使用、如何正确使用。多写代码、多读优秀开源项目的源码,才能真正领悟面向对象的精髓喵~ ヽ(✿゚▽゚)ノ

用心记录代码与生活