java - mensajes - ¿Es posible declarar que un Proveedor<T> necesita lanzar una Excepción?
mensajes de dialogo en java (5)
Así que estoy tratando de refactorizar el siguiente código:
/**
* Returns the duration from the config file.
*
* @return The duration.
*/
private Duration durationFromConfig() {
try {
return durationFromConfigInner();
} catch (IOException ex) {
throw new IllegalStateException("The config file (/"" + configFile + "/") has not been found.");
}
}
/**
* Returns the duration from the config file.
*
* Searches the log file for the first line indicating the config entry for this instance.
*
* @return The duration.
* @throws FileNotFoundException If the config file has not been found.
*/
private Duration durationFromConfigInner() throws IOException {
String entryKey = subClass.getSimpleName();
configLastModified = Files.getLastModifiedTime(configFile);
String entryValue = ConfigFileUtils.readFileEntry(configFile, entryKey);
return Duration.of(entryValue);
}
Se me ocurrió lo siguiente para empezar:
private <T> T getFromConfig(final Supplier<T> supplier) {
try {
return supplier.get();
} catch (IOException ex) {
throw new IllegalStateException("The config file (/"" + configFile + "/") has not been found.");
}
}
Sin embargo, no se compila (obviamente), ya que el Supplier
no puede lanzar una IOException
. ¿Hay alguna manera en que pueda agregar eso a la declaración del método de getFromConfig
?
¿O es la única manera de hacerlo como la siguiente?
@FunctionalInterface
public interface SupplierWithIO<T> extends Supplier<T> {
@Override
@Deprecated
default public T get() {
throw new UnsupportedOperationException();
}
public T getWithIO() throws IOException;
}
Actualización , me acabo de dar cuenta de que la interfaz del Supplier
es muy simple, ya que solo tiene el método get()
. La razón original por la que extendí al Supplier
es para que prevalezca la funcionalidad básica, como los métodos predeterminados, por ejemplo.
Agregué mi propia solución, no necesariamente una respuesta directa a mi pregunta, que introduce lo siguiente después de algunas revisiones:
@FunctionalInterface
public interface CheckedSupplier<T, E extends Exception> {
public T get() throws E;
}
private <R> R getFromConfig(final String entryKey, final Function<String, R> converter) throws IOException {
Objects.requireNonNull(entryKey);
Objects.requireNonNull(converter);
configLastModified = Files.getLastModifiedTime(configFile);
return ConfigFileUtils.readFileEntry(configFile, entryKey, converter);
}
private <T> T handleIOException(final CheckedSupplier<T, IOException> supplier) {
Objects.requireNonNull(supplier);
try {
return supplier.get();
} catch (IOException ex) {
throw new IllegalStateException("The config file (/"" + configFile + "/") has not been found.");
}
}
Estas fueron todas las declaraciones de una sola vez, ahora agrego dos variantes del código de llamada:
private Duration durationFromConfig() {
return handleIOException(() -> getFromConfig(subClass.getSimpleName(), Duration::of));
}
private int threadsFromConfig() {
return handleIOException(() -> getFromConfig(subClass.getSimpleName() + "Threads", Integer::parseInt));
}
No estoy muy contento de convertir una IOException
en una UncheckedIOException
, como:
- Necesitaría agregar una variante sin marcar de cada método que pueda
IOException
unaIOException
. - Prefiero el manejo obvio de la
IOException
, en lugar de confiar en la esperanza de que no se olvide de capturar laIOException
UncheckedIOException
.
Considere esta solución genérica:
// We need to describe supplier which can throw exceptions
@FunctionalInterface
public interface ThrowingSupplier<T> {
T get() throws Exception;
}
// Now, wrapper
private <T> T callMethod(ThrowingSupplier<T> supplier) {
try {
return supplier.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
return null;
}
// And usage example
String methodThrowsException(String a, String b, String c) throws Exception {
// do something
}
String result = callMethod(() -> methodThrowsException(x, y, z));
En la lista de correo lambda esto fue discutido a fondo . Como puede ver, Brian Goetz sugirió que la alternativa es escribir su propio combinador:
O podrías escribir tu propio combinador trivial:
static<T> Block<T> exceptionWrappingBlock(Block<T> b) { return e -> { try { b.accept(e); } catch (Exception e) { throw new RTE(e); } }; }
Puede escribirlo una vez, en menos de lo que le llevó escribir su correo electrónico original. Y de manera similar una vez por cada tipo de SAM que uses.
Prefiero que veamos esto como "vaso 99% lleno" en lugar de la alternativa. No todos los problemas requieren nuevas características de lenguaje como soluciones. (Sin mencionar que las nuevas características del lenguaje siempre causan nuevos problemas).
En esos días la interfaz del consumidor se llamaba Bloque.
Creo que esto se corresponde con la respuesta de JB Nizet sugerida por Marko arriba.
Más tarde, Brian explica por qué esto fue diseñado de esta manera (la razón del problema)
Sí, tendrías que proporcionar tus propios SAM excepcionales. Pero entonces la conversión lambda funcionaría bien con ellos.
El EG discutió el soporte de biblioteca y lenguaje adicional para este problema, y al final consideró que se trataba de una mala relación costo / beneficio.
Las soluciones basadas en bibliotecas causan una explosión 2x en los tipos SAM (excepcional vs. no), que interactúan mal con las explosiones combinatorias existentes para la especialización primitiva.
Las soluciones basadas en el idioma disponibles fueron perdedoras de una compensación de complejidad / valor. Aunque hay algunas soluciones alternativas que vamos a seguir explorando, aunque claramente no para 8 y probablemente tampoco para 9.
Mientras tanto, tienes las herramientas para hacer lo que quieres. Entiendo que prefiere que le proporcionemos esa última milla (y, en segundo lugar, su solicitud es en realidad una solicitud poco clara para "¿por qué no se da por vencido ya con las excepciones verificadas?"), Pero creo que el estado actual permite usted hace su trabajo
Ya que tengo un punto adicional que hacer sobre este tema, he decidido agregar mi respuesta.
Usted tiene la opción de escribir un método de conveniencia que:
- envuelve un lambda que arroja excepciones marcadas en uno sin marcar;
- simplemente llama a la lambda, deseleccionando la excepción.
Con el primer enfoque, necesita un método de conveniencia por firma de método funcional, mientras que con el segundo enfoque, necesita un total de dos métodos (excepto los métodos de retorno primitivo):
static <T> T uncheckCall(Callable<T> callable) {
try { return callable.call(); }
catch (Exception e) { return sneakyThrow(e); }
}
static void uncheckRun(RunnableExc r) {
try { r.run(); } catch (Exception e) { sneakyThrow(e); }
}
interface RunnableExc { void run() throws Exception; }
Esto le permite insertar una llamada a uno de estos métodos pasando un lambda nulo, pero uno que se está cerrando sobre cualquier argumento del lambda externo, que está pasando a su método original. De esta manera, puede aprovechar la función de lenguaje de conversión lambda automática sin repetitivo.
Por ejemplo, puedes escribir
stream.forEachOrdered(o -> uncheckRun(() -> out.write(o)));
comparado con
stream.forEachOrdered(uncheckWrapOneArg(o -> out.write(o)));
También he encontrado que la sobrecarga del nombre del método de envoltura para todas las firmas lambda a menudo conduce a errores de expresión lambda ambiguos . Por lo tanto, necesita nombres distintos más largos, lo que da como resultado un código char-for-char cada vez más largo que con el enfoque anterior.
Al final, tenga en cuenta que el enfoque mencionado todavía no impide escribir métodos de envoltura simples que reutilicen uncheckedRun/Call
, pero lo encontré simplemente poco interesante porque los ahorros son insignificantes en el mejor de los casos.
Si lo hiciera, no podría utilizarlo como
Supplier
ya que solo emitiría la excepción Excepción de operación no admitida.Teniendo en cuenta lo anterior, ¿por qué no crear una nueva interfaz y declarar el método
getWithIO
en ella?@FunctionalInterface public interface SupplierWithIO<T> { public T getWithIO() throws IOException; }
Tal vez algunas cosas son mejores de las interfaces de Java de estilo antiguo? Java de estilo antiguo no se ha ido solo porque ahora hay Java 8.