Что такое сценарий использования универсального конструктора? - PullRequest
8 голосов
/ 23 февраля 2012

Рассмотрим следующий конструктор для класса Foo (который для ясности не универсальный класс):

public <T> Foo(T obj) { }

Это допустимый синтаксис для конструкторов, как и в случае обычных универсальных методов .

Но какой смысл в этом синтаксисе? Обычно универсальные методы обеспечивают безопасность типов для своего возвращаемого типа и могут получить выгоду от вывода типа компилятором. Например:

Pair<String, Integer> stringInt = Pair.of("asfd", 1234);

Но вызов конструктора всегда возвращает экземпляр объявленного им класса, поэтому параметры его типа не влияют на тип возвращаемого значения. Приведенный выше конструктор можно просто заменить его erasure :

public Foo(Object obj) { }

Конечно, дженерики - это не только безопасность типов для возвращаемых типов. Конструктор может просто захотеть ограничить тип передаваемого аргумента (аргументов). Однако приведенные выше рассуждения все еще применимы к параметру ограниченного типа:

public <N extends Number> Foo(N number) { }

public Foo(Number number) { } //same thing

Даже параметры вложенного типа с границами обрабатываются с использованием подстановочных знаков:

public <N extends Number, L extends List<N>> Foo(L numList) { }

public Foo(List<? extends Number> numList) { } //same thing

Итак, каков законный вариант использования универсального конструктора?

Ответы [ 5 ]

6 голосов
/ 23 февраля 2012

Вот возможный вариант, адаптированный из функционального программирования. Предположим, у нас есть тип Stream, который имеет некоторое внутреннее состояние, многократно возвращая новые элементы, пока он не вернет null. Внешним абонентам все равно, какой тип внутреннего состояния у типа потока, поэтому вы можете получить что-то вроде

class Stream<E> {
  <S> Stream(S initialState, StepFunction<E, S> stepFun) {
    ...
  }
}

без указания получателем типа внутреннего состояния.

5 голосов
/ 23 февраля 2012

Единственное, о чем я могу подумать, так это то, что вы можете гарантировать одинаковое выполнение границ для нескольких параметров.

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

public <T> Foo (List<T> listA, List<T> listB) {
    listA.addAll(listB);
}

Использование подстановочных знаков здесь быстро станет довольно неприятным и, вероятно, в любом случае не будет делать то, что вы хотите. Было бы также совершенно произвольным ограничением запретить его. Поэтому для меня имеет смысл, что языковая спецификация позволяет это.

2 голосов
/ 23 февраля 2012

Один из вариантов использования, о котором я могу подумать, это когда вы хотите ограничить аргумент конструктора несколькими типами.Только общий синтаксис позволяет объявлять конструктор, принимающий List из Number с, который также реализует RandomAccess:

public <L extends List<? extends Number> & RandomAccess> Foo(L raNumList) { }

...

Foo f1 = new Foo(new ArrayList<Integer>());
Foo f2 = new Foo(new LinkedList<Integer>()); //compiler error
1 голос
/ 23 февраля 2012

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

public <T> AssemblyLine(T[] starting, List<T> components) {
  T[] a = components.toArray(starting);
  Arrays.sort(a);
  this.conveyorBelt.add(a);
}

Здесь <T> гарантирует, что T[] и List<T> содержат одинаковый тип T,не (скажем) Integer[] и List<string>.

1 голос
/ 23 февраля 2012

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

<T extends InterfaceA & InterfaceB > Foo(T t1, T t2) {

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...