Duboko razumevanje try-with-resources u Javi
„Erge, konačno si počeo sa try-with-resources!” Sanmejin preterani izraz lica me je donekle iznenadio.
„Sanmej, ne uzbuđuj se! Pre nego što počnemo, moramo se podsetiti try-catch-finally, kao uvod,” rekoh. „Pogledaj ovaj kod.”
public class TrycatchfinallyDecoder {
public static void main(String[] args) {
BufferedReader br = null;
try {
String path = TrycatchfinallyDecoder.class.getResource("/moćno.txt").getFile();
String decodePath = URLDecoder.decode(path,"utf-8");
br = new BufferedReader(new FileReader(decodePath));
String str = null;
while ((str =br.readLine()) != null) {
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}„Da ti ukratko objasnim.” Kada je Sanmej pročitala kod, nastavio sam: „U try bloku čitamo sadržaj datoteke i liniju po liniju ga ispisujemo na konzolu. Ako datoteku ne možemo pronaći ili nastupi IO greška pri čitanju, u catch hvatamo izuzetak i ispisujemo stek greške. Konačno, u finally zatvaramo baferovani čitač znakova BufferedReader, čime se efikasno izbegavaju ozbiljni performansni problemi koje bi doneo nezatvoren resurs.”
„Pre Jave 7, try-catch-finally je zaista bio najbolji način da se osigura da će resursi biti pravovremeno zatvoreni, bez obzira na to da li program baca izuzetak.”
Sanmej je klonula glavom u znak slaganja.
„Međutim, ovaj kod je i dalje pomalo pretrpan, posebno deo u finally,” rekoh. „Osim toga, try-catch-finally od samog početka krije jednu ozbiljnu zamku: br.readLine() u try može baciti IOException, a br.close() u finally takođe može baciti IOException. Ako nesrećnim slučajem oba bace IOException, postaje komplikovano otkriti gde je greška nastala — treba uložiti trud, a to ne želimo.”
„Daću ti demonstraciju, Sanmej.”
„Prvo, da definišemo klasu MyfinallyReadLineThrow sa dva metoda: readLine() i close(), čija tela aktivno bacaju izuzetak.”
class MyfinallyReadLineThrow {
public void close() throws Exception {
throw new Exception("close");
}
public void readLine() throws Exception {
throw new Exception("readLine");
}
}„Zatim u main() metodi pozivamo metode readLine() i close() klase MyfinallyReadLineThrow koristeći try-catch-finally.”
public class TryfinallyCustomReadLineThrow {
public static void main(String[] args) throws Exception {
MyfinallyReadLineThrow myThrow = null;
try {
myThrow = new MyfinallyReadLineThrow();
myThrow.readLine();
} finally {
myThrow.close();
}
}
}Nakon pokretanja gorenavedenog koda, stek greške izgleda ovako:
Exception in thread "main" java.lang.Exception: close
at com.cmower.dzone.trycatchfinally.MyfinallyOutThrow.close(TryfinallyCustomOutThrow.java:17)
at com.cmower.dzone.trycatchfinally.TryfinallyCustomOutThrow.main(TryfinallyCustomOutThrow.java:10)„Vidiš li problem, Sanmej?”
„Ha? Informacija o izuzetku iz metoda readLine() progutala je stek informacija iz metoda close()!”
„Bravo, Sanmej, oštro oko; zaista, to nas može navesti na pogrešan trag da je cilj istraživanja close() umesto readLine() — iako je on takođe osumnjičeni.”
„Ali sa try-with-resources, ovaj problem se lako rešava. Jedini uslov je da resurs koji treba osloboditi (npr. BufferedReader) implementira interfejs AutoCloseable.”
try (BufferedReader br = new BufferedReader(new FileReader(decodePath));) {
String str = null;
while ((str =br.readLine()) != null) {
System.out.println(str);
}
} catch (IOException e) {
e.printStackTrace();
}„Vidiš, Sanmej, finally blok je nestao; umesto toga, resurs koji treba osloboditi pišemo u () iza try. Ako ima više resursa (BufferedReader i PrintWriter) za oslobađanje, mogu se dodati direktno u ().”
try (BufferedReader br = new BufferedReader(new FileReader(decodePath));
PrintWriter writer = new PrintWriter(new File(writePath))) {
String str = null;
while ((str =br.readLine()) != null) {
writer.print(str);
}
} catch (IOException e) {
e.printStackTrace();
}„Ako želite da oslobodite sopstveni resurs, samo ga naterajte da implementira interfejs AutoCloseable i pružite metod close().”
public class TrywithresourcesCustom {
public static void main(String[] args) {
try (MyResource resource = new MyResource();) {
} catch (Exception e) {
e.printStackTrace();
}
}
}
class MyResource implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("zatvaranje prilagođenog resursa");
}
}Pogledajmo izlaz nakon pokretanja koda:
zatvaranje prilagođenog resursa„Kao čarolija!” Sanmej je bila ushićena. „U try () samo napravimo novi MyResource objekat, ništa drugo, a close() metod se izvrši!”
„Želiš li da znaš zašto, Sanmej?”
„Naravno da želim.”
„Pogledaj dekompajlirani bajtkod.”
class MyResource implements AutoCloseable {
MyResource() {
}
public void close() throws Exception {
System.out.println("zatvaranje prilagođenog resursa");
}
}
public class TrywithresourcesCustom {
public TrywithresourcesCustom() {
}
public static void main(String[] args) {
try {
MyResource resource = new MyResource();
resource.close();
} catch (Exception var2) {
var2.printStackTrace();
}
}
}„Aha, eto kako. Kompilator je aktivno transformisao try-with-resources: u try poziva close() metod.”
„Tako je. Hajde da u klasu MyResourceOut dodamo još jedan metod out().”
class MyResourceOut implements AutoCloseable {
@Override
public void close() throws Exception {
System.out.println("zatvaranje prilagođenog resursa");
}
public void out() throws Exception{
System.out.println("Chenmo Wang Er, jedan zanimljiv programer");
}
}„Ovog puta u try pozovimo metod out().”
public class TrywithresourcesCustomOut {
public static void main(String[] args) {
try (MyResourceOut resource = new MyResourceOut();) {
resource.out();
} catch (Exception e) {
e.printStackTrace();
}
}
}„Ponovo pogledaj dekompajlirani bajtkod.”
public class TrywithresourcesCustomOut {
public TrywithresourcesCustomOut() {
}
public static void main(String[] args) {
try {
MyResourceOut resource = new MyResourceOut();
try {
resource.out();
} catch (Throwable var5) {
try {
resource.close();
} catch (Throwable var4) {
var5.addSuppressed(var4);
}
throw var5;
}
resource.close();
} catch (Exception var6) {
var6.printStackTrace();
}
}
}„Ovog puta, catch blok aktivno poziva resource.close(), i postoji jedna ključna linija: var5.addSuppressed(var4).”
„Čemu to služi?” upita Sanmej.
„Kada se izuzetak baci, drugi izuzeci koji su zbog njega potisnuti možda ne mogu normalno da se bace. Tada pomoću metoda addSuppressed() možemo zabeležiti te potisnute izuzetke; potisnuti izuzeci će se onda pojaviti u steku bačenog izuzetka, a može im se pristupiti pomoću metoda getSuppressed(). Prednost je što se nijedan izuzetak ne gubi, što olakšava debagovanje,” rekoh.
„Da li ti pada na pamet onaj raniji primer — u try-catch-finally, informacija o izuzetku iz readLine() progutala je stek iz close()? Sada, sa try-with-resources, hajde da vidimo da li će out(), koji je istog tipa kao readLine(), biti progutan od close().”
class MyResourceOutThrow implements AutoCloseable {
@Override
public void close() throws Exception {
throw new Exception("close()");
}
public void out() throws Exception{
throw new Exception("out()");
}
}„Pozovimo ova dva metoda.”
public class TrywithresourcesCustomOutThrow {
public static void main(String[] args) {
try (MyResourceOutThrow resource = new MyResourceOutThrow();) {
resource.out();
} catch (Exception e) {
e.printStackTrace();
}
}
}„Izlaz programa je sledeći.”
java.lang.Exception: out()
at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.out(TrywithresourcesCustomOutThrow.java:20)
at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:6)
Suppressed: java.lang.Exception: close()
at com.cmower.dzone.trycatchfinally.MyResourceOutThrow.close(TrywithresourcesCustomOutThrow.java:16)
at com.cmower.dzone.trycatchfinally.TrywithresourcesCustomOutThrow.main(TrywithresourcesCustomOutThrow.java:5)„Vidiš, ovog puta neće; stek izuzetka iz out() se ispisao, a na stek metoda close() dodata je ključna reč Suppressed — jasno na prvi pogled.”
„Sanmej, pa šta kažeš? Ne čini ti se da je try-with-resources mnogo prijatniji? Da ukratko sumiram: kada se radi sa resursima koji se moraju zatvoriti, uvek dajte prednost try-with-resources nad try-catch-finally. Prvi daje sažetiji i jasniji kod, i pouzdanije poruke o izuzecima.”
„Pouzdano!” reče Sanmej.
