Niezmienna klasa?


Odpowiedzi:


135

Co to jest niezmienny obiekt?

Niezmienny obiekt to taki, który nie zmieni stanu po utworzeniu wystąpienia.

Jak uczynić obiekt niezmiennym?

Ogólnie rzecz biorąc, niezmienny obiekt można utworzyć, definiując klasę, która nie ma ujawnionego żadnego ze swoich elementów członkowskich i nie ma żadnych metod ustawiających.

Następująca klasa utworzy niezmienny obiekt:

class ImmutableInt {
  private final int value;

  public ImmutableInt(int i) {
    value = i;
  }

  public int getValue() {
    return value;
  }
}

Jak widać w powyższym przykładzie, wartość the ImmutableIntmożna ustawić tylko wtedy, gdy obiekt jest tworzony, a mając tylko metodę getter ( getValue), stan obiektu nie może zostać zmieniony po utworzeniu instancji.

Należy jednak uważać, aby wszystkie obiekty, do których odwołuje się obiekt, również były niezmienne, w przeciwnym razie może istnieć możliwość zmiany stanu obiektu.

Na przykład zezwolenie na odwołanie do tablicy lub ArrayListuzyskanie go za pomocą metody pobierającej pozwoli na zmianę stanu wewnętrznego przez zmianę tablicy lub kolekcji:

class NotQuiteImmutableList<T> {
  private final List<T> list;

  public NotQuiteImmutableList(List<T> list) {
    // creates a new ArrayList and keeps a reference to it.
    this.list = new ArrayList(list); 
  }

  public List<T> getList() {
    return list;
  }
}

Problem z powyższym kodem polega na tym, że ArrayListmożna go uzyskać getListi manipulować nim, co prowadzi do zmiany stanu samego obiektu, a zatem nie jest niezmienny.

// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));

// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");

Jednym ze sposobów obejścia tego problemu jest zwrócenie kopii tablicy lub kolekcji po wywołaniu z metody pobierającej:

public List<T> getList() {
  // return a copy of the list so the internal state cannot be altered
  return new ArrayList(list);
}

Jaka jest zaleta niezmienności?

Zaletą niezmienności jest współbieżność. Trudno jest zachować poprawność w obiektach zmiennych, ponieważ wiele wątków może próbować zmienić stan tego samego obiektu, co prowadzi do tego, że niektóre wątki widzą inny stan tego samego obiektu, w zależności od czasu odczytów i zapisów do wspomnianego obiekt.

Mając niezmienny obiekt, można zapewnić, że wszystkie wątki, które patrzą na obiekt, będą widzieć ten sam stan, ponieważ stan niezmiennego obiektu nie ulegnie zmianie.


5
Bezpieczeństwo wątków (ważne ze względu na współbieżność) nie jest jedyną zaletą niezmienności; oznacza to również, że nie musisz tworzyć kopii obronnych obiektów i zapobiega błędom, ponieważ nie możesz przez pomyłkę modyfikować obiektów, które nie powinny być modyfikowane.
Jesper

7
Zamiast zwracać kopię listy, możesz również return Collections.unmodifiableList(list);zwrócić na liście widok tylko do odczytu.
Jesper

18
Klasa też musi być zrobiona final. W przeciwnym razie można go rozszerzyć o metodę ustawiającą (lub inne rodzaje metod mutujących i pól mutowalnych).
Abhinav Sarkar,

6
@AbhinavSarkar Nie musi tak być, finaljeśli klasa ma tylko privatepola, ponieważ nie można uzyskać do nich dostępu z podklas.
icza

3
Klasa @icza musi być ostateczna, ponieważ mamy publiczną metodę getter, możemy rozszerzyć klasę i nadpisać metodę getter, a następnie zmienić pole na swój sposób ... więc nie jest już niezmienne
sunil

19

Oprócz odpowiedzi już udzielonych, polecam przeczytanie o niezmienności w Effective Java, wyd. 2, ponieważ jest kilka szczegółów, które łatwo przeoczyć (np. Kopie obronne). Dodatkowo Effective Java 2nd Ed. to lektura obowiązkowa dla każdego programisty Java.


3
To jest dokładny zasób, któremu należy się przyjrzeć. Wspomniane korzyści obejmują zarówno kod mniej podatny na błędy, jak i bezpieczeństwo wątków.
gpampara

6

Tworzysz niezmienną klasę w następujący sposób:

public final class Immutable
{
    private final String name;

    public Immutable(String name) 
    {
        this.name = name;
    }

    public String getName() { return this.name; } 

    // No setter;
}

