Java zaawansowane #12: obiekty niezmienne (immutable)

Przetwarzanie wielowątkowe niesie ze sobą jeden podstawowy problem. Praca wielu wątków na jednej klasie przechowującej stan (np. zwykła klasa typu POJO), wymaga odpowiedniej synchronizacji. Innym sposobem jest wymuszenie na klasie, która ma przechowywać dane, aby była odporna na zmiany. Taka klasa często nosi miano klasy niezmiennej (ang. immutable).

Spójrz na przykładową implementację poniższej klasy POJO.

public class ImmutableClass {
	private int intValue;
	private Date date;
	private List<String> strings;
	private String myString;
	
	public ImmutableClass(int intValue, Date date, List<String> strings, String myString) {
		this.intValue = intValue;
		this.date = date;
		this.strings = strings;
		this.myString = myString;
	}
	public int getIntValue() {
		return intValue;
	}
	public void setIntValue(int intValue) {
		this.intValue = intValue;
	}
	public Date getDate() {
		return date;
	}
	public void setDate(Date date) {
		this.date = date;
	}
	public List<String> getStrings() {
		return strings;
	}
	public void setStrings(List<String> strings) {
		this.strings = strings;
	}
	public String getMyString() {
		return myString;
	}
	public void setMyString(String myString) {
		this.myString = myString;
	}
}

Zasady

Teraz należy przekształcić klasę ImmutableClass, tak aby zawsze zwracała ten sam stan. Klasy tego typu charakteryzują się pewnymi cechami wspólnymi:

  • należy zabezpieczyć klasę przed dziedziczeniem (final przed nazwą klasy albo w sygnaturze metod),
  • wszystkie pola muszą być prywatne (brak dostępu bezpośredniego z poza klasy) oraz finalne (musisz przypisać do nich wartość lub referencję),
  • konstruktor parametryzowany powinien inicjalizować zmienne za pomocą głębokiej kopii (ang. deep copy*),
  • gettery zmiennych klasowych zwracają kopię defensywną (ang. defensive copy**),
  • z racji, że wszystkie pola są finalne, nie istnieją settery.

Ograniczenie dziedziczenia

public final class ImmutableClass {
	private final int intValue;
	private final Date date;
	private final List<String> strings;
	private final String myString;

Klasa finalna rozwiązała problem z polimorfizmem. Finalne pola wymuszają na programiście przypisanie do nich wartości (pola typu prostego) lub ustawienie stałej referencji.

Aktualizacja getterów

	public int getIntValue() {
		return intValue;
	}

	public Date getDate() {
		return new Date(date.getTime());
	}

	public List<String> getStrings() {
		return strings;
	}

	public String getMyString() {
		return myString;
	}

W przypadku pola typu prostego albo referencyjnego, ale odwołującego się do innej klasy niezmiennej, wystarczy stworzyć zwykły getter. Niestety, jeśli jest to obiekt, którego zawartość może ulec zmianie, należy stworzyć jego nową instancję, po czym skopiować dane drugiego poprzez konstruktor z parametrem.

Nowe referencje w konstruktorze

public ImmutableClass(int intValue, Date date, List<String> strings, String myString) {
    this.intValue = intValue;
    this.date = new Date(date.getTime());
    this.strings = Collections.unmodifiableList(strings);
    this.myString = myString; //String is immutable also, you don't have to create new object
}

Ostatni punktem jest modernizacja konstruktora, ustawiając nową referencję w przypadku daty (kopia defensywna) lub skorzystanie z kolekcji niemodyfikowalnych (kopia głęboka).

Link do kodu: https://github.com/developeronthego/java-advanced/tree/master/src/main/java/advanced/lesson12

*Różnica między kopią głęboką i płytką

*Różnica między kopią głęboką a defensywną

Może Ci się również spodoba

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *