tutorial - java->
¿Por qué un método hace referencia a ctor que "lanza"... también lanza? (3)
Debe proporcionar una interfaz personalizada ThrowingFunction
que tenga un método que arroje Exception
.
public interface ThrowingFunction<ParameterType, ReturnType> {
ReturnType invoke(ParameterType p) throws Exception;
}
public class Mcve {
public Mcve(String s) throws Exception {
// whatever
}
public static void main(String[] args) {
ThrowingFunction<String, Mcve> mcveFactory = Mcve::new;
}
}
El uso de este enfoque hace que se llame a mcveFactory.invoke("lalala");
obligándote a manejar la excepción lanzada por el constructor.
El motivo del error es que la referencia a la función real que desea almacenar (no está 100% segura de la terminología) genera una excepción y, por lo tanto, los tipos simplemente no coinciden. Si pudiera almacenar Mcve::new
dentro de una función, el que llame a la función ya no sabe que se puede lanzar una Exception
. ¿Qué pasaría entonces si la excepción realmente fuera lanzada? Tanto lanzar la excepción como descartarla no funcionan.
Alternativa: si necesita recuperar realmente una Function<String, Mcve>
al final, debe escribir una función (o lambda) que invoque al constructor, detecte la excepción y la descarte o la vuelva a ejecutar dentro de una excepción RuntimeException
.
public class Mcve {
public Mcve(String s) throws Exception {
// whatever
}
public static void main(String[] args) {
Function<String, Mcve> mcveFactory = parameter -> {
try {
return new Mcve(parameter);
} catch (Exception e) {
throw new RuntimeException(e); // or ignore
}
};
}
}
Yo diría que el mensaje de error en sí mismo es al menos un poco engañoso, ya que normalmente lo ve cuando realmente invoca el método. Ciertamente puedo entender la confusión que resulta en la primera subpregunta. Sería más claro (lamentablemente no es posible) decir algo como
Tipos incompatibles
Function<String,Mcve>
vs.Function<String,Mcve> throws Exception
.
Estoy buscando una forma elegante de crear una fábrica para la inyección de dependencia. En mi caso, la fábrica simplemente tiene que llamar a un constructor de un solo argumento. Encontré esta answer describiendo cómo usar una Function<ParamType, ClassToNew>
para tales propósitos.
Pero mi problema es: en mi caso, mi ctor declara lanzar una excepción comprobada.
Lo que no entiendo: crear esa función usando una referencia de método para ese constructor no funciona. Como en:
import java.util.function.Function;
public class Mcve {
public Mcve(String s) throws Exception {
// whatever
}
public static void main(String[] args) {
Function<String, Mcve> mcveFactory = Mcve::new;
}
}
me dice acerca de "Excepción no Mcve::new
: java.lang.Exception" para Mcve::new
. Aunque este código no está invocando al constructor.
Dos preguntas:
- ¿Por qué ese error? El código anterior no invoca el ctor (todavía)?
- ¿Hay alguna manera elegante de resolver este rompecabezas? (simplemente agregar
throws Exception
a mimain()
no ayuda)
He estado pensando en esto por un tiempo y, de hecho, si quieres tener una Function
que declare claramente tu intención, creo que necesitas una Function
que extienda java.util.Function
, algo como esto:
@FunctionalInterface
public interface ThrowingFunction<T, R> extends Function<T, R> {
R applyWithExc(T t) throws Exception;
@Override
default R apply(T t) {
try {
return applyWithExc(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Puede por cierto elegir a qué método llamar cuando defina la referencia de su constructor: el que emitiría una Exception
y el que la envolvería silenciosamente con una Exception
RuntimeException
.
Tuve que hacer eso recientemente ... Si puedes cambiar la definición de la clase, puedes usar la infame forma de hacer cosas:
static class OneArg {
private final String some;
@SuppressWarnings("unchecked")
public <E extends Exception> OneArg(String some) throws E {
try {
this.some = some;
// something that might throw an Exception...
} catch (Exception e) {
throw (E) e;
}
}
public String getSome() {
return some;
}
}
Function<String, OneArg> mcveFactory = OneArg::new;