Ява | Самый безопасный способ закрыть ресурсы

Вопрос

Каков наилучший/безопасный способ закрыть «ресурс ввода-вывода» в Java?

Контекст

Исходя из фона python, который использует with, который создает оператор Context Manager для Чтение и запись файлов, это гарантирует, что ресурс контекста, если он закрыт / очищен (технически это может быть что угодно, но встроенные операции ввода-вывода, например, будут закрываться, когда он выходит за пределы области видимости, даже сокеты)

Итак, взяв этот код:

file = open('workfile', encoding = "utf-8")
read_data = f.read()
file.close()

Рекомендуемый способ - использовать оператор with

with open('workfile', encoding = "utf-8") as file:
    read_data = file.read()

К тому времени, когда мы выйдем из области видимости (что буквально означает предыдущий уровень отступа), даже если произошла ошибка, файл всегда будет закрыт.

Конечно, мы могли бы сделать это

try:
    file = open('workfile', encoding = "utf-8")
    read_data = f.read()
except Exception as _: 
    pass
finally:
    file.close()

Но в большинстве случаев контекстный менеджер лучше. Одна из причин заключается в том, что просто кто-то может забыть записать file.close(), но это просто более чисто и безопасно.

Пример кода Java

Теперь в java, что рекомендуется, похоже, зачем с этим справляться?

Возьмем, к примеру, Scanner:

