¿Cómo puedo lanzar excepciones COMPROBADAS desde las secuencias Java 8?

287

¿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 Class.forName()método 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 lanzar la excepción envuelta sin marcar. Quiero lanzar la excepción marcada , y sin agregar feo try/ catchesa la secuencia.

MarcG
fuente
42
La respuesta corta es, no puedes, sin romper las reglas con respecto a las excepciones. Puede hacer trampa, por supuesto, y otros con gusto le mostrarán cómo, pero tenga en cuenta que esto es trampa y que a menudo vuelve a morderle. Deberías atrapar la excepción y lidiar con ella. Si desea envolverlo y luego volver a lanzar la excepción marcada, puede hacerlo de forma segura.
Brian Goetz
35
@Brian, no necesito que otros me digan cómo hacer trampa, sé cómo engañarme y publiqué mi forma de hacer trampa en la respuesta a continuación, que rechazaste. Sé que estás involucrado en la discusión de Java que decidió que no había una buena manera de lidiar con las excepciones comprobadas en Streams, por lo que me parece sorprendente que hayas notado esta pregunta mía, pero estoy decepcionado por tu respuesta que simplemente dice "esto es no es bueno ", no da ninguna razón por la cual, y luego agrega uno try / catch de nuevo.
MarcG
22
@Brian, francamente, en la práctica, cuando las personas intentan refactorizar las declaraciones anteriores heredadas, la mitad de ellas se convierten en transmisiones, pero la otra mitad abandonan la refactorización, porque nadie quiere agregar estas pruebas / capturas. Hacen que el código sea mucho más difícil de leer, ciertamente más que las declaraciones for originales. En mi ejemplo de código anterior, siempre y cuando mantengas "throws ClassNotFoundException", no veo ninguna diferencia con respecto al código externo. ¿Podría darme algunos ejemplos de la vida real en los que esto infringe las reglas con respecto a las excepciones?
MarcG
10
La escritura de métodos de envoltura que se ajustan a excepciones no verificadas aborda la objeción de "desorden de código" y no interrumpe el sistema de tipos. La respuesta aquí que recurre a un "lanzamiento furtivo" de una excepción marcada rompe el sistema de tipos, porque el código de llamada no esperará (ni se le permitirá atrapar) la excepción marcada.
Brian Goetz
14
No aborda la objeción de desorden de código porque entonces necesita un segundo intento / captura alrededor de la secuencia, para desenvolver y volver a lanzar la excepción original. Por el contrario, si lanza la excepción marcada, solo necesita mantener la throws ClassNotFoundExceptiondeclaración en el método que contiene la secuencia, para que el código de llamada espere y se le permita capturar la excepción marcada.
MarcG

Respuestas:

250

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 cuando diseñaron las interfaces funcionales, flujos, lambda, etc.

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 en las que 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 ( throwscláusula).

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 impide actualmente el compilador de reenvío de la excepción - no hay ninguna declaración que diga Stream.map()que si Function.apply() throws E, Stream.map() throws Etambié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 que throwSomeMorenos gustaría ver que IOExceptionse nos extrañe, 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, Edebe resolverse a un común superde ClassNotFoundExceptiony 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 una lista de tipos es permisible ( throwscláusula). Entonces, el tipo de excepción informado por el compilador sería tan específico como la throwsdeclaración original de las excepciones comprobadas del método referenciado, 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 conozca para mitigar las interacciones entre las excepciones comprobadas y las lambdas.

