Почему метод ссылается на ctor, который "бросает"... бросает также?
Я ищу элегантный способ создания factory для инъекции зависимостей. В моем случае factory просто нужно вызвать конструктор с одним аргументом. Я нашел этот ответ, в котором излагаются, как использовать Function<ParamType, ClassToNew>
для таких целей.
Но моя проблема: в моем случае мой ctor объявляет о том, чтобы бросить какое-то проверенное исключение.
То, что я не получаю: создание этой функции с использованием ссылки метода на этот конструктор не работает. Как в:
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;
}
}
рассказывает мне об "Необработанном исключении: java.lang.Exception" для Mcve::new
. Хотя этот код не вызывает конструктор.
Два вопроса:
- почему эта ошибка? Вышеупомянутый код не вызывает ctor (пока)?
- Есть ли какие-нибудь элегантные способы решить эту загадку? (просто добавив
throws Exception
в мой main()
справки не)
Ответы
Ответ 1
Вам нужно предоставить пользовательский интерфейс ThrowingFunction
, который имеет один метод, который выдает 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;
}
}
Используя этот подход, вы вызываете mcveFactory.invoke("lalala");
, заставляя обрабатывать исключение, созданное конструктором.
Причина ошибки заключается в том, что фактическая ссылка на функцию, которую вы хотите сохранить (не на 100% уверенной в терминологии), выдает исключение, и поэтому типы просто не совпадают. Если вы можете сохранить Mcve::new
внутри функции, то тот, кто вызывает функцию, уже не знает, что может быть выбрано Exception
. Что тогда произойдет, если исключение действительно будет выброшено? Оба бросания исключения и отбрасывания его не работают.
Альтернатива: если вам нужно действительно получить Function<String, Mcve>
в конце, тогда вам нужно написать функцию (или лямбда), которая вызывает конструктор, выхватывает исключение и либо отбрасывает, либо пересказывает его в непроверенный 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
}
};
}
}
Я бы сказал, что само сообщение об ошибке, по крайней мере, немного вводит в заблуждение, поскольку вы обычно видите его при фактическом вызове метода. Я, безусловно, могу понять путаницу, приводящую к первому вопросу. Было бы яснее (к сожалению, невозможно) указать что-то вроде
Несовместимые типы Function<String,Mcve>
vs. Function<String,Mcve> throws Exception
.
Ответ 2
Мне нужно было это сделать совсем недавно... Если вы можете изменить определение класса, вы можете использовать позорный подлый способ сделать что-то:
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
Я думал об этом некоторое время, и действительно, если вы хотите иметь Function
, который четко заявляет о своем намерении, я думаю, что вам нужно иметь Function
, который расширяет java.util.Function
, что-то вроде этого:
@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);
}
}
}
Вы можете btw выбрать, какой метод вы вызываете, когда вы определяете ссылку на конструктор - тот, который выкинул бы Exception
, и тот, который беззвучно обернул бы его с помощью RuntimeException
.