Время на прочтение
8 мин
Количество просмотров 18K
Независимо от того, новичок вы или профессионал, всегда полезно освежить в памяти методы обработки исключений, чтобы убедиться, что вы и ваша команда можете справиться с проблемами.
Обработка исключений в Java — непростая тема. Новичкам сложно понять, и даже опытные разработчики могут часами обсуждать, как и какие исключения следует создавать или обрабатывать.
Вот почему у большинства команд разработчиков есть собственный набор правил их использования. И если вы новичок в команде, вас может удивить, насколько эти правила могут отличаться от тех, которые вы использовали раньше.
Тем не менее, есть несколько передовых практик, которые используются большинством команд. Вот 9 самых важных из них, которые помогут вам начать работу или улучшить обработку исключений.
1. Освободите ресурсы в блоке finally или используйте инструкцию «Try-With-Resource»
Довольно часто вы используете ресурс в своем блоке try, например InputStream, который вам нужно закрыть позже. Распространенной ошибкой в таких ситуациях является закрытие ресурса в конце блока try.
public void doNotCloseResourceInTry() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// используем inputStream для чтения файла
// не делайте этого
inputStream.close();
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
Проблема в том, что этот подход работает отлично до тех пор, пока не генерируется исключение. Все операторы в блоке try будут выполнены, и ресурс будет закрыт.
Но вы не зря добавили блок try. Вы вызываете один или несколько методов, которые могут вызвать исключение, или, может быть, вы сами вызываете исключение. Это означает, что вы можете не дойти до конца блока try. И как следствие, вы не закроете ресурсы.
Поэтому вам следует поместить весь код очистки в блок finally или использовать оператор try-with-resource.
Используйте блок Finally
В отличие от последних нескольких строк вашего блока try, блок finally всегда выполняется. Это происходит либо после успешного выполнения блока try, либо после обработки исключения в блоке catch. Благодаря этому вы можете быть уверены, что освободите все захваченные ресурсы.
public void closeResourceInFinally() {
FileInputStream inputStream = null;
try {
File file = new File("./tmp.txt");
inputStream = new FileInputStream(file);
// используем inputStream для чтения файла
} catch (FileNotFoundException e) {
log.error(e);
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error(e);
}
}
}
}
Оператор Java 7 «Try-With-Resource»
Другой вариант — это оператор try-with-resource, который я объяснил более подробно во введении в обработку исключений Java.
Вы можете использовать его, если ваш ресурс реализует интерфейс AutoCloseable. Это то, что делает большинство стандартных ресурсов Java. Когда вы открываете ресурс в предложении try, он автоматически закрывается после выполнения блока try или обработки исключения.
public void automaticallyCloseResource() {
File file = new File("./tmp.txt");
try (FileInputStream inputStream = new FileInputStream(file);) {
// используем inputStream для чтения файла
} catch (FileNotFoundException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
}
}
2. Конкретные исключения предпочтительнее
Чем конкретнее исключение, которое вы генерируете, тем лучше. Всегда помните, что коллеге, который не знает вашего кода, а может быть, и вам через несколько месяцев, необходимо вызвать ваш метод и обработать исключение.
Поэтому постарайтесь предоставить им как можно больше информации. Это упрощает понимание вашего API. В результате вызывающий ваш метод сможет лучше обработать исключение или избежать его с помощью дополнительной проверки.
Поэтому всегда старайтесь найти класс, который лучше всего подходит для вашего исключительного события, например, генерируйте NumberFormatException вместо IllegalArgumentException. И избегайте создания неспецифического исключения.
public void doNotDoThis() throws Exception {
...
}
public void doThis() throws NumberFormatException {
...
}
3. Документируйте определенные вами исключения
Каждый раз, когда вы определяете исключение в сигнатуре вашего метода, вы также должны задокументировать его в своем Javadoc. Это преследует ту же цель, что и предыдущая передовая практика: предоставить вызывающему как можно больше информации, чтобы он мог избежать или обработать исключение.
Итак, не забудьте добавить объявление @throws в свой Javadoc и описать ситуации, которые могут вызвать исключение.
/**
* Этот метод делает что-то чрезвычайно полезное ...
*
* @param input
* @throws MyBusinessException, если ... происходит
*/
public void doSomething(String input) throws MyBusinessException {
...
}
4. Генерирование исключений с описательными сообщениями
Идея, лежащая в основе этой передовой практики, аналогична двум предыдущим. Но на этот раз вы не предоставляете информацию вызывающей стороне вашего метода. Сообщение об исключении читают все, кто должен понимать, что произошло, когда исключение было зарегистрировано в файле журнала или в вашем инструменте мониторинга.
Следовательно, он должен как можно точнее описать проблему и предоставить наиболее актуальную информацию для понимания исключительного события.
Не поймите меня неправильно; вы не должны писать абзац текста. Но вам следует объяснить причину исключения в 1-2 коротких предложениях. Это помогает вашей группе эксплуатации понять серьезность проблемы, а также упрощает анализ любых инцидентов, связанных с обслуживанием.
Если вы выберете конкретное исключение, его имя класса, скорее всего, уже будет описывать тип ошибки. Таким образом, вам не нужно предоставлять много дополнительной информации. Хорошим примером этого является NumberFormatException. Оно вызывается конструктором класса java.lang.Long, когда вы предоставляете String в неправильном формате.
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
}
Название класса NumberFormatException уже говорит вам о типе проблемы. Его сообщение должно содержать только строку ввода, которая вызвала проблему. Если имя класса исключения не так выразительно, вам необходимо предоставить необходимую информацию в сообщении.
17:17:26,386 ERROR TestExceptionHandling:52 - java.lang.NumberFormatException: For input string: "xyz"
5. Сначала перехватите наиболее конкретное исключение
Большинство IDE помогут вам в этой лучшей практике. Они сообщают о недостижимом блоке кода, когда вы сначала пытаетесь перехватить менее конкретное исключение.
Проблема в том, что выполняется только первый блок catch, соответствующий исключению. Итак, если вы сначала поймаете IllegalArgumentException, вы никогда не достигнете блока catch, который должен обрабатывать более конкретное NumberFormatException, потому что это подкласс IllegalArgumentException.
Всегда сначала перехватывайте наиболее конкретный класс исключения и добавляйте менее конкретные блоки перехвата в конец вашего списка.
Пример такого оператора try-catch представлен в следующем фрагменте кода. Первый блок catch обрабатывает все NumberFormatException, а второй — все IllegalArgumentException, которые не являются NumberFormatException.
public void catchMostSpecificExceptionFirst() {
try {
doSomething("Сообщение");
} catch (NumberFormatException e) {
log.error(e);
} catch (IllegalArgumentException e) {
log.error(e)
}
}
6. Не перехватывайте Throwable
Throwable — это суперкласс всех исключений и ошибок. Вы можете использовать его в предложении catch, но никогда не должны этого делать!
Если вы используете Throwable в предложении catch, он не только перехватит все исключения; он также перехватит все ошибки. JVM выдает ошибки, чтобы указать на серьезные проблемы, которые не предназначены для обработки приложением. Типичными примерами этого являются OutOfMemoryError или StackOverflowError. И то, и другое вызвано ситуациями, которые находятся вне контроля приложения и не могут быть обработаны.
Итак, лучше не перехватывайте Throwable, если вы не абсолютно уверены, что находитесь в исключительной ситуации, в которой вы можете или обязаны обрабатывать ошибку.
public void doNotCatchThrowable() {
try {
// делает что-нибудь
} catch (Throwable t) {
// не делает этого!
}
}
7. Не игнорируйте исключения
Вы когда-нибудь анализировали отчет об ошибке, в котором выполнялась только первая часть вашего сценария использования?
Часто это вызвано игнорируемым исключением. Разработчик, вероятно, был уверен, что оно никогда не будет вызвано, и добавил блок catch, который не обрабатывает и не регистрирует его. И когда вы найдете этот блок, вы, скорее всего, даже найдете один из известных комментариев «Этого никогда не будет».
public void doNotIgnoreExceptions() {
try {
// делает что-нибудь
} catch (NumberFormatException e) {
// это никогда не выполнится
}
}
Что ж, возможно, вы анализируете проблему, в которой произошло невозможное.
Поэтому, пожалуйста, никогда не игнорируйте исключения. Вы не знаете, как код изменится в будущем. Кто-то может удалить проверку, которая предотвратила исключительное событие, не осознавая, что это создает проблему. Или код, который генерирует исключение, изменяется и теперь генерирует несколько исключений одного и того же класса, а вызывающий код не предотвращает их все.
Вы должны хотя бы написать сообщение в журнале, сообщающее всем, что произошло немыслимое и что кто-то должен это проверить.
public void logAnException() {
try {
// делает что-нибудь
} catch (NumberFormatException e) {
log.error("Это никогда не должно происходить: " + e);
}
}
8. Не пишите в лог сгенерированные исключения
Это, вероятно, наиболее часто игнорируемая передовая практика в списке. Вы можете найти множество фрагментов кода и даже библиотек, в которых исключение перехватывается, регистрируется и повторно генерируется.
try {
new Long("xyz");
} catch (NumberFormatException e) {
log.error(e);
throw e;
}
Может показаться интуитивно понятным регистрировать исключение, когда оно произошло, а затем повторно генерировать его, чтобы вызывающий мог обработать его соответствующим образом. Но, в таком случае, приложение будет писать в лог несколько сообщений об ошибках для одного и того же исключения.
17:44:28,945 ERROR TestExceptionHandling:65 - java.lang.NumberFormatException: For input string: "xyz"
Exception in thread "main" java.lang.NumberFormatException: For input string: "xyz"
at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
at java.lang.Long.parseLong(Long.java:589)
at java.lang.Long.(Long.java:965)
at com.stackify.example.TestExceptionHandling.logAndThrowException(TestExceptionHandling.java:63)
at com.stackify.example.TestExceptionHandling.main(TestExceptionHandling.java:58)
Повторные сообщения также не добавляют никакой информации. Как объясняется в лучшей практике №4, сообщение об исключении должно описывать исключительное событие. А трассировка стека сообщает вам, в каком классе, методе и строке было сгенерировано исключение.
Если вам нужно добавить дополнительную информацию, вы должны перехватить исключение и обернуть его в пользовательское. Но обязательно следуйте передовой практике номер 9.
public void wrapException(String input) throws MyBusinessException {
try {
// делает что-нибудь
} catch (NumberFormatException e) {
throw new MyBusinessException("Сообщение с описанием ошибки.", e);
}
}
Итак, перехватывайте исключение, только если вы хотите его обработать. В противном случае укажите это в сигнатуре метода и позвольте вызывающей стороне позаботиться об этом.
9. Оберните исключение, не обрабатывая его
Иногда лучше поймать стандартное исключение и превратить его в настраиваемое. Типичным примером такого исключения является бизнес-исключение для конкретного приложения или платформы. Это позволяет вам добавлять дополнительную информацию, а также вы можете реализовать специальную обработку для вашего класса исключения.
Когда вы это сделаете, обязательно установите исходное исключение в качестве причины. Класс Exception предоставляет определенные методы конструктора, которые принимают Throwable в качестве параметра. В противном случае вы потеряете трассировку стека и сообщение об исходном исключении, что затруднит анализ исключительного события, вызвавшего ваше исключение.
public void wrapException(String input) throws MyBusinessException {
try {
// делает что-нибудь
} catch (NumberFormatException e) {
throw new MyBusinessException("Сообщение с описанием ошибки.", e);
}
}
Резюме
Как вы видели, есть много разных вещей, которые вы должны учитывать, когда генерируете или перехватываете исключение. Большинство из них имеют цель улучшить читаемость вашего кода или удобство использования вашего API.
Чаще всего исключения являются одновременно механизмом обработки ошибок и средством связи. Поэтому вам следует обязательно обсудить передовые практики и правила, которые вы хотите применять, со своими коллегами, чтобы все понимали общие концепции и использовали их одинаково.
I have the following code:
TestClass test=new TestClass();
test.setSomething1(0); //could, but probably won't throw Exception
test.setSomething2(0); //could, but probably won't throw Exception
I would like to execute: test.setSomething2(0);
even if test.setSomething(0)
(the line above it) throws an exception. Is there a way to do this OTHER than:
try{
test.setSomething1(0);
}catch(Exception e){
//ignore
}
try{
test.setSomething2(0);
}catch(Exception e){
//ignore
}
I have a lot of test.setSomething’s in a row and all of them could throw Exceptions. If they do, I just want to skip that line and move to the next one.
For clarification, I don’t care if it throws an Exception, and I can’t edit the source code of the code which throws this exception.
THIS IS A CASE WHERE I DON’T CARE ABOUT THE EXCEPTIONS (please don’t use universally quantified statements like «you should never ignore Exceptions»). I am setting the values of some Object. When I present the values to a user, I do null checks anyway, so it doesn’t actually matter if any of the lines of code execute.
asked Feb 22, 2015 at 15:15
NickNick
1,7235 gold badges23 silver badges38 bronze badges
7
try {
// Your code...
} catch (Exception ignore) { }
Use the word ignore
after the Exception
keyword.
answered Sep 15, 2016 at 19:17
Joe AlmoreJoe Almore
3,9169 gold badges51 silver badges76 bronze badges
3
There is no way to fundamentally ignore a thrown exception. The best that you can do is minimize the boilerplate you need to wrap the exception-throwing code in.
If you are on Java 8, you can use this:
public static void ignoringExc(RunnableExc r) {
try { r.run(); } catch (Exception e) { }
}
@FunctionalInterface public interface RunnableExc { void run() throws Exception; }
Then, and implying static imports, your code becomes
ignoringExc(() -> test.setSomething1(0));
ignoringExc(() -> test.setSomething2(0));
answered Feb 22, 2015 at 15:26
Marko TopolnikMarko Topolnik
195k28 gold badges315 silver badges432 bronze badges
4
IntelliJ Idea IDE suggests to rename a variable to ignored
when it isn’t used.
This is my sample code.
try {
messageText = rs.getString("msg");
errorCode = rs.getInt("error_code");
} catch (SQLException ignored) { }
answered Jul 2, 2019 at 10:52
ivanoklidivanoklid
811 silver badge4 bronze badges
Unfortunately no, there isn’t, and this is by intention. When used correctly, exceptions should not be ignored as they indicate that something didn’t work and that you probably shouldn’t continue down your normal execution path. Completely ignoring exceptions is an example of the ‘Sweep it under the rug’ anti-pattern, which is why the language doesn’t support doing so easily.
Perhaps you should look at why TestClass.setSomething is throwing exceptions. Is whatever you’re trying to ‘test’ really going to be valid if a bunch of setter methods didn’t work correctly?
answered Feb 22, 2015 at 15:21
DogsDogs
2,8051 gold badge17 silver badges15 bronze badges
You can’t ignore exception in Java. If a method declares being able to throw something this is because something important can’t be done, and the error can’t be corrected by the method designer. So if you really wan’t to simplify your life encapsulate the method call in some other method like this :
class MyExceptionFreeClass {
public static void setSomething1(TestClass t,int v) {
try {
t.setSomething1(v);
} catch (Exception e) {}
public static void setSomething2(TestClass t,int v) {
try {
t.setSomething2(v);
} catch (Exception e) {}
}
and call it when you need it:
TestClass test=new TestClass();
MyExceptionFreeClass.setSomething1(test,0);
MyExceptionFreeClass.setSomething2(test,0);
answered Feb 22, 2015 at 15:25
You should not ignore Exceptions. You should handle them. If you want to make your test code simple, then add the try-catch block into your functions. The greatest way to ignore exceptions is to prevent them by proper coding.
answered Feb 22, 2015 at 15:21
Lajos ArpadLajos Arpad
62.4k37 gold badges98 silver badges174 bronze badges
I know this is old, but I do think there are occasions when you want to ignore an exception. Consider you have a string that contains a delimited set of parts to be parsed. But, this string can sometimes contain say, 6 or 7 or 8 parts. I don’t feel that checking the len each time in order to establish an element exists in the array is as straight forward as simply catching the exception and going on. For example, I have a string delimited by ‘/’ character that I want to break apart:
public String processLine(String inLine) {
partsArray = inLine.split("/");
//For brevity, imagine lines here that initialize
//String elems[0-7] = "";
//Now, parts array may contains 6, 7, or 8 elements
//But if less than 8, will throw the exception
try {
elem0 = partsArray[0];
elem1 = partsArray[1];
elem2 = partsArray[2];
elem3 = partsArray[3];
elem4 = partsArray[4];
elem5 = partsArray[5];
elem6 = partsArray[6];
elem7 = partsArray[7];
catch (ArrayIndexOutOfBoundsException ignored) { }
//Just to complete the example, we'll append all the values
//and any values that didn't have parts will still be
//the value we initialized it to, in this case a space.
sb.append(elem0).append(elem1).append(elem2)...append(elem7);
//and return our string of 6, 7, or 8 parts
//And YES, obviously, this is returning pretty much
//the same string, minus the delimiter.
//You would likely do things to those elem values
//and then return the string in a more formatted way.
//But was just to put out an example where
//you really might want to ignore the exception
return sb.toString();
}
answered Jul 31, 2019 at 14:59
Those who write empty catch blocks shall burn in the Hell for the eternity.
Or worse, they will be forced to debug the damn rubbish they wrote forever and ever.
That said, one thing you might want to do is writing exception handling in a less verbose way. The NoException library is very good at that.
answered Nov 16, 2020 at 18:12
zakmckzakmck
2,6431 gold badge35 silver badges51 bronze badges
Sometimes, you may want to ignore an exception thrown by your Java program without stopping the program execution.
To ignore an exception in Java, you need to add the try...catch
block to the code that can throw an exception, but you don’t need to write anything inside the catch
block.
Let’s see an example of how to do this.
Suppose you have a checkAge()
method that checks whether the age
variable in your code is greater than 18
as follows:
static void checkAge(int age) throws Exception {
if (age < 18) {
throw new Exception( "Age must be greater than 18" );
}
}
In your main()
method, you can surround the call to the checkAge()
method with a try...catch
block.
The catch
block parameter should be named as ignored
to let Java know that this exception is ignored:
public static void main(String[] args) {
try {
checkAge(15);
} catch (Exception ignored) { }
System.out.println("Ignoring the exception");
System.out.println("Continue code as normal");
}
When the exception happens and the catch
block above is executed, nothing will be done by Java.
The exception is ignored and the next line of code below the catch
block will be executed as if nothing has happened.
The output:
Ignoring the exception
Continue code as normal
Process finished with exit code 0
When you have multiple exceptions to ignore, you need to put the try...catch
block in each of the exceptions as shown below:
try {
checkAge(12);
} catch (Exception ignored) { }
try {
checkAge(15);
} catch (Exception ignored) { }
try {
checkAge(16);
} catch (Exception ignored) { }
The code above gets redundant pretty fast. You can optimize the code by creating a wrapper method that has the try...catch
block ready for your runnable function.
You need to create a functional interface that serves as the type of the method you want to ignore.
Consider the ignoreExc()
function below:
static void ignoreExc(Runnable r) {
try {
r.run();
} catch (Exception ignored) { }
}
@FunctionalInterface
interface Runnable {
void run() throws Exception;
}
The call to run()
method inside the ignoreExc()
function will execute the Runnable
you passed into the method.
Finally, you only need to use the ignoreExc()
function to call your actual method with a lambda expression as follows:
public static void main(String[] args) {
ignoreExc(() -> checkAge(16));
ignoreExc(() -> checkAge(17));
ignoreExc(() -> checkAge(18));
System.out.println("Ignoring the exception");
System.out.println("Continue code as normal");
}
The checkAge()
method exceptions will be ignored because there’s nothing in the catch
block of the ignoreExc()
function.
With the ignoreExc()
function, you’ve reduced the need to wrap your code with the try...catch
block each time you want to ignore an exception.
Here’s the full code for ignoring exceptions in Java:
class Main {
public static void main(String[] args) {
ignoreExc(() -> checkAge(16));
ignoreExc(() -> checkAge(17));
ignoreExc(() -> checkAge(18));
System.out.println("Ignoring the exception");
System.out.println("Continue code as normal");
}
static void checkAge(int age) throws Exception {
if (age < 18) {
throw new Exception("Age must be greater than 18");
}
}
static void ignoreExc(Runnable r) {
try {
r.run();
} catch (Exception ignored) { }
}
@FunctionalInterface
interface Runnable {
void run() throws Exception;
}
}
Now you’ve learned how to ignore exceptions thrown by a Java program.
Feel free to use the code in this tutorial for your project. 👍
In this article, I show how to ignore checked exceptions in Java. I will start by describing the rationale behind it and the common pattern to resolve this issue. Then I will present some libraries for that purpose.
Checked and Unchecked Exceptions
In Java, a method can force its caller to deal with the occurrence of potential exceptions. The caller can use the try/catch clause, where the try contains the actual code and catch contains the code to execute when the exception occurs.
Alternatively, the caller can pass on that burden to its „parent caller“. This can go upwards until the main method is reached. If the main method also passes on the exception, the application will crash when an exception happens.
In the case of an exception, there are many scenarios where the application cannot continue to run and needs to stop. There are no alternative paths. Unfortunately, that means Java forces us to write code for a situation where the application shouldn‘t run anymore. Quite useless!
An option is to minimise that boilerplate code. We can wrap the exception into a RuntimeException
, which is an unchecked exception. This has the effect that, even though the application still crashes, we don’t have to provide any handling code.
By no means do we log the exception and let the application continue like nothing has happened. It is possible, but similar to opening Pandora‘s Box.
We call exceptions we have to write extra code for “checked exceptions”, and the other ones of type RuntimeException
“unchecked exceptions”.
Why Checked Exceptions at all?
We can find lots of checked exceptions in third-party libraries and even in the Java Class Library itself. The reason is quite obvious. A library vendor cannot predict in which context the developer will use their code.
Logically, they don’t know if our application has alternative paths. So they leave the decision to us. Their responsibility is to „label“ methods that can potentially throw exceptions. Those labels give us the chance to implement counter-actions.
A good example is the connection to a database. The library vendor marks the connection retrieval method with an exception. If we use the database as a cache, we can send our queries directly to our primary database. This is the alternative path.
If our database is not the cache, there is no way the application can continue to run. And it’s OK if the application crashes:
A lost database connection
Let’s put our theoretical example to real code:
public DbConnection getDbConnection(String username, String password) { try { return new DbProvider().getConnection(username, password); } catch (DbConnectionException dce) { throw new RuntimeException(dce); } }
The database is not used as cache. In the event of a lost connection, we need to stop the application at once.
As described above, we wrap the DbConnectionException into a RuntimeException.
The required code is relatively verbose and always the same. This creates lots of duplication and decreases the readability.
The RuntimeException Wrapper
We can write a function to simplify this. It should wrap a RuntimeException over some code and return the value. We cannot simply pass code in Java. The function must be part of a class or interface. Something like this:
public interface RuntimeExceptionWrappable<T> { T execute() throws Exception; } public class RuntimeExceptionWrapper { public static <T> T wrap(RuntimeExceptionWrappable<T> runtimeExceptionWrappable) { try { return runtimeExceptionWrappable.execute(); } catch (Exception exception) { throw new RuntimeException(exception); } } } public class DbConnectionRetrieverJava7 { public DbConnection getDbConnection(final String username, final String password) { RuntimeExceptionWrappable<DbConnection> wrappable = new RuntimeExceptionWrappable<DbConnection>() { public DbConnection execute() throws Exception { return new DbProvider().getConnection(username, password); } }; return RuntimeExceptionWrapper.wrap(wrappable); } }
The RuntimeException wrapping has been extracted into its own class. In terms of software design this might be the more elegant solution. Still, given the amount of code, we hardly can say the situation got better.
With Java 8 lambdas, things got easier. If we have an interface with one method only, then we just write the specific code of that method. The compiler does the rest for us. The unnecessary or „syntactic sugar code“ to create a specific or anonymous class is not required any more. That’s the basic use case for lambdas.
In Java 8, our example above looks like:
@FunctionalInterface public interface RuntimeExceptionWrappable<T> { T execute() throws Exception; } public class DbConnectionRetrieverJava8 { public DbConnection getDbConnection(String username, String password) { return RuntimeExceptionWrapper.wrap(() -> new DbProvider().getConnection(username, password)); } }
The difference is quite obvious. The code is more concise.
Exceptions in Streams & Co.
RuntimeExceptionWrappable
is a very generic interface. It is just a function that returns a value. Use cases for that function, or its variations, appear all over. For our convenience, Java‘s Class Library has a set of such common Interfaces built-in. They are in the package java.util.function
and are better known as the „Functional Interfaces“. Our RuntimeExceptionWrappable
is similar to java.util.function.Supplier<T>
.
These interfaces form the prerequisite of the powerful Stream
, Optional
and other features which are also part of Java 8. In particular, Stream
comes with a lot of different methods for processing collections. Many of these methods have a «Functional Interface» as parameter.
Let’s quickly switch the use case. We have a list of URL strings that we want to map into a list of objects of type java.net.URL
.
The following code does not compile:
public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(this::createURL) .collect(Collectors.toList()); } private URL createURL(String url) throws MalformedURLException { return new URL(url); }
There is a big problem when it comes to exceptions. The Interfaces defined in java.util.function
don‘t throw exceptions. That’s why our method createURL
doesn’t have the same signature as java.util.funcion.Function
, which is the parameter of the map
method.
What we can do, is to write the try/catch block inside the lambda:
public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(url -> { try { return this.createURL(url); } catch (MalformedURLException e) { throw new RuntimeException(e); } }) .collect(Collectors.toList()); }
This compiles, but doesn‘t look nice either. We can now take a step further and write a wrapper function along a new interface similar to RuntimeExceptionWrappable
:
@FunctionalInterface public interface RuntimeWrappableFunction<T, R> { R apply(T t) throws Exception; } public class RuntimeWrappableFunctionMapper { public static <T, R> Function<T, R> wrap( RuntimeWrappableFunction<T, R> wrappable) { return t -> { try { return wrappable.apply(t); } catch(Exception exception) { throw new RuntimeException(exception); } }; } }
And apply it to our Stream example:
public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(RuntimeWrappableFunctionMapper.wrap(this::createURL)) .collect(Collectors.toList()); } private URL createURL(String url) throws MalformedURLException { return new URL(url); }
Great! Now we have a solution, where we can:
- run code without catching checked exceptions,
- use exception-throwing lambdas in Stream, Optional, etc.
SneakyThrow to Help
The SneakyThrow library lets you skip copying and pasting the code snippets from above. Full disclosure: I am the author.
SneakyThrow comes with two static methods. One runs code without catching checked exceptions. The other method wraps an exception-throwing lambda into one of the Functional Interfaces:
//SneakyThrow returning a result public DbConnection getDbConnection(String username, String password) { return sneak(() -> new DbProvider().getConnection(username, password)); } //SneakyThrow wrapping a function public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(sneaked(this::createURL)) .collect(Collectors.toList()); }
Alternative Libraries
ThrowingFunction
//ThrowingFunction returning a result public DbConnection getDbConnection(String username, String password) { return unchecked(() -> new DbProvider().getConnection(username, password)) .get(); } //ThrowingFunction returning a function public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(unchecked(this::createURL)) .collect(Collectors.toList()); }
In contrast to SneakyThrow, ThrowingFunction can’t execute code directly. Instead, we have to wrap it into a Supplier and call the Supplier afterwards. This approach can be more verbose than SneakyThrow.
If you have multiple unchecked
Functional Interfaces in one class, then you have to write the full class name with each static method. This is because unchecked
does not work with method overloading.
On the other hand, ThrowingFunction provides you with more features than SneakyThrow. You can define a specific exception you want to wrap. It is also possible that your function returns an Optional
, aka “lifting”.
I designed SneakyThrow as an opinionated wrapper of ThrowingFunction.
Vavr
Vavr, aka JavaSlang, is another alternative. In contrast to SneakyThrow and ThrowingFunction it provides a complete battery of useful features that enhance Java’s functionality.
For example, it comes with pattern matching, tuples, an own Stream and much more. If you haven’t heard of it, it is definitely worth a look. Prepare to invest some time in order to understand its full potential.
//Vavr returning a result public DbConnection getDbConnection(String username, String password) { return Try.of(() -> new DbProvider().getConnection(username, password)) .get(); } //Vavr returning a function public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(url -> Try.of(() -> this.createURL(url)).get()) .collect(Collectors.toList()); }
Project Lombok
Such a list of libraries is not complete without mentioning Lombok. Like Vavr, it offers much more functionality than just wrapping checked exceptions. It is a code generator for boilerplate code and creates full Java Beans, Builder objects, logger instances and much more.
Lombok achieves its goals by bytecode manipulation. Therefore, we require an additional plugin in our IDE.
@SneakyThrows is Lombok’s annotation for manipulating a function with a checked exception into one that doesn’t. This approach doesn’t depend on the usage of lambdas, so you can use it for all cases. It is the least verbose library.
Please keep in mind that Lombok manipulates the bytecode which can cause problems with other tooling you might use.
//Lombok returning a result @SneakyThrows public DbConnection getDbConnection(String username, String password) { return new DbProvider().getConnection(username, password); } //Lombok returning a function public List<URL> getURLs() { return Stream .of("https://www.hahnekamp.com", "https://www.austria.info") .map(this::createURL) .collect(Collectors.toList()); } @SneakyThrows private URL createURL(String url) { return new URL(url); }
Further Reading
The code is available on GitHub.
- https://docs.oracle.com/javase/tutorial/essential/exceptions/runtime.html
- https://www.artima.com/intv/handcuffs.html
- http://www.informit.com/articles/article.aspx?p=2171751&seqNum=3
- https://github.com/rainerhahnekamp/sneakythrow
- https://projectlombok.org/features/SneakyThrows
- https://github.com/pivovarit/ThrowingFunction
- https://github.com/vavr-io/vavr
- http://www.baeldung.com/java-lambda-exceptions
We all write from time to time code like this:
try {
// ... some code.
} catch (SomeException e) {
// ... No action is required, just ignore.
}
Is there any standard code fragment like annotation to show we really intend to ignore exception? Something that shows to other team members and static analyzers we really need to skip this situation like InterruptedException
after Thread.sleep()
? Something like:
Exception.ignore(e);
Googled around but have not found something standard for such case.
This is especially relevant to tests that assure exceptions:
try {
action();
fail("We expected this to fail.");
} catch (ExpectedException e) {
ignore(e, "OK, so far so good.");
}
asked Aug 12, 2014 at 13:55
Roman NikitchenkoRoman Nikitchenko
12.7k7 gold badges74 silver badges109 bronze badges
6
The only way to ignore an exception is to catch & swallow it, being very specific on the exception of course, you wouldn’t want to catch Exception e
, that would be a very bad idea.
try{
... //code
}
catch( VerySpecificException ignore){
Log(ignore);
}
Logging is obviously optional but a good practice.
answered Aug 12, 2014 at 13:57
3
in order to keep the code up with your exception handling or ignoring, it’s nice to name the exception var as ignored
:
try {
action();
} catch (ExpectedException ignored ) {}
answered Aug 12, 2014 at 14:02
injecteerinjecteer
19.8k4 gold badges44 silver badges87 bronze badges
2
Concerning your update referring to testing, if you use a testing framework, you can annotate the test in such a way that an exception is expected, e.g. using TestNG
@Test(expectedExceptions = ClassNotFoundException.class)
public void testSomething() {
// will succeed if a ClassNotFoundException is thrown
}
answered Aug 12, 2014 at 14:12
FlorianFlorian
1,1321 gold badge9 silver badges21 bronze badges
1