Christian Hujer
fuente
16
Interesante. Creo que algunas personas aprecian las transmisiones por permitir un código paralelo más fácil, mientras que otras por permitir un código más limpio. Brian Goetz obviamente se preocupa más por el paralelismo (ya que fue autor de Concurrencia de Java en la práctica), mientras que Robert Martin se preocupa más por el código limpio (ya que fue autor del libro Código limpio). Los intentos / capturas repetitivos son un precio menor a pagar por el paralelismo, por lo que no es de extrañar que Brian Goetz no esté horrorizado por los problemas de usar excepciones comprobadas dentro de las transmisiones. Tampoco es de extrañar que Robert Martin odie las excepciones comprobadas, ya que aumentan el desorden.
MarcG
55
Predigo que, en unos pocos años, la dificultad de lidiar con las excepciones verificadas dentro de las transmisiones dará lugar a uno de estos dos resultados: la gente simplemente dejará de usar excepciones verificadas, O todos comenzarán a usar algún truco muy similar al que publiqué en mi respuesta de UtilException. Apostaría a que las transmisiones Java-8 son el último clavo en el ataúd de las excepciones verificadas, no por el hecho de que las excepciones verificadas son parte del JDK. Aunque me gustan y uso las excepciones marcadas en el código comercial (para algunos casos de uso específicos), hubiera preferido todas las excepciones JDK extendidas en tiempo de ejecución extendido.
MarcG
9
@Unihedro El problema sigue siendo que las interfaces funcionales no envían excepciones. Necesitaría el try-catchbloque dentro de la lambda, y eso simplemente no tiene ningún sentido. Tan pronto como Class.forNamese usa de alguna manera en la lambda, por ejemplo names.forEach(Class::forName), en , el problema está ahí. Básicamente, los métodos que arrojan excepciones comprobadas han sido excluidos de participar en la programación funcional como interfaces funcionales directamente, por diseño (¡pobre!).
Christian Hujer
26
@ChristianHujer La exploración de "Transparencia de excepción" fue solo eso: una exploración (que se originó en la propuesta BGGA). Tras un análisis más profundo, descubrimos que ofrecía un equilibrio pobre de valor y complejidad, y tenía algunos problemas serios (condujo a problemas de inferencia indecidibles, y "atrapar X" no era sólido, entre otros). Es extremadamente común que una idea de lenguaje parece prometedor, incluso "obvio", pero después de una exploración más profunda, resultó ser defectuoso. Este fue uno de esos casos.
Brian Goetz
13
@BrianGoetz ¿Hay alguna información pública disponible sobre los problemas de inferencia indecidibles que mencionó? Tengo curiosidad y me gustaría entenderlo.
Christian Hujer
169

