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. 👍
Исключение (exception) — это ненормальная ситуация (термин «исключение» здесь следует понимать как «исключительная ситуация»), возникающая во время выполнения программного кода. Иными словами, исключение — это ошибка, возникающая во время выполнения программы (в runtime).
Исключение — это способ системы Java (в частности, JVM — виртуальной машины Java) сообщить вашей программе, что в коде произошла ошибка. К примеру, это может быть деление на ноль, попытка обратиться к массиву по несуществующему индексу, очень распространенная ошибка нулевого указателя (NullPointerException
) — когда вы обращаетесь к ссылочной переменной, у которой значение равно null и так далее.
В любом случае, с формальной точки зрения, Java не может продолжать выполнение программы.
Обработка исключений (exception handling) — название объектно-ориентированной техники, которая пытается разрешить эти ошибки.
Программа в Java может сгенерировать различные исключения, например:
-
программа может пытаться прочитать файл из диска, но файл не существует;
-
программа может попытаться записать файл на диск, но диск заполнен или не отформатирован;
-
программа может попросить пользователя ввести данные, но пользователь ввел данные неверного типа;
-
программа может попытаться осуществить деление на ноль;
-
программа может попытаться обратиться к массиву по несуществующему индексу.
Используя подсистему обработки исключений Java, можно управлять реакцией программы на появление ошибок во время выполнения. Средства обработки исключений в том или ином виде имеются практически во всех современных языках программирования. В Java подобные инструменты отличаются большей гибкостью, понятнее и удобнее в применении по сравнению с большинством других языков программирования.
Преимущество обработки исключений заключается в том, что она предусматривает автоматическую реакцию на многие ошибки, избавляя от необходимости писать вручную соответствующий код.
В Java все исключения представлены отдельными классами. Все классы исключений являются потомками класса Throwable
. Так, если в программе возникнет исключительная ситуация, будет сгенерирован объект класса, соответствующего определенному типу исключения. У класса Throwable
имеются два непосредственных подкласса: Exception
и Error
.
Исключения типа Error
относятся к ошибкам, возникающим в виртуальной машине Java, а не в прикладной программе. Контролировать такие исключения невозможно, поэтому реакция на них в приложении, как правило, не предусматривается. В связи с этим исключения данного типа не будут рассматриваться в книге.
Ошибки, связанные с работой программы, представлены отдельными подклассами, производными от класса Exception
. В частности, к этой категории относятся ошибки деления на нуль, выхода за пределы массива и обращения к файлам. Подобные ошибки следует обрабатывать в самой программе. Важным подклассом, производным от Exception
, является класс RuntimeException
, который служит для представления различных видов ошибок, часто возникающих во время выполнения программ.
Каждой исключительной ситуации поставлен в соответствие некоторый класс. Если подходящего класса не существует, то он может быть создан разработчиком.
Так как в Java ВСЁ ЯВЛЯЕТСЯ ОБЪЕКТОМ, то исключение тоже является объектом некоторого класса, который описывает исключительную ситуацию, возникающую в определенной части программного кода.
«Обработка исключений» работает следующим образом:
-
когда возникает исключительная ситуация, JVM генерирует (говорят, что JVM ВЫБРАСЫВАЕТ исключение, для описания этого процесса используется ключевое слово
throw
) объект исключения и передает его в метод, в котором произошло исключение; -
вы можете перехватить исключение (используется ключевое слово
catch
), чтобы его каким-то образом обработать. Для этого, необходимо определить специальный блок кода, который называется обработчиком исключений, этот блок будет выполнен при возникновении исключения, код должен содержать реакцию на исключительную ситуацию; -
таким образом, если возникнет ошибка, все необходимые действия по ее обработке выполнит обработчик исключений.
Если вы не предусмотрите обработчик исключений, то исключение будет перехвачено стандартным обработчиком Java. Стандартный обработчик прекратит выполнение программы и выведет сообщение об ошибке.
Рассмотрим пример исключения и реакцию стандартного обработчика Java.
public static void main(String[] args) {
System.out.println(5 / 0);
Мы видим, что стандартный обработчик вывел в консоль сообщение об ошибке. Давайте разберемся с содержимым этого сообщения:
«C:Program FilesJavajdk1.8.0_60binjava»…
Exception in thread «main» java.lang.ArithmeticException: / by zero
at ua.opu.Main.main(Main.java:6)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
Process finished with exit code 1
Exception in thread «main» java.lang.ArithmeticException: / by zero
сообщает нам тип исключения, а именно класс ArithmeticException
(про классы исключений мы будем говорить позже), после чего сообщает, какая именно ошибка произошла. В нашем случае это деление на ноль.
at ua.opu.Main.main(Main.java:6)
в каком классе, методе и строке произошло исключение. Используя эту информацию, мы можем найти ту строчку кода, которая привела к исключительной ситуации, и предпринять какие-то действия. Строки
at ua.opu.Main.main(Main.java:6)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
называются «трассировкой стека» (stack tracing). О каком стеке идет речь? Речь идет о стеке вызовов (call stack). Соответственно, эти строки означают последовательность вызванных методов, начиная от метода, в котором произошло исключение, заканчивая самым первым вызванным методом.
Для вызова методов в программе используется инструкция «call». Когда вы вызываете метод в программе, важно сохранить адрес следующей инструкции, чтобы, когда вызванный метод отработал, программа продолжила работу со следующей инструкции. Этот адрес нужно где-то хранить в памяти. Также перед вызовом необходимо сохранить аргументы функции, которые тоже необходимо где-то хранить.
Вся эта информация хранится в специальной структуре – стеке вызовов. Каждая запись в стеке вызовов называется кадром или фреймом (stack frame).
Таким образом, зная, какая строка привела к возникновению исключения, вы можете изменить код либо предусмотреть обработчик событий.
Как уже было сказано выше, исключение это объект некоторого класса. В Java существует разветвленная иерархия классов исключений.
В Java, класс исключения служит для описания типа исключения. Например, класс NullPointerException
описывает исключение нулевого указателя, а FileNotFoundException
означает исключение, когда файл, с которым пытается работать приложение, не найден. Рассмотрим иерархию классов исключений:
На самом верхнем уровне расположен класс Throwable
, который является базовым для всех исключений (как мы помним, JVM «выбрасывает» исключение», поэтому класс Throwable
означает – то, что может «выбросить» JVM).
От класса Throwable
наследуются классы Error
и Exception
. Среди подклассов Exception
отдельно выделен класс RuntimeException
, который играет важную роль в иерархии исключений.
В Java существует некоторая неопределенность насчет того – существует ли два или три вида исключений.
Если делить исключения на два вида, то это:
-
1.
контролируемые исключения (checked exceptions) – подклассы класса
Exception
, кроме подклассаRuntimeException
и его производных; -
2.
неконтролируемые исключения (unchecked exceptions) – класс
Error
с подклассами, а также классRuntimeException
и его производные;
В некоторых источниках класс Error
и его подклассы выделяют в отдельный вид исключений — ошибки (errors).
Далее мы видим класс Error
. Классы этой ветки составляют вид исключений, который можно обозначить как «ошибки» (errors). Ошибки представляют собой серьезные проблемы, которые не следует пытаться обработать в собственной программе, поскольку они связаны с проблемами уровня JVM.
На самом деле, вы конечно можете предпринять некоторые действия при возникновении ошибок, например, вывести сообщение для пользователя в удобном формате, выслать трассировку стека себе на почту, чтобы понять – что вообще произошло.
Но, по факту, вы ничего не можете предпринять в вашей программе, чтобы эту ошибку исправить, и ваша программа, как правило, при возникновении такой ошибки дальше работать не может.
В качестве примеров «ошибок» можно привести: переполнение стека вызова (класс StackOverflowError
); нехватка памяти в куче (класс OutOfMemoryError
), вследствие чего JVM не может выделить память под новый объект и сборщик мусора не помогает; ошибка виртуальной машины, вследствие которой она не может работать дальше (класс VirtualMachineError
) и так далее.
Несмотря на то, что в нашей программе мы никак не можем помочь этой проблеме, и приложение не может работать дальше (ну как может работать приложение, если стек вызовов переполнен или JVM не может дальше выполнять код?!); знание природы этих ошибок поможет вам предпринять некоторые действия, чтобы избежать этих ошибок в дальнейшем. Например, ошибки типа StackOverflowError
и OutOfMemoryError
могут быть следствием вашего некорректного кода.
Например, попробуем спровоцировать ошибку StackOverflowError
public static void main(String[] args) {
public static void methodA() {
private static void methodB() {
Получим такое сообщение об ошибке
Exception in thread «main» java.lang.StackOverflowError
at com.company.Main.methodB(Main.java:14)
at com.company.Main.methodA(Main.java:10)
at com.company.Main.methodB(Main.java:14)
at com.company.Main.methodA(Main.java:10)
at com.company.Main.methodB(Main.java:14)
at com.company.Main.methodA(Main.java:10)
at com.company.Main.methodB(Main.java:14)
at com.company.Main.methodA(Main.java:10)
Ошибка OutOfMemoryError
может быть вызвана тем, что ваш код, вследствие ошибки при программировании, создает очень большое количество массивных объектов, которые очень быстро заполняют кучу и свободного места не остается.
Exception in thread «main» java.lang.OutOfMemoryError: Java heap space
at java.base/java.util.Arrays.copyOf(Arrays.java:3511)
at java.base/java.util.Arrays.copyOf(Arrays.java:3480)
at java.base/java.util.ArrayList.grow(ArrayList.java:237)
at java.base/java.util.ArrayList.grow(ArrayList.java:244)
at java.base/java.util.ArrayList.add(ArrayList.java:454)
at java.base/java.util.ArrayList.add(ArrayList.java:467)
at com.company.Main.main(Main.java:13)
Process finished with exit code 1
Ошибка VirtualMachineError
может означать, что следует переустановить библиотеки Java.
В любом случае, следует относиться к типу Error
не как к неизбежному злу и «воле богов», а просто как к сигналу к тому, что в вашем приложении что-то не так, или что-то не так с программным или аппаратным обеспечением, которое вы используете.
Класс Exception
описывает исключения, связанные непосредственно с работой программы. Такого рода исключения «решаемы» и их грамотная обработка позволит программе работать дальше в нормальном режиме.
В классе Exception описаны исключения двух видов: контролируемые исключения (checked exceptions) и неконтролируемые исключения (unchecked exceptions).
Неконтролируемые исключения содержатся в подклассе RuntimeException
и его наследниках. Контролируемые исключения содержатся в остальных подклассах Exception
.
В чем разница между контролируемыми и неконтролируемыми исключениями, мы узнаем позже, а теперь рассмотрим вопрос – а как же именно нам обрабатывать исключения?
Обработка исключений в методе может выполняться двумя способами:
-
1.
с помощью связки
try-catch
; -
2.
с помощью ключевого слова
throws
в сигнатуре метода.
Рассмотрим оба метода поподробнее:
Способ 1. Связка try-catch
Этот способ кратко можно описать следующим образом.
Код, который теоретически может вызвать исключение, записывается в блоке try{}
. Сразу за блоком try
идет блок код catch{}
, в котором содержится код, который будет выполнен в случае генерации исключения. В блоке finally{}
содержится код, который будет выполнен в любом случае – произошло ли исключение или нет.
Теперь разберемся с этим способом более подробно. Рассмотрим следующий пример – программу, которая складывает два числа, введенные пользователем из консоли
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println(«Введите первое число: «);
String firstNumber = scanner.nextLine();
System.out.println(«Введите второе число: «);
String secondNumber = scanner.nextLine();
a = Integer.parseInt(firstNumber);
b = Integer.parseInt(secondNumber);
System.out.println(«Результат: « + (a + b));
Первое, что нам нужно определить – и что является главным при работе с исключениями, КАКАЯ ИНСТРУКЦИЯ МОЖЕТ ПРИВЕСТИ К ВОЗНИКНОВЕНИЮ ИСКЛЮЧЕНИЯ?
То есть, мы должны понять – где потенциально у нас может возникнуть исключение? Понятно, что речь идет не об операции сложения и не об операции чтения данных из консоли. Потенциально опасными строчками кода здесь являются строчки
a = Integer.parseInt(firstNumber);
b = Integer.parseInt(secondNumber);
в которых происходит преобразование ввода пользователя в целое число (метод parseInt()
преобразует цифры в строке в число).
Почему здесь может возникнуть исключение? Потому что пользователь может ввести не число, а просто какой-то текст и тогда непонятно – что записывать в переменную a
или b
. И да, действительно, если пользователь введет некорректное значение, возникнет исключение в методе Integer.parseInt()
.
Итак, что мы можем сделать. «Опасный код» нужно поместить в блок try{}
Обратите внимание на синтаксис блока try
. В самом простом случае это просто ключевое слово try
, после которого идут парные фигурные скобки. Внутри этих скобок и заключается «опасный» код, который может вызвать исключение. Сразу после блока try должен идти блок catch()
.
a = Integer.parseInt(firstNumber);
b = Integer.parseInt(secondNumber);
} catch (NumberFormatException e) {
// сохранить текст ошибки в лог
System.out.println(«Одно или оба значения некорректны!»);
System.out.println(«Результат: « + (a + b));
Обратите внимание на синтаксис блока catch
. После ключевого слова, в скобках описывается аргумент с именем e
типа NumberFormatException
.
Когда произойдет исключение, то система Java прервет выполнение инструкций в блоке try
и передаст управление блоку catch
и запишет в этот аргумент объект исключения, который сгенерировала Java-машина.
То есть, как только в блоке try
возникнет исключение, то дальше инструкции в блоке try выполняться не будут! А сразу же начнут выполняться действия в блоке catch
.
Обработчик исключения находится в блоке catch
, в котором мы можем отреагировать на возникновение исключения. Также, в этом блоке нам будет доступен объект исключения, от которого мы можем получить дополнительные сведения об исключении.
Блок catch
сработает только в том случае, если указанный в скобках тип объекта исключения будет суперклассом или будет того же типа, что и объект исключения, который сгенерировала Java.
Например, если в нашем примере мы напишем код, который потенциально может выбросить исключение типа IOException
, но не изменим блок catch
} catch (NumberFormatException e) {
// сохранить текст ошибки в лог
System.out.println(«Одно или оба значения некорректны!»);
тогда обработчик не будет вызван и исключение будет обработано стандартным обработчиком Java.
Способ 2. Использование ключевого слова throws
Второй способ позволяет передать обязанность обработки исключения тому методу, который вызывает данный метод (а тот, в свою очередь может передать эту обязанность выше и т.д.).
Изменим наш пример и выделим в отдельный метод код, который будет запрашивать у пользователя число и возвращать его как результат работы метода
public static void main(String[] args) {
int a = getNumberFromConsole(«Введите первое число»);
int b = getNumberFromConsole(«Введите второе число»);
System.out.println(«Результат: « + (a + b));
public static int getNumberFromConsole(String message) {
Scanner scanner = new Scanner(System.in);
System.out.print(message + «: «);
String s = scanner.nextLine();
return Integer.parseInt(s);
Мы понимаем, что в данном методе может произойти исключение, но мы не хотим или не можем его обработать. Причины могут быть разными, например:
-
1.
обработка исключений может происходить централизованно однотипным способом (например, показ окошка с сообщением и с определенным текстом);
-
2.
это не входит в нашу компетенцию как программиста – обработкой исключений занимается другой программист;
-
3.
мы пишем только некоторую часть программы и непонятно – как будет обрабатывать исключение другой программист, который потом будет использовать наш код (например, мы пишем просто какую-то библиотеку, которая производит вычисления, и как будет выглядеть обработка – это не наше дело).
В любом случае, мы знаем, что в этом коде может быть исключение, но мы не хотим его обрабатывать, а хотим просто предупредить другой метод, который будет вызывать наш код, что выполнение кода может привести к исключению. В этом случае, используется ключевое слово throws
, которое указывается в сигнатуре метода
public static int getNumberFromConsole(String message) throws NumberFormatException {
Scanner scanner = new Scanner(System.in);
System.out.print(message + «: «);
String s = scanner.nextLine();
return Integer.parseInt(s);
Обратите внимание на расположение сигнатуру метода. Мы привыкли, что при объявлении метода сразу после скобок входных аргументов мы открываем фигурную скобку и записываем тело метода. Здесь же, после входных аргументов, мы пишем ключевое слово throws
и потом указываем тип исключения, которое может быть сгенерировано в нашем методе. Если метод может выбрасывать несколько типов исключений, они записываются через запятую
public static void foo() throws NumberFormatException, ArithmeticException, IOException {
Тогда, в методе main мы должны написать примерно следующее
public static void main(String[] args) {
a = getNumberFromConsole(«Введите первое число»);
b = getNumberFromConsole(«Введите второе число»);
} catch (NumberFormatException e) {
// сохранить текст ошибки в лог
System.out.println(«Одно или оба значения некорректны!»);
System.out.println(«Результат: « + (a + b));
Основное преимущество этого подхода – мы передаем обязанность по обработке исключений другому, вышестоящему методу.
Отличия между контролируемыми и неконтролируемыми исключениями
Отличия между контролируемыми и неконтролируемыми исключениями
Если вы вызываете метод, который выбрасывает checked исключение, то вы ОБЯЗАНЫ предусмотреть обработку возможного исключения, то есть связку try-catch
.
Яркий пример checked исключения – класс IOException
и его подклассы.
Рассмотрим пример – попробуем прочитать файл и построчно вывести его содержимое на экран консоли:
public static void main(String[] args) {
Path p = Paths.get(«c:\temp\file.txt»);
BufferedReader reader = Files.newBufferedReader(p);
while ((line = reader.readLine()) != null) {
System.out.println(line);
Как мы видим, компилятор не хочет компилировать наш код. Чем же он недоволен? У нас в коде происходит вызов двух методов – статического метода Files.newBufferedReader()
и обычного метода BufferedReader.readLine()
.
Если посмотреть на сигнатуры этих методов то можно увидеть, что оба этих метода выбрасывают исключения типа IOException
. Этот тип исключения относится к checked-исключению и поэтому, если вы вызываете эти методы, компилятор ТРЕБУЕТ от вас предусмотреть блок catch
, либо в самом вашем методе указать throws IOException
и, таким образом, передать обязанность обрабатывать исключение другому методу, который будет вызывать ваш.
Таким образом, «оборачиваем» наш код в блок try
и пишем блок catch
.
public static void main(String[] args) {
Path p = Paths.get(«c:\temp\file.txt»);
BufferedReader reader = Files.newBufferedReader(p);
while ((line = reader.readLine()) != null) {
System.out.println(line);
} catch (IOException e) {
System.out.println(«Ошибка при чтении файла!»);
Еще один способ — указать в сигнатуре метода, что он выбрасывает исключение типа IOException
и переложить обязанность обработать ошибку в вызывающем коде
public static void main(String[] args) {
Path p = Paths.get(«c:\temp\file.txt»);
} catch (IOException e) {
System.out.println(«Ошибка при чтении файла!»);
public static void printFile(Path p) throws IOException {
BufferedReader reader = Files.newBufferedReader(p);
while ((line = reader.readLine()) != null) {
System.out.println(line);
Eсли метод выбрасывает checked-исключение, то проверка на наличие catch-блока происходит на этапе компиляции. И вы обязаны предусмотреть обработку исключения для checked-исключения.
Что касается unchecked-исключения, то обязательной обработки исключения нет – вы можете оставить подобные ситуации без обработки.
Зачем необходимо наличие двух видов исключений?
Зачем необходимо наличие двух видов исключений?
В большинстве языков существует всего лишь один тип исключений – unchecked. Некоторые языки, например, C#, в свое время отказались от checked-исключений.
Во-первых, мы не можем сделать все исключения checked, т.к. очень многие операции могут генерировать исключения, и если каждый такой участок кода «оборачивать» в блок try-catch
, то код получится слишком громоздким и нечитабельным.
С другой стороны, зачем нужно делать некоторые типы исключений checked? Почему просто не сделать все исключения unchecked и оставить решения об обработке исключений целиком на совести программиста?
В официальной документации написано, что unchecked-исключения – это те исключения, от которых программа «не может восстановиться», тогда как checked-исключения позволяют откатить некоторую операцию и повторить ее снова.
На самом деле, если вы посмотрите на различные типы unchecked-исключений, то вы увидите, что большинство их связаны с ошибками самого программиста. Выход за пределы массива, исключение нулевого указателя, деление на ноль – большинство из подобного рода исключений целиком лежат на совести программистов. Тогда мы можем сказать, что лучше программист пишет более хороший код, чем везде вставляет проверки на исключения.
Контролируемые исключения, как правило, представляют те ошибки, которые возникают не из-за программиста и предусмотреть которые программист не может. Например, это отсутствующие файлы, работа с сокетами, подключение к базе данных, сетевые соединения, некорректный пользовательский ввод.
Вы можете написать идеальный код, но потом вы отдадите приложение пользователю, а он введет название файла, которого нет или напишет неправильный IP для сокет-соединения. Таким образом, мы заранее должны быть готовыми к неверным действиям пользователя или к программным или аппаратным проблемам на его стороне и в обязательном порядке предусмотреть обработку возможных исключений.
Дополнительно об исключениях
Дополнительно об исключениях
Рассмотрим детально различные возможности механизма исключений, которые позволяют программисту максимально эффективно противодействовать исключениям:
Java позволяет вам для одного блока try
предусмотреть несколько блоков catch
, каждый из которых должен обрабатывать свой тип исключения
public static void foo() {
} catch (ArithmeticException e) {
// обработка арифметического исключения
} catch (IndexOutOfBoundsException e) {
// обработка выхода за пределы коллекции
} catch (IllegalArgumentException e) {
// обработка некорректного аргумента
Важно помнить, что Java обрабатывает исключения последовательно. Java просматривает блок catch сверху вниз и выполняет первый подходящий блок, который может обработать данное исключение.
Так как вы можете указать как точный класс, так и суперкласс, то если первым блоком будет блок для суперкласса – выполнится он. Например, исключение FileNotFoundException
является подклассом IOException
. И поэтому если вы первым поставите блок с IOException
– он будет вызываться для всех подтипов исключений, в том числе и для FileNotFoundException
и блок c FileNotFoundException
никогда не выполнится.
public static void main(String[] args) {
Path p = Paths.get(«c:\temp\file.txt»);
} catch (IOException e) {
System.out.println(«Ошибка при чтении файла!»);
} catch (FileNotFoundException e) {
// данный блок никогда не будет вызван
public static void printFile(Path p) throws IOException {
BufferedReader reader = Files.newBufferedReader(p);
while ((line = reader.readLine()) != null) {
System.out.println(line);
Один блок для обработки нескольких типов исключений
Один блок для обработки нескольких типов исключений
Начиная с версии Java 7, вы можете использовать один блок catch
для обработки исключений нескольких, не связанных друг с другом типов. Приведем пример
public static void foo() {
} catch (ArithmeticException | IllegalArgumentException | IndexOutOfBoundsException e) {
// три типа исключений обрабатываются одинаково
Как мы видим, один блок catch используется для обработки и типа IOException
и NullPointerException
и NumberFormaException
.
Вы можете использовать вложенные блоки try
, которые могут помещаться в других блоках try
. После вложенного блока try
обязательно идет блок catch
public static void foo() {
} catch (IllegalArgumentException e) {
// обработка вложенного блока try
} catch (ArithmeticException e) {
Выбрасывание исключения с помощью ключевого слова throw
С помощью ключевого слова throw
вы можете преднамеренно «выбросить» определенный тип исключения.
public static void foo(int a) {
throw new IllegalArgumentException(«Аргумент не может быть отрицательным!»);
Кроме блока try
и catch
существует специальный блок finally
. Его отличительная особенность – он гарантированно отработает, вне зависимости от того, будет выброшено исключение в блоке try
или нет. Как правило, блок finally
используется для того, чтобы выполнить некоторые «завершающие» операции, которые могли быть инициированы в блоке try
.
public static void foo(int a) {
FileOutputStream fout = null;
File file = new File(«file.txt»);
fout = new FileOutputStream(file);
} catch (IOException e) {
// обработка исключения при записи в файл
} catch (IOException e) {
При любом развитии события в блоке try
, код в блоке finally
отработает в любом случае.
Блок finally
отработает, даже если в try-catch
присутствует оператор return
.
Как правило, блок finally
используется, когда мы в блоке try
работаем с ресурсами (файлы, базы данных, сокеты и т.д.), когда по окончании блока try-catch
мы освобождаем ресурсы. Например, допустим, в процессе работы программы возникло исключение, требующее ее преждевременного закрытия. Но в программе открыт файл или установлено сетевое соединение, а, следовательно, файл нужно закрыть, а соединение – разорвать. Для этого удобно использовать блок finally
.
Блок try-with-resources
является модификацией блока try
. Данный блок позволяет автоматически закрывать ресурс после окончания работы блока try
и является удобной альтернативой блоку finally
.
public static void foo() {
Path p = Paths.get(«c:\temp\file.txt»);
try (BufferedReader reader = Files.newBufferedReader(p)) {
while ((line = reader.readLine()) != null)
System.out.println(line);
} catch (IOException e) {
Внутри скобок блока try
объявляется один или несколько ресурсов, которые после отработки блока try-catch
будут автоматически освобождены. Для этого объект ресурса должен реализовывать интерфейс java.lang.AutoCloseable
.
Создание собственных подклассов исключений
Создание собственных подклассов исключений
Встроенные в Java исключения позволяют обрабатывать большинство распространенных ошибок. Тем не менее, вы можете создавать и обрабатывать собственные типы исключений. Для того, чтобы создать класс собственного исключения, достаточно определить как его произвольный от Exception
или от RuntimeException
(в зависимости от того, хотите ли вы использовать checked или unchecked – исключения).
Насчет создания рекомендуется придерживаться двух правил:
-
1.
определитесь, исключения какого типа вы хотите использовать для собственных исключений (checked или unchecked) и старайтесь создавать исключения только этого типа;
-
2.
старайтесь максимально использовать стандартные типы исключений и создавать свои типы только в том случае, если существующие типы исключений не отражают суть того исключения, которое вы хотите добавить.
Плохие практики при обработке исключений
Плохие практики при обработке исключений
Ниже представлены действия по обработке ошибок, которые характерны для плохого программиста. Ни в коем случае не рекомендуется их повторять!
-
1.
Указание в блоке catch объекта исключения типа Exception. Существует очень большой соблазн при создании блока
catch
указать тип исключенияException
и, таким образом, перехватывать все исключения, которые относятся к этому классу (а это все исключения, кроме системных ошибок). Делать так крайне не рекомендуется, т.к. вместо того чтобы решать проблему с исключениями, мы фактически игнорируем ее и просто реализуем некоторую «заглушку», чтобы приложение продолжило работу дальше. Кроме того, каждый тип исключения должен быть обработан своим определенным образом. -
2.
Помещение в блок
try
всего тела метода. Следующий плохой прием используется, когда программист не хочет разбираться с кодом, который вызывает исключение и просто, опять же, реализует «заглушку». Этот прием очень «хорошо» сочетается с первым приемом. В блокtry
должен помещаться только тот код, который потенциально может вызвать исключение, а не всё подряд, т.к. лень обрабатывать исключения нормально. -
3.
Игнорирование исключения. Следующий плохой прием состоит в том, что мы просто игнорируем исключение и оставляем блок
catch
пустым. Программа должна реагировать на исключения и должна информировать пользователя и разработчика о том, что что-то пошло не так. Безусловно, исключение это не повод тут же закрывать приложение, а попытаться повторить то действие, которое привело к исключению (например, повторно указать название файла, попытаться открыть базу данных через время и т.д.). В любом случае, когда приложение в ответ на ошибку никак не реагирует – не выдает сообщение, но и не делает того, чего от нее ожидали – это самый плохой вариант.
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
by Rainer Hahnekamp
In this article, I will 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 is similar to opening Pandora’s Box.
We call these exceptions, for which we have to write extra code, checked exceptions. The others of the RuntimeException type we call 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 pretty straightforward. 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
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 a 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 can hardly 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:
@FunctionalInterfacepublic 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 clear: 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.function.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`
:
@FunctionalInterfacepublic 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, and
- use exception-throwing lambdas in Stream, Optional, and so on.
SneakyThrow to the rescue
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 resultpublic DbConnection getDbConnection(String username, String password) { return sneak(() -> new DbProvider().getConnection(username, password));} //SneakyThrow wrapping a functionpublic 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 resultpublic DbConnection getDbConnection(String username, String password) { return unchecked(() -> new DbProvider().getConnection(username, password)).get();} //ThrowingFunction returning a functionpublic 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, otherwise known as “lifting.”
I designed SneakyThrow as an opinionated wrapper of ThrowingFunction.
Vavr
Vavr, or “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, its 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 resultpublic DbConnection getDbConnection(String username, String password) { return Try.of(() -> new DbProvider().getConnection(username, password)) .get();} //Vavr returning a functionpublic 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@SneakyThrowspublic DbConnection getDbConnection(String username, String password) { return new DbProvider().getConnection(username, password);} //Lombok returning a functionpublic List<URL> getURLs() { return Stream .of(“https://www.hahnekamp.com", “https://www.austria.info") .map(this::createURL) .collect(Collectors.toList());} @SneakyThrowsprivate 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
Originally published at www.rainerhahnekamp.com on March 17, 2018.
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started