Полное решение Java Generics — абсолютно самое подробное

Java

задний план

Я всегда был наполовину знаком с дженериками java, и на самом деле я мало им пользуюсь. Только когда я прочитал «Effect Java» и увидел множество применений, которые я обычно не понимаю, я принял решение, и мне нужно было систематически изучить его и записать.


1. Обзор дженериков:

1.1 Происхождение дженериков

Согласно описанию в «Java Programming Thought», мотивация появления дженериков:

有很多原因促成了泛型的出现,而最引人注意的一个原因,就是为了创建容器类。

Идея дженериков существует уже давно, например Templates в C++. Дух шаблона:параметризованный тип

1.2 Общий обзор

  • Дженерики по сути являются «параметризованными типами». Что касается параметров, наиболее знакомым из них является то, что вам нужны формальные параметры, когда вы определяете метод, и вам нужно передавать фактические параметры, когда вы вызываете метод. Этот «параметризованный тип» предназначен для параметризации исходного конкретного типа.
  • Появление дженериков позволяет избежать операции принудительного преобразования, а преобразование типов выполняется в компиляторе, что также позволяет избежать ошибок запуска.

1.3 Назначение дженериков

  • Генераторы Java также являются своего рода синтаксическим сахаром, который завершает работу по преобразованию типов на этапе компиляции, чтобы избежать ClassCastException и исключений преобразования типов, вызванных принудительным преобразованием типов во время выполнения.

1.4 Примеры

В JDK 1.5 были добавлены дженерики, что значительно облегчает использование коллекций.

  • Без использования дженериков:
public static void main(String[] args) {
        List list = new ArrayList();
        list.add(11);
        list.add("ssss");
        for (int i = 0; i < list.size(); i++) {
            System.out.println((String)list.get(i));
        }
    }

Поскольку тип списка — Object. Следовательно, данные типа int и String можно вводить и вынимать. Однако приведенный выше код вызовет исключение преобразования типа при запуске, что, я думаю, понятно каждому.

  • Используйте дженерики:
public static void main(String[] args) {
        List<String> list = new ArrayList();
        list.add("hahah");
        list.add("ssss");
        for (int i = 0; i < list.size(); i++) {
            System.out.println((String)list.get(i));
        }
    }

В приведенном выше примере мы можем добавить только данные типа String, иначе компилятор сообщит об ошибке.


2. Использование дженериков

Три способа использования дженериков:общий класс,общий метод,общий интерфейс

2.1 Общие классы

  • Схема дикого класса: определение дженериков в классе
  • Формат определения:
public class 类名 <泛型类型1,...> {
    
}
  • Примечание. Универсальные типы должны быть ссылочными типами (не примитивными типами данных).

2.2 Общие методы

  • Обзор универсальных методов: определение универсальных методов для методов
  • Формат определения:
public <泛型类型> 返回类型 方法名(泛型类型 变量名) {
    
}
  • Обратите внимание:
    • Формальные параметры, определенные в объявлении метода, могут использоваться только в методе, тогда как параметры типа, определенные в объявлении интерфейса и класса, могут использоваться во всем интерфейсе и классе. При вызове метода fun() в соответствии с переданным фактическим объектом компилятор определит фактический тип, представленный параметром типа T.
class Demo{  
  public <T> T fun(T t){   // 可以接收任意类型的数据  
   return t ;     // 直接把参数返回  
  }  
};  
public class GenericsDemo26{  
  public static void main(String args[]){  
    Demo d = new Demo() ; // 实例化Demo对象  
    String str = d.fun("汤姆") ; // 传递字符串  
    int i = d.fun(30) ;  // 传递数字,自动装箱  
    System.out.println(str) ; // 输出内容  
    System.out.println(i) ;  // 输出内容  
  }  
};

2.3 Общий интерфейс

  • Обзор универсального интерфейса: определение универсальных интерфейсов в интерфейсах
  • Формат определения:
public interface 接口名<泛型类型> {
    
}
  • Пример:
/**
 * 泛型接口的定义格式:        修饰符  interface 接口名<数据类型> {}
 */
public interface Inter<T> {
    public abstract void show(T t) ;
}

/**
 * 子类是泛型类
 */
public class InterImpl<E> implements Inter<E> {
    @Override
    public void show(E t) {
        System.out.println(t);
    }
}


Inter<String> inter = new InterImpl<String>() ;
inter.show("hello") ;

2.4 Использование дженериков в исходном коде, ниже приведены фрагменты кода интерфейса List и класса ArrayList.

//定义接口时指定了一个类型形参,该形参名为E
public interface List<E> extends Collection<E> {
   //在该接口里,E可以作为类型使用
   public E get(int index) {}
   public void add(E e) {} 
}

