Как стать автором
Обновить

Комментарии 82

Это просто статья о try-with-resources что ли?
Кто мешает перейти на 7? Java же делает упор на обратную совместимость. Чесно не могу представить себе Java 6 код, который не скомпилится (хоть и warning) в Java 7.
Попробуйте это объяснить заказчику. Он не будет рисковать переходом на Java 7 на своих production серверах только ради каких-то там синтаксических плюшек.
Могут быть и ошибки компиляции — в java 7 исправлены баги влияющие на видимость некоторых полей класса. Но это исправляется легко. Другое дело, если в коде есть криво написанные equals и compareTo методы, то работать в java 7 они могут иначе. Поэтому опасения вполне обоснованные. Подобный переход необходимо тщательно тестировать.
А ещё есть мир Android… Уговорите Гугл сделать поддержку классов, сгенерированных 7-й явой, в своём dx-компиляторе.
С компиляцией ошибки — легко. В Java 7 изменились некоторые интерфейсы. Например, java.sql.Connection. Если в вашем проекте этот интерфейс был реализован, вы получите ошибку компиляции.

Ещё в деплой могут быть встроены тулзы для автоматического анализа и преобразования кода (скажем, обфускация), которые могут не поддерживать новые исходники или байткод.
Ну я надеюсь вы поняли, что сказали глупость. Нереально иногда просто поднять какой нибудь Tapestry до версии хотя бы 4.1 не то что 1.7 java внедрить.
Обратная совместимость, говорите?
Если через X-сервер запустить свинговое приложение на 7 JRE, то после потери фокуса ввода, вы никак не сможете этот фокус ввода вернуть обратно. Из работающих решений — только запуск под более ранней версией JRE. Баг до сих пор не закрыт.
Погуглите «swing application lost focus on x-server».
Про совместимость уже понял.

А вот баг про который вы говорите, я с ним даже столкнулся! В продакшене на Debian 6 и последнем JRE, фокус не востанавливается в модальных диалоговых окнах. На тестовом никак не мог воспроизвести, хотя была таже Debian с чуть боле новыми пакетами. Баг в принципе был не критичен, я и забил, но вашу мысль про совместимость теперь понял точно.
Мало того, в пределах даже одной версии Java нету абсолютной обратной совместимости. Например, в апдейте 6u21 System.getProperty(«java.vendor») вместо Sun Microsystems Inc. стал возвращать Oracle Corporation. Кто-то мог на это завязаться, в итоге некоторые приложения переставали работать правильно.
это не баг, а фича, связанная с продажей.
Была бы там идиома RAII, или хотя бы гарантия, что close не будет вызывать исключения… Но действительно, почему не сделать так, чтобы освобождение ресурсов гарантированно исполнялось?
Если честно, не понял, что вы хотели сказать. Можете каждое предложение пояснить?
Для начала посмотрите как в С++ вообще обходятся без блока finally. Держится это на двух вещах.
Во первых для объектов, размещенных на стеке, в момент выхода за область видимости будет вызван деструктор, где будет размещено освобождение ресурсов. Во вторых существует соглашение благодаря которому деструкторы ни при каких обстоятельствах не кидают исключений.
В случае с Java деструкторов нет как таковых, но вот потребовать гарантий, что освобождение ресурсов не будет кидать исключений, мы можем. Тогда второй вариант станет, внезапно, правильным и самым лаконичным.
А как мы потребуем, что освобождение не будет кидать исключений? Взять тот же пример BufferedOutputStream(FileOutputStream). Там метод close() по сути сбрасывает информацию на диск. Если диск в момент вызова close() сдох?
А какой смысл в структурной обработке исключений в таких случаях?
Вместо экстремального «сдох винт» там могло бы быть «кончилось место»
А чем этот случай отличается от других? Почему в данном случае «разрешено» не обрабатывать исключения?
За сброс отвечает функция flush(), close() же просто освобождает ресурс и ничего не гарантирует кроме освобождения ресурса. Так сделано везде. Кроме явы. И проведение в яве — идиотизм.
Может и идиотизм, но сдаётся мне, что если бы было так, как вы сказали, то Java-программы содержали бы ещё больше ошибок. Потому что многие бы забывали про вызов ещё плюс одной функции. Это как с checked исключениями. Идея благая, но на деле мало кто умеет правильно обрабатывать исключения. По этой причине от checked исключений отказались в C# и Scala.
Что за бред вы пишите? Еще раз повторяю — описанное мною поведение стандартно и привычно. Я вот до вчерашнего дня даже не подозревал, что флашить в яве необязательно.
Стандартно? В C# StreamWriter.Dispose() тоже делает Flush(). Я уже назвал две платформы (JVM и CLR), где это не так. Можете подтвердить свои слова про «стандартно и привычно»?
В C# хотя бы using есть. Там не нужно было ждать 16 лет, пока его добавят.
Стандартная функция fclose() в любой операционной системе закрывает дескриптор файла и не гарантирует успешность флаша. Это написано в любой документации, хоть к с++ хоть к паскалю.
В C++ есть std::ofstream, в котором flush() необязательно делать. Ещё раз, о какой стандартноcти и привычности вы говорите?
close() же просто освобождает ресурс и ничего не гарантирует кроме освобождения ресурса. Так сделано везде.
Нет. Везде close() или (его синоним) гарантированно вызывает flush().
Но он не гарантирует успешность, гарантия только на закрытие файла.
Нет. Весь файловый IO не гарантирует успешность.
В случае с Java деструкторов нет как таковых
Справедливости ради стоит отметить, что в Java есть финализаторы, которые вызываются когда объект собирается сборщиком мусора.