Esta LambdaExceptionUtilclase auxiliar le 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::forNametiros ClassNotFoundException, que está marcada . La transmisión en sí también se lanza 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 rethrowmétodos de la LambdaExceptionUtilclase anterior se pueden usar sin temor, y se pueden usar en cualquier situación . Muchas gracias 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 uncheckmétodos de la LambdaExceptionUtilclase anterior son métodos de bonificación y pueden eliminarse 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 uncheckmétodos 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 uncheckmétodos si está implementando una interfaz estricta en la que 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 uncheckmétodos, tenga en cuenta estas 2 consecuencias de lanzar excepciones CHECKED sin una cláusula throws: 1) El código de llamada no podrá capturarlo por su nombre (si lo intenta, el 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 atrapar RuntimeExceptionpara poder 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.

MarcG
fuente
44
Siento que esta respuesta fue injustamente rechazada. El código funciona Se supone que las excepciones marcadas deben ser lanzadas o tratadas. Si desea lanzarlos, simplemente mantenga la "cláusula throws" en el método que contiene la secuencia. Pero si desea lidiar con ellos simplemente envolviendo y volviendo a lanzar, supongo que prefiero usar el código anterior para "desentrañar" las excepciones y dejar que burbujeen por sí mismas. La única diferencia que conozco es que la excepción burbujeante no extenderá RuntimeException. Sé que a los puristas no les gustará eso, pero ¿esto "inevitablemente volverá a morder a alguien"? No parece probable.
MarcG
44
@Christian Hujer, para ser honesto con el votante, votó a favor de una versión anterior antes de agregar la explicación de "ventajas, desventajas y limitaciones". Entonces tal vez fue merecido en ese momento. No puedes enseñarle a alguien cómo romper las reglas sin al menos tratar de entender y explicar las consecuencias. La razón principal por la que publiqué esta pregunta fue para obtener comentarios sobre las desventajas de mi respuesta. Terminé recibiendo esta retroalimentación no aquí, sino de otra pregunta en programmers.stackexchange. Luego regresé aquí y actualicé mi respuesta.
MarcG
16
Voté solo porque esto fomenta el código que no se puede mantener . Este es un truco feo, aunque inteligente, y nunca encontraré útil esta respuesta. Este es, nuevamente, otro "no uso" del lenguaje.
Unihedron
12
@Unihedro pero ¿por qué se volvió imposible de mantener? No puedo ver por qué. Algun ejemplo?
MarcG
2
En mi opinión, el @SuppressWarnings ("unchecked")truco del compilador es completamente inaceptable.
Thorbjørn Ravn Andersen
26

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 captar 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.

Brian Goetz
fuente
18
Sólo una pregunta; ¿Cuál fue la decisión de diseño que llevó a las lambdas a no poder propagar las excepciones verificadas fuera de su contexto? Tenga en cuenta que entiendo que las interfaces funcionales como Functionetc no hacen throwsnada; Tengo curiosidad.
fge
44
¿Eso throw w.cause;no haría que el compilador se quejara de que el método no arroja ni atrapa Throwable? Por lo tanto, es probable que IOExceptionse necesite un yeso allí. Además, si la lambda arroja más de un tipo de excepción marcada, el cuerpo de la captura se volvería algo feo con algunas instanceofcomprobaciones (u otra cosa con un propósito similar) para verificar qué excepción comprobada fue arrojada.
Victor Stafusa
10
@schatten Una razón es que podrías olvidarte de atrapar WE, y luego se filtraría una extraña excepción (que nadie sabe cómo tratar). (Puede decir "pero captó la excepción, así que es seguro". En este ejemplo de juguete. Pero cada vez que he visto una base de código adoptar este enfoque, eventualmente alguien olvida. La tentación de ignorar las excepciones no tiene límites). Otro riesgo es que usarlo de manera segura es específico de una combinación particular (usar sitio, excepción). No escala bien a múltiples excepciones o usos no homogéneos.
Brian Goetz el
2
@hoodaticus Estoy de acuerdo contigo. Dicho eso, ¿prefiere envolver más y más (como se muestra arriba, aumentando el riesgo de "olvidar") o simplemente crear 4 interfaces inteligentes y usar lambdas sin envoltura, como se muestra en stackoverflow.com/a/30974991/2365724 ? Gracias
PaoloC
10
Francamente, esta solución es completamente inviable. Pensé que el objetivo de las corrientes era reducir el repetitivo, no aumentarlo.
wvdz
24

¡Usted puede!

Extendiendo @marcg 's UtilExceptiony agregando throw Edonde sea necesario: de esta manera, el compilador le pedirá que agregue cláusulas de lanzamiento y todo es como si pudiera lanzar excepciones marcadas de forma nativa en las transmisiones de java 8.

Instrucciones: simplemente copie / pegue LambdaExceptionUtilen su IDE y luego úselo como se muestra a continuación 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());
    }

}
PaoloC
fuente
1
Lo siento @setheron tienes razón, solo agrega <Integer>antes map. De hecho, el compilador de Java no puede inferir el Integertipo de retorno. Todo lo demás debe ser correcto.
PaoloC
1
Esto funcionó para mí. Hizo que la respuesta de MarcG fuera perfecta al imponer el manejo de la excepción.
Skychan
1
Solución al problema anterior: declare una variable como esta Consumer <ThingType> expression = rethrowConsumer ((ThingType thing) -> thing.clone ()); luego usa esa expresión dentro del foreach interno.
Skychan
1
@Skychan: Dado que en esta nueva versión modificada ya no está suprimiendo ninguna excepción, probablemente sea un poco más difícil para el sistema de inferencia. En algún comentario a continuación, Brian Goetz habla sobre la "transparencia de excepción" que conduce a "problemas de inferencia indecidibles".
MarcG
3
Muy agradable. Lo único desafortunado es que no funciona perfectamente con un método que arroja múltiples excepciones marcadas. En este caso, el compilador te hará atrapar un supertipo común, por ejemplo Exception.
wvdz
12

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));
Robert Važan
fuente
7

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());
}
Jeffrey
fuente
7

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
        }
Radoslav Stoyanov
fuente
1
Esa es una solución simple y efectiva.
Lin W
2
Esto es exactamente lo que Op no quería: intente bloques en la lambda. Además, solo funciona como se esperaba, siempre que ningún otro código fuera del bloque try envuelva una IOException en una RuntimeException. Para evitar esto, se podría usar una envoltura personalizada-RuntimeException (definida como una clase interna privada).
Malte Hartwig
5

No puedes.

Sin embargo, es posible que desee echar un vistazo a uno de mis proyectos que le permite manipular más fácilmente tales "arrojar lambdas".

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 tener .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.

