Как не выбрасывать общее исключение?

Я создал "продюсерский" интерфейс (для использования с ссылками на методы, соответственно, чтобы их легко высмеивали для модульных тестов):

@FunctionalInterface
public interface Factory<R, T, X extends Throwable> {
    public R newInstanceFor(T t) throws X;
}

который я создал таким образом, так как моему первому варианту использования действительно нужно было бросить некоторое исключение WhateverException.

Но у моего второго варианта использования нет X для броска.

Лучшее, что я смог придумать, чтобы сделать компилятор счастливым:

Factory<SomeResultClass, SomeParameterClass, RuntimeException> factory;

Это компилирует и делает то, что мне нужно, но все же некрасиво. Есть ли способ сохранить этот единственный интерфейс, но не предоставлять X при объявлении конкретных экземпляров?

Ответы

Ответ 1

Единственный способ сделать это - подклассификация - но я уверен, вы это знали. Чтобы усилить мой аргумент, посмотрите на BinaryOperator который расширяет BiFunction.

Ответ 2

Вы не можете сделать это на Java. Единственный способ - создать дополнительный интерфейс.

public interface DefaultExceptionFactory<R, T>
        extends Factory<R, T, RuntimeException>

Ответ 3

Это скорее ответ на "социальную инженерию": мы размещаем контракт на форму лямбды, что он ничего не бросает:

public interface Factory<T, R, X> {

    public R newInstanceFor(T arg) throws X;

    public static Factory<R, U, AssertionError> neverThrows(Factory<U, V, ?> input) {
        return u -> {
            try {
                return input.newInstanceFor(u);
            }
            catch(Throwable t) {
                throw new AssertionError("Broken contract: exception thrown", t);
            }
        };
    }
}

Использование такое, или что-то вроде:

class MyClass {
    Factory<MyInput, MyOtherClass, AssertionError> factory;

    MyClass(Factory<MyInput, MyOtherClass, ?> factory) {
        this.factory = Factory.neverThrows(factory);
    }

    public void do() {
      factory.newInstanceFor(new MyInput()).do();
    }
}

Недостаток этого подхода: вы не можете точно указать контракт в сигнатуре типа, тогда контракт является детализацией реализации. Если вы хотите иметь это в сигнатуре типа, вам понадобится второй суб-интерфейс.

Ответ 4

Вы можете определить метод как общий, например, код ниже, если это возможно для вас:

@FunctionalInterface
public interface Factory<R, T> {
    public <X extends Throwable> R newInstanceFor(T t) throws X;
}

Ответ 5

Вы можете использовать аннотацию Project Lombok @SneakyThrows:

@FunctionalInterface
public interface Factory<R, T> {

    @SneakyThrows
    R newInstanceFor(T t);
}

Это позволяет вам исключить любое исключение (отмечено или не отмечено). Но прочитайте документацию, потому что эту функцию нужно обрабатывать с осторожностью.

Ответ 6

Вы должны сделать исключение общим? Почему бы не определить интерфейс как

@FunctionalInterface
public interface Factory<R, T> {
    public R newInstanceFor(T t) throws Throwable;
}

Вы всегда можете поймать свое исключение и проверить тип, если вам нужна функция вызова.