Как параметризованные методы разрешают <T>, если это не входной параметр?
Как ссылки на < T → обрабатывается компилятором в следующем коде, так как метод не принимает параметров, которые позволили бы вывести T? Существуют ли какие-либо ограничения на то, какой тип объекта может быть помещен в список? Является ли приведение даже в строке, где я добавляю строку в список? Моя первая мысль состоит в том, что без каких-либо выводов T от T становится типом объекта. Спасибо заранее.
public class App {
private <T> void parameterizedMethod()
{
List<T> list = new ArrayList<>();
for(int i = 0; i < 10; i++)
{
list.add((T)new String()); //is a cast actually occurring here?
}
}
public App()
{
parameterizedMethod();
}
public static void main(String[] args) {
new App();
}
}
Ответы
Ответ 1
Первоначально это определяется 18.1.3:
Когда начинается вывод, связанный набор обычно генерируется из списка объявлений параметров типа P1, ..., Pp
и связанных переменных вывода α1, ..., αp
. Такое связанное множество строится следующим образом. Для каждого l (1 ≤ l ≤ p):
-
Если Pl
не имеет TypeBound
, в наборе появляется связанная αl <: Object
.
-
В противном случае для каждого типа T
, ограниченного &
в TypeBound
, в наборе появляется рамка αl <: T[P1:=α1, ..., Pp:=αp]
; [...].
В конце вывода связанный набор получает "разрешен" к выведенному типу. Без какого-либо дополнительного контекста связанный набор будет состоять только из начальных оценок, основанных на объявлении параметра типа.
Связанная с формой, подобной αl <: Object
, означает αl
(переменная вывода) Object
или подтип Object
. Эта граница разрешена для Object
.
Итак, в вашем случае да, Object
выведено.
Если мы объявили ограничение типа:
private <T extends SomeType> void parameterizedMethod()
то SomeType
будет выведено.
В этом случае никакого наложения не происходит (стирание). Вот почему это "непроверено". Литье происходит только тогда, когда объект экспонируется из-за, например,:
<T> T parameterizedMethodWithAResult()
{
return (T) new String();
}
// the cast happens out here
Integer i = parameterizedMethodWithAResult();
// parameterizedMethodWithAResult returns Object actually,
// and we are implicitly doing this:
Integer i = (Integer) parameterizedMethodWithAResult();
Существуют ли какие-либо ограничения на то, какой тип объекта может быть помещен в список?
Семантически (время компиляции), да. Обратите внимание, что ограничение определяется вне метода. Внутри метода мы не знаем, каково это ограничение на самом деле. Поэтому мы не ставим String
в List<T>
. Мы не знаем, что такое T
.
Практически (время выполнения), нет. Это всего лишь List
и там не проверено. parameterizedMethod
не приведет к исключению... но это справедливо только для такого рода изолированного примера. Этот код может очень сильно привести к проблемам.
Ответ 2
Внутри тела метода Java не предоставляет нам никакой информации о замене для T
, поэтому как мы можем сделать что-нибудь полезное с помощью T
?
Иногда T
не очень важен для тела метода; это более удобно для вызывающего
public static List<T> emptyList(){...}
List<String> emptyStringList = emptyList();
Но если T
важен для тела метода, должен существовать внеполосный протокол, не подлежащий принудительному исполнению компилятором, который должен подчиняться как вызывающему, так и вызываемому. Например
class Conf
<T> T get(String key)
//
<conf>
<param name="size" type="int" ...
//
String name = conf.get("name");
Integer size = conf.get("size");
API использует <T>
здесь только для того, чтобы вызывающему абоненту не нужно было делать явный перевод. Обязанностью вызывающего абонента является обеспечение правильного T
.
В вашем примере, вызываемый предполагает, что T
является супертипом String
; вызывающий должен отстаивать это предположение. Было бы хорошо, если бы такое ограничение могло быть выражено компилятору как
<T super String> void parameterizedMethod()
{
List<T> list
...
list.add( new String() ); // obviously correct; no cast is needed
}
//
this.<Integer>parameterizedMethod(); // compile error
К сожалению, java не поддерживает <T super Foo>
...:) Итак, вам нужно javadoc ограничение вместо
/** T must be a supertype of String! **/
<T> void parameterizedMethod()
У меня есть фактический пример API именно так.
Ответ 3
List<T> list = new ArrayList<>();
for(int i = 0; i < 10; i++)
{
list.add((T)new String()); //is a cast actually occurring here?
}
Нет, на самом деле никакого акта не происходит. Если вы сделали что-либо с list
, что вынудило его быть List<T>
- например, вернуть его - тогда это может вызвать ClassCastException
в точке, где компилятор вставил реальный листинг.