Отметьте метод как возвращающий существующий закрывающийся объект, чтобы избежать ложного предупреждения на сайте использования.

Учитывая класс MyClass, использующий внутренний закрываемый объект, myCloseable и предоставляющий метод getCloseable(), который его возвращает; Eclipse, если он настроен для такого рода предупреждений о закрываемых ресурсах, будет систематически предупреждать каждый раз, когда кто-либо вызывает getCloseable(), говоря, что закрываемый объект «не может быть закрыт в этом месте».

Приятно иметь такие предупреждения о ресурсах, которые следует закрыть, если кто-то забудет их закрыть, поэтому я предпочитаю оставлять эту проверку включенной. Но в только что описанном сценарии это довольно раздражает. В принципе представляется возможным пометить метод getCloseable() аннотацией, скажем, @existingCloseable, сообщая компилятору, что «все в порядке, я просто возвращаю уже существующий ресурс, а не создаю новый, поэтому вызывающая сторона не должна закрой его".

Рассматривалась ли такая аннотация для принятия Eclipse или другими IDE? Обсуждений на эту тему я не нашел, так что подозреваю, что нет. Интересно, почему: закономерность, описанная в моем примере, кажется мне вполне обычной и естественной. Будет ли решение, которое я рассматриваю с аннотациями, не работать или иметь недостатки, о которых я не подумал?

Пример

Вот (глупый) пример, где закрывающимся является OutputStream. Метод fromPath выдает предупреждение (если не подавлен) о том, что s не закрыт, и я не обращаю на это внимания: это предупреждение кажется адекватным, и его нужно подавить только один раз. Раздражающая часть и объект моего вопроса — это предупреждение, которое появляется из метода main: «Потенциальная утечка ресурсов: «цель» не может быть закрыта». Это предупреждение будет появляться каждый раз, когда любой пользователь моего класса будет использовать getTarget. Я хотел бы отключить его раз и навсегда, аннотировав метод getTarget, чтобы компилятор знал, что этот метод возвращает ресурс, который вызывающая сторона не должна закрывать. Насколько мне известно, в настоящее время это не поддерживается в Eclipse, и мне интересно, почему.

public class MyWriter implements AutoCloseable {
    public static void main(String[] args) throws Exception {
        try (MyWriter w = MyWriter.fromPath(Path.of("out.txt"))) {
            // …
            OutputStream target = w.getTarget();
            target.flush();
            // …
        }

    }

    @SuppressWarnings("resource")
    public static MyWriter fromPath(Path target) throws IOException {
        OutputStream s = Files.newOutputStream(target);
        return new MyWriter(s);
    }

    private OutputStream target;

    public OutputStream getTarget() {
        return target;
    }

    private MyWriter(OutputStream target) {
        this.target = target;
    }

    @Override
    public void close() throws Exception {
        target.close();
    }
}

Обсуждение

Я отредактировал свой вопрос, в котором изначально спрашивалось, можно ли изменить мой код, чтобы избежать предупреждения: я понял, что на самом деле это не тот вопрос, который меня интересует, меня скорее интересует решение на основе аннотаций, о котором я думал - извините за это.

Я понимаю, что этот пример глупый: без дальнейшего контекста, и, как правильно указывают несколько ответов, похоже, что я должен скорее обернуть все методы потока и никогда не возвращать поток во внешний мир. К сожалению, трудно сделать этот пример реалистичным, при этом сохранив его размер.

Однако один пример хорошо известен, поэтому мне не нужно приводить его здесь в подробном виде: в сервлете можно вызвать getOutputStream(), и это типичный случай, иллюстрирующий мою точку зрения: метод getOutputStream() возвращает поток, который вызывающей стороне не нужно закрывать , но Eclipse будет предупреждать о потенциальной утечке ресурсов при каждом вызове (то есть в каждом сервлете), что доставляет неудобства. Также понятно, почему концепты этого метода просто не обернули все вокруг вместо того, чтобы вернуть сам поток: полезно получить объект, реализующий этот всем известный интерфейс, ведь его потом можно будет использовать с другими библиотеками и методами которые ожидают взаимодействия с потоком.

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


person Olivier Cailloux    schedule 26.12.2020    source источник
comment
Аннотации для этого нет.   -  person Andreas    schedule 27.12.2020
comment
toPath должен возвращать Path, чтобы соответствовать соглашению, поэтому имя неверно. Вероятно, это должно быть ofPath(...).   -  person Jim Garrison    schedule 27.12.2020
comment
Кроме того, зачем утруждать себя обертыванием OutputStream только для того, чтобы открыть обернутый объект? Без дополнительного контекста неясно, чего вы пытаетесь достичь. Почему клиенту MyWriter нужен необработанный доступ к базовому OutputStream? Не может MyWriter иметь методы, которые проксируют необходимую функциональность, не раскрывая файл OutputStream. Извините, но этот код выглядит забавно, и я подозреваю, что здесь скрывается проблема XY.   -  person Jim Garrison    schedule 27.12.2020
comment
@JimGarrison Вы правы в том, что toPath - неподходящее имя; Я забыл переименовать метод после того, как как-то упростил свой пример. Я также отредактировал сообщение, чтобы прояснить свой вопрос и лучше описать некоторые примеры сценариев, в которых может возникнуть проблема.   -  person Olivier Cailloux    schedule 27.12.2020


Ответы (1)


Потенциальная проблема с утечкой ресурсов

Здесь вы получаете предупреждение Потенциальная утечка ресурсов, которое отключено по умолчанию, а не обычное предупреждение Утечка ресурсов, которое включено по умолчанию. В отличие от предупреждения Утечка ресурсов, предупреждение Потенциальная утечка ресурсов будет отображаться, если вы не создаете AutoCloseable самостоятельно, а получаете его откуда-то и не закрывайте его ни явно, вызывая close(), ни неявно, используя try-with-resource.

Закрыт ли ресурс где-то, откуда вы его взяли, как в вашем случае, может быть вычислено для простых случаев, но не для всех случаев. Это та же проблема, что и проблема остановки. Например, создание AutoCloseable или его закрытие может быть делегировано куда-то, где оно делегируется снова, и так далее, и если есть if, switch или цикл, все ветви должны быть соблюдены, чтобы быть уверенным. .

Даже в вашем простом на вид примере все не так просто. Делая единственный конструктор закрытым, вы предотвращаете возможность расширения класса, иначе можно было бы переопределить close() без вызова super.close(), что приведет к тому, что target.close() никогда не будет вызван. Но так как private OutputStream target; не final, все еще может иметь место реальная утечка ресурсов, как показано ниже:

public static void main(String[] args) throws Exception {
    try (MyWriter w = MyWriter.toPath(Path.of("out.txt"))) {
        // …
        OutputStream target = w.getTarget();
        w.target = new FileOutputStream("out2.txt");
        // …
    }
}

Таким образом, компилятор должен будет проверить, кроме того, что класс не может быть переопределен, если поле, содержащее внутреннее AutoCloseable, является либо final, либо privat и эффективно окончательным (устанавливается только при инициализации), и что внутреннее AutoCloseable будет закрыто в close() внешнего AutoCloseable .

Подводя итог, если вы не генерируете ресурс самостоятельно, а получаете его откуда-то, нет никакой гарантии, что компилятор сможет за ограниченное время вычислить, будет ли он закрыт (см. проблема остановки). Для таких случаев существует предупреждение Потенциальная утечка ресурсов в дополнение к предупреждению Утечка ресурсов, которое можно отключить отдельно и оно отключено по умолчанию.

Решение на основе аннотаций?

@SuppressWarnings предназначена для подавления именованных предупреждений компилятора в аннотированном элементе. Но в этом случае здесь потребуется аннотация, которая сообщает компилятору, что возвращаемый AutoCloseable не нужно закрывать, подобно @SafeVarargs или в нулевые аннотации. Насколько мне известно, такой аннотации еще не существует, по крайней мере, в системной библиотеке. Чтобы использовать такую ​​аннотацию, нужно было бы сначала добавить в проект зависимость, содержащую эту аннотацию (точно такая же ситуация, как и для нулевых аннотаций, которые, к сожалению, до сих пор не являются частью системной библиотеки, может быть потому, что javac делает в настоящее время не поддерживает нулевой анализ на основе аннотаций, в отличие от компилятора Eclipse и других IDE и инструментов).

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

public static BufferedOutputStream toBufferedOutputStream(OutputStream outputStream) {
    return new BufferedOutputStream(outputStream);
}

Итак, во-первых, такие аннотации должны существовать (лучше не специфичные для Eclipse и как часть системной библиотеки), чтобы Eclipse могла их поддерживать.

Решение для данного примера

В качестве решения не подвергайте target воздействию, например, обернув его и расширив от OutputStream:

public class MyWriter extends OutputStream {
    public static void main(String[] args) throws Exception {
        try (MyWriter w = MyWriter.of(Path.of("out.txt"))) {
            // …
            w.flush();
            // …
        }
    }

    @SuppressWarnings("resource")
    public static MyWriter of(Path target) throws IOException {
        OutputStream s = Files.newOutputStream(target);
        return new MyWriter(s);
    }

    private final OutputStream target;

    private MyWriter(OutputStream target) {
        this.target = target;
    }

    @Override
    public void close() throws IOException {
        target.close();
    }

    @Override
    public void write(int b) throws IOException {
        target.write(b);
    }
    
    @Override
    public void flush() throws IOException {
        target.flush();
    }
}

Или еще лучше:

public class MyWriter {
    
    public static void main(String[] args) throws Exception {
        MyWriter.writeToFile(Path.of("out.txt"), w -> {
            try {
                // …
                w.flush();
                // …
            } catch (IOException e) {
                // error handling
            }
        });
    }

    public static void writeToFile(Path target, Consumer<OutputStream> writer) throws IOException {
        try (OutputStream out = Files.newOutputStream(target);) {
           writer.accept(out);
        }
    }

}
person howlger    schedule 27.12.2020
comment
Спасибо. Но это в основном не отвечает на мой вопрос: я знаю, что это предупреждение о потенциальной утечке ресурсов; и я знаю, что он отключен по умолчанию, но я написал, что мне нравится, когда он включен. Кроме того, я никогда не упоминал, что ожидаю, что компилятор обнаружит что-то умное; наоборот, предлагаю себе аннотировать метод, чтобы компилятору не пришлось ничего доказывать. (Подобно написанию утверждения, != null можно использовать для отключения некоторых предупреждений о потенциальных нулевых ссылках.) Действительно, конец этого поста является обходным путем для моего конкретного примера - я только что уточнил свой вопрос. - person Olivier Cailloux; 27.12.2020