Я иду, вы все еще используете try-catch-finally

Java
Я иду, вы все еще используете try-catch-finally

Брат, твоя предыдущая статьяя иду переключатьСтатья тоже очень интересная.После прочтения не могу нарадоваться.Хочешь написать еще одну? Хотя используется синтаксис Java 13, он не очень дружелюбен к старой версии. Но кто может гарантировать, что у Java не будет очередного крупного обновления, как у Java 8, которое убьет Java 6 на берегу. Java 8 хороша, но рано или поздно ее обновят, поддерживаю тебя, второй брат, плевать на эти возражения.

Это сообщение, которое читательница Элис специально прислала мне на прошлой неделе, и оно меня очень тронуло. Действительно, объем чтения последнего «Я иду» был чрезвычайно высок, и было перепечатано несколько больших номеров, включая CSDN, а следующую статью в тот же день прочитали 15 000 человек. Но есть также много критиков, таких как «Я думал, что у вас есть какие-то новые трюки, но я не ожидал, что вы будете использовать Java 13».

Но мое сердце всегда было большим. С тех пор, как я написал свою первую статью, количество раз, когда меня опрыскивали, равно количеству густых волос на моей макушке, и я не могу их сосчитать. Поэтому я решил продолжить в том же духе и выложить новый пост "Я иду".

В этот раз удаленная проверка не требуется, так как наша компания также возобновила работу. Код для этого обзора по-прежнему принадлежит Сяо Вану.Большая часть кода, который он написал, красива, строга и хорошо прокомментирована, что меня очень устраивает. Но когда я увидел, что он не использует try-with-resources, я не мог не закричать: «Черт, Сяо Ван, ты все еще используешь try-catch-finally!»

Давайте посмотрим на код, написанный Сяо Ваном.

public class Trycatchfinally {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("/牛逼.txt"));
            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();
                }
            }
        }
    }
}

Эй, я чувствую, что этот код идеален, try-catch-finally используется довольно хорошо, особенно имя файла牛逼.txtОчень яркий. Вам не нужно писать комментарий, чтобы понять, что делает этот код: прочитать содержимое файла в блоке try и вывести его на консоль построчно. Если файл не найден или возникает ошибка чтения/записи ввода-вывода, перехватите и распечатайте информацию о стеке ошибок в catch. Наконец, буферизованный объект чтения символов BufferedReader закрывается в finally, что эффективно устраняет серьезные последствия для производительности, когда ресурс не закрыт.

До Java 7 try-catch-finally действительно был лучшим способом гарантировать, что ресурсы будут закрыты своевременно, независимо от того, выдает ли программа исключение или нет.

Однако опытные читатели найдут в приведенном выше коде 2 серьезные проблемы:

1) Имя файла "Niubi.txt" содержит китайский язык и его необходимо пропуститьjava.net.URLDecoderКатегорияdecode()метод, чтобы избежать его, иначе этот код обязательно выдаст исключение «Файл не найден» во время выполнения.

2) При прямом прохожденииnew FileReader("牛逼.txt")Чтобы создать объект FileReader, «Niubi.txt» должен находиться в каталоге того же уровня, что и src проекта, в противном случае также будет выдано исключение, что файл не может быть найден. Но в большинстве случаев файлы (конфигурации) будут помещены в каталог ресурсов, так что скомпилированные файлы появятся в каталоге классов, как показано на рисунке ниже.

Чтобы решить две вышеупомянутые проблемы, нам нужно оптимизировать код:

public class TrycatchfinallyDecoder {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            String path = TrycatchfinallyDecoder.class.getResource("/牛逼.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();
                }
            }
        }
    }
}

Запустив этот код, программа сможет правильно вывести содержимое файла на консоль. Но если вы тоскуете по слову «чистый», вы почувствуете, что этот код сильно раздут, особенно код в финале, как пузико, наполненное 12 бутылками снежного пива.

Я увидел в Интернете картинку, на которой программист на Python дразнит программиста на Java, и скопировал ее напрямую (вторжение и удаление), чтобы рассмешить вас:

Более того, try-catch-finally имеет серьезную скрытую опасность от начала до конца: try-catch-finallybr.readLine()может броситьIOException, в конце концовbr.close()также может броситьIOException. К сожалению, если IOException выдается в обоих местах, задача отладки программы усложняется, и требуется много работы, чтобы найти, какая часть ошибки неверна, а это результат, который мы не хотим видеть.

Чтобы смоделировать описанную выше ситуацию, давайте настроим класс MyfinallyReadLineThrow, который имеет два метода, а именноreadLine()иclose(), тело метода активно генерирует исключение.

class MyfinallyReadLineThrow {
    public void close() throws Exception {
        throw new Exception("close");
    }

    public void readLine() throws Exception {
        throw new Exception("readLine");
    }
}

Тогда мыmain()Метод использует try-finally для вызова MyfinallyReadLineThrow.readLine()иclose()метод:

public class TryfinallyCustomReadLineThrow {
    public static void main(String[] args) throws Exception {
        MyfinallyReadLineThrow myThrow = null;
        try {
            myThrow = new MyfinallyReadLineThrow();
            myThrow.readLine();
        } finally {
            myThrow.close();
        }
    }
}

