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 class | interface |
| 构造方法 | 可以有 | 不能有 |
| 成员变量 | 可以有普通字段 | 只能有 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 swim、Bird 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 的精华应用,以下是初学者应该优先掌握的模式:
- 单例模式:确保一个类只有一个实例(如数据库连接池)
- 工厂模式:将对象创建逻辑集中管理
- 策略模式:定义一组算法,运行时可以互相替换
- 观察者模式:实现事件驱动的松耦合通信
- 装饰器模式:动态地给对象添加功能
总结
| 特性 | 核心思想 | 关键字 | 使用场景 |
|---|---|---|---|
| 封装 | 隐藏实现,暴露接口 | private、public | 数据校验、权限控制 |
| 继承 | 代码复用,建立层次 | extends | "is-a" 关系 |
| 多态 | 统一接口,不同实现 | @Override | 策略切换、工厂模式 |
掌握 OOP 不仅仅是记住语法,更重要的是理解何时使用、如何正确使用。多写代码、多读优秀开源项目的源码,才能真正领悟面向对象的精髓喵~ ヽ(✿゚▽゚)ノ