потребовать гарантий, что освобождение ресурсов не будет кидать исключений, мы можем
Метод close() у некоторых объектов (например буфферизированный output stream) не только освобождает ресурсы, а и может производить запись (и это не зависит от языка программирования, в С++ ofstream::close() тоже сбрасывает буфер). Как Вы предлагаете проверять на ошибку в таком случае? Как в C++? Делать мануально close() в области видимости (т.е. до вызова деструктора) и проверять флаг failbit в iostate (или ловить исключение)? Разве это будет «лаконично»?
В Java финализаторы такие, что лучше и не знать об их существовании…
Запись производит flush(), я уже чуть выше отписал.
Но действительно, почему не сделать так, чтобы освобождение ресурсов гарантированно исполнялось?
Потому, что это невозможно в общем случае.
Да потому что RAII добавит работы сборщику мусора и «сломает» существующие алгоритмы. При выходе из области видимости сборщик мусора должен запустится, что не проблема для простого сборщика как в Python или Ruby, уменьшили счетчик ссылок при достижении нуля удаляем объекты, но проблема для Java/.NET потому что запуск сборщика означает «замри мир».
>>Да потому что RAII добавит работы сборщику мусора и «сломает» существующие алгоритмы. При выходе из области видимости сборщик мусора должен запустится

Зачем запускаться сборщику мусора ?? Нужно просто выполнить деструктор и всё.
Код с гуавой плох тем, что глотает checked exception
То есть если " // что-то делаем со stream" кидает CheckedUserException, который я объявил в сигнатуре функции, то гуава обернет его во что-то
Для этого там конечно есть еще два rethrow, так что до двух CheckedException еще можно выкинуть… Ну вобщем стремно это все.
По сути куда проще в try() делать flush а на close всегда логировать и игнорировать исключение. Такой код хотя бы читабелен
Лучше тогда делать close(), а не flush()
Ну и делать flush/close в try приемлемо, только если у вас один ресурс. Часто бывает два ресурса (и даже три) в одном блоке try
Нет, так не бывает :)
Один try — один ресурс, иначе никаких гарантий.
То есть нормальный паттерн, в котором нет ни одной описанной Вами проблем, и в то же время нет проблем с checked exception(на примере трех стримов, два инпут и один отпут):

InputStream g1 = new InputStream(...)
try{
  //Some actions
  OutputStream g2 = new OutputStream(...);
  try {
    //Some actions
    InputStream g3 = new InputStream(...);
    try{
      //Some actions
    } finally {
      IOUtils.closeQuietly(g3);
    }
    //Some actions
    g2.flush();
  } finally {
     IOUtils.closeQuietly(g2);
  }
  //Again, some actions
} finally {
  IOUtils.closeQuietly(g1);
}
В случае с Closer можно обойтись одним try:

