问题
I am looking for an elegant way to create a factory for dependency injection. In my case, the factory simply has to call a one-argument constructor. I found this answer outlining how to use a Function<ParamType, ClassToNew>
for such purposes.
But my problem is: in my case, my ctor declares to throw some checked exception.
What I don't get: creating that Function using a method reference to that constructor doesn't work. As in:
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;
}
}
tells me about "Unhandled exception: java.lang.Exception" for Mcve::new
. Although this code is not invoking the constructor.
Two questions:
- why that error? The above code does not invoke the ctor (yet)?
- are there any elegant ways to solve this puzzle? ( simply adding
throws Exception
to mymain()
does not help )
回答1:
You need to provide a custom interface ThrowingFunction
which has one method that throws 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;
}
}
Using this approach results in calling mcveFactory.invoke("lalala");
forcing you to handle the exception thrown by the constructor.
Reason for the error is that the actual function reference you want to store (not 100% sure about the terminology) throws an exception and therefore the types simply do not match up. If you could store Mcve::new
inside a function then whoever calls the function no longer knows an Exception
can be thrown. What would then happen if the exception would actually be thrown? Both throwing the exception and discarding it do not work.
Alternative: if you need to actually retrieve a Function<String, Mcve>
in the end then you need to write a function (or lambda) that invokes the constructor, catches the exception and either discards it or rethrows it wrapped inside a unchecked 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
}
};
}
}
I would argue that the error message itself is at least a bit misleading since you normally see it when actually invoking the method. I can certainly understand the confusion resulting in the first sub-question. It would be clearer (sadly not possible) to state something like
Incompatible types
Function<String,Mcve>
vs.Function<String,Mcve> throws Exception
.
回答2:
I had to do that just recently... If you can change the class definition, you could use the infamous sneaky throws way of doing things:
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;
回答3:
I've been thinking about this for a while and indeed - if you want to have a Function
that declares clearly your intention, I think that you need to have a Function
that would extend the java.util.Function
, something like this:
@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);
}
}
}
You can btw choose which method you call when you define your constructor reference - the one that would throw an Exception
and one that would silently wrap it with a RuntimeException
.
来源:https://stackoverflow.com/questions/47434766/why-does-a-method-reference-to-ctor-that-throws-throw-as-well