Как верхний-связанный самореферентный тип?
У меня есть вещи (скажем, для контекста, числа), которые могут выполнять операции по своему типу:
interface Number<N> {
N add(N to);
}
class Int implements Number<Int> {
Int add(Int to) {...}
}
и актеров, которые действуют на все подтипы определенной верхней границы:
interface Actor<U> {
<E extends U> E act(Iterable<? extends E> items);
}
Я хочу сделать актера, который действует полиморфно на любой числовой тип:
class Sum implements Actor<Number> {
<N extends Number<N>> N act(Iterable<? extends N> items) {...}
}
Теперь, очевидно, это не работает, потому что Number
и Number<N>
не совпадают. Фактически, поскольку Number
не ограничивает параметр типа конструктора как своего собственного типа, такой актер не может работать. Но я вообще не хочу работать с Number
в целом - я доволен своей функциональностью работать только на Numbers некоторого типа N extends Number<N>
В качестве альтернативы я могу объявить:
interface Actor<E> {
E act(Iterable<? extends E> items);
}
class Sum<N extends Number<N>> implements Actor<N> {
N act(Iterable<? extends N> items) {...}
}
Но это не работает для меня, потому что он заставляет меня знать N
, когда я создаю свой Sum
, который мне не подходит. Это также приводит к уродливому <N extends Number<N>>
для каждого класса или метода, который полиморфно использует a Sum
, вызывая пролиферацию типа беспорядка.
Есть ли какой-нибудь элегантный способ сделать то, что я хочу?
Пример:
Вот пример кода, выражающий то, что я хотел бы сделать.
interface Folder<U> {
<E extends U> E fold(Iterable<? extends E> items);
}
class Sum implements Folder<Number> {
<N extends Number<N>> N fold(Iterable<? extends N> items) {
Iterator<? extends N> iter = items.iterator();
N item = iter.next();
while (iter.hasNext())
item = item.add(iter.next());
return item;
}
}
class Concat implements Folder<String> {
<S extends String> fold(Iterable<? extends S> items) {
StringBuilder concatenation = new StringBuilder();
for (S item : items)
concatenation.append(item);
return concatenation.toString();
}
}
class FoldUtils {
static <U, E extends U> E foldDeep(Folder<U> folder, Iterable<? extends Iterable<? extends E>> itemses) {
Collection<E> partialResults = new ArrayList<E>();
for (Iterable<? extends E> items : itemses)
partialResults.add(folder.fold(items));
return folder.fold(partialResults);
}
}
Ответы
Ответ 1
Глядя на ваш пример, я не уверен, что вы получаете от того, что общий метод предоставляет конкретный параметр и его наличие в актере:
class Sum<T extends Number<T>> implements Actor<T> {
T act(Iterable<? extends T> items) {...}
}
Какова добродетель наличия Sum<any-self-referential-Number>
против просто наличия Sum<Int>
и a Sum<Float>
и т.д.
Если вы беспокоитесь о незначительной накладной памяти для создания разных экземпляров, вы можете просто возвращать один и тот же экземпляр каждый раз с помощью непроверенного броска, как это обычно бывает в безопасном режиме (см., например, Guava Optional.absent()
или Collections.emptyList()
).
В вашем примере кому-то в конце концов придется делать:
List<List<Int>> list;
foldDeep(new Sum(), list)
Итак, почему бы просто не потребоваться параметр типа?
foldDeep(new Sum<Int>(), list)
Или, если он заключен за factory,
foldDeep(Sum.instance(), list)
foldDeep(NumberFolders.sum(), list)
Короче говоря, мне не ясно, почему это не будет работать так же хорошо:
interface Folder<U> {
U fold(Iterable<? extends U> items);
}
class Sum<T extends Number<T>> implements Folder<T> {
public T fold(Iterable<? extends T> items) {
//...
}
}
class FoldUtils {
static <E> E foldDeep(Folder<E> folder, Iterable<? extends Iterable<? extends E>> itemses) {
Collection<E> partialResults = new ArrayList<>();
for (Iterable<? extends E> items : itemses)
partialResults.add(folder.fold(items));
return folder.fold(partialResults);
}
}
//...
FoldUtils.foldDeep(new Sum<>(), list);