1. 为什么Getter/Setter是Java开发的必修课?
刚接触Java的新手常会疑惑:为什么每个字段都要写getXxx()和setXxx()方法?直接访问public字段不是更简单吗?我在2015年接手一个老项目时就吃过这个亏——那个系统里到处是直接访问的public字段,导致业务逻辑和字段访问代码纠缠不清,修改一个字段类型需要检查几十个调用点。这就是典型的"贫血模型"反模式。
面向对象的核心原则之一就是封装(Encapsulation),而Getter/Setter正是实现封装的基础手段。它们像保险箱的密码锁:Getter是查看保险箱内容的授权通道,Setter是修改内容的审批流程。没有这层控制,你的类字段就像把现金堆在办公桌上任人取用。
2. Getter/Setter标准实现与原理
2.1 基础模板代码
标准的Getter/Setter应该长这样:
public class BankAccount { private double balance; // 关键:字段必须private // Getter命名规范:get+字段名(首字母大写) public double getBalance() { return this.balance; } // Setter规范:参数类型与字段一致 public void setBalance(double amount) { // 可以添加校验逻辑 if (amount < 0) { throw new IllegalArgumentException("余额不能为负"); } this.balance = amount; } }警告:永远不要返回可变对象的直接引用!比如返回List应该用Collections.unmodifiableList()包装。
2.2 IDEA快速生成技巧
在IntelliJ IDEA中:
- 输入字段声明
private String name; - 按Alt+Insert → 选择Getter and Setter
- 勾选需要生成方法的字段 → OK
这会自动生成符合规范的代码,比手动编写效率高10倍。我团队要求所有新人都必须掌握这个快捷键。
3. 高级封装技巧与陷阱规避
3.1 什么时候不该用Setter?
不是所有字段都需要Setter。比如订单ID应该在创建时确定后不可修改:
public class Order { private final String orderId; // final修饰 public Order(String id) { this.orderId = id; } // 只有Getter,没有Setter public String getOrderId() { return orderId; } }3.2 延迟初始化模式
对于创建成本高的对象,可以使用懒加载:
private Image thumbnail; public Image getThumbnail() { if (thumbnail == null) { thumbnail = loadThumbnail(); // 按需加载 } return thumbnail; }3.3 防御性拷贝实战
这是新手最容易踩的坑:
// 危险写法 private Date createTime; public Date getCreateTime() { return createTime; // 调用者可以修改内部状态! } // 正确写法 public Date getCreateTime() { return (Date) createTime.clone(); // 返回副本 }4. Lombok的智能封装方案
4.1 注解式开发
添加Lombok依赖后:
@Data // 自动生成所有Getter/Setter public class User { private Long id; private String name; @Setter(AccessLevel.NONE) // 禁止生成Setter private String registerTime; }4.2 与JPA的完美配合
在Spring Data JPA实体中:
@Entity @Getter // 只生成Getter public class Product { @Id @GeneratedValue private Long id; @Column(nullable = false) @Setter(AccessLevel.PROTECTED) // 仅允许子类修改 private String skuCode; }5. 企业级封装规范
5.1 阿里巴巴开发手册要求
- POJO类必须使用包装类型(Integer而不是int)
- 布尔类型字段Getter应该用is前缀
- 集合类型返回空集合而非null
5.2 性能优化技巧
高频访问的Getter可以考虑:
@Getter(lazy=true) private final double cachedValue = expensiveCalculation();6. 常见问题排雷指南
6.1 循环引用问题
当两个对象互相引用时,直接生成toString()会导致栈溢出。解决方案:
@ToString(exclude = "oppositeObj") public class Node { private Node oppositeObj; }6.2 JSON序列化陷阱
Jackson默认通过Getter访问字段。如果Getter名不规范会导致序列化失败:
public boolean isActive(); // 正确 public boolean getActive(); // 导致JSON字段名为"active"6.3 版本兼容性问题
修改字段名但保留原Getter/Setter名可以保持API兼容:
private String newFieldName; @Deprecated public String getOldName() { return newFieldName; }7. 设计模式中的精妙用法
7.1 代理模式封装
通过Getter实现懒加载代理:
public Image getHighResImage() { return new ImageProxy(this.imagePath); }7.2 观察者模式集成
在Setter中添加通知逻辑:
public void setPrice(double newPrice) { this.price = newPrice; notifyObservers(); // 通知所有观察者 }我见过最优雅的实现是在Setter中集成了参数校验、权限检查、变更日志和事件通知,把简单的属性访问变成了业务流控中心。这正体现了封装的艺术——把复杂留给自己,把简单留给他人。