Как параметризованные методы разрешают <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 в точке, где компилятор вставил реальный листинг.