Razgovor o nepromenljivim objektima u Javi
„Erge, možeš mi reći zašto je String immutable klasa (nepromenljivi objekat)? Želim da to istražujem, želim da znam zašto je nepromenljiva, ovako snažna želja je kao išao da istražujem beskrajne zvezde. Ali nažalost, moja moć je ograničena, uvek se osećam da gledam kroz maglu.“ Sanmejina rečenica je puna laskanja.
„Budući da Sanmej tako lepo govoriš, počnimo!“ Moje veselje je kao da sam jeo dve tegle meda (😂).
01, Što je nepromenljiva klasa
Ako se stanje objekta klase neće više menjati nakon kreiranja kroz konstruktor, onda je to nepromenljiva (immutable) klasa. Dodeljivanje svih članskih promenljivih vrši se samo u konstruktoru, neće pružati nijednu setter metodu za spoljašnje klase da modifikuju.
Još uvek se sećate groba Xiaolongnü u „Return of the Condor Heroes“? Sa tim ogromnim zvukom, jedini prolaz je nemilosrdno zatvoren. Ne pretvaraj se da je taj tajni prolaz, ovako kažem samo da otvorim vašu maštu, da vam dam intuitivniji utisak o nepromenljivoj klasi.
Otkako postoji multithreading, produktivnost je beskonačno uvećana, svi programeri je vole, budući da su moćne hardverske sposobnosti potpuno iskorišćene. Ali u isto vreme, svi programeri se je plaše, budući da ako malo pazi, multithreading će stanje objekta učiniti totalnim haosom.
Da bismo zaštitili atomičnost, vidljivost i uređenost stanja, mi programeri smo dali sve od sebe. Od toga, synchronized (sinhronizacija) ključna reč je najjednostavnije i najosnovnije rešenje.
Ako je klasa nepromenljiva, onda je stanje objekta takođe nepromenljivo. U tom slučaju, svaka izmena stanja objekta će proizvesti novi objekat za različite niti da koriste, mi programeri više ne moramo da brinemo o concurrency problemima.
02, Uobičajene nepromenljive klase
Kada se govori o nepromenljivim klasama, skoro svi programeri prvo misle na String klasu. Zašto je String klasa dizajnirana da bude nepromenljiva?
1) Potreba za konstantnim bazenom
Constantni bazen znakovnih nizova je specijalna oblast skladištenja u Java heap memoriji, kada se kreira String objekat, ako ovaj znakovni niz ne postoji u konstantnom bazenu, onda se kreira; ako već postoji, neće se kreirati, već će direktno referencirati postojeći objekat. Ovo može smanjiti memorijske troškove JVM-a, poboljšati efikasnost.
2) Potreba za hashCode
Budući da su znakovni nizovi nepromenljivi, prilikom kreiranja, njegov hashCode je keširan, stoga je veoma pogodan kao heš vrednost (na primer kao ključ HashMap-a), višestruko pozivanje vraća istu vrednost, poboljšavajući efikasnost.
3) Sigurnost niti
Kao što ranije rekosmo, ako je stanje objekta promenljivo, onda u multithreading okruženju, lako može dovesti do neočekivanih rezultata. A String je nepromenljiv, može se deliti između više niti, ne treba sinhronizacija.
Stego, kada pozivamo bilo koju metodu String klase (na primer trim(), substring(), toLowerCase()), uvek će vratiti novi objekat, neće uticati na prethodnu vrednost.
String cmower = "Chenmo Wang Er, jedan zanimljiv programer";
cmower.substring(0,4);
System.out.println(cmower);// Chenmo Wang Er, jedan zanimljiv programerIako je pozvana substring() metoda za isecanje cmower-a, vrednost cmower se nije promenila.
Pored String klase, wrapper klase Integer, Long itd. su takođe nepromenljive klase.
03, Ručno napiši nepromenljivu klasu
Možda je lako razumeti nepromenljivu klasu, ali kreiranje prilagođene nepromenljive klase možda je malo teže. Ali napredovanje prema teškoćama je neophodan kvalitet odličnog programera, upravo zato što nije lako, možemo je zaista savladati.
Sledeće, dozvolite mi da vam se pridružim da napravimo prilagođenu nepromenljivu klasu. Jedna nepromenljiva klasa mora zadovoljiti sledeća 4 uslova:
1) Osigurajte da je klasa final, ne dozvoljava da je druge klase nasleđuju*.
2) Osigurajte da su sve članske promenljive (polja) final, time mogu inicijalizovati vrednosti samo u konstruktoru, i neće se kasnije menjati.
3) Ne pružajte nijednu setter metodu.
4) Ako treba izmeniti stanje klase, mora vratiti novi objekat.
Sledeći gore uslove, napravićemo jednostavnu nepromenljivu klasu Writer.
public final class Writer {
private final String name;
private final int age;
public Writer(String name, int age) {
this.name = name;
this.age = age;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
}Writer klasa je final, name i age su takođe final, nema setter metoda.
OK, kažu da ovaj autor podelio mnogo blogova, široko je voljen od strane čitalaca, stego je određena izdavačka kuća tražila da napiše knjigu (Book). Book klasa je ovako definisana:
public class Book {
private String name;
private int price;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
@Override
public String toString() {
return "Book{" +
"name='" + name + '\'' +
", price=" + price +
'}';
}
}2 polja, redom name i price, kao i getter i setter, prepisana toString() metoda. Zatim, u Writer klasi dodajemo polje promenljivog objekta book.
public final class Writer {
private final String name;
private final int age;
private final Book book;
public Writer(String name, int age, Book book) {
this.name = name;
this.age = age;
this.book = book;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public Book getBook() {
return book;
}
}I u konstruktor smo dodali Book parametar, i Book getter metodu.
Nakon završetka ovog rada, napravićemo novu test klasu, da vidimo da li je stanje Writer klase zaista nepromenljivo.
public class WriterDemo {
public static void main(String[] args) {
Book book = new Book();
book.setName("Ergeov napredni put za Javu");
book.setPrice(79);
Writer writer = new Writer("Chenmo Wang Er",18, book);
System.out.println("Cena: " + writer.getBook());
writer.getBook().setPrice(59);
System.out.println("Akcija cena: " + writer.getBook());
}
}Rezultat izlaza programa je sledeći:
Cena: Book{name='Ergeov napredni put za Javu', price=79}
Akcija cena: Book{name='Ergeov napredni put za Javu', price=59}Teško, nepromenljivost Writer klase je narušena, cena se promenila. Da bismo rešili ovaj problem, moramo dodati još jedan sadržaj pravilima definicije nepromenljive klase:
Ako nepromenljiva klasa sadrži objekat promenljive klase, onda treba osigurati da se vraća kopija promenljivog objekta. To jest, getBook() metoda u Writer klasi treba izmeniti u:
public Book getBook() {
Book clone = new Book();
clone.setPrice(this.book.getPrice());
clone.setName(this.book.getName());
return clone;
}Na taj način, Book objekat inicijalizovan u konstruktoru neće se više menjati. U ovom trenutku, pokrenite WriterDemo, i otkrićete da se cena više ne menja.
Cena: Book{name='Ergeov napredni put za Javu', price=79}
Akcija cena: Book{name='Ergeov napredni put za Javu', price=79}04, Zaključak
Nepromenljive klase imaju mnoge prednosti, kao što sam ranije spomenuo String klasa, naročito u multithreading okruženju, vrlo su sigurne. Iako svaka izmena kreira novi objekat, povećava memorijsku potrošnju, ali ovaj nedostatak u poređenju sa prednostima koje donosi, očigledno je zanemariv — zaista je da ste uzeli dinju, izgubili susam.
„Dobro, Sanmej, sada razumiješ?“
„Mmm, ge, tvoja ova knjiga „Napredni put za Javu“ još nije objavljena?“ Sanmej je sumnjičavo upitala.
„Ej, izdavačka kuća je već tražila da potpišemo ugovor, morao sam da se izgovaram da ću razmisliti kada GitHub doseže 10000 zvezdica, prvo optimizuj, posle gledam priliku.“
„Oh oh, iskako, tako je.“ Sanmej je olakšala.