Poniżej przedstawiono wymagania, aby uczynić niezmienną klasę Javy:

  • Klasa musi być zadeklarowana jako final(aby nie można było tworzyć klas podrzędnych)
  • Składowe w klasie muszą być zadeklarowane jako final(abyśmy nie mogli zmienić jej wartości po utworzeniu obiektu)
  • Napisz metody Getter dla wszystkich zawartych w nim zmiennych, aby uzyskać wartości Members
  • Brak metod ustawiających

Niezmienne klasy są przydatne, ponieważ
- są bezpieczne dla wątków.
- Wyrażają również coś głębokiego w Twoim projekcie: „Nie można tego zmienić”. Kiedy ma zastosowanie, jest dokładnie tym, czego potrzebujesz.


5

Niezmienność można osiągnąć głównie na dwa sposoby:

  • używanie finalatrybutów instancji, aby uniknąć ponownego przypisania
  • używanie interfejsu klasy, który po prostu nie pozwala na żadną operację, która jest w stanie zmodyfikować zawartość Twojej klasy (tylko pobierające i nie ustawiające

Zaletami niezmienności są założenia, które możesz poczynić na tym obiekcie:

  • zyskujesz zasadę braku efektów ubocznych (która jest bardzo popularna w funkcjonalnych językach programowania) i pozwala na łatwiejsze używanie obiektów w środowisku współbieżnym, ponieważ wiesz, że nie można ich zmienić w sposób atomowy lub nie atomowy, gdy są używany przez wiele wątków
  • implementacje języków mogą traktować te obiekty w inny sposób, umieszczając je w strefach pamięci, które są używane dla danych statycznych, umożliwiając szybsze i bezpieczniejsze korzystanie z tych obiektów (tak dzieje się wewnątrz JVM dla stringów)

Niezmienny to nie to samo, co równy efekt uboczny wolny. Na przykład niezmienny obiekt może powodować efekty uboczne, takie jak rejestrowanie do pliku. Powiedzenie, że uczynienie obiektu niezmiennym sprawia, że ​​jest on wolny od skutków ubocznych, jest trochę niepoprawne.
Grundlefleck

1
@Grundleflek, myślę, że to może być rozdwajanie włosów. Klasa nie jest niezmienna, jeśli modyfikuje plik dziennika w ramach kontraktu, a ten plik dziennika jest dostępny dla innych klas. Jeśli plik dziennika jest ukryty przed innymi klasami i nie jest częścią kontraktu klasy, to klasa jest faktycznie niezmienna i naprawdę bez efektów ubocznych. (Niezasłużone) wprowadzenie na stronie efektów ubocznych Wikipedii brzmi: „... mówi się, że wyrażenie ma efekt uboczny, jeśli oprócz tworzenia wartości modyfikuje również stan lub ma obserwowalną interakcję z funkcjami wywołującymi”.
Jeff Axelrod

3

Niezmienne klasy nie mogą ponownie przypisać wartości po utworzeniu instancji. Konstruktor przypisuje wartości do swoich prywatnych zmiennych. Dopóki obiekt nie stanie się pusty, wartości nie mogą być zmieniane z powodu niedostępności metod ustawiających.

być niezmiennym powinno spełniać następujące,

  • Wszystkie zmienne powinny być prywatne .
  • Nie są dostarczane żadne metody mutatora (ustawiające).
  • Unikaj zastępowania metod, ustawiając klasę jako ostateczną (silna niezmienność) lub ostateczną metodę (niezmienność tygodniowa).
  • Klonuj głęboko, jeśli zawiera nieprymitywne lub zmienne klasy.

/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {

// make the variables private
private String Name;

//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}

//provide getters to access values
public String getName() {

return this.Name;
}
}

Zalety: Niezmienne obiekty zawierają zainicjalizowane wartości, dopóki nie umrą.

java-immutable-classes-short-note


2

Niezmienne klasy to takie, których obiektów nie można zmienić po utworzeniu.

Niezmienne klasy są przydatne dla

  • Cel buforowania
  • Środowisko współbieżne (ThreadSafe)
  • Trudne do dziedziczenia
  • Wartości nie można zmienić w żadnym środowisku

Przykład

Klasa ciągów

Przykład kodu

public final class Student {
    private final String name;
    private final String rollNumber;

    public Student(String name, String rollNumber) {
        this.name = name;
        this.rollNumber = rollNumber;
    }

    public String getName() {
        return this.name;
    }

    public String getRollNumber() {
        return this.rollNumber;
    }
}

2

W jaki sposób można uczynić niezmienną klasę Javy?

Z JDK 14+, który ma JEP 359 , możemy użyć " records". Jest to najprostszy i bezproblemowy sposób tworzenia klasy Immutable.

Klasa rekordu to płytko niezmienny , przezroczysty nośnik dla ustalonego zestawu pól, zwanego rekordem, componentsktóry zawiera stateopis rekordu. Każda z nich componentdaje początek finalpolu, które przechowuje podaną wartość i accessormetodę pobierania wartości. Nazwa pola i nazwa akcesorium są zgodne z nazwą komponentu.

Rozważmy przykład tworzenia niezmiennego prostokąta

record Rectangle(double length, double width) {}

Nie ma potrzeby deklarowania żadnego konstruktora, nie ma potrzeby implementowania metod equals i hashCode. Dowolne rekordy wymagają nazwy i opisu stanu.

var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1

Jeśli chcesz sprawdzić poprawność wartości podczas tworzenia obiektu, musimy jawnie zadeklarować konstruktora.

public Rectangle {

    if (length <= 0.0) {
      throw new IllegalArgumentException();
    }
  }

Treść rekordu może deklarować metody statyczne, pola statyczne, inicjatory statyczne, konstruktory, metody wystąpień i typy zagnieżdżone.

Metody instancji

record Rectangle(double length, double width) {

  public double area() {
    return this.length * this.width;
  }
}

pola statyczne, metody

Ponieważ stan powinien być częścią komponentów, nie możemy dodawać pól instancji do rekordów. Ale możemy dodać statyczne pola i metody:

record Rectangle(double length, double width) {

  static double aStaticField;

  static void aStaticMethod() {
    System.out.println("Hello Static");
  }
}

jaka jest potrzeba niezmienności i czy jest jakaś korzyść z jej używania?

Wcześniej opublikowane odpowiedzi są wystarczająco dobre, aby uzasadnić potrzebę niezmienności i są to zalety


0

Innym sposobem na stworzenie niezmiennego obiektu jest użycie biblioteki Immutables.org :

Zakładając, że zostały dodane wymagane zależności, utwórz klasę abstrakcyjną z abstrakcyjnymi metodami dostępu. Możesz zrobić to samo, dodając adnotacje za pomocą interfejsów, a nawet adnotacji (@interface):

package info.sample;

import java.util.List;
import java.util.Set;
import org.immutables.value.Value;

@Value.Immutable
public abstract class FoobarValue {
  public abstract int foo();
  public abstract String bar();
  public abstract List<Integer> buz();
  public abstract Set<Long> crux();
}

Teraz można wygenerować, a następnie użyć wygenerowanej niezmiennej implementacji:

package info.sample;

import java.util.List;

public class FoobarValueMain {
  public static void main(String... args) {
    FoobarValue value = ImmutableFoobarValue.builder()
        .foo(2)
        .bar("Bar")
        .addBuz(1, 3, 4)
        .build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}

    int foo = value.foo(); // 2

    List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
  }
}

