Why does a method reference to ctor that “throws” … throw as well?

扶醉桌前 提交于 2019-12-06 16:58:59

问题


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 my main() 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!