Предисловие: Вы задали ряд вопросов о стирании типов, в том числе в чате. Хотя этот ответ будет касаться этого конкретного вопроса, он может ответить и на некоторые другие ваши вопросы (возможно, даже те, которые вы еще не задавали).
Предупреждение: Этот ответ длинный, и вопрос, специально заданный в этом посте, адресован только непосредственно в конце.
Что такое стирание типа?
Это было достаточно хорошо рассмотрено вдругие вопросы и ответы, поэтому я просто сошлюсь на них:
Каковы правилатипа стирания?
Правила стирания типа указаны в §4.6 Тип стирания Спецификации языка Java (JLS) :
Стирание типа - это отображение типов (возможно, включая параметризованные типы и переменные типов) на типы (которыеникогда не параметризованные типы или переменные типа). Пишем |T|
для стирания типа T
. Отображение стирания определяется следующим образом:
Стирание параметризованного типа ( §4.5 ) G<T1,...,Tn>
равно |G|
.
Удаление вложенного типа T.C
равно |T|.C
.
Удаление типа массива T[]
равно |T|[]
.
Стирание переменной типа ( §4.4 ) - это стирание ее крайней левой границы.
Стирание любого другого типаэто сам тип.
Стирание типа также сопоставляет подпись ( §8.4.2 ) конструктора или метода с сигнатурой, которая не имеет параметризованных типов или типовпеременные. Стирание сигнатуры конструктора или метода s
- это сигнатура, состоящая из того же имени, что и s
, и стирания всех типов формальных параметров, приведенных в s
.
Тип возврата метода( §8.4.5 ) и параметры типа универсального метода или конструктора ( §8.4.4 , §8.8.4 ) также стираются, еслисигнатура метода или конструктора стирается.
Удаление сигнатуры универсального метода не имеет параметров типа.
Для этого ответа нам следует сосредоточиться на первой и четвертой пунктах маркировки, которыескажем:
Стирание параметризованного типа ( §4.5 ) G<T1,...,Tn>
равно |G|
.
И:
Стирание переменной типа ( §4.4 ) - это стирание ее самой левой границы.
Соответственно. В частности, обратите внимание, что четвертая точка маркера только объясняет стирание переменных типа . Почему это важно? Я вернусь к этому.
Терминология
Понимание терминов важно для понимания того, как применяются правила:
Genericкласс
Общий интерфейс
Универсальный метод
Общий конструктор
- Указано в §8.8.4. Общие конструкторы JLS.
- A универсальный конструктор является конструктором, который объявляет одну или несколько переменных типа .
переменную типа
- Указано в §4.4 Переменные типа JLS.
- * Переменная типа представлена параметром типа , объявленным вуниверсальный член.
- Синтаксис переменной типа :
{Annotation} TypeIdentifier
Тип параметра
- Указано во многих разделах JLS. В каждом разделе, с которым я ссылаюсь для вышеупомянутых терминов, упоминается параметры типа .
- параметр типа является объявлением переменной типа плюс ееbounds, для универсального члена.
- Синтаксис параметра типа :
{TypeParameterModifier} TypeIdentifier [TypeBound]
, где TypeParameterModifier
расширяется до Annotation
.
Параметризованный тип
- Указано в §4.5 Параметризованные типы JLS.
- A параметризованный тип - это фактическое использование универсального класса или универсального интерфейса с аргументами типа .
аргумент типа
Обратите внимание, что разницамежду параметром типа и аргументом типа аналогично тому, как Java различает параметр метода и аргумент. Когда вы объявляете метод как void bar(Object obj)
, Object obj
является параметром. Однако, когда вы вызываете метод, такой как bar(someObjInstance)
, значение someObjInstance
является аргументом.
Код Примеры терминологии
Просмотр некоторых примеров в коде может помочь понять, какие частикод, к которому относится каждый термин.
универсальный класс с параметрами типа
public class Foo<T extends CharSequence, U> {
// class body...
}
Есть два параметра типа :
T extends Charsequence
- Переменная типа равна
T
. - Границы типа равны
extends CharSequence
U
- Переменная типа равна
U
- Границы типа являются
extends Object
(неявно определены)
Код выглядит аналогично для универсальных интерфейсов.
Универсальный метод с параметром типа
public void <V extends Number> bar(V obj) {
// method body...
}
Этот метод имеет один параметр типа :
V extends Number
- Переменная типа равна
V
. - Границы типа :
extends Number
.
Код выглядит похожим для универсальных конструкторов.
Параметризованный тип (без подстановочных знаков)
public <E extends Number> void bar(List<E> list) {
// method body...
}
Существует один параметризованный тип :
List<E>
Параметризованный тип(с подстановочным знаком)
public void bar(List<? extends Number> list) {
// method body...
}
Существует один параметризованный тип :
List<? extends Number>
- Аргумент типа is
? extends Number
Вернуться к правилам
Как я уже говорил, важно отметить, что в правилах упоминается только удаление переменные типа . Это важно по той причине, что подстановочные знаки недопустимы в местах, где можно определить переменные типа (т. Е. В параметры типа ). Подстановочный знак может использоваться только в аргументе типа , который является частью параметризованного типа .
Удаление параметризованного типа имеет видпросто необработанный тип.
Когда имеет значение стирание типа?
В повседневной разработке стирания универсального кода практически не имеет значения. Единственный случай, когда вам нужно узнать, как стирание типов работает в деталях, - это работа с raw types . В идеальном и справедливом мире вы будете работать только с необработанными типами при работе с устаревшим кодом (начиная с дней до Java 5), которые вы не можете изменить. Другими словами, если вы не вынуждены работать с необработанными типами, вы должны всегда надлежащим образом использовать дженерики.
Однако, возможно, вы вынуждены работать с необработанными типами, или вы простолюбопытно. В этом случае вам нужно знать, как работает стирание типов, потому что стирание определяет, какие типы используются. Вот пример универсального класса:
public class Foo<T extend CharSequence, U> {
private List<T> listField;
private U objField;
public void bar(List<? extends T> listParam) {
// method body...
}
public U baz(T objParam) {
// method body...
}
public <V extends Number> V qux(V objParam) {
// method body...
}
}
И, следуя вышеупомянутым правилам стирания типов, вот как выглядит вышеупомянутый класс впоследствии:
// the raw type of Foo
public class Foo {
private List listField;
private Object objField;
public void bar(List listParam) {
// method body...
}
public Object baz(CharSequence objParam) {
// method body...
}
public Number qux(Number objParam) {
// method body...
}
}
Но опять же, вы будетеНужно знать о последней версии только при использовании необработанных типов.
Применение этих знаний к вашему вопросу
Кое-что из того, что мы узнали до сих пор, заключается в том, что подстановочные знаки могутиспользоваться только в аргументах типа и, следовательно, применимы только к параметризованным типам . Стирание параметризованного типа является просто необработанным типом. Если мы применим эти знания к вашим примерам, вы получите следующее:
Пример # 1
Пример # 2
Пример № 3
Оригинал (забыл указать тип возвращаемого вопроса, предполагая void
)
public static <T extends Integer> void funcC(List<? extends T> l)
Стерто
public static void funcC(List l)
Пример № 4
Усиление точки
Чтобы действительно указать на разницу между стиранием переменной типа и стирание параметризованного типа давайте рассмотрим другой пример.
public class Foo<T extends Number> {
public void bar(T obj) {
// method body...
}
public void baz(List<? extends T> list) {
// method body...
}
}
Метод bar
имеет единственный параметр типа T
. Параметр напрямую использует переменную типа . Стирание переменной типа - это стирание ее крайней левой границы, в данном случае Number
. Это означает, что после стирания параметр метода равен Number
.
Метод baz
имеет единственный параметр типа List<? extends T>
. Здесь переменная типа T
используется в качестве верхней границы в аргументе type параметризованного типа . Другими словами, несмотря на то, что используется переменная типа , фактически здесь используется стирание с параметризованным типом . Это означает, что после стирания параметр метода равен List
. Это может произойти, даже если аргумент типа был подстановочным знаком с ограничением снизу (например, List<? super T>
), неограниченным подстановочным знаком (например, List<?>
) или даже не подстановочным знаком (например, List<T>
).
Как обрабатываются подстановочные знаки с помощью типа Erasure
Чтобы прямо ответить на ваш вопрос о том, как типовое стирание обрабатывает подстановочные знаки, вы получите эффективный ответ: Нет, не напрямую Подстановочные знаки просто исчезают (когда стирается параметризованный тип ) и не имеют значения в результирующем необработанном типе.
Подстановочные знаки обеспечивают гибкость для тех, кто использует универсальный API. Вот некоторые вопросы и ответы, которые касаются этой концепции:
Надеемся, что эти вопросы и ответы также помогут ответитьваш вспомогательный вопрос, чем отличаются второй и четвертый примеры от вашего поста.