Цей UtilException
допоміжний клас дозволяє використовувати будь-які перевірені винятки в потоках Java, наприклад:
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
Зверніть увагу на Class::forName
кидки ClassNotFoundException
, які перевіряються . Сам потік також викидає ClassNotFoundException
, і НЕ якийсь обертовий неперевірений виняток.
public final class UtilException {
@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
void accept(T t) throws E;
}
@FunctionalInterface
public interface BiConsumer_WithExceptions<T, U, E extends Exception> {
void accept(T t, U u) throws E;
}
@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
R apply(T t) throws E;
}
@FunctionalInterface
public interface Supplier_WithExceptions<T, E extends Exception> {
T get() throws E;
}
@FunctionalInterface
public interface Runnable_WithExceptions<E extends Exception> {
void run() throws E;
}
/** .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name)))); or .forEach(rethrowConsumer(ClassNameUtil::println)); */
public static <T, E extends Exception> Consumer<T> rethrowConsumer(Consumer_WithExceptions<T, E> consumer) throws E {
return t -> {
try { consumer.accept(t); }
catch (Exception exception) { throwAsUnchecked(exception); }
};
}
public static <T, U, E extends Exception> BiConsumer<T, U> rethrowBiConsumer(BiConsumer_WithExceptions<T, U, E> biConsumer) throws E {
return (t, u) -> {
try { biConsumer.accept(t, u); }
catch (Exception exception) { throwAsUnchecked(exception); }
};
}
/** .map(rethrowFunction(name -> Class.forName(name))) or .map(rethrowFunction(Class::forName)) */
public static <T, R, E extends Exception> Function<T, R> rethrowFunction(Function_WithExceptions<T, R, E> function) throws E {
return t -> {
try { return function.apply(t); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
};
}
/** rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))), */
public static <T, E extends Exception> Supplier<T> rethrowSupplier(Supplier_WithExceptions<T, E> function) throws E {
return () -> {
try { return function.get(); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
};
}
/** uncheck(() -> Class.forName("xxx")); */
public static void uncheck(Runnable_WithExceptions t)
{
try { t.run(); }
catch (Exception exception) { throwAsUnchecked(exception); }
}
/** uncheck(() -> Class.forName("xxx")); */
public static <R, E extends Exception> R uncheck(Supplier_WithExceptions<R, E> supplier)
{
try { return supplier.get(); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
}
/** uncheck(Class::forName, "xxx"); */
public static <T, R, E extends Exception> R uncheck(Function_WithExceptions<T, R, E> function, T t) {
try { return function.apply(t); }
catch (Exception exception) { throwAsUnchecked(exception); return null; }
}
@SuppressWarnings ("unchecked")
private static <E extends Throwable> void throwAsUnchecked(Exception exception) throws E { throw (E)exception; }
}
Багато інших прикладів його використання (після статичного імпорту UtilException
):
@Test
public void test_Consumer_with_checked_exceptions() throws IllegalAccessException {
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.forEach(rethrowConsumer(className -> System.out.println(Class.forName(className))));
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.forEach(rethrowConsumer(System.out::println));
}
@Test
public void test_Function_with_checked_exceptions() throws ClassNotFoundException {
List<Class> classes1
= Stream.of("Object", "Integer", "String")
.map(rethrowFunction(className -> Class.forName("java.lang." + className)))
.collect(Collectors.toList());
List<Class> classes2
= Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
}
@Test
public void test_Supplier_with_checked_exceptions() throws ClassNotFoundException {
Collector.of(
rethrowSupplier(() -> new StringJoiner(new String(new byte[]{77, 97, 114, 107}, "UTF-8"))),
StringJoiner::add, StringJoiner::merge, StringJoiner::toString);
}
@Test
public void test_uncheck_exception_thrown_by_method() {
Class clazz1 = uncheck(() -> Class.forName("java.lang.String"));
Class clazz2 = uncheck(Class::forName, "java.lang.String");
}
@Test (expected = ClassNotFoundException.class)
public void test_if_correct_exception_is_still_thrown_by_method() {
Class clazz3 = uncheck(Class::forName, "INVALID");
}
Але не використовуйте його, перш ніж зрозуміти наступні переваги, недоліки та обмеження :
• Якщо код-виклик повинен обробляти перевірений виняток, ПОВИНЕН додати його до пункту кидків методу, що містить потік. Компілятор більше не змусить вас додавати його, тому простіше забути його.
• Якщо call-код уже обробляє перевірений виняток, компілятор нагадає вам додати пункт кидок до декларації методу, що містить потік (якщо ви цього не зробите, то виняток ніколи не викидається в тіло відповідного оператора спробу ).
• У будь-якому випадку, ви не зможете оточити сам потік, щоб зловити перевірений виняток ВНУТРИ метод, який містить потік (якщо ви спробуєте, компілятор скаже: Виняток ніколи не викидається в тіло відповідного оператора спробу).
• Якщо ви викликаєте метод, який буквально ніколи не може викинути той виняток, який він оголошує, тоді ви не повинні включати пункт про кидки. Наприклад: нова String (byteArr, "UTF-8") кидає UnsupportedEncodingException, але UTF-8 гарантує, що специфікація Java завжди буде присутня. Тут декларація кидків - це неприємність, і будь-яке рішення замовкнути її мінімальними плитами вітається.
• Якщо ви ненавидите перевірені винятки і вважаєте, що їх ніколи не слід додавати до мови Java (для цього зростає кількість людей, і я НЕ один з них), тоді просто не додайте перевірені винятки до кидає пункт методу, який містить потік. Тоді перевірений виняток буде поводитись як виняток, що перевіряється ООН.
• Якщо ви реалізуєте суворий інтерфейс, коли у вас немає можливості додавати декларацію про кидки, і все ж викид винятку цілком доречний, тоді перенесення винятку просто для отримання привілею для його кидання призводить до стек-тракту з помилковими винятками які не дають інформації про те, що насправді пішло не так. Хороший приклад - Runnable.run (), який не кидає перевірених винятків. У цьому випадку ви можете вирішити не додавати перевірений виняток до пункту кидків методу, який містить потік.
• У будь-якому випадку, якщо ви вирішили НЕ додати (або забути додати) перевірений виняток до пункту кидків методу, що містить потік, пам’ятайте про ці 2 наслідки відкидання перевірених винятків:
1) Код виклику не зможе зловити його по імені (якщо ви спробуєте, компілятор скаже: Виняток ніколи не викидається в тіло відповідного оператора спробу). Він буде бульбашком і, ймовірно, буде схоплений у головному циклі програми деяким "catch Exception" або "catch Throwable", який може бути тим, що ви хочете в будь-якому випадку.
2) Це порушує принцип найменшого здивування: його більше не буде достатньо, щоб зловити RuntimeException, щоб мати змогу гарантувати вилов усіх можливих винятків. З цієї причини я вважаю, що це слід робити не в рамковому коді, а лише в бізнес-коді, який ви повністю контролюєте.
На закінчення: я вважаю, що обмеження тут не є серйозними, і UtilException
клас можна використовувати без побоювання. Однак, це залежить від вас!