public class App 
{
    public static void main( String... args )
    {
        Scanner reader = new Scanner(System.in);
        try {
            System.out.print(">>> username: ");
            if (reader.hasNextLine()) {
                String username = reader.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (Exception error) {
            System.out.format("Error while reading scanner %s", error);
        } finally {
            reader.close();
        }


    }
}

Как видите, вот как я с этим справляюсь,

это java способ?

В Java есть try-with-resources, что, вероятно, является лучшим способом, за исключением определенных обстоятельств, один из которых вы используете System.in (как вы это делаете) и хотите избежать его преждевременного закрытия. После закрытия этот поток остается закрытым.

Hovercraft Full Of Eels 15.04.2023 21:08

Хорошо спасибо. Но я не понимаю одного. Вы говорите о System.in, но я закрываю сканер. Я что-то пропустил ?

Federico Baù 15.04.2023 21:16

Ах я вижу. Я добавляю System.in в сканер.

Federico Baù 15.04.2023 21:18

Ваш сканер сканирует поток — какой поток он сканирует?

Hovercraft Full Of Eels 15.04.2023 21:18

Когда вы закрываете этот сканер, вы приближаете поток.

Hovercraft Full Of Eels 15.04.2023 21:18

Это действительно хороший момент. Но в некотором смысле хорошо, что я написал это так, так как это дает пример плохого, почему нужно закрывать его слишком рано (я не знал. Просто так получилось)

Federico Baù 15.04.2023 21:19
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
6
59
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Я думаю, вы можете использовать try(...){...}catch{} утверждение (try with resource statement).

например:

try (
           Scanner reader = new Scanner(System.in);
           // other resource initalization
 ) {
            System.out.print(">>> username: ");
            if (reader.hasNextLine()) {
                String username = reader.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (Exception error) {
            System.out.format("Error while reading scanner %s", error);
        }

Начиная с JDK 1.7, () после try используется для инициализации одного или нескольких ресурсов, которые должны быть закрыты после завершения программы. Этот подход не требует блока finally

Итак, это оказывается довольно старой и хорошо документированной функцией, я просто не видел ее. Эта функция называется Заявление о попытке использования ресурсов.

Я сохраняю @shmily как принятый ответ, поскольку он правильный и отвечает на вопрос (а также был первым), я просто добавлю сюда немного, так как это может помочь, и особенно я добавлю код для моего варианта использования что мне это было нужно.

Кроме того, прочитайте последнюю заметку о том, что пользователь «Hovercraft Full Of Eels» сказал о моей реализации.

Итак, в более простой форме мы можем использовать его как

    public static void main( String... args )
    {
        try (Scanner reader = new Scanner(System.in)) {
            System.out.print(">>> username: ");
            if (reader.hasNextLine()) {
                String username = reader.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (NoSuchElementException | IllegalStateException error) {
            System.out.println("Error while reading scanner");
            error.printStackTrace();
        }
    }

Теперь, следуя этим документам baeldung.com/java-try-with-resources, он показывает, что вы действительно можете объявить его снаружи, если long является либо окончательным.

final Scanner scanner = new Scanner(new File("testRead.txt"));
PrintWriter writer = new PrintWriter(new File("testWrite.txt"))
try (scanner;writer) { 
    // omitted
}

Теперь, для моего конкретного случая использования и первой оценки, я бы не рекомендовал всегда (поскольку на самом деле даже в их примере PrintWriter может выдать ошибку FileNotFoundException), поэтому имеет смысл, если эта ошибка улавливается заранее или ожидается . иначе я вижу, что вы можете сделать вариацию

  public static void main( String... args )
    {
        String fileName = "output.txt";
        final Scanner readerTerminal = new Scanner(System.in);
        try (
                readerTerminal;
                PrintWriter writer = new PrintWriter("output.txt");
        ) {
            System.out.print(">>> username: ");
            if (readerTerminal.hasNextLine()) {
                String username = readerTerminal.nextLine();
                System.out.format("Hello %s", username);

                writer.write(username);
            }
        } catch (FileNotFoundException error) {
            System.out.format("File %s not found, reason: %s", fileName, error);
            error.printStackTrace();
        } catch (NoSuchElementException | IllegalStateException error) {
            System.out.format("Error while reading scanner, reason: %s", error);
            error.printStackTrace();
        } catch (Exception error) {
            System.out.format("Something went wrong while reading scanner, reason: %s", error);
            error.printStackTrace();
        }
    }

Это особенно имеет смысл, если reader исходит из параметров функции.

Сказав это, эта функция очень и очень похожа на менеджер контекста Python, вы можете определить/настроить свой собственный класс, который реализует AutoCloseable.

public class MyResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("Closed MyResource");
    }
}

Пока он вызывает close, вы можете делать с ним все, что хотите (любая очистка, закрытие ресурсов, закрытие API и многое другое).

Единственная разница, которую я вижу (по крайней мере, с точки зрения пользователя, очевидно, что реализация может сильно отличаться на каждом языке), заключается в том, что для включения этой функции в java вам действительно нужно поместить ее в блок try/catch, тогда как в python это полностью независимый от ошибки - сценарий перехвата.

Почему это актуально?

Как я вижу, использование его в качестве сценария try-catch может ограничить то, что вы на самом деле можете с ним делать, и особенно вы хотите закрыть этот ресурс не потому, что возникла ошибка, а потому, что он существовал в области!, которая находится в на самом деле, как это работает, но это может заставить вас думать, что он закрывается только тогда, когда поднят.

В следующем примере я покажу, как использовать его без необходимости выдавать исключение, но, если честно, это не выглядит так очевидно, как в python (что не является концом света).

Нормальный AutoCloseable

public class Main
{
    public static class MyResource implements AutoCloseable {
        @Override
        public void close() throws Exception {
            System.out.println("Resource closed, let's call some resource ... ");
        }
    }

    public static void main( String[] args )
    {
        final MyResource resource1 = new MyResource();

        try (resource1; ) {
            System.out.println("Inside Try - catch block");
        } catch (Exception error) {
            error.printStackTrace();
        }

    }
}

Ммм, а что, если я не хочу выдавать ошибку? И я просто хочу вызвать какую-то другую вещь, которую я хочу убедиться, что она вызывается, когда этот объект закрывается.

public class Main
{
    public static class MyResource implements AutoCloseable {
        @Override
        public void close() {
            System.out.println("Resource closed, let's call some resource ... ");
        }
    }

    public static void main( String[] args )
    {
        final MyResource resource1 = new MyResource();

        try (resource1; ) {
            System.out.println("Inside Try - catch block");
        }

    }
}

Сделайте это так, это выглядит очень похоже на версию Python, мне не нужно ничего ловить (и выбрасывать), так как это не точка моего конкретного автозакрытия,

Круто то, что любые необнаруженные ошибки, которые произошли с этим try-with-resource, все равно вызовут метод close

public class Main
{
    public static class MyResource implements AutoCloseable {
        @Override
        public void close() {
            System.out.println("Resource closed, let's call some resource ... ");
        }
    }

    public static void main( String[] args ) throws FileNotFoundException {
        final MyResource resource1 = new MyResource();

        try (resource1; ) {
            System.out.println("Inside Try - catch block");
            final Scanner readerTerminal = new Scanner(new File("dont_exits.txt"));
        }

    }
}

Какие выходы


Inside Try - catch block
Resource closed, let's call some resource ... 
Exception in thread "main" java.io.FileNotFoundException: dont_exits.txt (No such file or directory)
    at java.base/java.io.FileInputStream.open0(Native Method)
    at java.base/java.io.FileInputStream.open(FileInputStream.java:216)
    at java.base/java.io.FileInputStream.<init>(FileInputStream.java:157)
    at java.base/java.util.Scanner.<init>(Scanner.java:639)
    at com.koubae.app1.Main.main(Main.java:30)

Process finished with exit code 1

Примечание к комментарию «Катер на воздушной подушке, полный угрей».

В Java есть try-with-resources, что, вероятно, является лучшим способом, за исключением определенных обстоятельств, один из которых вы используете System.in (как вы это делаете) и хотите избежать его преждевременного закрытия. После закрытия этот поток остается закрытым.

Итак, несмотря на весь приведенный выше пример, я не думаю, что вам следует закрывать сканер, который сканирует System.in (но могут быть причины)

  public static void main( String[] args )
    {
        final Scanner readerUserTerminal = new Scanner(System.in);
        try (
            readerUserTerminal;
        ) {
            System.out.print(">>> username: ");
            if (readerUserTerminal.hasNextLine()) {
                String username = readerUserTerminal.nextLine();
                System.out.format("Hello %s", username);
            }
        } catch (NoSuchElementException | IllegalStateException error) {
            System.out.format("Error while reading scanner, reason: %s", error);
            error.printStackTrace();
        } catch (Exception error) {
            System.out.format("Something went wrong while reading scanner, reason: %s", error);
            error.printStackTrace();
        }
        // yea no ... will break...
        Scanner second = new Scanner(System.in);
        System.out.print(">>> username: ");
        String username2 = second.nextLine();
        System.out.format("Hello %s", username2);



    }

выход

Hello fede>>> username: Exception in thread "main" java.util.NoSuchElementException: No line found
    at java.base/java.util.Scanner.nextLine(Scanner.java:1651)
    at com.koubae.app1.Main.main(Main.java:39)

Так что будьте осторожны! Я на самом деле удивлен, так как во многих учебниках они используют сканер для чтения пользовательского ввода с терминала, а затем показывают, чтобы закрыть его, который не является файлом, а System.in (для меня это похоже на stdin как в C или python sys.stdin Ужасный уровень туториалов. Осторожно! :D

Другие вопросы по теме