Wednesday 16 July 2014

try-with-resources

Недавно узнал про новую фичу, название которой, try-with-resources. С новой (уже не такой уж) семеркой, появилась возможность не закрывать ресурсы в блоке try catch, как обычно это делается в finally. Это считается стандартной практикой - ресурсы которые мы одалживаем надо возвращать. В них входят соединения с БД, I/O, разные объекты из всевозможных пулов и которые ждут своего возвращения назад после своей эксплуатации. Ресурсы в основном возвращаются при вызове метода close (это далеко не значит что это единственный метод, который может вернуть заемщику ресурс).


conn.close();

После выхода семерки на сцену, появился Interface AutoCloseable, и практически все пользуемые ресурсы, по крайней мере те что состоят в в пакеджах java, javax, его имплементируют, так-же  имеются и и интерфейсы расширяющие этот. Таким образом все ресурсы выполняющие этот интерфейс могут быть задекларированы в try-with-resources.

Суть и ее отображение

Последующий фрагмент, или снипет является примером работы с каким-то ресурсом.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
  SomeResource sr = null;
  
  try {
   
    sr = new SomeResource();
   
    sr.output(data);
   
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   try {
    if (sr != null)
     sr.close();
   } catch (Exception e) {
    e.printStackTrace();
   }
  }

На первой строке - декларация ресурса, затем уже в try на пятой мы придаем ему значение, на седьмой - используем. Затем идет catch, в основном все ресурсы декларируют выброс exception-а како-го либо типа - в данном случае это просто Exception. Затем в finally который обеспечивает нас выполнением при любом исходе работы ресурса (выполнился ли код в try или выдал Exception) мы по обязанности возвращаем ресурс - строка 14, проверив перед тем если такой есть веди при выбросе Exception ресурс мог так и не инициализироваться. Заметьте что закрытие ресурса тоже выдает Exception, то-же обычное дело.

Новый формат

Вот как выглядит то-же самое при помощи try-with-resoources.

1
2
3
4
5
  try (SomeResource sr = new SomeResource();){
   sr.output(data);
  } catch (Exception e) {
   e.printStackTrace();
  } 

Первое что бросается в глаза, по сравнению со стандартным выполнением, так это то что код намного лаконичней. Ресурс на первой строке декларируется в скобках. После выполнения try блока, JVM вызовет метод close() интерфейса AutoClosable, просто это не указывается. Самое интересное что finally все равно можно указать, но переменная "sr" там уже оказывается вне существования. Лаконичность кода достигается за счет того что нету finally блока в котором производится проверка и закрытия ресурса. Хотя если бы в finally блоке нужно было-бы делать другие операции, то разница бы так не ощущалась. Читаемость и восприятие старого выполнения все равно четче.

AutoClosable

Интерфейс советует реализаторам, бросать InterruptedException и не подавлять ее, так как ее передача сигнализирует о состоянии interrupted статуса потока, сигнал на который какой-то процесс спроектирован реагировать. Еще с выполнение интерфейса может не быть идемпотентным, а это значит что поведение close() может быть динамичным и каждый вызов может нести разные последствия, например повторный вызов может бросить Exception какой нибудь, это не запрещается.  

Сравнение при запуске

Вот весь код, который мы сей час запустим и сравним как вылетает Exception при вызове close().

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
package com.j8.test;

public class SomeResource implements AutoCloseable {

 @Override
 public void close() throws Exception {
  
  throw new Exception ("This is just a test.");
  
 }
 
 public void output(String data) throws Exception {
  System.out.println(data);
 }

}


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package com.j8.test;

public class TestTryWithResources {

 public static void main(String[] args) {
  TestTryWithResources test = new TestTryWithResources();

  test.theOldWayOfDealingWithResources("Test");
  test.newTryWithResources("Test");
 }

 public void theOldWayOfDealingWithResources(String data) {

  SomeResource sr = null;

  try {
   sr = new SomeResource();

   sr.output(data);
  } catch (Exception e) {
   e.printStackTrace();
  } finally {
   try {
    if (sr != null)
     sr.close();
   } catch (Exception e) {
    e.printStackTrace();
   }
  }

 }

 public void newTryWithResources(String data) {

  try (SomeResource sr = new SomeResource();) {
   sr.output(data);
  } catch (Exception e) {
   e.printStackTrace();
  }

 }

}


Output:

Test
java.lang.Exception: This is just a test.
 at com.j8.test.SomeResource.close(SomeResource.java:8)
 at com.j8.test.TestTryWithResources.theOldWayOfDealingWithResources(TestTryWithResources.java:25)
 at com.j8.test.TestTryWithResources.main(TestTryWithResources.java:8)
Test
java.lang.Exception: This is just a test.
 at com.j8.test.SomeResource.close(SomeResource.java:8)
 at com.j8.test.TestTryWithResources.newTryWithResources(TestTryWithResources.java:37)
 at com.j8.test.TestTryWithResources.main(TestTryWithResources.java:9)

Заметь-те что stack trace вызова theOldWayOfDealingWithResources указывает на линию 25, как раз там где sr.close(); - то есть четко видно где вылетела Exception. А вот stack trace newTryWithResources указывает на 37-ю строку - там где Exception ловится, и прямого указателя, в коде класса TestTryWithResources нет, правда в stack trace указанно что именно метод close()послужил Exception-у. 

Сказать что код читать намного сложнее с использованием tr-with-resources нельзя, и места он занимает намного меньше и выглядит проще.  

No comments:

Post a Comment