fge
fuente
2
Pero luego, si desea lanzar la excepción marcada original, tendrá que agregar el try / catch alrededor de la transmisión, para desenvolverla, ¡lo cual sigue siendo terrible! Me gusta la idea de que PUEDE lanzar una excepción no marcada si lo desea, y que PUEDE devolver un valor predeterminado a la secuencia si lo desea, pero también creo que debería agregar algún .orThrowChecked()método a su proyecto que permita que se arroje la excepción marcada. . Mire mi UtilExceptionrespuesta en esta página y vea si le gusta la idea de agregar esta tercera posibilidad a su proyecto.
MarcG
"Pero entonces, si quieres lanzar la excepción original marcada, tendrás que agregar el try / catch alrededor de la transmisión, para desenvolverla, ¡lo cual sigue siendo terrible!" <- sí, pero no tienes elección. Lambdas no puede propagar excepciones comprobadas fuera de su contexto, esa es una "decisión" de diseño (personalmente lo veo como una falla, pero bueno)
fge
En cuanto a su idea, no sigo muy bien lo que hace, lo siento; después de todo, todavía arrojas sin marcar, entonces, ¿cómo es esto diferente de lo que hago? (excepto que tengo una interfaz diferente)
fge
De todos modos, ¡puedes contribuir al proyecto! Además, ¿has notado que se Streamimplementa AutoCloseable?
fge
Déjame preguntarte esto: ¿Es MyExceptionnecesario que lo anterior sea una excepción no verificada?
MarcG
3

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);
    }
}
introspectado
fuente
3

TL; DR Solo usa Lombok's@SneakyThrows .

Christian Hujer ya ha explicado en detalle por qué no es posible lanzar excepciones comprobadas de 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 con el requisito de lanzar "la excepción marcada en sí, 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 Methodrefactorizació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 RuntimeExceptiony sin tener que declararla explícitamente.

sergut
fuente
44
Se desaconseja el uso de lombok.
Dragas
2

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:

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;
}
Piotr Niewinski
fuente
2

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.

    @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);
    }
OSGI Java
fuente
1

Estoy de acuerdo con los comentarios anteriores, al usar Stream.map está limitado a implementar una función que no arroje excepciones.

Sin embargo, puede crear su propia FunctionalInterface que se muestra como 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");

 }

}
JohnnyO
fuente
1

La única forma integrada de manejar las excepciones comprobadas que puede generar una mapoperación es encapsularlas dentro de a CompletableFuture. (Una Optionales 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 mapoperació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 applyOrDiemétodo toma un Functionque arroja una excepción, y lo convierte en un Functionque devuelve un ya completado CompletableFuture, ya sea completado normalmente con el resultado de la función original o completado excepcionalmente con la excepción lanzada.

La segunda mapoperación ilustra que ahora tienes un en Stream<CompletableFuture<T>>lugar de solo un Stream<T>. CompletableFuturesolo se encarga de ejecutar esta operación si la operación ascendente tuvo éxito. El API lo hace explícito, pero relativamente indoloro.

Hasta que llegues a la collectfase, 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 finisherque no necesita saber nada en absoluto sobre CompletableFuture.

Los métodos auxiliares se pueden encontrar en GitHub en mi MonadUtilsclase, que todavía es un trabajo en progreso.

Matt McHenry
fuente
1

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 Trata 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. throwUncheckedfunciona porque casi siempre todas las excepciones genéricas se tratan como no marcadas en java.

Mikhail Kholodkov
fuente
1

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();
    }
}

Pruébalo en línea!

Aunque la excepción se volverá a lanzar de todos modos durante la primera rethrow()llamada (oh, genéricos de Java ...), de esta manera se obtiene una definición estática estricta de posibles excepciones (se requiere declararlas throws). Y no instanceofo se necesita algo.

Taras
fuente
-1

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 Callableen un UndeclaredThrowableException(ese es el caso de uso para esta excepción) y desenvolviéndola afuera.

Sí, lo encuentro 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 a esta situación, y espero que una de ellas la convierta en una versión futura de Java.

Matthias Ronge
fuente
1
(1) Ya hay varias respuestas que muestran un ejemplo como este, entonces, ¿qué agrega su respuesta a las preguntas y respuestas que aún no están cubiertas? Publicar respuestas duplicadas como esta solo agrega desorden al sitio. (2) El OP dice específicamente que no quieren hacer esto. "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 lanzar la excepción envuelta sin marcar".
Radiodef