4

我正在尝试按照 Effective Java Item 15 (Minimize Mutability) 中给出的建议将可变类转换为不可变类。谁能告诉我我创建的类是否完全不可变?

可变类

public class Record {
    public int sequenceNumber;
    public String id;
    public List<Field> fields;

    /**
     * Default Constructor
     */
    public Record() {
        super();
    }

    public Record addField(Field fieldToAdd) {
        fields.add(fieldToAdd);
        return this;
    }

    public Record removeField(Field fieldToRemove) {
        fields.remove(fieldToRemove);
        return this;
    }

    public int getSequenceNumber() {
        return sequenceNumber;
    }

    public String getId() {
        return id;
    }

    public List<Field> getFields() {
        return fields;
    }

    public void setSequenceNumber(int sequenceNumber) {
        this.sequenceNumber = sequenceNumber;
    }

    public void setFields(List<Field> fields) {
        this.fields = fields;
    }

    public void setId(String id) {
        this.id = id;
    }
}

字段类

public class Field {
    private String name;
    private String value;

    public Field(String name,String value) {
        this.name = name;
        this.value = value;
    }

    public String getName() {
        return name;
    }

    public String getValue() {
        return value;
    }
}

不可变类

public class ImmutableRecord {
    private final int sequenceNumber;
    private final String id;
    private final List<Field> fields;

    private ImmutableRecord(int sequenceNumber, List<Field> fields) {
        this.sequenceNumber = sequenceNumber;
        this.fields = fields;
        this.id = UUID.randomUUID().toString();
    }

    public static ImmutableRecord getInstance(int sequenceNumber, List<Field> fields) {
        return new ImmutableRecord(sequenceNumber, fields);
    }

    /********************* Only Accessor No Mutator *********************/

    public int getSequenceNumber() {
        return sequenceNumber;
    }

    public String getId() {
        return id;
    }

    public List<Field> getFields() {
        return Collections.unmodifiableList(fields);
    }

    /********************* Instance Methods *********************/

    public ImmutableRecord addField(Field fieldToAdd) {
        Field field = new Field(fieldToAdd.getName(), fieldToAdd.getValue());
        List<Field> newFields = new ArrayList<Field>(fields);
        newFields.add(field);
        Collections.unmodifiableList(newFields);
        ImmutableRecord immutableRecord = new ImmutableRecord(sequenceNumber, newFields);  
        return immutableRecord;
    }

    public ImmutableRecord removeField(Field fieldToRemove) {
        Field field = new Field(fieldToRemove.getName(), fieldToRemove.getValue());
        List<Field> newFields = new ArrayList<Field>(fields);
        newFields.remove(field);
        Collections.unmodifiableList(newFields);
        ImmutableRecord immutableRecord = new ImmutableRecord(sequenceNumber, newFields);  
        return immutableRecord;
    }
}

谢谢

谢卡尔

4

3 回答 3

11

不,不是,列表字段应该被复制而不是存储直接引用

private ImmutableRecord(int sequenceNumber, List<Field> fields) {
    this.sequenceNumber = sequenceNumber;
    this.fields = fields; // breaks immutability!!!
    this.id = UUID.randomUUID().toString();
}

如果有人修改了 List 字段引用,您的类也会反映它。最好在分配到 this.fields 之前将集合的内容复制到另一个

此外,您的类似乎是可变的,因为它具有添加和删除方法:)

于 2010-07-25T08:35:09.060 回答
2

正如 naikus 指出的那样,fields构造函数中的参数可以在类之外进行修改。它也可能是一个“非标准”List实现。所以,

    this.fields = fields;

应该改为

    this.fields = new ArrayList<Field>(fields);

或者可能

    this.fields = Collections.unmodifiableList(new ArrayList<Field>(fields));

使该字段成为不可修改的列表有利有弊。首先,它说明了您的意思。它可以防止错误/维护工程师。分配有点偶然——你不会在每次获取时都分配;优化分配(可能会出现逃逸分析);让对象四处游荡并不是一个好主意,因为它会减慢垃圾收集的速度(并且在较小程度上消耗内存)。

还要使所有类和字段-说出您的意思,并且有一些微妙之处。

“添加”方法很好。看BigInteger,说(尽管忽略它的某些特征!)。

一个稍微有争议的观点是,不可变类get中的那些访问器方法中的所有内容都是噪音。删除.get

制作构造函数private并添加一个名为 的静态创建方法of会增加一点,但您几乎不需要“新”对象。还允许类型推断,在我们获得当前 JDK7 中的菱形运算符之前。构造函数还允许在和private中创建新实例时删除复制的可变对象。addFieldremoveField

equalshashCode也许toString很高兴拥有。虽然有 YAGNI 与构造接口(概念,而不是 Java 关键字)作为 API。

于 2010-07-25T11:25:28.167 回答
1

"1. 不要提供任何修改对象的方法(称为 mutators)。

  1. 确保没有方法可以被覆盖。这可以防止粗心或恶意的子类损害类的不可变行为。防止方法覆盖通常通过将类设为 final 来完成。

  2. 使所有字段最终。这以系统强制执行的方式清楚地表达了您的意图。此外,如果对新创建实例的引用在没有同步的情况下从一个线程传递到另一个线程,则可能需要确保正确的行为,这取决于对内存模型进行持续努力的结果。

  3. 将所有字段设为私有。这可以防止客户端直接修改字段。虽然技术上允许不可变类具有包含原始值或对不可变对象的引用的公共 final 字段,但不建议这样做,因为它会阻止在以后的版本中更改内部表示(第 12 条)。

  4. 确保对任何可变组件的独占访问。如果您的类有任何引用可变对象的字段,请确保该类的客户端无法获取对这些对象的引用。既不会将此类字段初始化为客户端提供的对象引用,也不会从访问器返回对象引用。在构造函数、访问器和 readObject 方法(第 56 项)中制作防御性副本(第 24 项)。”

http://wiki.glassfish.java.net/attach/JavaProgramming/ej.html#immutablerecipe

于 2010-07-25T14:53:20.967 回答