Questa UtilException
classe di supporto consente di utilizzare tutte le eccezioni verificate nei flussi Java, in questo modo:
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
La nota Class::forName
lancia ClassNotFoundException
, che è selezionata . Anche il flusso stesso genera ClassNotFoundException
, e NON alcune eccezioni non verificate.
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; }
}
Molti altri esempi su come usarlo (dopo l'importazione statica 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");
}
Ma non utilizzarlo prima di aver compreso i seguenti vantaggi, svantaggi e limitazioni :
• Se il codice chiamante deve gestire l'eccezione selezionata, è NECESSARIO aggiungerlo alla clausola dei tiri del metodo che contiene il flusso. Il compilatore non ti costringerà più ad aggiungerlo, quindi è più facile dimenticarlo.
• Se il codice chiamante gestisce già l'eccezione selezionata, il compilatore ricorderà di aggiungere la clausola plausibili alla dichiarazione del metodo che contiene lo stream (in caso contrario, verrà indicato: l'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente ).
• In ogni caso, non sarai in grado di circondare il flusso stesso per catturare l'eccezione selezionata INSIDE il metodo che contiene il flusso (se provi, il compilatore dirà: L'eccezione non viene mai generata nel corpo dell'istruzione try corrispondente).
• Se si chiama un metodo che letteralmente non può mai generare l'eccezione dichiarata, non è necessario includere la clausola di lancio. Ad esempio: new String (byteArr, "UTF-8") genera UnsupportedEncodingException, ma UTF-8 è garantito dalle specifiche Java sempre presenti. Qui, la dichiarazione di lancio è una seccatura e qualsiasi soluzione per silenziarla con una piastra di cottura minima è benvenuta.
• Se odi le eccezioni verificate e ritieni che non dovrebbero mai essere aggiunte al linguaggio Java (un numero crescente di persone la pensa in questo modo, e io NON sono una di queste), non aggiungere l'eccezione controllata al genera la clausola del metodo che contiene il flusso. L'eccezione selezionata si comporterà quindi come un'eccezione NON selezionata.
• Se stai implementando un'interfaccia rigorosa in cui non hai la possibilità di aggiungere una dichiarazione di lancio, e tuttavia lanciare un'eccezione è del tutto appropriata, quindi avvolgere un'eccezione solo per ottenere il privilegio di lanciarla si traduce in uno stack stack con eccezioni spurie che non forniscono informazioni su ciò che è effettivamente andato storto. Un buon esempio è Runnable.run (), che non genera alcuna eccezione verificata. In questo caso, è possibile decidere di non aggiungere l'eccezione selezionata alla clausola throws del metodo che contiene il flusso.
• In ogni caso, se decidete di NON aggiungere (o dimenticate di aggiungere) l'eccezione verificata alla clausola dei tiri del metodo che contiene il flusso, tenete presente queste 2 conseguenze del lancio di eccezioni VERIFICATE:
1) Il codice chiamante non sarà in grado di prenderlo per nome (se si tenta, il compilatore dirà: l'eccezione non viene mai generata nel corpo della corrispondente istruzione try). Bolle e probabilmente sarà catturato nel ciclo principale del programma da qualche "catch Exception" o "catch Throwable", che può essere comunque quello che vuoi.
2) viola il principio della minima sorpresa: non sarà più sufficiente catturare RuntimeException per essere in grado di garantire la cattura di tutte le possibili eccezioni. Per questo motivo, credo che ciò non dovrebbe essere fatto nel codice del framework, ma solo nel codice aziendale che controlli completamente.
In conclusione: credo che i limiti qui non siano gravi e che la UtilException
classe possa essere usata senza paura. Tuttavia, dipende da te!