Как я могу использовать параметры метода и типа класса в одном ограничении?
Я попытаюсь проиллюстрировать свою проблему в следующем упрощенном примере:
public class DataHolder<T> {
private final T myValue;
public DataHolder(T value) {
myValue = value;
}
public T get() {
return myValue;
}
// Won't compile
public <R> DataHolder<R super T> firstNotNull(DataHolder<? extends R> other) {
return new DataHolder<R>(myValue != null ? myValue : other.myValue); }
public static <R> DataHolder<R> selectFirstNotNull(DataHolder<? extends R> first,
DataHolder<? extends R> second) {
return new DataHolder<R>(first.myValue != null ? first.myValue : second.myValue);
}
}
Здесь я хочу написать общий метод firstNotNull
, который возвращает DataHolder
, параметризованный общим супертипом параметра типа T
аргумента this
и other
, поэтому позже я мог бы написать, например.
DataHolder<Number> r = new DataHolder<>(3).firstNotNull(new DataHolder<>(2.0));
или
DataHolder<Object> r = new DataHolder<>("foo").firstNotNull(new DataHolder<>(42));
Проблема заключается в том, что это определение firstNotNull
отклоняется компилятором с сообщением о том, что super T
часть ограничения типа является незаконной (синтаксически).
Однако без этого ограничения определение также неверно (очевидно), потому что в этом случае T
и R
не связаны друг с другом.
Интересно, что определение аналогичного статического метода selectFirstNotNull
является правильным, и последнее работает так, как ожидалось. Можно ли достичь такой же гибкости с помощью нестатических методов в системе типа Java?
Ответы
Ответ 1
Это невозможно сделать. Авторы Guava столкнулись с той же проблемой: Optional.or
. Из этой документации метода:
Примечание о дженериках: подпись public T or(T defaultValue)
чрезмерно ограничительный. Однако идеальная сигнатура public <S super T> S or(S)
не является законной Java. В результате, некоторые разумные операции с подтипами являются ошибки компиляции:
Optional<Integer> optionalInt = getSomeOptionalInt();
Number value = optionalInt.or(0.5); // error
FluentIterable<? extends Number> numbers = getSomeNumbers();
Optional<? extends Number> first = numbers.first();
Number value = first.or(0.5); // error
В качестве обходного пути всегда можно Optional<? extends T> to Optional<T>
. Литье любого из вышеперечисленных пример Optional
экземпляры Optional<Number>
(где Number
- это желаемый тип вывода) решает проблему:
Optional<Number> optionalInt = (Optional) getSomeOptionalInt();
Number value = optionalInt.or(0.5); // fine
FluentIterable<? extends Number> numbers = getSomeNumbers();
Optional<Number> first = (Optional) numbers.first();
Number value = first.or(0.5); // fine
Так как DataHolder
является неизменным, как Optional
, вышеописанное обходное решение будет работать и для вас.
См. также: Ответ Rotsor на ограничение общих типов с ключевым словом 'super'
Ответ 2
Я не думаю, что есть простой и безопасный способ сделать это. Я пробовал несколько подходов, но единственный рабочий подход, который я нашел, - это начать с типичного экземпляра типа super
и сделать этот метод довольно простым:
public DataHolder<T> firstNotNull(DataHolder<? extends T> other) {
return new DataHolder<T>(myValue != null ? myValue : other.myValue);
}
Теперь вам нужно изменить свой вызов на:
DataHolder<Number> r = new DataHolder<Number>(3).firstNotNull(new DataHolder<>(2.0));
Вы можете утверждать, что это на самом деле не отвечает на ваш вопрос, но это самая простая вещь, которую вы собираетесь получить, или лучше использовать метод static
. Конечно, вы можете придумать некоторые чрезвычайно запутанные (и небезопасные) методы, но читаемость здесь должна быть главной проблемой.
Ответ 3
Попробуйте изменить свой метод следующим образом:
public <R> DataHolder<R> firstNotNull(DataHolder<? super T> other) {
return new DataHolder<R>((R)(this.myValue != null ? myValue : other.myValue));
}
ПРЕДУПРЕЖДЕНИЕ. Это компилирует и дает внешний вид правильной проверки по большей части, но не идеально. Он будет ограничивать входные параметры, но не выход. Это невозможно сделать отлично. В некотором смысле вам может быть лучше делать это неконтролируемым, а не давать иллюзию проверки. Вот несколько примеров:
DataHolder<BigDecimal> a = new DataHolder<>(new BigDecimal(34.0));
DataHolder<Number> b = new DataHolder<>(new Integer(34));
DataHolder<String> c = new DataHolder<>("");
DataHolder<Number> p = a.firstNotNull(b); // WORKS (good)
DataHolder<BigDecimal> q = b.firstNotNull(a); // FAILS (good)
DataHolder<BigDecimal> r = b.firstNotNull(c); // FAILS (good)
DataHolder<String> s = a.firstNotNull(b); // WORKS (not good!!!)