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