要在公有类而非公有域中使用访问方法《Effective java第四章第十六条 》

引言

本条的核心思想: 对于公有类,不应该直接暴露数据字段,而应该通过访问方法(getter)和修改方法(setter)来封装这些字段。这种做法提供了更好的封装性、灵活性和可维护性。

一、为什么不应该使用公有域

1、问题示例

// 不好的实践 - 直接暴露公有域
public class Point {
    public double x;
    public double y;
}

2、这种直接暴露公有域的做法存在严重问题

  1. 失去了对内部表示的控制权:一旦类被广泛使用,改变内部表示几乎不可能,因为客户端代码直接依赖这些字段
  2. 无法强制不变式:无法保证字段值始终满足某些约束条件
  3. 无法在字段被访问时采取辅助操作:如日志记录、验证或触发事件等

二、应该使用访问方法的正确方式

1、解决方案示例

// 好的实践 - 使用访问方法
public class Point {
    private double x;
    private double y;
    
    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }
    
    public double getX() { return x; }
    public double getY() { return y; }
    
    public void setX(double x) { this.x = x; }
    public void setY(double y) { this.y = y; }
}

2、这种使用访问方法的做法具有以下优势

  1. 封装内部实现:可以自由改变内部表示而不影响客户端
  2. 添加约束条件:可以在setter中添加验证逻辑
  3. 线程安全控制:可以添加同步机制
  4. 添加辅助行为:可以在访问时记录日志或触发事件

三、特殊情况讨论

1、包级私有或私有嵌套类

原文强调:“如果类是包级私有的,或者是私有的嵌套类,直接暴露它的数据域并没有本质的错误”

这种情况下,因为类的可见性已经受限,修改不会影响到外部代码,所以可以直接暴露字段。

示例:

// 包级私有类,可以直接暴露字段
class PackagePrivatePoint {
    public double x;
    public double y;
}

2、JavaBean模式

作者指出:JavaBean模式(即每个属性都有getter和setter)在某些情况下也有问题,特别是在需要让类不可变时。

2.1 JavaBean模式分析:

JavaBean模式是指遵循特定约定的Java类,通常具有以下特征:

  1. 一个无参公共构造函数
  2. 每个属性都有对应的getter和setter方法
  3. 实现Serializable接口

2.2 JavaBean模式的标准示例

// 典型的JavaBean
public class PersonBean implements Serializable {
    private String name;
    private int age;

    public PersonBean() {}  // 1. 无参构造函数

    // 2. 每个属性都有getter和setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    // 其他方法...
}

2.3 JavaBean模式在不可变类中的问题

**不可变类(Immutable Class)**是指其实例在创建后状态不能被修改的类。JavaBean模式与不可变性存在根本冲突,原因如下:

2.3.1 setter方法破坏了不可变性

核心矛盾:不可变类要求实例创建后状态不能改变,而JavaBean的setter方法专门用于修改状态。

// 尝试创建"不可变"的JavaBean - 自相矛盾
public final class ImmutablePerson implements Serializable {
    private String name;
    private int age;

    public ImmutablePerson() {}

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }  // 破坏了不可变性!

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }  // 破坏了不可变性!
}
2.3.2 无参构造函数导致不完整初始化

不可变类通常要求在构造时提供所有必要属性,确保对象一旦创建就处于完整、一致的状态。

JavaBean模式下:

// 问题示例:通过无参构造+setter创建的不可变对象
ImmutablePerson person = new ImmutablePerson();  // 此时name=null, age=0
person.setName("Alice");  // 分步设置,中间状态不一致
person.setAge(25);

问题:在setName和setAge调用之间,对象处于不一致状态(有名字无年龄或有年龄无名字)。

2.3.3 正确的不可变类实现方式
// 正确的不可变类实现
public final class ImmutablePerson {
    private final String name;  // final字段
    private final int age;      // final字段

    // 构造时要求提供所有属性
    public ImmutablePerson(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 只有getter,没有setter
    public String getName() { return name; }
    public int getAge() { return age; }

    // 没有提供修改状态的方法
}
2.3.4 关键区别
  1. 所有字段声明为final
  2. 没有无参构造函数
  3. 构造时必须提供所有属性
  4. 不提供setter方法
2.3.5 总结

Effective Java的观点:JavaBean模式由于强制要求提供setter方法和无参构造器,使得它无法用于实现真正的不可变类。在设计不可变类时,应该:

  1. 使所有字段为final
  2. 不提供setter方法
  3. 通过构造函数强制完整初始化
  4. 防止子类化(类声明为final

这种模式牺牲了JavaBean的灵活性,但换来了线程安全、易于推理等不可变对象的重要优势。

3、不可变类

对于不可变类,公开final字段是可以接受的,因为无法修改这些字段。

示例

// 不可变类可以直接公开final字段
public final class Time {
    private static final int HOURS_PER_DAY = 24;
    private static final int MINUTES_PER_HOUR = 60;
    
    public final int hour;
    public final int minute;
    
    public Time(int hour, int minute) {
        if (hour < 0 || hour >= HOURS_PER_DAY)
            throw new IllegalArgumentException("Hour: " + hour);
        if (minute < 0 || minute >= MINUTES_PER_HOUR)
            throw new IllegalArgumentException("Min: " + minute);
        this.hour = hour;
        this.minute = minute;
    }
    ... // 其他方法
}

关键点:即使在这种情况下,如果未来需要改变内部表示,公开字段的方式也会带来问题。

四、总结要点

  1. 公有类永远不应该暴露可变的字段:这会破坏封装性,使类难以演化
  2. 让公有类暴露不可变字段的危害较小:但仍有局限性
  3. 包级私有和私有嵌套类可以更灵活:因为它们的修改不会影响外部代码

最终建议:在公有类中,总是使用私有字段和公共访问方法(getter),而不是直接暴露公有字段。这种面向对象的基本实践提供了必要的灵活性,可以在以后改进类的内部表示而不影响现有客户端代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值