CMB intervjuer: pričajmo o Java ključnoj reči transient
Hajde, Xiao'er je najviše u Javi, ali mnoge osnove Jave ne zna — na primer, ključnu reč transient ranije nikad nije koristio, pa nije znao čemu služi. Danas je na intervjuu u CMB intervjuer upravo to i pitao: pričajmo o Java ključnoj reči transient, a Xiao'er je potpuno ostao bez teksta.
Ispod je rezime koji je sam napravio nakon što je pao na intervjuu; delim ga sa vama da svi zajedno nešto naučimo ~~~ u redu, bez daljih priča, krećemo:
01. Uloga i način upotrebe ključne reči transient
Znamo da objekat, čim implementira interfejs Serilizable, može biti serijalizovan.
U stvarnom razvoju često nailazimo na sledeći problem: neka polja klase treba serijalizovati, a neka ne — na primer, osetljive korisničke informacije (kao što su lozinka, broj bankovne kartice i sl.) iz bezbednosnih razloga ne želimo da prenosimo u mrežnim operacijama niti da ih trajno upisujemo u fajlove na disku. Takva polja možemo obeležiti ključnom rečju transient.
Važno je napomenuti da će člansku promenljivu obeleženu ključnom reči transient pri deserijalizaciji sistem automatski inicijalizovati na podrazumevanu vrednost — na primer, primitivni tipovi na 0, a referentni tipovi na null.
Pogledajmo primer:
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("Chenmo Wang Er");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("user.txt"));
os.writeObject(user); // Upisuje User objekat u fajl
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"user.txt"));
user = (User) is.readObject(); // Čita podatke User-a iz toka
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
private String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}Izlaz je:
read before Serializable:
username: Chenmo Wang Er
password: 123456
read after Serializable:
username: Chenmo Wang Er
password: nullPolje lozinke je null, što znači da pri deserijalizaciji ta informacija uopšte nije pročitana iz fajla.
02. Kratak osvrt na upotrebu ključne reči transient
Čim je polje obeleženo sa transient, ta članska promenljiva više nije deo trajnog stanja objekta; vrednost te promenljive ne može joj se pristupiti nakon serijalizacije.
Ključna reč transient može obeležiti samo polja, a ne i metode i klase.
Polja obeležena ključnom reči transient ne mogu se serijalizovati; statička promenljiva (obeležena ključnom rečju static) se, bez obzira na to da li je obeležena sa transient ili ne, ne može serijalizovati, kao što smo već ranije spomenuli.
Pogledajmo primer:
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("Chenmo Wang Er");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("user.txt"));
os.writeObject(user); // Upisuje User objekat u fajl
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
// Pre deserijalizacije izmeni vrednost username-a
User.username = "Chenmo Wang San";
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"user.txt"));
user = (User) is.readObject(); // Čita podatke User-a iz toka
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
public static String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}Rezultat izvršavanja je:
read before Serializable:
username: Chenmo Wang Er
password: 123456
read after Serializable:
username: Chenmo Wang San
password: nullPre serijalizacije, statičko polje username bilo je Chenmo Wang Er, a zatim smo ga pre deserijalizacije izmenili u Chenmo Wang San. Ako statička polja mogu da očuvaju stanje, nakon deserijalizacije bi trebalo da bude Chenmo Wang Er, zar ne?
Ali rezultat je Chenmo Wang San, što dokazuje naš raniji zaključak: polja obeležena sa static ne mogu se serijalizovati.
03. Da li polja obeležena sa transient zaista ne mogu da se serijalizuju?
Razmotrite sledeći primer:
public class ExternalizableTest implements Externalizable {
private transient String content = "Da, biću serijalizovan, bez obzira na to da li sam obeležen ključnom reči transient";
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(content);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
content = (String) in.readObject();
}
public static void main(String[] args) throws Exception {
ExternalizableTest et = new ExternalizableTest();
ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
new File("test")));
out.writeObject(et);
ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
"test")));
et = (ExternalizableTest) in.readObject();
System.out.println(et.content);
out.close();
in.close();
}
}Pogledajmo izlaz:
Da, biću serijalizovan, bez obzira na to da li sam obeležen ključnom reči transientZašto je to tako? Zar nije rečeno da polja obeležena ključnom reči transient ne mogu da se serijalizuju?
Reći ću najpre zaključak: to je zato što smo koristili interfejs Externalizable umesto Serializable — ovu temu smo već ranije pokrili.
U Javi se serijalizacija objekata može postići implementacijom jednog od dva interfejsa: ako se implementira Serializable, serijalizacija se odvija potpuno automatski; ako se implementira Externalizable, potrebno je u metodi writeExternal eksplicitno navesti koja polja se serijalizuju, što nema veze sa ključnom reči transient.
Zato primer ispisuje sadržaj promenljive content, a ne null.
04. Rezime
Ključna reč transient služi za obeležavanje članskih promenljivih klase; pri serijalizaciji objekta, obeležena članska promenljiva se ne serijalizuje i ne snima u fajl. Njena uloga je da saopšti JVM-u da pri serijalizaciji objekta ne mora da trajno upisuje vrednost te promenljive, čime se izbegavaju određeni bezbednosni ili performansni problemi. Međutim, članska promenljiva obeležena sa transient će pri deserijalizaciji biti inicijalizovana na svoju podrazumevanu vrednost (na primer, tip int na 0, referentni tip na null), pa je u programu potrebno obezbediti odgovarajuću obradu.
I ključna reč transient i ključna reč static mogu obeležavati članske promenljive klase. Ključna reč transient znači da ta članska promenljiva ne učestvuje u serijalizaciji i deserijalizaciji, dok ključna reč static znači da ta članska promenljiva pripada klasi, a ne objektu, pa prema tome ne zahteva serijalizaciju ni deserijalizaciju.
U interfejsima Serializable i Externalizable ponašanje ključne reči transient je drugačije: u Serializable-u ona znači da članska promenljiva ne učestvuje u serijalizaciji i deserijalizaciji, dok u Externalizable-u ne daje nikakav efekt, jer interfejs Externalizable zahteva implementaciju metoda readExternal i writeExternal, pri čemu se serijalizacija i deserijalizacija obavljaju ručno.