После запуска приведенного выше кода стек ошибок выглядит следующим образом:

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)

readLine()Информация об исключении метода на самом делеclose()Информация стека метода съедена, что должно привести к ошибочному предположению, что цель, которую нужно исследовать,close()метод вместоreadLine()— хотя и является объектом подозрения.

Но с помощью try-with-resources эти проблемы решаются, пока ресурсы, которые необходимо освободить (такие как BufferedReader), реализуют интерфейс AutoCloseable. Теперь, когда у нас есть решение, давайте уменьшим предыдущий блок finally.

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();
}

О чудо, блок finally пропал, а на его место прописаны освобождаемые ресурсы после попытки()середина. Если необходимо освободить несколько ресурсов (BufferedReader и PrintWriter), вы можете напрямую()добавил в.

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();
}

Если вы хотите выпустить пользовательский ресурс, просто сделайте так, чтобы он реализовал интерфейс AutoCloseable и предоставил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("关闭自定义资源");
    }
}

Вывод после запуска кода выглядит следующим образом:

关闭自定义资源

Разве это не удивительно? мы вtry ()только создал объект MyResource и больше ничего не делал, но это произошлоclose()Оператор вывода в методе выполняется. Хотите знать, почему? Давайте посмотрим на декомпилированный байт-код.

class MyResource implements AutoCloseable {
    MyResource() {
    }

    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }
}

public class TrywithresourcesCustom {
    public TrywithresourcesCustom() {
    }

    public static void main(String[] args) {
        try {
            MyResource resource = new MyResource();
            resource.close();
        } catch (Exception var2) {
            var2.printStackTrace();
        }

    }
}

Эй, компилятор на самом деле взял на себя инициативу преобразования для try-with-resources и вызвал его в tryclose()метод.

Далее мы добавляем еще один в пользовательский классout()метод,

class MyResourceOut implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("关闭自定义资源");
    }

    public void out() throws Exception{
        System.out.println("沉默王二,一枚有趣的程序员");
    }
}

На этот раз мы называем это попыткойout()метод:

public class TrywithresourcesCustomOut {
    public static void main(String[] args) {
        try (MyResourceOut resource = new MyResourceOut();) {
            resource.out();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Давайте посмотрим на декомпилированный байт-код:

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();
        }

    }
}

в этот раз,catchАктивно звонил в блокresource.close(), и есть очень важный кусок кодаvar5.addSuppressed(var4). Для чего это? Когда возникает исключение, другие исключения могут быть подавлены из-за исключения и не могут быть вызваны обычным образом. В это время черезaddSuppressed()method записывает эти подавленные методы. Подавленные исключения появятся в информации о стеке выброшенного исключения, и к ним также можно будет получить доступ черезgetSuppressed()способ получить эти исключения. Преимущество этого в том, что никакие исключения не теряются, что удобно нашим разработчикам для отладки.

Вау, а вы помните наш предыдущий пример - в попытке-наконец-то,readLine()Информация об исключении метода на самом делеclose()Информация стека метода съедена. Теперь, когда у нас есть попытка с ресурсами, давайте посмотрим на роль иreadLine()метод последовательныйout()метод не будетclose()есть.

существуетclose()иout()Метод вызывает исключение напрямую:

class MyResourceOutThrow implements AutoCloseable {
    @Override
    public void close() throws Exception {
        throw  new Exception("close()");
    }

    public void out() throws Exception{
        throw new Exception("out()");
    }
}

Вызовите эти 2 метода:

public class TrywithresourcesCustomOutThrow {
    public static void main(String[] args) {
        try (MyResourceOutThrow resource = new MyResourceOutThrow();) {
            resource.out();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Вывод программы следующий:

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)

Слушай, не в этот раз,out()Информация о стеке исключений распечатывается, иclose()Ключевое слово добавляется в информацию о стеке методаSuppressed. На первый взгляд, хорошо хорошо, мне нравится.

Подводя итог, всегда уделяйте ограниченное внимание использованию try-with-resources вместо try-catch-finally при работе с ресурсами, которые должны быть закрыты. Код, сгенерированный первым, более лаконичен и понятен, а сгенерированная информация об исключении более надежна. Обещай мне, хорошо? Прекратите использовать try-catch-finally.

Благодарность

Ну что же, мои дорогие читатели, на этом и закончилось содержание этой статьи, чувствуете ли вы, что узнали что-то новое? Я Silent King Er, интересный программист.Оригинальность непроста, не просите пустой билет, пожалуйста, поставьте лайк этой статье, что будет моей сильнейшей мотивацией писать больше качественных статей.

Если вы считаете, что статья немного полезна для вас, пожалуйста, выполните поиск по запросу "Тихий король 2"Прочтите это в первый раз, ответьте [666] Есть также обучающие видео высокой четкости 500G (по категориям), которые я тщательно подготовил для вас. Эта статья GitHubОн был включен, и есть полные тестовые площадки для интервью на крупных заводах Добро пожаловать в Star.