воскресенье, 3 июня 2012 г.

Try с ресурсами Java 7

Управление ресурсами с помощью конструкции Try-Catch-Finally, как это было раньше

До выхода Java 7 управление ресурсами требовало явного закрытия, что было несколько утомительно.

Посмотрите на этот метод, который читает данные из файла и выводит их в System.out:
private static void printFile() throws IOException {
    InputStream input = null;

    try {
        input = new FileInputStream("file.txt"); //!

        int data = input.read(); //!
        while(data != -1){
            System.out.print((char) data);
            data = input.read(); //!
        }
    } finally {
        if(input != null){
            input.close(); //!
        }
    }
}
Код, помеченный восклицательным знаком, это места где код может выбросить исключение. Как вы можете видеть это может случиться в трёх местах внутри блока try и в одном месте внутри блока finally.

Блок finally всегда выполняется вне зависимости будет выброшено исключение в блоке try или нет. Это означает, что InputStream будет закрыт вне зависимости от того, что произойдёт в блоке try. Метод close() класса InputStream так же может выбрасывать исключение, если не удалось закрыть поток.

Представте, что исключение возникает в блоке try. Блок finally будет выполнен. После чего представте, что в блоке finally так же возникнет исключение. Как вы думаете, какое из исключений будет на верху стека вызовов?

Исключение из блока finally будет на верху стека вызовов, даже не смотря на то, что исключение из блока try, вероятно,  будет более подходящим.

try с ресурсами

В Java 7 вы можете написать код из примера выше используя try c ресурсами например так:

private static void printFileJava7() throws IOException {

    try(FileInputStream input = new FileInputStream("file.txt")) {

        int data = input.read();
        while(data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    }
}
Обратите на первую строку метода:
try(FileInputStream input = new FileInputStream("file.txt")) {
Это констукция try с ресурсами. FileInputStream объявлена в круглых скобках после ключевого слова try. Кроме того для класса FileInputStream создана и объявлена переменная.

В конце блока try FileInputStream будет закрыт автоматически. Это возможно потому, что FileInputStream реализует интерфейс java.lang.AutoCloseable. Все классы, реализующие этот интерфейс, могут быть использованы в конструкции try с ресурсами.

Если исключение будет выброшенно и в конструкции try с ресурсами и при закрытии FileInputStream (вызову метода close()), то исключение выброшенное в внутри блока try будет выборшенно во внешний мир. Исключение, выбрасываемое во время закрытия FileInputStream будет подавлено. Это альтернатива тому, что происходит, когда мы используем конструкцию try-finally.


Использование нескольких ресурсов

Внутри констукции try с ресурсами вы можете использовать несколько ресурсов и все они будут автоматически закрыты. Например:

private static void printFileJava7() throws IOException {

    try(  FileInputStream     input         = new FileInputStream("file.txt");
          BufferedInputStream bufferedInput = new BufferedInputStream(input)
    ) {

        int data = bufferedInput.read();
        while(data != -1){
            System.out.print((char) data);
    data = bufferedInput.read();
        }
    }
}
В этом примере создаются два ресурса внутри круглых скобок после ключевого слова try — FileInputStream  и  BufferedInputSream. Оба ресурса будут автоматически закрыты, как только код дойдёт до конца блока try

Ресурсы будут закрыты в обратном порядке тому, в котором они создавались в круглых скобках. Сначала будет закрыт BufferedInputSream, затем FileInputStream.


Создание классов реализующих интерфейс AutoClosable

Конструкция try с ресурсами может работать не только со встроенными в стандартную библиотеку языка Java классами. Вы так же можете реализовывать интерфейс java.lang.AutoCloseable в вашем собственном классе, и затем использовать его в конструкции try с ресурсами.

Интерфейс AutoCloseable описан всего один метод с именем close(). Вот как он выглядит:

public interface AutoClosable {

    public void close() throws Exception;
}
Любой класс, реализующий этот интерфейс может быть использован в конструкции  try с ресурсами. Вот пример такого класса:

public class MyAutoClosable implements AutoCloseable {

    public void doIt() {
        System.out.println("MyAutoClosable Работает!");
    }

    @Override
    public void close() throws Exception {
        System.out.println("MyAutoClosable закрыт!");
    }
}
Метод doIt() не является частью интерфейса AutoCloseable. Он присутствует в нашем классе потому, что мы хотим сделать нечто более, чем просто закрыть объект.

Приведём пример, как можно использовать созданный нами класс в конструкции try с ресурсами:

private static void myAutoClosable() throws Exception {

    try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
        myAutoClosable.doIt();
    }
}
Если мы запустим этот код, то увидим строку, выведенную с помощью System.out из вызванного метода myAutoClosable():
MyAutoClosable работает!
MyAutoClosable закрыт!
Как вы можете видеть, конструкция try с ресурсами достаточно мощный способ быть уверенным в том, что ресурсы внутри try-catch блока будут закрыты корректно, вне зависимости создали мы эти ресурсы на основе своих собственных классов, или с помощью встроенных в стандартную библиотеку языка Java.

Это перевод статьи.

6 комментариев:

  1. Спасибо, а то в книге упоминалось про try-с-ресурсами, но ничего не объяснялось.

    ОтветитьУдалить
  2. Не описали главное! При закрытии ресурсов в методах close() у классов по работе с тем или иным ресурсом тоже могут быть брошены исключения. Я о Supressed ;)

    ОтветитьУдалить
  3. Спасибо! Понятно написано

    ОтветитьУдалить
  4. Спасибо за статью, очень помогла разобраться)

    ОтветитьУдалить
  5. Спасибо очень полезно!

    ОтветитьУдалить