Подтвердить что ты не робот

Почему метод ссылается на 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() справки не)
4b9b3361

Ответ 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.