Является ли этот класс полностью неизменным? - PullRequest
4 голосов
/ 25 июля 2010

Я пытаюсь преобразовать изменяемый класс в неизменяемый класс, следуя советам, приведенным в пункте 15 «Эффективная Java» (свести к минимуму изменчивость).Кто-нибудь может сказать мне, является ли созданный мной класс полностью неизменным или нет?

Изменяемый класс

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;
    }
}

Спасибо

Шекхар

Ответы [ 3 ]

11 голосов
/ 25 июля 2010

Нет, поля списка должны быть скопированы и не должны сохраняться в прямой ссылке

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

Если кто-то из них изменяет ссылку на поля списка, ваш класс также будет отражать ее.Лучше скопировать содержимое коллекции в другое, прежде чем перейти к this.fields

Кроме того, ваш класс is выглядит изменяемым, поскольку в нем есть методы добавления и удаления:)

2 голосов
/ 25 июля 2010

Как указывает 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 конструкторы также позволяют удалять копируемые изменяемые файлы при создании нового экземпляра в addField и removeField.

equals, hashCode и, возможно, toString приятно иметь.Хотя в качестве API есть интерфейсы YAGNI и конструкции (концепция, а не ключевое слово Java).

1 голос
/ 25 июля 2010

"1. Не предоставляйте какие-либо методы, которые изменяют объект (известный как мутаторы).

Убедитесь, что никакие методы не могут быть переопределены.Это препятствует тому, чтобы небрежные или злонамеренные подклассы поставили под угрозу неизменное поведение класса.Предотвращение переопределений методов, как правило, выполняется путем создания класса final.

Сделайте все поля окончательными.Это ясно выражает ваши намерения таким образом, который обеспечивается системой.Кроме того, может потребоваться обеспечить правильное поведение, если ссылка на вновь созданный экземпляр передается из одного потока в другой без синхронизации, в зависимости от результатов текущих усилий по переработке модели памяти.

Сделать все поля приватными.Это не позволяет клиентам изменять поля напрямую.Хотя для неизменяемых классов технически допустимо иметь открытые конечные поля, содержащие примитивные значения или ссылки на неизменяемые объекты, это не рекомендуется, поскольку оно исключает изменение внутреннего представления в более позднем выпуске (элемент 12).

Обеспечить эксклюзивный доступ к любым изменяемым компонентам.Если в вашем классе есть поля, ссылающиеся на изменяемые объекты, убедитесь, что клиенты этого класса не могут получить ссылки на эти объекты.Ни инициализируйте такое поле для предоставленной клиентом ссылки на объект, ни верните ссылку на объект из средства доступа.Делайте защитные копии (элемент 24) в конструкторах, методах доступа и методах readObject (элемент 56). "

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

...