0

Niezmienna klasa to po prostu klasa, której instancji nie można modyfikować.

Wszystkie informacje zawarte w każdej instancji są ustalone przez cały okres istnienia obiektu, więc nie można zaobserwować żadnych zmian.

Niezmienne klasy są łatwiejsze do projektowania, implementowania i używania niż klasy mutowalne.

Aby uczynić klasę niezmienną, przestrzegaj pięciu zasad:

  1. Nie podawaj metod modyfikujących stan obiektu

  2. Upewnij się, że klasa nie może zostać przedłużona.

  3. Spraw, aby wszystkie pola były ostateczne.

  4. Ustaw wszystkie pola jako prywatne.

  5. Zapewnij wyłączny dostęp do wszelkich zmiennych komponentów.

Niezmienne obiekty są z natury bezpieczne dla wątków; nie wymagają synchronizacji.

Niezmienne obiekty można swobodnie udostępniać.

Niezmienne obiekty są świetnymi cegiełkami do budowania innych obiektów


0

@Jack, Posiadanie końcowych pól i seterów w klasie nie spowoduje, że klasa będzie niezmienna. końcowe słowo kluczowe zapewnia tylko, że zmienna nigdy nie zostanie ponownie przypisana. Musisz zwrócić głęboką kopię wszystkich pól w metodach pobierających. Zapewni to, że po pobraniu obiektu z metody getter stan wewnętrzny obiektu nie zostanie zakłócony.


-1

Jako osoba, dla której angielski nie jest językiem ojczystym, nie podoba mi się powszechna interpretacja istoty „niezmiennej klasy”, „skonstruowane obiekty klas są niezmienne”; raczej skłaniam się do interpretowania tego jako „sam obiekt klasy jest niezmienny”.

To powiedziawszy, „niezmienna klasa” jest rodzajem niezmiennego obiektu. Różnica polega na udzieleniu odpowiedzi na pytanie, jakie są korzyści. Zgodnie z moją wiedzą / interpretacją, niezmienna klasa zapobiega modyfikacjom zachowania swoich obiektów w czasie wykonywania.


-1

Większość odpowiedzi tutaj jest dobrych, a niektórzy wspominali o zasadach, ale czuję, że dobrze jest opisać słowami, dlaczego i kiedy musimy ich przestrzegać. Więc podaję poniższe wyjaśnienie

  • Zadeklaruj zmienne składowe jako „końcowe” - kiedy deklarujemy je jako końcowe, kompilator zmusza nas do ich zainicjowania. Możemy zainicjować bezpośrednio, domyślnie konstruktor, przez konstruktor arg. (Zobacz przykładowy kod poniżej) i po inicjalizacji nie możemy ich modyfikować, ponieważ są ostateczne.
  • I oczywiście, jeśli spróbujemy użyć Setterów dla tych końcowych zmiennych, kompilator zgłosi błąd.

    public class ImmutableClassExplored {
    
        public final int a; 
        public final int b;
    
        /* OR  
        Generally we declare all properties as private, but declaring them as public 
        will not cause any issues in our scenario if we make them final     
        public final int a = 109;
        public final int b = 189;
    
         */
        ImmutableClassExplored(){
            this. a = 111;
            this.b = 222;
        }
    
        ImmutableClassExplored(int a, int b){
            this.a = a;
            this.b= b;
        }
    }
    

Czy musimy zadeklarować klasę jako „ostateczną”?

  • Bez końcowego słowa kluczowego w deklaracji klasy klasa może być dziedziczona. Więc podklasa może przesłonić metody pobierające. Tutaj musimy wziąć pod uwagę dwa scenariusze:

1. Posiadanie tylko prymitywów: nie mamy problemu Jeśli klasa ma tylko prymitywne składowe, to nie musimy deklarować klasy jako ostatecznej.

2.Mając obiekty jako zmienne składowe: Jeśli mamy obiekty jako zmienne składowe, to musimy uczynić składowe tych obiektów również ostatecznymi. Oznacza to, że musimy przejść głęboko w drzewo i uczynić wszystkie obiekty / prymitywy ostatecznymi, co może nie być możliwe za każdym razem. Tak więc obejściem jest uczynienie klasy ostateczną, co zapobiega dziedziczeniu. Nie ma więc kwestii przesłaniania metod pobierających przez podklasy.


-1

Adnotacja @Value Lombok może służyć do generowania niezmiennych klas. To tak proste, jak poniższy kod.

@Value
public class LombokImmutable {
    int id;
    String name;
}

Zgodnie z dokumentacją na stronie Lombok:

@Value jest niezmiennym wariantem @Data; wszystkie pola są domyślnie ustawiane jako prywatne i ostateczne, a metody ustawiające nie są generowane. Sama klasa jest również domyślnie określana jako ostateczna, ponieważ niezmienność nie jest czymś, co można wymusić na podklasie. Podobnie jak @Data, generowane są również przydatne metody toString (), equals () i hashCode (), każde pole otrzymuje metodę pobierającą, a także generowany jest konstruktor obejmujący każdy argument (z wyjątkiem pól końcowych, które są inicjowane w deklaracji pola) .

W pełni działający przykład można znaleźć tutaj.


Przed udzieleniem odpowiedzi na stare pytanie z zaakceptowaną odpowiedzią (poszukaj zielonego ✓) oraz innymi odpowiedziami, upewnij się, że Twoja odpowiedź dodaje coś nowego lub jest w związku z nimi pomocna. Proszę uważać, odpowiadając na pytanie OP. Jak można uczynić niezmienną klasę Javy, jaka jest potrzeba niezmienności i czy jest jakaś korzyść z jej używania? . - Dajesz tylko częściową odpowiedź, która po prostu stwierdza, że ​​można używać Lombok, platformy / biblioteki innej firmy, co niekoniecznie jest tym, o co chodzi w pytaniu OP. Wydano również Java 15, po co używać Lombok, skoro recordmożna używać Javy ?
Ivo Mori

Podaję jedną z opcji, jak to osiągnąć. Nie wszyscy używają Javy 15, większość dzisiejszych aplikacji działa na poprzednich wersjach, więc mówienie, dlaczego korzystanie z Lombok nie ma większego sensu. W moim obecnym projekcie niedawno przeprowadziliśmy migrację do języka Java 11 i używamy Lombok do tworzenia niezmiennych klas. Moja odpowiedź jest również uzupełnieniem odpowiedzi, które były już obecne. Odpowiedź na to, co jest niezmienne, została już udzielona, ​​myślę, że spodziewasz się, że przepiszę to tylko dla ukończenia.
Anubhav

Słusznie. Ani w dół, ani w górę nie głosowałem. Próbowałem wskazać powody, dla których dwie inne osoby odrzucały tę odpowiedź. To od Ciebie zależy, czy i jak chcesz edytować swoją odpowiedź.
Ivo Mori
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.