1 Система типов JAVA Type
- Сначала разберитесь с системой типов типов Java (класс класса => тип). Тип - это общий тип всех типов (примитивный тип-класс, параметризованный тип-параметризованный тип, тип массива-GenericArrayType, тип переменной-TypeVariable, базовый тип-класс) Интерфейс ; Class
, упомянутый в первых двух размышлениях и аннотациях, является классом реализации Type - Существует четыре подинтерфейсных класса ParameterizedType, TypeVariable, GenericArrayType, WildcardType в разделе Type.
-
List
представляет универсальный тип, E — тип TypeVariable, а List , String в List— ParameterizedType (параметризованный тип). называется фактическим типом параметра - При уточнении типов в дженериках вы можете использовать ?extensions или ?super для обозначения наследования, например
List<? extends Data>
, а ? внутри называется подстановочным типом WildcardType - GenericArrayType представляет тип массива, тип элемента которого — ParameterizedType (параметризованный тип) или TypeVariable (переменная типа), например T[] или List
[]
-
List
- Аннотации появились только в JDK 1.5.Для представления аннотированного типа был добавлен тип AnnotatedElement, что буквально означает аннотированный элемент. JDK1.8 имеет AnnotatedType, чтобы связать концепцию Type с аннотированным элементом.
- AnnotatedType также имеет четыре подинтерфейса, которые соответствуют один к одному четырем подинтерфейсам Type.Например, если тип ParameterizedType аннотирован, он будет проанализирован компилятором в AnnotatedParameterizedType:
@AnTest("list")List<String>list
2 Понятие дженериков
- Дженерики Java (дженерики) — новая возможность, представленная в JDK1.5, суть которой — параметризованный тип, который решает проблему неопределенности конкретного типа объекта, тип данных, которым он оперирует, указывается в виде параметра (параметра типа). типы могут использоваться при создании классов, интерфейсов и методов, которые называются универсальными классами, универсальными интерфейсами и универсальными методами соответственно.
Обобщения: отложите работу с конкретным типом до создания объекта или вызова метода определенных типов.
3 Примеры универсальных классов и универсальных методов
- Общее определение класса
public class MainTest<T> {
private T param;
}
public static void main(String[] args){
MainTest<String> data = new MainTest<String>(){};
ParameterizedType genType1 = (ParameterizedType)data.getClass().getGenericSuperclass();
}
- Определение универсального метода
public class MainTest{
public static void main(String[] args){
printData("siting");
}
static <T> T printData(T t){
System.out.println(t);
return t;
}
}
- И интерфейсы, и абстрактные классы могут использовать дженерики.
4 Введите стирание
- При создании универсального экземпляра JVM удалит конкретный тип; скомпилированный байт-код не содержит параметров типа в универсальном, то есть ArrayList
и ArrayList стираются в ArrayList, то есть это стирается в «примитивный тип», который является общим стиранием
public class MainTest {
public static void main(String[] args){
List<String> strArr = new ArrayList<>();
List<Integer> intArr = new ArrayList<>();
Type strClazz = strArr.getClass();
Type intClazz = intArr.getClass();
}
}
- Посмотрите, как представлен скомпилированный файл байт-кода: меню идеи -> вид -> показать байт-код
public class MainTest<T> {
T param;
public static void main(String[] args){
MainTest<String> test = new MainTest<>();
test.setParam("siting");
}
public T getParam() { return param; }
public void setParam(T param) { this.param = param; }
}
public class com/MainTest {
...省略
public static main([Ljava/lang/String;)V
L0
LINENUMBER 7 L0
NEW com/MainTest
DUP
INVOKESPECIAL com/MainTest.<init> ()V
ASTORE 1
L1
LINENUMBER 8 L1
ALOAD 1
LDC "siting" // 调用类型擦除后的setParam(Object)
INVOKEVIRTUAL com/MainTest.setParam (Ljava/lang/Object;)V
L2
...省略//getParam 的返回值是Object
public getParam()Ljava/lang/Object;
L0
LINENUMBER 10 L0
ALOAD 0
GETFIELD com/MainTest.param : Ljava/lang/Object;
ARETURN
...省略//setParam 的入参是Object
public setParam(Ljava/lang/Object;)V
L0
LINENUMBER 11 L0
ALOAD 0
ALOAD 1
PUTFIELD com/MainTest.param : Ljava/lang/Object;
RETURN
...
}
- Видно, что T(String) был преобразован в тип Object, а исходная инициализированная String исчезла.
5 Общее наследование
- Подкласс может указывать общие параметры родительского класса, который может быть известным классом (Integer, String и т. д.), или может быть указан собственными общими параметрами подкласса.
- При наследовании универсального типа и указании универсального параметра родительского класса в качестве родительского класса подкласса используется дополнительно сгенерированный тип ParameterizedType; если универсальный параметр родительского класса не указан, нативный тип наследуется напрямую
public class MainTest<T> {
T param;
static public class SubTest1 extends MainTest<String>{}
static public class SubTest2<R> extends MainTest<R>{}
//SubTest3继承的时原生类型
static public class SubTest3 extends MainTest{}
}
6 Общая переменная TypeVariable
- (Сначала временно определите имя, E в Test
является универсальным параметром); универсальная переменная TypeVariable: универсальным параметром универсального типа является TypeVariable; когда родительский класс использует универсальный параметр подкласса для указания своего собственного универсального параметра Когда универсальный атрибут определен в универсальном классе A и используется универсальный параметр T универсального класса A , универсальный параметр будет установлен компилятором как универсальная переменная TypeVariable, а не стерт.
public class MainTest<T> {
List<T> param;
public static void main(String[] args) throws Exception{
Class clazz = MainTest.class;
TypeVariable[] typeVariable = clazz.getTypeParameters();
// 1
Field field = clazz.getDeclaredField("param");
ParameterizedType arrayType = (ParameterizedType)field.getGenericType();
// interface List<E> 的泛型类型E被T,具体化,因此其被识别为 TypeVariable
TypeVariable variable1 = (TypeVariable)arrayType.getActualTypeArguments()[0];
// 2
ParameterizedType type = (ParameterizedType)SubTest.class.getGenericSuperclass();
TypeVariable variable2 = (TypeVariable)type.getActualTypeArguments()[0];
}
static class SubTest<R> extends MainTest<R>{}
}
7 Параметризованный тип
public interface ParameterizedType extends Type {
//获取实际参数,List<String>里的String; 如果是List<T>则是TypeVariable类型
Type[] getActualTypeArguments();
// 获取原始类型List<String> -> List<E>
Type getRawType();
Type getOwnerType();
}
- Следует отметить, что мы не можем напрямую получить тип универсального типа, который указывает конкретные параметры, такие как
Class clazz = List<String>.class
Он не проходит во время компиляции, также есть объект, созданный непосредственно через дженерик-класс new, у которого Class не типа ParameterizedType, а класс самого дженерика, пример такой
public class MainTest<T> {
public static void main(String[] args){
MainTest<String> str = new MainTest<String>();
Class variable = str.getClass();
Type genType1 = variable.getGenericSuperclass();
}
}
- Универсальный тип, специально параметризованный, может быть распознан компилятором как тип ParameterizedType.Существует три способа получить тип ParameterizedType.
// 1 子类继承泛型时,指定具体参数(可以是String等已知类型,也可以是子类的泛型参数)
// 2 获取在类内部定义的泛型属性,需指定具体泛型参数
// 3 局部代码,可以通过泛型的匿名内部子类(需指定具体泛型参数)获取ParameterizedType类型
public class MainTest<T> {
List<T> list;
public static void main(String[] args) throws NoSuchFieldException {
SubTest<String> str = new SubTest<>();
// 方式一
Class variable = str.getClass();
// 父类是(521)ParameterizedType类型
ParameterizedType genType = (ParameterizedType)variable.getGenericSuperclass();
// (521)ParameterizedType类型的原生类型是(479)class com.MainTest
Type clazz = genType.getRawType();
//MainTest.class 的原生类型是 (479)class com.MainTest
Class rawClazz = MainTest.class;
//方式二,泛型属性
Field field = rawClazz.getDeclaredField("list");
//属性list 类型是(546)ParameterizedType类型List<T>
ParameterizedType fieldType = (ParameterizedType)field.getGenericType();
// 方式三
MainTest<String> sub3 = new MainTest<String>(){};
// clazz3是匿名子类
Class clazz3 = sub3.getClass();
//父类是(555)ParameterizedType类型
ParameterizedType genType3 = (ParameterizedType) clazz3.getGenericSuperclass();
// (555)ParameterizedType类型的原生类型是(479)class com.MainTest
Type type3 = genType3.getRawType();
}
public static class SubTest<R> extends MainTest<R>{ }
}
8 подстановочных знаков (WildcardType)
Неограниченные подстановочные знаки: Неограниченный подстановочный знак ? можно использовать с любым типом ссылки:
- Когда параметр метода должен быть передан в универсальном типе, и его тип не может быть определен. Прямое использование дженериков без конкретных универсальных переменных может вызвать риски безопасности; если преобразование типов выполняется в коде метода, очень вероятно возникновение ошибок ClassCastException.
- Можно ли заменить общие переменные на Object?Однако отношения наследования между ParameterizedType (параметризованным типом) универсального класса и конкретным преобразованием параметра отсутствуют, то есть Object является родительским классом для String, но типы List. Следовательно, подстановочный знак типа ?
public static void print(List list){}
----->>>
public static void print(List<?> list){}
- Неограниченные подстановочные знаки могут соответствовать любому типу, но использование ? Когда значение не устанавливается в переменную универсального класса, потому что мы не знаем, что это за конкретный тип; если новое значение устанавливается принудительно, последующее чтение подвержено ошибкам ClassCastException. Значит, компилятор ограничивает подстановочный знак **? Общий тип ** можно только читать, но не записывать.
Подстановочный знак верхней границы
- Если я хочу получить коллекцию List, она может работать только с элементами числового типа [Float, Integer, Double, Byte и другие числовые типы в порядке], как это сделать? можно использовать
List<? extends Number的子类>
, указывая, что все элементы в списке являются подклассами класса Number
public static void print(List<? extends Number> list) {
Number n = new Double("1.0");
list.add(n);
Number tmp = list.get(0);
}
- На картинке видно, что есть подстановочный знак верхней границы, потому что конкретный тип неясен, его можно только читать, но не записывать
Подстановочный знак нижней границы
class Parent{ }
class Child extends Parent{ }
public class MainTest<T> {
T param;
public static void main(String[] args){
MainTest<? super Child> parent_m = new MainTest<>();
parent_m.setParam(new Child());
Object parent = parent_m.getParam();
}
public T getParam() { return param; }
public void setParam(T param) { this.param = param; }
}
- Если родительский класс подстановочного знака определен, это подстановочный знак нижней границы; такие подстановочные знаки доступны для чтения и записи, и при преобразовании в любой родительский класс не возникнет ошибки ClassCastException.
- Личная догадка: это потому, чтоподстановочный знакиподстановочный знак верхней границыОбщая склонность к ошибке ClassCastException, иПодстановочный знак нижней границыВ восходящем преобразовании нет ошибки ClassCastException, поэтому спецификация java ограничивает прежнюю ошибку компиляции, в то время как последняя компиляция проходит?
9 Общий массив (GenericArrayType)
public interface GenericArrayType extends Type {
//获得这个数组元素类型,即获得:A<T>(A<T>[])或 T(T[])
Type getGenericComponentType();
}
- GenericArrayType, обобщенный массив, описывает тип ParameterizedType и массив типов TypeVariable, то есть в виде: Test
[][], T[] и т.д., является подинтерфейсом GenericArrayType
public class MainTest<T> {
T[] param;
public static void main(String[] args) throws Exception{
Class clazz = MainTest.class;
Field field = clazz.getDeclaredField("param");
GenericArrayType arrayType = (GenericArrayType)field.getGenericType();
TypeVariable variable = (TypeVariable) arrayType.getGenericComponentType();
}
}