Основы: Углубленный анализ дженериков JAVA и системы типов типов.

Java
Основы: Углубленный анализ дженериков JAVA и системы типов типов.

1 Система типов JAVA Type

  • Сначала разберитесь с системой типов типов Java (класс класса => тип). Тип - это общий тип всех типов (примитивный тип-класс, параметризованный тип-параметризованный тип, тип массива-GenericArrayType, тип переменной-TypeVariable, базовый тип-класс) Интерфейс ; Class, упомянутый в первых двух размышлениях и аннотациях, является классом реализации Type
  • Существует четыре подинтерфейсных класса ParameterizedType, TypeVariable, GenericArrayType, WildcardType в разделе Type.
    • List представляет универсальный тип, E — тип TypeVariable, а List — ParameterizedType (параметризованный тип)., String в List называется фактическим типом параметра
    • При уточнении типов в дженериках вы можете использовать ?extensions или ?super для обозначения наследования, напримерList<? extends Data>, а ? внутри называется подстановочным типом WildcardType
    • GenericArrayType представляет тип массива, тип элемента которого — ParameterizedType (параметризованный тип) или TypeVariable (переменная типа), например T[] или 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 и List — это два разных типа. ParameterizedTypes, которые не существуют, отношение наследования. Следовательно, подстановочный знак типа ?
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();
    }
}

Добро пожаловать на ошибку в тексте

Следите за официальной учетной записью, общайтесь друг с другом и ищите в WeChat: Sneak forward