//定义类时指定了一个类型形参,该形参名为E
public class ArrayList<E> extends AbstractList<E> implements List<E> {
   //在该类里,E可以作为类型使用
   public void set(E e) {
   .......................
   }
}

2.5 Подкласс, производный от общего класса

Когда родительский класс является производным от подкласса, он не может включать параметр типа, и ему необходимо передать конкретный тип.

  • Неправильный способ:

public class A extends Container<K, V> {}

  • Правильно:

public class A extends Container<Integer, String> {}

  • Вы также можете не указывать конкретный тип, и система будет рассматривать параметры K и V как типы объектов.

public class A extends Container {}

2.6 Общие конструкторы

  • Конструктор также является методом, поэтому создается так называемый общий конструктор.
  • Нет никакой разницы между использованием обычных методов, один из которых заключается в явном указании общих параметров, а другой - в неявном выводе.
public class Person {
    public <T> Person(T t) {
        System.out.println(t);
    }
    
}

использовать:

public static void main(String[] args) {
    new Person(22);// 隐式
    new <String> Person("hello");//显示
}
  • Специальные инструкции:

    • Если конструктор является универсальным конструктором, а класс также является универсальным классом, как использовать универсальный конструктор: Поскольку универсальный конструктор может явно указывать параметры своего собственного типа (требуются ромбы, помещаемые в конструктор перед конструктором), а Аргументы типа универсального класса также должны быть указаны (ромб помещается после конструктора), поэтому два ромба появляются одновременно, что вызовет небольшие проблемы, и конкретное использование будет кратко изложено здесь. Представлено следующим примером
    public class Person<E> {
        public <T> Person(T t) {
            System.out.println(t);
        }
    }
    

    Правильное использование:

    public static void main(String[] args) {
        Person<String> person = new Person("sss");
    }
    

    PS: компилятор напомнит вам, как это сделать

2.7 Дополнительные подстановочные знаки

2.7.1 Предыстория:

2.7.2 extends T> Подстановочный знак верхней границы

  • Как следует из названия, подстановочный знак верхней границы extends T> представляет собой верхнюю границу типа [включая самого себя], поэтому параметризованный тип подстановочного знака может быть T или подклассом T.

    • Поскольку определить конкретный тип невозможно, метод add ограничен (можно добавить null, поскольку null представляет любой тип), но он может получать элементы из списка и назначать их родительскому типу. Как и в первом примере выше, третья операция add() ограничена, поскольку List и List являются подтипами List.
    它表示集合中的所有元素都是Animal类型或者其子类
     List<? extends Animal>
    
  • Это так называемый подстановочный знак верхней границы, который реализуется с помощью ключевого слова extends.При создании экземпляра указанный аргумент типа может быть только подклассом типа после extends или самим собой.

    • Например:
    • Таким образом определяется тип элементов в коллекции.Хотя конкретный тип не определен, по крайней мере известен родительский класс. Затем займитесь другими делами.
    //Cat是其子类
        List<? extends Animal> list = new ArrayList<Cat>();
    

2.7.3 Подстановочный знак Пустоты

  • Подстановочный знак нижней границы указывает, что параметризованный тип является супертипом T (включая самого себя), слой за слоем, пока Object

    • У компилятора нет способа определить, какой тип объекта возвращает get(), поэтому метод get() ограничен. Но можно использовать метод add().Метод add() может добавлять тип T и подтип типа T. Например, во втором примере сначала добавляется объект типа Cat, а затем добавляются два объекта подтипа Cat. метод возможен, но если вы добавите объект типа Animal, это, очевидно, изменит отношение наследования, что невозможно.
     它表示集合中的所有元素都是Cat类型或者其父类
     List <? super Cat>   
    
  • Это так называемый подстановочный знак нижней границы, который реализуется с помощью ключевого слова super.При создании экземпляра указанный аргумент типа может быть только подклассом типа после расширения или самого себя.

    • Например
    //Animal是其父类
    List<? super Cat> list = new ArrayList<Animal>();
    

2.7.4 > Неограниченный подстановочный знак

  • Любой тип, если не указан, то это Object и любой класс Java
  • Неограниченные подстановочные знаки представлены >, ? представляет любой тип, и только null может представлять любой тип (сам объект также является типом, но он не может представлять какой-либо тип, поэтому значение List и List различно, тип первый — это Object, который является верхним уровнем дерева наследования, а тип второго совершенно неизвестен)

3. Общее стирание

3.1 Концепция

Компилятор удаляет информацию о типе при компиляции коллекции с указанием типа.

3.2 Пример проверки:

public class GenericTest {
    public static void main(String[] args) {
        new GenericTest().testType();
    }

