Экспорт дженериков
Мы вводим понятие почему дженерики через следующий пример.
public class Test {
public static void main(String[] args) {
List list = new ArrayList();
list.add("abc");
list.add(2);
for (int i = 0; i < list.size(); i++) {
String name = (String) list.get(i); // error
System.out.println("name:" + name);
}
}
}
При получении второго элемента в списке будет сообщено об ошибке,java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String. Это распространенная ошибка преобразования типа.
Когда мы помещаем элементы в список, мы не используем указанный тип, а при удалении элементов используется значение по умолчанию.ObjectТипы. Поэтому легко иметь исключения преобразования типов.
Результат, которого мы хотим достичь, заключается в том, что коллекция может запоминать типы элементов в коллекции и может добиться того, что, если во время компиляции нет проблем, она не появится во время выполнения.java.lang.ClassCastExceptionаномальный. Дженерики — это то, что нам нужно.
Что такое дженерики?
общий, т.е.参数化类型. Когда дело доходит до параметров, наиболее привычным является определение метода с формальными параметрами, а затем передача фактических параметров при вызове метода. Итак, как вы понимаете параметризованные типы? Как следует из названия, тип параметризуется исходным конкретным типом, аналогично параметру переменной в методе.В настоящее время тип также определяется как форма параметра (которую можно назвать параметром типа), а затем конкретный тип передается при использовании/вызове Type (аргумент типа).
Суть дженериков заключается в параметризации типов, то есть в управлении типами конкретных ограничений формальных параметров через разные типы, указанные дженериками, без создания новых типов. При использовании универсальных типов тип данных операции указывается в качестве параметра, и этот тип параметра может использоваться в классах, интерфейсах и методах, которые называются универсальными классами, универсальными интерфейсами и универсальными методами соответственно.
Особенности дженериков
Введение дженериков в язык Java является важным усовершенствованием. Не только язык, система типов и компилятор значительно изменились для поддержки дженериков, но и библиотека классов также претерпела серьезные изменения, поэтому многие важные классы, такие как структура коллекций, стали универсальными. Это дает много преимуществ:
- Безопасность типа. Основная цель дженериков — повысить безопасность типов Java-программ. Зная ограничения типа переменных, определенных с помощью универсальных шаблонов, компилятор может гораздо лучше проверить предположения о типах.
- Устранить слепки. Дополнительным преимуществом дженериков является то, что они устраняют множество приведений типов в исходном коде. Это делает код более читаемым и снижает вероятность ошибок.
- Потенциальный прирост производительности. Дженерики открывают возможность более масштабной оптимизации. В первоначальной реализации дженериков компилятор вставлял приведения (которые программист указал бы без дженериков) в сгенерированный байт-код.
параметры именованного типа
Рекомендуемое соглашение об именах состоит в том, чтобы использовать имена из одной буквы в верхнем регистре для параметров типа. Для распространенных универсальных шаблонов рекомендуемые имена:
- K: клавиша, например сопоставленная клавиша
- V: Значение, такое как содержимое List и Set или значение в Map
- Е: элемент
- Т: универсальный
public class Generic<T> {
//key的类型为T
private T key;
public Generic(T key) {
//泛型构造方法形参key的类型也为T
this.key = key;
}
public T getKey() {
//泛型方法getKey的返回值类型为T
return key;
}
}
Общий универсальный класс определяется, как указано выше, тип переменной-члена — T, а тип T указывается извне. То же самое касается универсальных методов и универсальных конструкторов.
Generic<Integer> genericInteger = new Generic<Integer>(123456); //1
Generic<String> genericString = new Generic<String>("key_vlaue"); // 2
System.out.println("key is " + genericInteger.getKey());
System.out.println("key is " + genericString.getKey());
Параметры универсального типа могут быть только типами классов (включая пользовательские классы), а не простыми типами. Тип передаваемого фактического параметра должен совпадать с типом параметра универсального типа, то есть Integer/String.
Как упоминалось выше, при определении универсального класса нужно ли передавать аргумент универсального типа?
Это не так, при использовании дженериков, если переданы дженерик-параметры, будут сделаны соответствующие ограничения по переданным дженерикам параметрам, и тогда дженерики будут играть роль ограничений, которые должны играть. Если аргумент универсального типа не передается, тип, определенный универсальным методом или переменной-членом в универсальном классе, может быть любого типа.
Generic genericString = new Generic("111111");
Generic genericInteger = new Generic(4444);
System.out.println("key is " + genericString.getKey());
System.out.println("key is " + genericInteger.getKey());
Приведенный выше фрагмент кода выведет следующие результаты:
key is 111111
key is 4444
Универсальные защитные элементы или переменные-члены, используемые в универсальном классе, могут относиться к любому другому типу, например Integer или String, без передачи аргумента универсального типа. Однако следует отметить, что параметр типа универсального может быть только типом класса, а не простым типом. и не может использоваться на точных универсальных типахinstanceofработать. Для различных входящих аргументов типа тип соответствующего экземпляра объекта генерируется одинаково? В частности, посмотрите на следующий пример:
public class GenericTest {
public static void main(String[] args) {
Generic<Integer> name = new Box<String>("111111");
Generic<String> age = new Box<Integer>(712);
System.out.println("name class:" + name.getClass());
System.out.println("age class:" + age.getClass());
System.out.println(name.getClass() == age.getClass()); // true
}
}
Из выходной структуры видно, что при использовании универсального класса, хотя и передаются разные универсальные аргументы, на самом деле он не генерирует разные типы.В памяти находится только один универсальный класс, который передает разные универсальные аргументы. исходный самый базовый тип (в данном случае универсальный), конечно, логически мы можем понимать его как несколько различных универсальных типов.
Причина в том, что цель концепции дженериков в Java состоит в том, что она действует только на этапе компиляции кода. В процессе компиляции, после правильной проверки результата универсального типа, соответствующая информация универсального типа будет стерта. То есть успешно скомпилированный файл класса не содержит никакой общей информации. Общая информация не входит в фазу выполнения.
Универсальные типы логически рассматриваются как несколько разных типов, все из которых на самом деле являются одним и тем же базовым типом.
подстановочный знак
Ingeter является подклассом Number, иGeneric<Ingeter>а такжеGeneric<Number>На самом деле это тот же базовый тип. Затем возникает проблема при использованииGeneric<Number>Можно ли использовать метод в качестве формального параметра?Generic<Ingeter>экземпляр входящего? логически похоже наGeneric<Number>а такжеGeneric<Ingeter>Можно ли его рассматривать как общий тип с отношениями родитель-потомок? Ниже мы проверяем, определяя метод.
public void show(Generic<Number> obj) {
System.out.println("key value is " + obj.getKey());
}
Сделайте следующие звонки:
Generic<Integer> genericInteger = new Generic<Integer>(123);
show(genericInteger); //error Generic<java.lang.Integer> cannot be applied to Generic<java.lang.Number>
Через оперативную информацию мы можем видетьGeneric<Integer>нельзя рассматривать какGeneric<Number>подкласс . Отсюда видно, что один и тот же универсальный тип может соответствовать нескольким версиям (поскольку тип параметра не определен), а экземпляры универсального класса разных версий несовместимы.
Поэтому мы не можем определитьshow(Generic<Integer> obj)для обработки, поэтому нам нужен ссылочный тип, который может логически представлять как Generic, так и родительский класс Generic. Этот тип подстановочного знака появился.
Набираются общие буквы, такие как T, K, V, E, а параметрам типа присваиваются определенные значения. В дополнение к типам подстановочные знаки также могут использоваться для выражения типов,?Неизвестный тип, параметру типа присваивается неопределенное значение, и любой тип может использоваться только в объявлении типа и параметра метода и не может использоваться в определении универсального класса. Перепишите метод следующим образом:
public void show(Generic<?> obj) {
System.out.println("key value is " + obj.getKey());
}
здесь?является аргументом типа, а не параметром типа. То есть, подобно Number, String и Integer, все они являются фактическими типами.?Рассматриваемый как родительский класс всех типов, это реальный тип. Это может быть решено, когда конкретный тип неясен, этот подстановочный знак?; При манипулировании типом, когда вам не нужно использовать определенные функции типа, используйте только функции класса Object. тогда вы можете использовать?Подстановочные знаки для представления неизвестных типов.
Общие верхние и нижние границы
При использовании дженериков мы также можем ограничить верхнюю и нижнюю границы входящих аргументов универсального типа, например: аргументы типа могут передаваться только в определенном типе родительского класса или в определенном типе подкласса. Добавьте верхнюю границу для дженериков, то есть передаваемый аргумент типа должен быть подтипом указанного типа.
public void show(Generic<? extends Number> obj) {
System.out.println("key value is " + obj.getKey());
}
Наш тип параметра в универсальном методе ограниченNumberподкласс .
Generic<String> genericString = new Generic<String>("11111");
Generic<Integer> genericInteger = new Generic<Integer>(2222);
showKeyValue1(genericString); // error
showKeyValue1(genericInteger);
Когда наш входной параметр имеет тип String, компилятор сообщает об ошибке, потому что тип String не является подклассом типа Number.
Заглавные буквы типа передаются какGeneric<? extends Number>Формальное определение, соответственно нижняя граница подстановочного знака типа равнаGeneric<? super Number>форма, его значение противоположно верхнему пределу подстановочного знака типа, поэтому я не буду здесь подробно останавливаться.
Общий массив
В java невозможно создать массив точного универсального типа, т.е.:
List<String>[] ls = new ArrayList<String>[10];
Приведенное выше скомпилирует ошибку, и можно использовать подстановочные знаки для создания общих массивов:
List<?>[] ls = new ArrayList<?>[10];
//List<String>[] ls = new ArrayList[10];
JDK1.7 упрощает дженерики, поэтому возможно и другое объявление.
Из-за механизма стирания дженериков JVM JVM не знает информации об дженериках во время выполнения. Универсальные массивы Фактические массивы объектов времени выполнения могут быть только примитивного типа ( Object[] для T[], Pair[] для Pair[]), в то время как фактические объекты массива времени выполнения могут быть типа T (хотя среда выполнения стирает до примитивного типа) . Единственный способ успешно создать общий массив — создать новый массив стертого типа и привести его.
public class GenericArray<T> {
private Object[] array; //维护Object[]类型数组
@SupperessWarning("unchecked")
public GenericArray(int v) {
array = new Object[v];
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return (T)array[index];
} //数组对象出口强转
public T[] rep() { return (T[])array; } //运行时无论怎样都是Object[]类型
public static void main (String[] args){
GenericArray<Integer> ga = new GenericArray<Integer>(10);
// Integer[] ia = ga.rep(); //依旧ClassCastException
Object[] oa = ga.rep(); //只能返回对象数组类型为Object[]
ga.put(0, 11);
System.out.println(ga.get(0)); // 11
}
}
Во время выполнения выход объекта массива преобразуется и выводится, а метод входа обеспечивает безопасность типов во время компиляции, поэтому метод выхода может безопасно привести тип для обеспечения успеха.
резюме
В этой статье в основном рассказывается о связанных концепциях и приложениях дженериков Java. Обобщения позволяют компилятору проверять типы во время компиляции, чтобы повысить безопасность типов и уменьшить количество исключений во время выполнения из-за несоответствия типов объектов. Рождение дженериков знакомит с родственными понятиями, а также с тем, как использовать дженерики для упрощения разработки и обеспечения качества кода.