todas - ¿Cómo puedo lanzar excepciones COMPROBADAS desde las secuencias Java 8?
todas las excepciones en java (17)
¿Cómo puedo lanzar excepciones CHECKED desde Java 8 streams / lambdas?
En otras palabras, quiero hacer un código como este compilar:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(className -> Class.forName(className))
.collect(Collectors.toList());
return classes;
}
Este código no se compila, ya que el método
Class.forName()
anterior arroja
ClassNotFoundException
, que está marcado.
Tenga en cuenta que NO quiero envolver la excepción marcada dentro de una excepción de tiempo de ejecución y en su lugar arrojar la excepción envuelta sin marcar.
Quiero lanzar la excepción marcada
, y sin agregar feos
try
/
catches
a la transmisión.
Aquí hay una vista o solución diferente para el problema original. Aquí muestro que tenemos una opción para escribir un código que procesará solo un subconjunto válido de valores con una opción para detectar y manejar casos cuando se produjo la excepción.
private void run() {
List<String> list = Stream.of(1, 2, 3, 4).map(wrapper(i ->
String.valueOf(++i / 0), i -> String.valueOf(++i))).collect(Collectors.toList());
System.out.println(list.toString());
}
private <T, R, E extends Exception> Function<T, R> wrapper(ThrowingFunction<T, R, E> function,
Function<T, R> onException) {
return i -> {
try {
return function.apply(i);
} catch (ArithmeticException e) {
System.out.println("Exception: " + i);
return onException.apply(i);
} catch (Exception e) {
System.out.println("Other: " + i);
return onException.apply(i);
}
};
}
@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
R apply(T t) throws E;
}
Creo que este enfoque es el correcto:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes;
try {
classes = Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String").map(className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
throw new UndeclaredThrowableException(e);
}
}).collect(Collectors.toList());
} catch (UndeclaredThrowableException e) {
if (e.getCause() instanceof ClassNotFoundException) {
throw (ClassNotFoundException) e.getCause();
} else {
// this should never happen
throw new IllegalStateException(e.getMessage(), e);
}
}
return classes;
}
Envolviendo la excepción marcada dentro de
Callable
en un
UndeclaredThrowableException
(ese es el caso de uso para esta excepción) y desenvolviéndola afuera.
Sí, me parece feo, y recomendaría no usar lambdas en este caso y simplemente recurrir a un buen ciclo anterior, a menos que esté trabajando con una secuencia paralela y la paralelización traiga un beneficio objetivo que justifique la imposibilidad de leer el código.
Como muchos otros han señalado, hay soluciones para esta situación, y espero que una de ellas la convierta en una versión futura de Java.
Estoy de acuerdo con los comentarios anteriores, al usar Stream.map está limitado a implementar la Función que no arroja Excepciones.
Sin embargo, puede crear su propia FunctionalInterface que se muestra a continuación.
@FunctionalInterface
public interface UseInstance<T, X extends Throwable> {
void accept(T instance) throws X;
}
luego impleméntelo usando Lambdas o referencias como se muestra a continuación.
import java.io.FileWriter;
import java.io.IOException;
//lambda expressions and the execute around method (EAM) pattern to
//manage resources
public class FileWriterEAM {
private final FileWriter writer;
private FileWriterEAM(final String fileName) throws IOException {
writer = new FileWriter(fileName);
}
private void close() throws IOException {
System.out.println("close called automatically...");
writer.close();
}
public void writeStuff(final String message) throws IOException {
writer.write(message);
}
//...
public static void use(final String fileName, final UseInstance<FileWriterEAM, IOException> block) throws IOException {
final FileWriterEAM writerEAM = new FileWriterEAM(fileName);
try {
block.accept(writerEAM);
} finally {
writerEAM.close();
}
}
public static void main(final String[] args) throws IOException {
FileWriterEAM.use("eam.txt", writerEAM -> writerEAM.writeStuff("sweet"));
FileWriterEAM.use("eam2.txt", writerEAM -> {
writerEAM.writeStuff("how");
writerEAM.writeStuff("sweet");
});
FileWriterEAM.use("eam3.txt", FileWriterEAM::writeIt);
}
void writeIt() throws IOException{
this.writeStuff("How ");
this.writeStuff("sweet ");
this.writeStuff("it is");
}
}
La única forma integrada de manejar las excepciones verificadas que puede generar una
map
operación es encapsularlas dentro de a
CompletableFuture
.
(Una
Optional
es una alternativa más simple si no necesita preservar la excepción). Estas clases están destinadas a permitirle representar operaciones contingentes de manera funcional.
Se requieren un par de métodos auxiliares no triviales, pero puede llegar a un código que sea relativamente conciso, al tiempo que hace evidente que el resultado de su flujo depende de que la
map
operación se haya completado con éxito.
Así es como se ve:
CompletableFuture<List<Class<?>>> classes =
Stream.of("java.lang.String", "java.lang.Integer", "java.lang.Double")
.map(MonadUtils.applyOrDie(Class::forName))
.map(cfc -> cfc.thenApply(Class::getSuperclass))
.collect(MonadUtils.cfCollector(ArrayList::new,
List::add,
(List<Class<?>> l1, List<Class<?>> l2) -> { l1.addAll(l2); return l1; },
x -> x));
classes.thenAccept(System.out::println)
.exceptionally(t -> { System.out.println("unable to get class: " + t); return null; });
Esto produce el siguiente resultado:
[class java.lang.Object, class java.lang.Number, class java.lang.Number]
El
applyOrDie
método toma un
Function
que arroja una excepción, y lo convierte en uno
Function
que devuelve un ya completado
CompletableFuture
, ya sea completado normalmente con el resultado de la función original o excepcionalmente completado con la excepción lanzada.
La segunda
map
operación ilustra que ahora tienes un en
Stream<CompletableFuture<T>>
lugar de solo un
Stream<T>
.
CompletableFuture
solo se encarga de ejecutar esta operación si la operación ascendente tuvo éxito.
La API lo hace explícito, pero relativamente indoloro.
Hasta que llegues a la
collect
fase, eso es.
Aquí es donde requerimos un método auxiliar bastante significativo.
Queremos "levantar" una operación normal de recogida (en este caso,
toList()
) "dentro" de la
CompletableFuture
-
cfCollector()
nos permite hacer que el uso de una
supplier
,
accumulator
,
combiner
, y
finisher
que no necesita saber nada en absoluto sobre
CompletableFuture
.
Los métodos auxiliares se pueden encontrar en GitHub en mi
MonadUtils
clase, que todavía es un trabajo en progreso.
Probablemente, una forma mejor y más funcional es envolver excepciones y propagarlas aún más en la secuencia. Echar un vistazo a la Try tipo de Vavr por ejemplo.
Ejemplo:
interface CheckedFunction<I, O> {
O apply(I i) throws Exception; }
static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
return i -> {
try {
return f.apply(i);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
} }
fileNamesToRead.map(unchecked(file -> Files.readAllLines(file)))
O
@SuppressWarnings("unchecked")
private static <T, E extends Exception> T throwUnchecked(Exception e) throws E {
throw (E) e;
}
static <I, O> Function<I, O> unchecked(CheckedFunction<I, O> f) {
return arg -> {
try {
return f.apply(arg);
} catch(Exception ex) {
return throwUnchecked(ex);
}
};
}
La segunda implementación evita envolver la excepción en a
RuntimeException
.
throwUnchecked
funciona porque casi siempre todas las excepciones genéricas se tratan como no marcadas en java.
Resumiendo los comentarios anteriores, la solución avanzada es utilizar un contenedor especial para funciones no verificadas con un generador como API que proporciona recuperación, relanzamiento y supresión.
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(Try.<String, Class<?>>safe(Class::forName)
.handle(System.out::println)
.unsafe())
.collect(toList());
El siguiente código lo demuestra para las interfaces de consumidor, proveedor y función. Se puede ampliar fácilmente. Algunas palabras clave públicas se eliminaron para este ejemplo.
Class Try es el punto final para el código del cliente. Los métodos seguros pueden tener un nombre único para cada tipo de función. CheckedConsumer , CheckedSupplier y CheckedFunction son análogos comprobados de las funciones lib que se pueden usar independientemente de Try
CheckedBuilder es la interfaz para manejar excepciones en alguna función marcada. or Try permite ejecutar otra función del mismo tipo si la anterior fallaba. El manejador proporciona manejo de excepciones, incluido el filtrado de tipo de excepción. El orden de los manejadores es importante. Reduzca los métodos inseguros y vuelva a lanzar la última excepción en la cadena de ejecución. Reduzca los métodos orElse y orElseGet devuelven valores alternativos como los opcionales si todas las funciones fallaron. También hay un método de supresión . CheckedWrapper es la implementación común de CheckedBuilder.
final class Try {
public static <T> CheckedBuilder<Supplier<T>, CheckedSupplier<T>, T>
safe(CheckedSupplier<T> supplier) {
return new CheckedWrapper<>(supplier,
(current, next, handler, orResult) -> () -> {
try { return current.get(); } catch (Exception ex) {
handler.accept(ex);
return next.isPresent() ? next.get().get() : orResult.apply(ex);
}
});
}
public static <T> Supplier<T> unsafe(CheckedSupplier<T> supplier) {
return supplier;
}
public static <T> CheckedBuilder<Consumer<T>, CheckedConsumer<T>, Void>
safe(CheckedConsumer<T> consumer) {
return new CheckedWrapper<>(consumer,
(current, next, handler, orResult) -> t -> {
try { current.accept(t); } catch (Exception ex) {
handler.accept(ex);
if (next.isPresent()) {
next.get().accept(t);
} else {
orResult.apply(ex);
}
}
});
}
public static <T> Consumer<T> unsafe(CheckedConsumer<T> consumer) {
return consumer;
}
public static <T, R> CheckedBuilder<Function<T, R>, CheckedFunction<T, R>, R>
safe(CheckedFunction<T, R> function) {
return new CheckedWrapper<>(function,
(current, next, handler, orResult) -> t -> {
try { return current.applyUnsafe(t); } catch (Exception ex) {
handler.accept(ex);
return next.isPresent() ? next.get().apply(t) : orResult.apply(ex);
}
});
}
public static <T, R> Function<T, R> unsafe(CheckedFunction<T, R> function) {
return function;
}
@SuppressWarnings ("unchecked")
static <T, E extends Throwable> T throwAsUnchecked(Throwable exception) throws E {
throw (E) exception;
}
}
@FunctionalInterface interface CheckedConsumer<T> extends Consumer<T> {
void acceptUnsafe(T t) throws Exception;
@Override default void accept(T t) {
try { acceptUnsafe(t); } catch (Exception ex) {
Try.throwAsUnchecked(ex);
}
}
}
@FunctionalInterface interface CheckedFunction<T, R> extends Function<T, R> {
R applyUnsafe(T t) throws Exception;
@Override default R apply(T t) {
try { return applyUnsafe(t); } catch (Exception ex) {
return Try.throwAsUnchecked(ex);
}
}
}
@FunctionalInterface interface CheckedSupplier<T> extends Supplier<T> {
T getUnsafe() throws Exception;
@Override default T get() {
try { return getUnsafe(); } catch (Exception ex) {
return Try.throwAsUnchecked(ex);
}
}
}
interface ReduceFunction<TSafe, TUnsafe, R> {
TSafe wrap(TUnsafe current, Optional<TSafe> next,
Consumer<Throwable> handler, Function<Throwable, R> orResult);
}
interface CheckedBuilder<TSafe, TUnsafe, R> {
CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next);
CheckedBuilder<TSafe, TUnsafe, R> handle(Consumer<Throwable> handler);
<E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handle(
Class<E> exceptionType, Consumer<E> handler);
CheckedBuilder<TSafe, TUnsafe, R> handleLast(Consumer<Throwable> handler);
<E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R> handleLast(
Class<E> exceptionType, Consumer<? super E> handler);
TSafe unsafe();
TSafe rethrow(Function<Throwable, Exception> transformer);
TSafe suppress();
TSafe orElse(R value);
TSafe orElseGet(Supplier<R> valueProvider);
}
final class CheckedWrapper<TSafe, TUnsafe, R>
implements CheckedBuilder<TSafe, TUnsafe, R> {
private final TUnsafe function;
private final ReduceFunction<TSafe, TUnsafe, R> reduceFunction;
private final CheckedWrapper<TSafe, TUnsafe, R> root;
private CheckedWrapper<TSafe, TUnsafe, R> next;
private Consumer<Throwable> handlers = ex -> { };
private Consumer<Throwable> lastHandlers = ex -> { };
CheckedWrapper(TUnsafe function,
ReduceFunction<TSafe, TUnsafe, R> reduceFunction) {
this.function = function;
this.reduceFunction = reduceFunction;
this.root = this;
}
private CheckedWrapper(TUnsafe function,
CheckedWrapper<TSafe, TUnsafe, R> prev) {
this.function = function;
this.reduceFunction = prev.reduceFunction;
this.root = prev.root;
prev.next = this;
}
@Override public CheckedBuilder<TSafe, TUnsafe, R> orTry(TUnsafe next) {
return new CheckedWrapper<>(next, this);
}
@Override public CheckedBuilder<TSafe, TUnsafe, R> handle(
Consumer<Throwable> handler) {
handlers = handlers.andThen(handler);
return this;
}
@Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R>
handle(Class<E> exceptionType, Consumer<E> handler) {
handlers = handlers.andThen(ex -> {
if (exceptionType.isInstance(ex)) {
handler.accept(exceptionType.cast(ex));
}
});
return this;
}
@Override public CheckedBuilder<TSafe, TUnsafe, R> handleLast(
Consumer<Throwable> handler) {
lastHandlers = lastHandlers.andThen(handler);
return this;
}
@Override public <E extends Throwable> CheckedBuilder<TSafe, TUnsafe, R>
handleLast(Class<E> exceptionType, Consumer<? super E> handler) {
lastHandlers = lastHandlers.andThen(ex -> {
if (exceptionType.isInstance(ex)) {
handler.accept(exceptionType.cast(ex));
}
});
return this;
}
@Override public TSafe unsafe() {
return root.reduce(ex -> Try.throwAsUnchecked(ex));
}
@Override
public TSafe rethrow(Function<Throwable, Exception> transformer) {
return root.reduce(ex -> Try.throwAsUnchecked(transformer.apply(ex)));
}
@Override public TSafe suppress() {
return root.reduce(ex -> null);
}
@Override public TSafe orElse(R value) {
return root.reduce(ex -> value);
}
@Override public TSafe orElseGet(Supplier<R> valueProvider) {
Objects.requireNonNull(valueProvider);
return root.reduce(ex -> valueProvider.get());
}
private TSafe reduce(Function<Throwable, R> orResult) {
return reduceFunction.wrap(function,
Optional.ofNullable(next).map(p -> p.reduce(orResult)),
this::handle, orResult);
}
private void handle(Throwable ex) {
for (CheckedWrapper<TSafe, TUnsafe, R> current = this;
current != null;
current = current.next) {
current.handlers.accept(ex);
}
lastHandlers.accept(ex);
}
}
También puede escribir un método de contenedor para ajustar excepciones no verificadas, e incluso mejorar el contenedor con parámetros adicionales que representen otra interfaz funcional (con el mismo tipo de retorno R ). En este caso, puede pasar una función que se ejecutará y devolverá en caso de excepciones. Ver ejemplo a continuación:
@Test
public void getClasses() {
String[] classNames = {"java.lang.Object", "java.lang.Integer", "java.lang.Foo"};
List<Class> classes =
Stream.of(classNames)
.map(className -> {
try {
return Class.forName(className);
} catch (ClassNotFoundException e) {
// log the error
return null;
}
})
.filter(c -> c != null)
.collect(Collectors.toList());
if (classes.size() != classNames.length) {
// add your error handling here if needed or process only the resulting list
System.out.println("Did not process all class names");
}
classes.forEach(System.out::println);
}
Yo uso este tipo de excepción de envoltura:
public class CheckedExceptionWrapper extends RuntimeException {
...
public <T extends Exception> CheckedExceptionWrapper rethrow() throws T {
throw (T) getCause();
}
}
Requerirá manejar estas excepciones estáticamente:
void method() throws IOException, ServletException {
try {
list.stream().forEach(object -> {
...
throw new CheckedExceptionWrapper(e);
...
});
} catch (CheckedExceptionWrapper e){
e.<IOException>rethrow();
e.<ServletExcepion>rethrow();
}
}
Aunque la excepción se volverá a lanzar de todos modos durante la primera
rethrow()
llamada (oh, genéricos de Java ...), de esta manera permite obtener una definición estática estricta de posibles excepciones (requiere declararlas
throws
).
Y no
instanceof
o se necesita algo.
¡Usted puede!
Extendiendo la UtilException de
UtilException
y agregando
throw E
cuando sea necesario: de esta manera,
el compilador le pedirá que agregue cláusulas throw
y todo es como si pudiera lanzar excepciones comprobadas de
forma nativa
en las transmisiones de java 8.
Instrucciones: simplemente copie / pegue
LambdaExceptionUtil
en su IDE y luego
LambdaExceptionUtilTest
como se muestra en el siguiente
LambdaExceptionUtilTest
.
public final class LambdaExceptionUtil {
@FunctionalInterface
public interface Consumer_WithExceptions<T, E extends Exception> {
void accept(T t) throws E;
}
@FunctionalInterface
public interface Function_WithExceptions<T, R, E extends Exception> {
R apply(T t) throws E;
}
/**
* .forEach(rethrowConsumer(name -> System.out.println(Class.forName(name))));
*/
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) {
throwActualException(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) {
throwActualException(exception);
return null;
}
};
}
@SuppressWarnings("unchecked")
private static <E extends Exception> void throwActualException(Exception exception) throws E {
throw (E) exception;
}
}
Algunas pruebas para mostrar el uso y el comportamiento:
public class LambdaExceptionUtilTest {
@Test(expected = MyTestException.class)
public void testConsumer() throws MyTestException {
Stream.of((String)null).forEach(rethrowConsumer(s -> checkValue(s)));
}
private void checkValue(String value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
}
private class MyTestException extends Exception { }
@Test
public void testConsumerRaisingExceptionInTheMiddle() {
MyLongAccumulator accumulator = new MyLongAccumulator();
try {
Stream.of(2L, 3L, 4L, null, 5L).forEach(rethrowConsumer(s -> accumulator.add(s)));
fail();
} catch (MyTestException e) {
assertEquals(9L, accumulator.acc);
}
}
private class MyLongAccumulator {
private long acc = 0;
public void add(Long value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
acc += value;
}
}
@Test
public void testFunction() throws MyTestException {
List<Integer> sizes = Stream.of("ciao", "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
assertEquals(2, sizes.size());
assertEquals(4, sizes.get(0).intValue());
assertEquals(5, sizes.get(1).intValue());
}
private Integer transform(String value) throws MyTestException {
if(value==null) {
throw new MyTestException();
}
return value.length();
}
@Test(expected = MyTestException.class)
public void testFunctionRaisingException() throws MyTestException {
Stream.of("ciao", null, "hello").<Integer>map(rethrowFunction(s -> transform(s))).collect(toList());
}
}
Escribí una biblioteca que extiende la API Stream para permitirle lanzar excepciones marcadas. Utiliza el truco de Brian Goetz.
Tu código se convertiría
public List<Class> getClasses() throws ClassNotFoundException {
Stream<String> classNames =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String");
return ThrowingStream.of(classNames, ClassNotFoundException.class)
.map(Class::forName)
.collect(Collectors.toList());
}
Esta clase auxiliar
LambdaExceptionUtil
permite usar cualquier excepción marcada en las secuencias de Java, como esta:
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(rethrowFunction(Class::forName))
.collect(Collectors.toList());
Nota
Class::forName
arroja
ClassNotFoundException
, que está
marcada
.
La secuencia en sí misma también arroja
ClassNotFoundException
, y NO alguna excepción de ajuste sin marcar.
public final class LambdaExceptionUtil {
@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; }
}
Muchos otros ejemplos sobre cómo usarlo (después de importar estáticamente
LambdaExceptionUtil
):
@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");
}
NOTA 1:
Los métodos de
LambdaExceptionUtil
clase
LambdaExceptionUtil
anterior se pueden usar sin temor y están
bien para usar en cualquier situación
.
Un gran agradecimiento al usuario @PaoloC que ayudó a resolver el último problema: ahora el compilador le pedirá que agregue cláusulas de lanzamiento y todo es como si pudiera lanzar excepciones comprobadas de forma nativa en las transmisiones de Java 8.
NOTA 2:
Los métodos para
uncheck
la clase
LambdaExceptionUtil
anterior son métodos de bonificación, y se pueden eliminar de forma segura de la clase si no desea usarlos.
Si los usó, hágalo con cuidado y no antes de comprender los siguientes casos de uso, ventajas / desventajas y limitaciones:
• Puede usar los métodos para
uncheck
si está llamando a un método que literalmente nunca puede lanzar la excepción que declara.
Por ejemplo: new String (byteArr, "UTF-8") produce UnsupportedEncodingException, pero la especificación de Java garantiza que UTF-8 esté siempre presente.
Aquí, la declaración de tiros es una molestia y cualquier solución para silenciarla con un mínimo repetitivo es bienvenida:
String text = uncheck(() -> new String(byteArr, "UTF-8"));
• Puede usar los métodos de
uncheck
si está implementando una interfaz estricta donde no tiene la opción de agregar una declaración de lanzamiento, y sin embargo, lanzar una excepción es completamente apropiado.
Ajustar una excepción solo para obtener el privilegio de lanzarla da como resultado un seguimiento de pila con excepciones espurias que no aportan información sobre lo que realmente salió mal.
Un buen ejemplo es Runnable.run (), que no arroja ninguna excepción marcada.
• En cualquier caso, si decide utilizar los métodos de
uncheck
, tenga en cuenta estas 2 consecuencias de lanzar excepciones COMPROBADAS sin una cláusula throws: 1) El código de llamada no podrá atraparlo por su nombre (si lo intenta, el compilador dirá: la excepción nunca se lanza en el cuerpo de la declaración de prueba correspondiente).
Burbujeará y probablemente quedará atrapado en el bucle principal del programa por alguna "excepción de captura" o "captura Throwable", que puede ser lo que desee de todos modos.
2) Viola el principio de la menor sorpresa: ya no será suficiente capturar
RuntimeException
para garantizar la captura de todas las posibles excepciones.
Por esta razón, creo que esto no debe hacerse en el código marco, sino solo en el código comercial que usted controla por completo.
-
Referencias
- http://www.philandstuff.com/2012/04/28/sneakily-throwing-checked-exceptions.html
- http://www.mail-archive.com/[email protected]/msg05984.html
- Anotación del Proyecto Lombok: @SneakyThrows
- Opinión de Brian Goetz (en contra) aquí: ¿Cómo puedo lanzar excepciones COMPROBADAS desde las secuencias Java 8?
- https://softwareengineering.stackexchange.com/questions/225931/workaround-for-java-checked-exceptions?newreg=ddf0dd15e8174af8ba52e091cf85688e *
Esta respuesta es similar a 17 pero evitando la definición de excepción de contenedor:
List test = new ArrayList();
try {
test.forEach(obj -> {
//let say some functionality throws an exception
try {
throw new IOException("test");
}
catch(Exception e) {
throw new RuntimeException(e);
}
});
}
catch (RuntimeException re) {
if(re.getCause() instanceof IOException) {
//do your logic for catching checked
}
else
throw re; // it might be that there is real runtime exception
}
No puedes hacer esto de manera segura. Puedes hacer trampa, pero luego tu programa está roto y esto inevitablemente volverá a morder a alguien (deberías ser tú, pero a menudo nuestra trampa explota en otra persona).
Aquí hay una forma un poco más segura de hacerlo (pero todavía no lo recomiendo).
class WrappedException extends RuntimeException {
Throwable cause;
WrappedException(Throwable cause) { this.cause = cause; }
}
static WrappedException throwWrapped(Throwable t) {
throw new WrappedException(t);
}
try
source.stream()
.filter(e -> { ... try { ... } catch (IOException e) { throwWrapped(e); } ... })
...
}
catch (WrappedException w) {
throw (IOException) w.cause;
}
Aquí, lo que está haciendo es detectar la excepción en el lambda, arrojar una señal fuera de la tubería de transmisión que indica que el cálculo falló excepcionalmente, captar la señal y actuar sobre esa señal para lanzar la excepción subyacente. La clave es que siempre está capturando la excepción sintética, en lugar de permitir que se filtre una excepción marcada sin declarar que se produce esa excepción.
No se puede.
Sin embargo, es posible que desee echar un vistazo a throwing-lambdas que le permite manipular más fácilmente tales "arrojadizas".
En su caso, podría hacer eso:
import static com.github.fge.lambdas.functions.Functions.wrap;
final ThrowingFunction<String, Class<?>> f = wrap(Class::forName);
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(f.orThrow(MyException.class))
.collect(Collectors.toList());
y atrapar
MyException
.
Ese es un ejemplo.
Otro ejemplo es que podría
.orReturn()
algún valor predeterminado.
Tenga en cuenta que esto TODAVÍA es un trabajo en progreso, hay más por venir. Mejores nombres, más funciones, etc.
Simplemente use cualquiera de NoException (mi proyecto), jOOλ''s Unchecked , throwing-lambdas , Throwable interfaces o Faux Pas .
// NoException
stream.map(Exceptions.sneak().function(Class::forName));
// jOOλ
stream.map(Unchecked.function(Class::forName));
// throwing-lambdas
stream.map(Throwing.function(Class::forName).sneakyThrow());
// Throwable interfaces
stream.map(FunctionWithThrowable.aFunctionThatUnsafelyThrowsUnchecked(Class::forName));
// Faux Pas
stream.map(FauxPas.throwingFunction(Class::forName));
TL; DR Solo usa Lombok''s
@SneakyThrows
.
Christian Hujer ya ha explicado en detalle por qué no es posible lanzar excepciones marcadas desde una transmisión, estrictamente hablando, debido a las limitaciones de Java.
Algunas otras respuestas han explicado trucos para sortear las limitaciones del lenguaje, pero aún así pueden cumplir el requisito de lanzar "la excepción marcada en sí misma y sin agregar feos intentos / capturas a la secuencia" , algunos de ellos requieren decenas de líneas adicionales. de repetitivo.
Voy a destacar otra opción para hacer esto, en mi humilde opinión es mucho más limpia que todas las demás: la de Lombok
@SneakyThrows
.
Se ha mencionado al pasar por otras respuestas, pero estaba un poco enterrado bajo muchos detalles innecesarios.
El código resultante es tan simple como:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(className -> getClass(className))
.collect(Collectors.toList());
return classes;
}
@SneakyThrows // <= this is the only new code
private Class<?> getClass(String className) {
return Class.forName(className);
}
Solo necesitábamos una
Extract Method
refactorización (realizada por el IDE) y
una
línea adicional para
@SneakyThrows
.
La anotación se encarga de agregar toda la plantilla para asegurarse de que puede lanzar su excepción marcada sin envolverla
RuntimeException
y sin tener que declararla explícitamente.
La respuesta simple a su pregunta es: no puede, al menos no directamente. Y no es tu culpa. Oracle lo estropeó. Se aferran al concepto de excepciones comprobadas, pero inconsistentemente se olvidaron de cuidar las excepciones comprobadas al diseñar las interfaces funcionales, flujos, lambda, etc. Todo eso es esencial para el grupo de expertos como Robert C. Martin, que llaman a las excepciones comprobadas un experimento fallido.
En mi opinión, este es un gran error en la API y un error menor en la especificación del lenguaje .
El error en la API es que no proporciona ninguna facilidad para reenviar excepciones comprobadas donde esto realmente tendría mucho sentido para la programación funcional. Como demostraré a continuación, tal instalación habría sido fácilmente posible.
El error en la especificación del lenguaje es que no permite que un parámetro de tipo infiera una lista de tipos en lugar de un solo tipo, siempre que el parámetro de tipo solo se use en situaciones en las que una lista de tipos es permisible (cláusula de
throws
).
Nuestra expectativa como programadores de Java es que se compile el siguiente código:
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
public class CheckedStream {
// List variant to demonstrate what we actually had before refactoring.
public List<Class> getClasses(final List<String> names) throws ClassNotFoundException {
final List<Class> classes = new ArrayList<>();
for (final String name : names)
classes.add(Class.forName(name));
return classes;
}
// The Stream function which we want to compile.
public Stream<Class> getClasses(final Stream<String> names) throws ClassNotFoundException {
return names.map(Class::forName);
}
}
Sin embargo, da:
cher@armor1:~/playground/Java/checkedStream$ javac CheckedStream.java
CheckedStream.java:13: error: incompatible thrown types ClassNotFoundException in method reference
return names.map(Class::forName);
^
1 error
La forma en que se definen las interfaces funcionales actualmente evita que el compilador reenvíe la excepción: no hay una declaración que
Stream.map()
a
Stream.map()
que si
Function.apply() throws E
,
Stream.map() throws E
también.
Lo que falta es una declaración de un parámetro de tipo para pasar por excepciones comprobadas. El siguiente código muestra cómo dicho parámetro de tipo de paso en realidad podría haberse declarado con la sintaxis actual. Excepto por el caso especial en la línea marcada, que es un límite que se analiza a continuación, este código se compila y se comporta como se esperaba.
import java.io.IOException;
interface Function<T, R, E extends Throwable> {
// Declare you throw E, whatever that is.
R apply(T t) throws E;
}
interface Stream<T> {
// Pass through E, whatever mapper defined for E.
<R, E extends Throwable> Stream<R> map(Function<? super T, ? extends R, E> mapper) throws E;
}
class Main {
public static void main(final String... args) throws ClassNotFoundException {
final Stream<String> s = null;
// Works: E is ClassNotFoundException.
s.map(Class::forName);
// Works: E is RuntimeException (probably).
s.map(Main::convertClass);
// Works: E is ClassNotFoundException.
s.map(Main::throwSome);
// Doesn''t work: E is Exception.
s.map(Main::throwSomeMore); // error: unreported exception Exception; must be caught or declared to be thrown
}
public static Class convertClass(final String s) {
return Main.class;
}
static class FooException extends ClassNotFoundException {}
static class BarException extends ClassNotFoundException {}
public static Class throwSome(final String s) throws FooException, BarException {
throw new FooException();
}
public static Class throwSomeMore(final String s) throws ClassNotFoundException, IOException {
throw new FooException();
}
}
En el caso de
throwSomeMore
nos gustaría ver
IOException
se pierda
IOException
, pero en realidad se pierde
Exception
.
Esto no es perfecto porque la inferencia de tipos parece estar buscando un solo tipo, incluso en el caso de excepciones.
Debido a que la inferencia de tipo necesita un solo tipo,
E
necesita resolverse en un
super
común de
ClassNotFoundException
e
IOException
, que es
Exception
.
Se necesita un ajuste en la definición de inferencia de tipos para que el compilador busque múltiples tipos si el parámetro de tipo se usa donde se permite una lista de tipos (cláusula
throws
).
Entonces, el tipo de excepción informado por el compilador sería tan específico como la declaración de
throws
original de las excepciones comprobadas del método al que se hace referencia, no un solo supertipo general.
La mala noticia es que esto significa que Oracle lo estropeó. Ciertamente no romperán el código de aterrizaje del usuario, pero la introducción de parámetros de tipo de excepción a las interfaces funcionales existentes interrumpiría la compilación de todo el código de aterrizaje del usuario que usa estas interfaces explícitamente. Tendrán que inventar un nuevo azúcar de sintaxis para solucionar esto.
La peor noticia es que Brian Goetz ya discutió este tema en 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (nuevo enlace: http://mail.openjdk.java.net/pipermail/lambda-dev/2010-June/001484.html ) pero estoy informado de que esta investigación finalmente no funcionó, y que no hay ningún trabajo actual en Oracle que yo sepa para mitigar las interacciones entre las excepciones verificadas y las lambdas.