Ответ 1
Единственный способ сделать это - подклассификация - но я уверен, вы это знали. Чтобы усилить мой аргумент, посмотрите на BinaryOperator
который расширяет BiFunction
.
Я создал "продюсерский" интерфейс (для использования с ссылками на методы, соответственно, чтобы их легко высмеивали для модульных тестов):
@FunctionalInterface
public interface Factory<R, T, X extends Throwable> {
public R newInstanceFor(T t) throws X;
}
который я создал таким образом, так как моему первому варианту использования действительно нужно было бросить некоторое исключение WhateverException
.
Но у моего второго варианта использования нет X для броска.
Лучшее, что я смог придумать, чтобы сделать компилятор счастливым:
Factory<SomeResultClass, SomeParameterClass, RuntimeException> factory;
Это компилирует и делает то, что мне нужно, но все же некрасиво. Есть ли способ сохранить этот единственный интерфейс, но не предоставлять X при объявлении конкретных экземпляров?
Единственный способ сделать это - подклассификация - но я уверен, вы это знали. Чтобы усилить мой аргумент, посмотрите на BinaryOperator
который расширяет BiFunction
.
Вы не можете сделать это на Java. Единственный способ - создать дополнительный интерфейс.
public interface DefaultExceptionFactory<R, T>
extends Factory<R, T, RuntimeException>
Это скорее ответ на "социальную инженерию": мы размещаем контракт на форму лямбды, что он ничего не бросает:
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();
}
}
Недостаток этого подхода: вы не можете точно указать контракт в сигнатуре типа, тогда контракт является детализацией реализации. Если вы хотите иметь это в сигнатуре типа, вам понадобится второй суб-интерфейс.
Вы можете определить метод как общий, например, код ниже, если это возможно для вас:
@FunctionalInterface
public interface Factory<R, T> {
public <X extends Throwable> R newInstanceFor(T t) throws X;
}
Вы можете использовать аннотацию Project Lombok @SneakyThrows:
@FunctionalInterface
public interface Factory<R, T> {
@SneakyThrows
R newInstanceFor(T t);
}
Это позволяет вам исключить любое исключение (отмечено или не отмечено). Но прочитайте документацию, потому что эту функцию нужно обрабатывать с осторожностью.
Вы должны сделать исключение общим? Почему бы не определить интерфейс как
@FunctionalInterface
public interface Factory<R, T> {
public R newInstanceFor(T t) throws Throwable;
}
Вы всегда можете поймать свое исключение и проверить тип, если вам нужна функция вызова.