Closer closer = Closer.create();
try {
   InputStream g1 = closer.register(new InputStream(...));
   // some actions
   OutputStream g2 = closer.register(new OutputStream(...));
   // some actions
   InputStream g3 = closer.register(new InputStream(...));
   // some actions
} catch (Throwable e) {
   throw closer.rethrow(e);
} finally {
   closer.close();
}
Кроме того, пропадает информация об исключении из close(), если основной код выбрасывает исключение.


Разве пропадает? А
e.getTargetException()
не годится?
getTargetException — это чей метод вообще? В Throwable/Exception нету такого
Хм, да, не везде оно есть.
Мне оно в рефлекшне помогло реальную причину отлавливать, вот тут InvocationTargetException.
Интересно, минус за коммент за то, что я ответил на заданный вопрос, чей метод я имел в виду? ;)
Если ищем идеал, то openOutputStream() надо вызывать внутри try, так как сам метод openOutputStream, вначале получит outputStream, потом его вернет. Если между «получит» и «вернет» произойдет ошибка, то outputStream не закроется.

Используя в «Правильном решении» «catch (Throwable e)» и «closer.rethrow(e)», идет замена реального исключения на RuntimeException. Иногда так делать нельзя.
Никогда так делать нельзя…
Ну там вообще-то в случае IOException не идёт замены. Оно выбрасывается as-is. Плюс ещё можно добавить одно (или два) «пользовательское» checked исключение, которое тоже не будет оборачиваться в RuntimeException. Это всё костыли конечно же…
> Если между «получит» и «вернет» произойдет ошибка, то outputStream не закроется
Он и с оборачиванием внутри try не закроется, потому что у вас не будет ссылки на него для его закрытия. Вообще не должно быть такого, что между «получит» и «вернет» есть какой-то код, способный выбросить исключение
Ссылка будет:
OutputStream os = null;
try {
  os = ...
} finally {
  ...
}


Между «получит» и «вернет» обычно всегда есть какой-то код, например, код конструктора: return new BufferedOutputStream(openOutputStream());
Если openOutputStream() кинет исключение, то os = null — вы не сможете закрыть ресурс, на который нету ссылки.
Я же объяснил. См. Неправильное решение №3
} catсh(Throwable t) {

Как меня утомили такие кэтчи.
Никогда не ловите Throwable или Error. В случае их возникновения система должна падать в дамп, после чего программист должен узнать причины возникновения этих Error или Throwable и разобраться с ними.
Конечно можно после падения автоматически поднять новенький процесс. Однако делать это нужно только из другого процесса, или из cron-а.

А то получается, что поймав Error или Throwable, система не может самостоятельно решить проблему, переходит в нештатное состояние и счастливо тупит мешая работе всех окружающих систем.

Я даже однажды видел попытку сделать memory dump в случае out of memory вручную путем запуска внешнего процесса из jsp.
В данном случае отлавливание Throwable корректно, потому что оно тут же перебросится наружу. Посмотрите как реализован метод Closer.rethrow()
Нет, не корректно.
1) В сигнатуре метода появится throws Throwable
2) В catch ничего нельзя делать, т.к. в случае Error JRE ничего не гарантирует

Closer.rethrow ничуть не более корректен. Во-первых он пытается создать объект (new RuntimeException(...)), т.е. в случае нехватки памяти код попытается создать новый объект и может получить нехватку памяти, но уже в другом месте. Во-вторых со стеком то же самое.
Не зря он помечен Beta.
1) Нет, не появится. С чего бы? Сигнатура метода будет throws IOException
2) Можно, если там не происходит ничего криминального типа выделения памяти в куче. Посмотрите, во что выливается try-with-resources в Java 7. Там есть catch (Throwable t) и даже несколько.

Closer.rethrow корректен. Он не делает new RuntimeException(), если исключение — это Error. Повторяю еще раз: посмотрите, как реализован метод rethrow. Со стеком проблемы чисто теоретически возможны, но вероятность их близка к нулю настолько, что это можно не учитывать

1) этот код в JDK 1.6 и более старых не пройдет компиляцию пока не добавишь в секцию throws Throwable.
import java.io.*;
class tmp {
    public void myMethod() throws IOException {
        try {
            File f = File.createTempFile("",""); // это ради IOException
            f.delete();
        } catch (Throwable t) {
            throw t;
        }
    }
}

2) Что можно? Создавать объекты нельзя, вызывать методы нельзя. И?
1) А вы не смотрите на первое решение. Оно кривое, и так никому не следует писать. Смотрите решение с Closer.rethrow(). Там не будет никакого throws Throwable
2) Вызывать методы можно, если они дают гарантию, что не будут делать ничего сложного вроде длинной рекурсии или выделения объектов. Closer.rethrow тому пример
Часто catch Error вполен осмысленен.
Ловить OutOfMemoryError и чистить кэши — достаточно корректное решение. А падать в дамп как правило не вариант.
Всевозможные LinkageError вылезающие из сторонних библиотек бывают вполне штатными и их надо корректно обработать.
При работе с reflection — Error-ы это штатные ошибки.
1. Я не понимаю, почему они не объявили rethrow так:

public <T extends Throwable> T rethrow(T throwable) throws T {/*...*/}


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

2. Почему нужно писать throw closer.rethrow(e);, а не просто closer.rethrow(e);?
1. Потому что так нельзя.
А можно вот как
public < T extends Throwable > T rethrow(Class< T > clazz, T throwable) throws T

Но собственно такой метод там есть.
2. Чтобы анализатор кода(в IDE например) понимал что выполнение здесь заканчивается.
1. Почему так нельзя? Скомпилировалось и работает. Или это из-за каких-то идеологических соображений?
Эмм, что именно вы скомпилировали?
Никаких идеалогических соображений.
Просто rethrow вызывается в конструкции вида
} catch(Throwable t) {
retrhow(t);
}
И ваш тип T выводится как Throwable.
А значит у вызывающего метода должен быть Throwable в списке проверяемых исключений.
А зачем мне писать Throwable t?

Я написал вот так:

catch (MyException e) {
    throw resource.rethrow(e);
}
И добавил MyException в список. Для ловли всех остальных я бы добавил метод rethrowUnknow c такой сигнатурой, какая у них сейчас.
А, ну то есть вы предлагаете городушку вида
catch(MyException1 e) {
throw rethrowChecked(e);
} catch(MyException2 e) {
throw rethrowChecked(e);
} catch(Throwable t) {
throw rethrowOthers(t);
}
?
Ну так короче вызвать
catch(Throwable t) {
throw rethrowOthers(t, MyException1.class, MyException2.class);
}
1. Как я уже написал — нужно будет самому добавлять кидаемые исключения в throws, а не полагаться на компилятор / IDE.
2. Исключения разных типов не просто так, их наверно по разному обрабатывать нужно.
1. Я не понял.
2. Эмм, я не хочу их обрабатывать в этом коде, я хочу пробросить их наверх. И хочу это сделать с минимальной головной болью.
Единственное, что пришло на ум — из-за multi-exception catches в 1.7+. Но для этого случая методы, которые принимают несколько Class.

У них все rethrow возвращают RuntimeException, что неудобно, т.к. нужно самому следить, какие исключения кидает метод, чтобы добавить их в throws
Прошу прощения IOException, а не RuntimeException в throws стоит.
3. Почему для 1.6 они просто пишут в лог, а не бросают, к примеру, AggregateIOException extends IOException (самописное), которое бы эмулировало addSuppressed?
… А зачем?
По вашему исключения только в лог пишутся?
А если там не IOException?
close кидает IOException, разве нет?
Да, но еще ведь есть Error и RuntimeException
Вечно они все портят…
Они, конечно, есть, но что теперь, каждую строчку в try-catch оборачивать?
А так не подойдет? 2 независимых try блока:

OutputStream stream;
try {
stream = openOutputStream();
// что-то делаем со stream
} catch (Throwable t) {
// что-то делаем с исключением
}
try {
if (stream != null) { // Я не уверен, что Java так работает
stream.close(); // Или можно просто ловить исключение, если метода нет, вместо if'a
}
} catch (Throwable unused) {
// что-то делаем, если нужно
}

Простите, теги не работают.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории