Czytałem, że aby uczynić klasę niezmienną w Javie, powinniśmy wykonać następujące czynności:
- Nie dostarczaj żadnych ustawiaczy
- Oznacz wszystkie pola jako prywatne
- Doprowadź zajęcia do finału
Dlaczego wymagany jest krok 3? Dlaczego mam oznaczyć zajęcia final
?
java
immutability
final
Anand
źródło
źródło
java.math.BigInteger
class jest przykładem, jego wartości są niezmienne, ale nie są ostateczne.BigInteger
, nie wiesz, czy jest niezmienny, czy nie. To pomieszany projekt. /java.io.File
jest bardziej interesującym przykładem.File
jest interesujący, ponieważ można mu ufać, że jest niezmienny, a zatem prowadzi do ataków TOCTOU (Time Of Check / Time Of Use). Zaufany kod sprawdza, czyFile
ścieżka ma akceptowalną ścieżkę, a następnie używa ścieżki. PodklasaFile
może zmieniać wartość między sprawdzeniem a użyciem.Make the class final
lub Upewnij się, że wszystkie podklasy są niezmienneOdpowiedzi:
Jeśli nie zaznaczysz klasy
final
, może być możliwe, że nagle zmienię twoją pozornie niezmienną klasę na zmienną. Na przykład rozważ ten kod:public class Immutable { private final int value; public Immutable(int value) { this.value = value; } public int getValue() { return value; } }
Teraz załóżmy, że wykonam następujące czynności:
public class Mutable extends Immutable { private int realValue; public Mutable(int value) { super(value); realValue = value; } public int getValue() { return realValue; } public void setValue(int newValue) { realValue = newValue; } public static void main(String[] arg){ Mutable obj = new Mutable(4); Immutable immObj = (Immutable)obj; System.out.println(immObj.getValue()); obj.setValue(8); System.out.println(immObj.getValue()); } }
Zauważ, że w mojej
Mutable
podklasie nadpisałem zachowanie,getValue
aby odczytać nowe, zmienne pole zadeklarowane w mojej podklasie. W rezultacie Twoja klasa, która początkowo wygląda na niezmienną, w rzeczywistości nie jest niezmienna. Mogę przekazać tenMutable
obiekt wszędzie tam, gdzieImmutable
oczekiwany jest obiekt, co może spowodować bardzo złe rzeczy w kodzie, zakładając, że obiekt jest naprawdę niezmienny. Oznaczenie klasy bazowejfinal
zapobiega temu.Mam nadzieję że to pomoże!
źródło
Immutable
argument. Mogę przekazaćMutable
obiekt do tej funkcji, ponieważMutable extends Immutable
. Wewnątrz tej funkcji, podczas gdy myślisz, że twój obiekt jest niezmienny, mógłbym mieć dodatkowy wątek, który zmienia wartość w trakcie działania funkcji. Mógłbym również podaćMutable
obiekt, który funkcja przechowuje, a następnie zmienić zewnętrznie jego wartość. Innymi słowy, jeśli twoja funkcja zakłada, że wartość jest niezmienna, może łatwo się zepsuć, ponieważ mógłbym dać ci zmienny obiekt i zmienić go później. Czy to ma sens?Mutable
obiekt, nie zmieni obiektu. Problem polega na tym, że ta metoda może zakładać, że obiekt jest niezmienny, podczas gdy tak naprawdę nie jest. Na przykład metoda może zakładać, że ponieważ uważa, że obiekt jest niezmienny, może być używany jako klucz w plikuHashMap
. Mógłbym wtedy przerwać tę funkcję, przekazując aMutable
, czekając, aż zapisze obiekt jako klucz, a następnie zmieniającMutable
obiekt. Teraz wyszukiwanie w tymHashMap
nie powiedzie się, ponieważ zmieniłem klucz powiązany z obiektem. Czy to ma sens?Wbrew temu, w co wielu ludzi sądzi, tworzenie niezmiennej klasy nie
final
jest wymagane.Standardowym argumentem przemawiającym za tworzeniem niezmiennych klas
final
jest to, że jeśli tego nie zrobisz, podklasy mogą dodać zmienność, naruszając w ten sposób kontrakt nadklasy. Klienci klasy przyjmą niezmienność, ale będą zaskoczeni, gdy coś spod nich wyskoczy.Jeśli doprowadzisz ten argument do jego logicznego ekstremum, należy zastosować wszystkie metody
final
, ponieważ w przeciwnym razie podklasa mogłaby przesłonić metodę w sposób, który nie jest zgodny z kontraktem jej nadklasy. To interesujące, że większość programistów Javy uważa to za śmieszne, ale w jakiś sposób zgadza się z ideą, że niezmienne klasy powinny byćfinal
. Podejrzewam, że ma to coś wspólnego z tym, że generalnie programiści Java nie są do końca zadowoleni z pojęcia niezmienności i być może z jakimś rozmytym myśleniem związanym z wieloma znaczeniamifinal
słowa kluczowego w Javie.Zgodność z umową twojej superklasy nie jest czymś, co może lub powinno być zawsze wymuszane przez kompilator. Kompilator może wymusić pewne aspekty kontraktu (np .: minimalny zestaw metod i ich sygnatury typów), ale istnieje wiele części typowych kontraktów, których kompilator nie może wymusić.
Niezmienność jest częścią kontraktu klasy. To trochę różni się od niektórych rzeczy, do których ludzie są bardziej przyzwyczajeni, ponieważ mówi, czego klasa (i wszystkie podklasy) nie mogą zrobić, podczas gdy myślę, że większość programistów Java (i ogólnie OOP) ma tendencję do myślenia o kontraktach jako odnoszących się do co klasa może zrobić, a nie czego nie może zrobić.
Niezmienność ma również wpływ nie tylko na pojedynczą metodę - ma wpływ na całą instancję - ale nie różni się to zbytnio od sposobu
equals
ihashCode
działania języka Java. Te dwie metody mają określoną umowę zawartą wObject
. Ta umowa bardzo dokładnie określa rzeczy, których te metody nie mogą zrobić. Ten kontrakt jest bardziej szczegółowy w podklasach. Bardzo łatwo jest to uchylićequals
lubhashCode
w sposób naruszający umowę. W rzeczywistości, jeśli zastąpisz tylko jedną z tych dwóch metod bez drugiej, istnieje prawdopodobieństwo, że naruszysz umowę. Więc należyequals
ihashCode
zostały uznanefinal
wObject
tego uniknąć? Myślę, że większość argumentowałaby, że nie powinni. Podobnie nie jest konieczne tworzenie niezmiennych klasfinal
.To powiedziawszy, większość twoich zajęć, niezmienna lub nie, prawdopodobnie powinna być
final
. Zobacz Effective Java Second Edition, pozycja 17: „Projektowanie i dokumentowanie do dziedziczenia albo zabronienie”.Tak więc poprawna wersja kroku 3 brzmiałaby: „Uczyń klasę ostateczną lub, projektując podklasę, jasno udokumentuj, że wszystkie podklasy muszą być niezmienne”.
źródło
X
iY
wartośćX.equals(Y)
będzie niezmienna (o ileX
iY
nadal będzie się odnosić do tych samych obiektów). Ma podobne oczekiwania co do kodów skrótów. Jest jasne, że nikt nie powinien oczekiwać, że kompilator wymusi niezmienność relacji równoważności i kodów skrótu (ponieważ po prostu nie może). Nie widzę powodu, dla którego ludzie mieliby oczekiwać, że będzie to egzekwowane w innych aspektach tego typu.double
. Można wyprowadzić a,GeneralImmutableMatrix
które wykorzystuje tablicę jako magazyn zapasowy, ale można również mieć np.ImmutableDiagonalMatrix
Który po prostu przechowuje tablicę elementów wzdłuż przekątnej (odczytanie elementu X, Y dałoby Arr [X], jeśli X == y i zero w przeciwnym razie) .final
ogranicza jej użyteczność, zwłaszcza podczas projektowania interfejsu API, który ma zostać rozszerzony. W interesie bezpieczeństwa wątków sensowne jest uczynienie klasy tak niezmiennej, jak to tylko możliwe, ale utrzymywanie jej rozszerzalności. Możesz utworzyć polaprotected final
zamiastprivate final
. Następnie wyraźnie ustal kontrakt (w dokumentacji) o podklasach zgodnych z gwarancjami niezmienności i bezpieczeństwa wątków.final
. To, żeequals
ihashcode
nie może być ostateczne, nie oznacza, że mogę tolerować to samo dla niezmiennej klasy, chyba że mam do tego ważny powód, aw tym przypadku mogę użyć dokumentacji lub testów jako linii obrony.Nie oceniaj całej klasy jako finału.
Istnieją uzasadnione powody, dla których zezwala się na przedłużenie niezmiennej klasy, jak stwierdzono w niektórych innych odpowiedziach, więc oznaczanie klasy jako ostatecznej nie zawsze jest dobrym pomysłem.
Lepiej jest oznaczyć swoje nieruchomości jako prywatne i ostateczne, a jeśli chcesz chronić „kontrakt”, oznacz swoje gettery jako ostateczne.
W ten sposób możesz pozwolić na rozszerzenie klasy (tak, być może nawet o zmienną klasę), jednak niezmienne aspekty twojej klasy są chronione. Właściwości są prywatne i nie można uzyskać do nich dostępu, metody pobierające te właściwości są ostateczne i nie można ich zastąpić.
Każdy inny kod, który używa instancji Twojej niezmiennej klasy, będzie mógł polegać na niezmiennych aspektach Twojej klasy, nawet jeśli przekazywana podklasa jest zmienna w innych aspektach. Oczywiście, ponieważ wymaga instancji Twojej klasy, nie wiedziałby nawet o innych aspektach.
źródło
Jeśli nie jest to ostateczne, każdy może rozszerzyć klasę i robić, co mu się podoba, na przykład dostarczać setery, cieniać prywatne zmienne i zasadniczo zmieniać je.
źródło
To ogranicza inne klasy rozszerzające twoją klasę.
klasa końcowa nie może być przedłużona o inne zajęcia.
Jeśli klasa rozszerza klasę, którą chcesz uczynić jako niezmienną, może zmienić stan klasy ze względu na zasady dziedziczenia.
Po prostu wyjaśnij, że „może się zmienić”. Podklasa może nadpisywać zachowanie nadklasy, takie jak używanie zastępowania metod (np. Odpowiedź templatetypedef / Ted Hop)
źródło
final
, nie widzę, jak ta odpowiedź pomaga.Jeśli nie sprawisz, że będzie ostateczna, mogę ją przedłużyć i uczynić niezmienną.
public class Immutable { privat final int val; public Immutable(int val) { this.val = val; } public int getVal() { return val; } } public class FakeImmutable extends Immutable { privat int val2; public FakeImmutable(int val) { super(val); } public int getVal() { return val2; } public void setVal(int val2) { this.val2 = val2; } }
Teraz mogę przekazać FakeImmutable do dowolnej klasy, która oczekuje Immutable i nie będzie zachowywać się zgodnie z oczekiwanym kontraktem.
źródło
Aby utworzyć niezmienną klasę, oznaczanie jej jako ostatecznej nie jest obowiązkowe.
Weźmy jeden z takich przykładów z samych klas java. Klasa „BigInteger” jest niezmienna, ale nie jest ostateczna.
Właściwie niezmienność to pojęcie, zgodnie z którym obiekt utworzył, a następnie nie można go modyfikować.
Pomyślmy z punktu widzenia JVM, z punktu widzenia JVM wszystkie wątki muszą dzielić tę samą kopię obiektu i jest ona w pełni skonstruowana, zanim jakikolwiek wątek uzyska do niego dostęp, a stan obiektu nie zmienia się po jego skonstruowaniu.
Niezmienność oznacza, że nie ma możliwości zmiany stanu obiektu po jego utworzeniu i jest to osiągane za pomocą trzech reguł kciuka, które sprawiają, że kompilator rozpoznaje, że klasa jest niezmienna i są one następujące: -
Więcej informacji można znaleźć pod poniższym adresem URL
http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html
źródło
Powiedzmy, że masz następującą klasę:
import java.util.ArrayList; import java.util.Date; import java.util.List; public class PaymentImmutable { private final Long id; private final List<String> details; private final Date paymentDate; private final String notes; public PaymentImmutable (Long id, List<String> details, Date paymentDate, String notes) { this.id = id; this.notes = notes; this.paymentDate = paymentDate == null ? null : new Date(paymentDate.getTime()); if (details != null) { this.details = new ArrayList<String>(); for(String d : details) { this.details.add(d); } } else { this.details = null; } } public Long getId() { return this.id; } public List<String> getDetails() { if(this.details != null) { List<String> detailsForOutside = new ArrayList<String>(); for(String d: this.details) { detailsForOutside.add(d); } return detailsForOutside; } else { return null; } } }
Następnie przedłużasz go i łamiesz jego niezmienność.
public class PaymentChild extends PaymentImmutable { private List<String> temp; public PaymentChild(Long id, List<String> details, Date paymentDate, String notes) { super(id, details, paymentDate, notes); this.temp = details; } @Override public List<String> getDetails() { return temp; } }
Tutaj to testujemy:
public class Demo { public static void main(String[] args) { List<String> details = new ArrayList<>(); details.add("a"); details.add("b"); PaymentImmutable immutableParent = new PaymentImmutable(1L, details, new Date(), "notes"); PaymentImmutable notImmutableChild = new PaymentChild(1L, details, new Date(), "notes"); details.add("some value"); System.out.println(immutableParent.getDetails()); System.out.println(notImmutableChild.getDetails()); } }
Wynik wyjściowy będzie:
Jak widać, podczas gdy oryginalna klasa zachowuje swoją niezmienność, klasy potomne mogą być modyfikowalne. W związku z tym w swoim projekcie nie możesz być pewien, że obiekt, którego używasz, jest niezmienny, chyba że sprawisz, że klasa będzie ostateczna.
źródło
Załóżmy, że następujące klasy nie były
final
:public class Foo { private int mThing; public Foo(int thing) { mThing = thing; } public int doSomething() { /* doesn't change mThing */ } }
Najwyraźniej jest niezmienny, ponieważ nawet podklasy nie mogą modyfikować
mThing
. Jednak podklasa może być zmienna:public class Bar extends Foo { private int mValue; public Bar(int thing, int value) { super(thing); mValue = value; } public int getValue() { return mValue; } public void setValue(int value) { mValue = value; } }
Teraz obiekt, który można przypisać do zmiennej typu,
Foo
nie jest już możliwy do zmodyfikowania. Może to powodować problemy z takimi rzeczami, jak haszowanie, równość, współbieżność itp.źródło
Sam projekt nie ma wartości. Projekt jest zawsze używany do osiągnięcia celu. Jaki jest tutaj cel? Czy chcemy zmniejszyć ilość niespodzianek w kodzie? Czy chcemy zapobiegać błędom? Czy ślepo przestrzegamy zasad?
Ponadto projekt zawsze wiąże się z kosztami. Każdy projekt, który zasługuje na miano, oznacza konflikt celów .
Mając to na uwadze, musisz znaleźć odpowiedzi na następujące pytania:
Załóżmy, że masz w swoim zespole wielu młodszych programistów. Będą desperacko próbować każdej głupoty tylko dlatego, że nie znają jeszcze dobrych rozwiązań swoich problemów. Ostateczna wersja klasy może zapobiec błędom (dobrze), ale może również spowodować, że znajdą "sprytne" rozwiązania, takie jak kopiowanie wszystkich tych klas do zmiennych, które można zmieniać w każdym miejscu kodu.
Z drugiej strony bardzo trudno będzie utworzyć klasę,
final
gdy będzie używana wszędzie, ale łatwo jest uczynićfinal
klasę inną niżfinal
później, jeśli okaże się, że musisz ją przedłużyć.Jeśli właściwie używasz interfejsów, możesz uniknąć problemu „muszę uczynić ten mutowalny”, zawsze używając interfejsu, a następnie dodając modyfikowalną implementację, gdy zajdzie taka potrzeba.
Wniosek: nie ma „najlepszego” rozwiązania dla tej odpowiedzi. To zależy od tego, jaką cenę chcesz, a którą musisz zapłacić.
źródło
Domyślne znaczenie equals () jest takie samo jak równość referencyjna. W przypadku niezmiennych typów danych jest to prawie zawsze błędne. Musisz więc nadpisać metodę equals (), zastępując ją własną implementacją. połączyć
źródło