1. 为什么面向对象是Java的核心
我第一次接触Java时,导师在黑板上画了三个圈:封装、继承、多态。当时觉得这就是些抽象概念,直到在真实项目中踩了坑才明白,面向对象(OOP)不是语法糖,而是Java工程师的生存法则。
最近帮团队新人排查一个商品系统的bug:修改商品分类时,连带把商品详情也清空了。打开代码一看,200行的Service方法里直接操作了十几个字段,没有任何对象边界的概念。这让我想起五年前自己写的第一个Java项目——同样是一坨面条代码,只不过那时没人告诉我OOP的重要性。
Java从语言设计层面就是为OOP而生的。看看这些特性:
- 所有代码必须写在类里(连main方法都不例外)
- 没有全局变量(所有状态必须属于某个对象)
- 访问控制修饰符(public/protected/private)
- 接口与抽象类的强制使用场景
如果你只把Java当作"能跑就行"的脚本语言,很快就会遇到这些典型问题:
- 修改一处代码引发多处报错(紧耦合)
- 重复代码随处可见(DRY原则被破坏)
- 新增需求时不敢动老代码(缺乏扩展性)
真实案例:某电商系统促销模块,因为早期没有用多态处理不同促销策略,后期每次新增促销类型都要修改核心计算逻辑,最终不得不重构。
2. 封装的艺术:不只是private
2.1 访问控制的实战意义
很多教程讲到封装就是"用private修饰字段",这其实只触及了表面。看这个商品类的演变:
java复制// 初级版
public class Product {
public String name;
public double price;
}
// 中级版
public class Product {
private String name;
private double price;
// getter/setter...
}
// 进阶版
public class Product {
private final String id;
private String name;
private BigDecimal price;
public Product(String id) {
this.id = id;
}
// 业务约束:价格不能为负
public void setPrice(BigDecimal price) {
if (price.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("价格不能为负");
}
this.price = price;
}
// 派生属性不暴露字段
public boolean isFree() {
return BigDecimal.ZERO.equals(price);
}
}
关键进阶点:
- 用final确保不变性(id创建后不可修改)
- 用BigDecimal代替double避免精度问题
- setter中加入业务规则校验
- 派生属性通过方法暴露
2.2 不变性的威力
在多线程环境下,可变状态是万恶之源。看这个计数器实现对比:
java复制// 可变版本(线程不安全)
class Counter {
private int value;
public void increment() {
value++; // 非原子操作
}
}
// 不可变版本
class Counter {
private final AtomicInteger value;
public Counter(int initValue) {
this.value = new AtomicInteger(initValue);
}
public Counter increment() {
return new Counter(value.get() + 1);
}
public int getValue() {
return value.get();
}
}
虽然不可变版本需要创建新对象,但彻底避免了竞态条件。Java 8的Stream API就是基于不可变思想设计的。
3. 继承的陷阱与救赎
3.1 何时使用继承
我见过最糟糕的继承滥用是一个BaseDAO类,被50多个子类继承,后来要加Redis缓存时差点崩溃。记住这条铁律:
继承表示"是一个(is-a)"关系,而不是"有一个(has-a)"或"用到了(uses-a)"关系
正确示例:图形类体系
java复制abstract class Shape {
abstract double area();
}
class Circle extends Shape {
private final double radius;
Circle(double r) { this.radius = r; }
@Override
double area() {
return Math.PI * radius * radius;
}
}
错误示例:把工具方法用继承实现
java复制// 错误!Logger不是一种StringUtil
class MyService extends StringUtil {
//...
}
3.2 组合优于继承
这是Effective Java的第一条准则。比较两种实现方式:
java复制// 继承方式
class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
// 问题:addAll()会调用add(),导致重复计数
}
// 组合方式
class InstrumentedSet<E> {
private final Set<E> set;
private int addCount = 0;
public InstrumentedSet(Set<E> s) { this.set = s; }
public boolean add(E e) {
addCount++;
return set.add(e);
}
// 明确控制哪些方法暴露
}
组合的优势:
- 不破坏封装(内部实现可变化)
- 避免父类方法间的隐含依赖
- 运行时更灵活(可动态更换组件)
4. 多态的动态之美
4.1 运行时类型识别
看这个电商支付处理的例子:
java复制interface Payment {
void pay(BigDecimal amount);
}
class Alipay implements Payment {
@Override
public void pay(BigDecimal amount) {
System.out.println("支付宝支付:" + amount);
}
}
class WechatPay implements Payment {
@Override
public void pay(BigDecimal amount) {
System.out.println("微信支付:" + amount);
}
}
public class PaymentService {
// 关键点:参数是接口类型
public void processPayment(Payment payment, BigDecimal amount) {
payment.pay(amount);
}
}
当新增银联支付时,只需新建UnionPay类,无需修改PaymentService。这就是开闭原则(OCP)的体现。
4.2 策略模式实战
在优惠计算场景中,不同活动有不同的计算规则:
java复制interface DiscountStrategy {
BigDecimal applyDiscount(Order order);
}
class FullReduction implements DiscountStrategy {
@Override
public BigDecimal applyDiscount(Order order) {
// 满减逻辑
}
}
class PercentageOff implements DiscountStrategy {
@Override
public BigDecimal applyDiscount(Order order) {
// 折扣逻辑
}
}
class DiscountContext {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy s) {
this.strategy = s;
}
public BigDecimal calculate(Order order) {
return strategy.applyDiscount(order);
}
}
这样在618大促时,只需组合不同的策略实现,而不是写满if-else。
5. 对象协作的黄金法则
5.1 迪米特法则(LoD)
也叫最少知识原则,看这个违反案例:
java复制class OrderService {
public void process(Order order) {
// 错误:直接深入到Customer的Address
String city = order.getCustomer().getAddress().getCity();
//...
}
}
应该改为:
java复制class Order {
public String getCustomerCity() {
return customer.getCity();
}
}
class Customer {
public String getCity() {
return address.getCity();
}
}
这样修改后,当Address结构变化时,只需修改Customer类。
5.2 依赖注入
对比两种对象获取方式:
java复制// 紧耦合方式
class OrderService {
private OrderRepository repository = new JdbcOrderRepository();
}
// 依赖注入方式
class OrderService {
private final OrderRepository repository;
public OrderService(OrderRepository repo) {
this.repository = repo;
}
}
使用Spring等框架时,构造函数注入是最佳实践:
java复制@Service
public class OrderService {
private final OrderRepository repository;
@Autowired // Spring 4.3+ 可省略
public OrderService(OrderRepository repo) {
this.repository = repo;
}
}
6. 设计模式启蒙
6.1 工厂模式进化史
从简单工厂到抽象工厂:
java复制// 简单工厂
class PaymentFactory {
public static Payment create(String type) {
switch(type) {
case "alipay": return new Alipay();
case "wechat": return new WechatPay();
default: throw new IllegalArgumentException();
}
}
}
// 工厂方法
interface PaymentFactory {
Payment create();
}
class AlipayFactory implements PaymentFactory {
@Override
public Payment create() {
return new Alipay();
}
}
// 抽象工厂
interface PaymentGateway {
Payment createPayment();
Refund createRefund();
}
6.2 观察者模式
Java自带实现:
java复制class OrderStatusNotifier extends Observable {
void statusChanged(Order order) {
setChanged(); // 必须调用
notifyObservers(order);
}
}
class LogisticsService implements Observer {
@Override
public void update(Observable o, Object arg) {
Order order = (Order)arg;
// 更新物流状态...
}
}
实际项目中更推荐使用事件总线如Guava EventBus。
7. 现代Java OOP特性
7.1 记录类(Record)
Java 14引入:
java复制// 替代传统的POJO
public record Product(
String id,
String name,
BigDecimal price
) {
// 自动生成equals/hashCode/toString
// 自动生成final字段和构造方法
}
7.2 密封类(Sealed Class)
Java 17引入:
java复制public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
}
public final class Circle implements Shape {
private final double radius;
@Override
public double area() {
return Math.PI * radius * radius;
}
}
这样编译器会检查所有允许的子类,避免意外继承。
8. 常见误区与性能考量
8.1 过度设计陷阱
我曾参与重构一个"完美OOP"系统:
- 每个类都有接口
- 三层继承起步
- 到处都是设计模式
结果简单需求要改10个文件。记住:
- 优先用简单方案
- 等到第二次出现相似代码再抽象
- 保持适度的冗余
8.2 对象创建开销
在性能敏感场景要注意:
- 避免大量短命对象(GC压力)
- 重用不可变对象(如枚举)
- 考虑对象池模式
但不要过早优化——先用清晰的设计实现功能,再针对性优化。
9. 实战建议
- 从领域驱动设计(DDD)中学习对象划分
- 多阅读JDK源码(如Collections框架)
- 定期用ArchUnit检查架构约束
- 尝试用OOP思想重写旧代码
- 参加面向对象设计挑战(如设计棋盘游戏)
我书架常备的三本OOP经典:
- 《Effective Java》(Joshua Bloch)
- 《Head First设计模式》
- 《领域驱动设计精粹》
