Looking for java Keywords? Try Ask4Keywords

Java Language Типичные дефекты дизайна, которые препятствуют тому, чтобы класс был непреложным


пример

Используя некоторые сеттеры, не устанавливая все необходимые свойства в конструкторе (конструкторах)

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

Легко показать, что класс Person не является неизменным:

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

Чтобы исправить это, просто удалите setSurname() и реорганизуйте конструктор следующим образом:

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

Не указывать переменные экземпляра как частные, так и окончательные

Взгляните на следующий класс:

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

Следующий фрагмент показывает, что указанный класс не является неизменным:

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

Чтобы исправить это, просто пометьте свойство name как private и final .


Выявление изменчивого объекта класса в геттере

Взгляните на следующий класс:

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Класс Names кажется неизменным с первого взгляда, но это не так, как показано в следующем коде:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Это произошло потому, что изменение ссылочного списка, возвращаемого getNames() может изменить фактический список Names .

Чтобы исправить это, просто избегайте возврата ссылок на изменяемые объекты ссылочного класса либо путем создания защитных копий, как показано ниже:

public List<String> getNames() {
   return new ArrayList<String>(this.names); // copies elements
}

или путем создания геттеров таким образом, чтобы возвращались только другие неизменные объекты и примитивы :

public String getName(int index) {
    return names.get(index);
}
public int size() {
    return names.size();
}

Конструктор инжекции с объектами (объектами), которые могут быть изменены вне неизменяемого класса

Это вариация предыдущего недостатка. Взгляните на следующий класс:

import java.util.List;
public final class NewNames {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = names;
    }
    public String getName(int index) {
        return names.get(index);
    }
    public int size() {
        return names.size();
    }
}

В качестве класса Names ранее класс NewNames кажется неизменным с первого взгляда, но это не так, на самом деле следующий фрагмент доказывает обратное:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Чтобы исправить это, как и в предыдущем изъяне, просто сделайте защитные копии объекта, не присваивая его непосредственно неизменному классу, т. Е. Конструктор можно изменить следующим образом:

    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }

Предотвращение переопределения методов класса

Взгляните на следующий класс:

public class Person {
    private final String name;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
}

Класс Person кажется неизменным с первого взгляда, но предположим, что новый подкласс Person определен:

public class MutablePerson extends Person {
    private String newName;
    public MutablePerson(String name) {
        super(name);            
    }
    @Override
    public String getName() {
        return newName;
    }
    public void setName(String name) {
        newName = name;
    }
}

теперь изменчивость Person (im) может быть использована посредством полиморфизма с использованием нового подкласса:

Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike    

Чтобы исправить это, либо пометьте класс как final чтобы он не мог быть расширен или объявить все его конструкторы (-и) как private .