    public void testType(){
        ArrayList<Integer> collection1 = new ArrayList<Integer>();
        ArrayList<String> collection2= new ArrayList<String>();
        
        System.out.println(collection1.getClass()==collection2.getClass());
        //两者class类型一样,即字节码一致
        
        System.out.println(collection2.getClass().getName());
        //class均为java.util.ArrayList,并无实际类型参数信息
    }
}
  • Выходной результат:
true
java.util.ArrayList
  • анализировать:
    • Это связано с тем, что независимо от того, какой параметр типа передается для параметра универсального типа, для Java они по-прежнему рассматриваются как один и тот же класс и занимают только одно место в памяти. С точки зрения концепции дженериков Java, он действует только на этапе компиляции кода.В процессе компиляции, после правильной проверки результатов дженериков, соответствующая информация о дженериках будет удалена, то есть успешная компиляция Более поздний файл класса не содержит никакой общей информации. Общая информация не входит в фазу выполнения.
    • Параметры типа не допускаются в объявлении и инициализации статических методов, статических блоков инициализатора или статических переменных. Поскольку универсальные классы на самом деле не генерируются в системе, универсальные классы нельзя использовать после оператора instanceof.

4. Обобщения и отражение

  • Рассматривайте универсальную переменную как параметр метода и используйте метод getGenericParameterTypes класса Method для получения параметра фактического типа универсального типа.
  • пример:
public class GenericTest {

    public static void main(String[] args) throws Exception {
        getParamType();
    }
    
     /*利用反射获取方法参数的实际参数类型*/
    public static void getParamType() throws NoSuchMethodException{
        Method method = GenericTest.class.getMethod("applyMap",Map.class);
        //获取方法的泛型参数的类型
        Type[] types = method.getGenericParameterTypes();
        System.out.println(types[0]);
        //参数化的类型
        ParameterizedType pType  = (ParameterizedType)types[0];
        //原始类型
        System.out.println(pType.getRawType());
        //实际类型参数
        System.out.println(pType.getActualTypeArguments()[0]);
        System.out.println(pType.getActualTypeArguments()[1]);
    }

    /*供测试参数类型的方法*/
    public static void applyMap(Map<Integer,String> map){

    }
}
  • Выходной результат:
java.util.Map<java.lang.Integer, java.lang.String>
interface java.util.Map
class java.lang.Integer
class java.lang.String
  • Обход ограничений типа компилятора для дженериков посредством отражения
public static void main(String[] args) throws Exception {
		//定义一个包含int的链表
		ArrayList<Integer> al = new ArrayList<Integer>();
		al.add(1);
		al.add(2);
		//获取链表的add方法,注意这里是Object.class,如果写int.class会抛出NoSuchMethodException异常
		Method m = al.getClass().getMethod("add", Object.class);
		//调用反射中的add方法加入一个string类型的元素,因为add方法的实际参数是Object
		m.invoke(al, "hello");
		System.out.println(al.get(2));
	}


5 ограничений дженериков

5.1 Ошибки неоднозначности

  • Для универсального класса User объявляются два параметра универсального класса. Переопределите метод show в классе с параметрами другого типа.
public class User<K, V> {
    
    public void show(K k) { // 报错信息:'show(K)' clashes with 'show(V)'; both methods have same erasure
        
    }
    public void show(V t) {

    }
}

Оба по сути являются объектными типами из-за общего стирания. Метод тот же, поэтому компилятор сообщит об ошибке.

По-другому:

public class User<K, V> {

    public void show(String k) {

    }
    public void show(V t) {

    }
}

Использовать результат:

можно использовать нормально

5.2 Невозможно создать экземпляр параметров типа

Компилятор также не знает, какой тип объекта создавать.

public class User<K, V> {

    private K key = new K(); // 报错:Type parameter 'K' cannot be instantiated directly

}

5.3 Ограничения для статических членов

Статический метод не может получить доступ к универсальному типу, определенному в классе; если тип операции статического метода не определен, универсальный тип должен быть определен в методе.

Если статический метод должен использовать дженерики, статический метод должен быть определен как дженерик..

public class User<T> {

    //错误
    private static T t;

    //错误
    public static T getT() {
        return t;
    }

    //正确
    public static <K> void test(K k) {

    }
}

5.4 Ограничения для универсальных массивов

  • Массив, тип элемента которого является параметром типа, не может быть создан, но можно указать массив на ссылку на массив совместимых типов.
public class User<T> {

    private T[] values;

    public User(T[] values) {
        //错误,不能实例化元素类型为类型参数的数组
        this.values = new T[5];
        //正确,可以将values 指向类型兼容的数组的引用
        this.values = values;
    }
}

5.5 Ограничения на общие исключения

Общие классы не могут расширять Throwable, что означает, что общие классы исключений не могут быть созданы.ссылка для ответа

Ссылаться на: