предисловие
Соответствующие знания о дженериках Java были разобраны, что является относительно базовым.Я надеюсь, что все смогут учиться и прогрессировать вместе.
1. Что такое дженерики Java
Дженерики Java (дженерики) — это новая функция, представленная в JDK 5. Суть ее — параметризованный тип, который решает проблему неопределенности конкретных типов объектов. Тип данных, с которым он работает, указан как параметр (параметр типа).Этот тип параметра может использоваться при создании классов, интерфейсов и методов, которые называются универсальными классами, универсальными интерфейсами и универсальными методами соответственно.
общий класс
Общий класс — это тот, который имеетодна или несколько переменных типатип. Простой пример универсального класса выглядит следующим образом:
//常见的如T、E、K、V等形式的参数常用于表示泛型,编译时无法知道它们类型,实例化时需要指定。
public class Pair <K,V>{
private K first;
private V second;
public Pair(K first, V second) {
this.first = first;
this.second = second;
}
public K getFirst() {
return first;
}
public void setFirst(K first) {
this.first = first;
}
public V getSecond() {
return second;
}
public void setSecond(V second) {
this.second = second;
}
public static void main(String[] args) {
// 此处K传入了Integer,V传入String类型
Pair<Integer,String> pairInteger = new Pair<>(1, "第二");
System.out.println("泛型测试,first is " + pairInteger.getFirst()
+ " ,second is " + pairInteger.getSecond());
}
}
Результаты приведены ниже:
泛型测试,first is 1 ,second is 第二
общий интерфейс
Дженерики также могут быть применены к интерфейсам.
public interface Generator<T> {
T next();
}
При реализации класса для реализации этого интерфейса он может указать конкретный тип универсального T.
Укажите класс реализации, конкретным типом которого является Integer:
public class NumberGenerator implements Generator<Integer> {
@Override
public Integer next() {
return new Random().nextInt();
}
}
Укажите класс реализации, конкретным типом которого является String:
public class StringGenerator implements Generator<String> {
@Override
public String next() {
return "测试泛型接口";
}
}
общий метод
Метод с одной или несколькими переменными типа называется универсальным методом.
public class GenericMethods {
public <T> void f(T x){
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("字符串");
gm.f(666);
}
}
результат операции:
java.lang.String
java.lang.Integer
Преимущества дженериков
Преимущество введения дженериков в язык Java заключается в том, чтоБезопасно и просто. Преимущество дженериков в том, что безопасность типов проверяется во время компиляции, а все приведения выполняются автоматически и неявно, что улучшает повторное использование кода.
Начнем с рассмотрения класса, который может содержать только один объект.
public class Holder1 {
private Automobile a;
public Holder1(Automobile a) {
this.a = a;
}
public Automobile getA() {
return a;
}
}
Мы можем обнаружить, что повторное использование этого класса не очень хорошо. Чтобы он содержал любой объект другого типа, перед дженериками jdk1.5 вы можете установить тип Object следующим образом:
public class Holder2 {
private Object a;
public Holder2(Object a) {
this.a = a;
}
public Object getA() {
return a;
}
public void setA(Object a) {
this.a = a;
}
public static void main(String[] args) {
Holder2 holder2 = new Holder2(new Automobile());
//强制转换
Automobile automobile = (Automobile) holder2.getA();
holder2.setA("测试泛型");
String s = (String) holder2.getA();
}
}
Мы вводим дженерики для реализации класса Holder3 с той же функцией, что и у класса Holder2, следующим образом:
public class Holder3<T> {
private T a;
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
public Holder3(T a) {
this.a = a;
}
public static void main(String[] args) {
Holder3<Automobile> holder3 = new Holder3<>(new Automobile());
Automobile automobile = holder3.getA();
}
}
Итак, преимущества дженериков очевидны:
- Никакого принуждения не требуется, поэтому код получается более лаконичным; (краткость)
- Вместо Object для представления других типов объектов, чтобы провести линию от исключения ClassCastException. (безопасность)
- Обобщения делают код более читабельным. (читаемость)
3. Общие подстановочные знаки
Когда мы определяем дженерики, мы часто сталкиваемся с T, E, K, V, ? Равные подстановочные знаки. По сути, это подстановочные знаки, которые являются соглашением в кодировании. Конечно, неважно, если вы поменяете еще одну букву в A-Z, но для удобства чтения обычно используются следующие определения:
- ? представляет неопределенный тип java
- T (тип) представляет определенный тип Java
- K V (значение ключа) соответственно представляют значение ключа в значении ключа java.
- E (элемент) означает элемент
Зачем нам нужно вводить подстановочные знаки?Что ж, давайте сначала рассмотрим пример:
class Fruit{
public int getWeigth(){
return 0;
}
}
//Apple是水果Fruit类的子类
class Apple extends Fruit {
public int getWeigth(){
return 5;
}
}
public class GenericTest {
//数组的传参
static int sumWeigth(Fruit[] fruits) {
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
static int sumWeight1(List<? extends Fruit> fruits) {
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
static int sumWeigth2(List<Fruit> fruits){
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
public static void main(String[] args) {
Fruit[] fruits = new Apple[10];
sumWeigth(fruits);
List<Apple> apples = new ArrayList<>();
sumWeight1(apples);
//报错
sumWeigth2(apples);
}
}
Мы можем обнаружить, что Fruit[] совместим с Apple[].List<Fruit>
иList<Apple>
Несовместимы, коллекция List не может быть ковариантной, и будет сообщено об ошибке, а List и List — в порядке, в чем прелесть подстановочных знаков. Подстановочные знаки обычно делятся на три категории:
- Неограниченные подстановочные знаки, такие как List>
- Верхняя граница определяет подстановочные знаки, такие как ;
- Подстановочный знак нижней границы, такой как ;
?Неограниченный подстановочный знак
Неограниченный подстановочный знак, используется в виде одиночного вопросительного знака: List>, то есть без какого-либо уточнения.
См. пример:
public class GenericTest {
public static void printList(List<?> list) {
for (Object object : list) {
System.out.println(object);
}
}
public static void main(String[] args) {
List<String> list1 = new ArrayList<>();
list1.add("A");
list1.add("B");
List<Integer> list2 = new ArrayList<>();
list2.add(100);
list2.add(666);
//报错,List<?>不能添加任何类型
List<?> list3 = new ArrayList<>();
list3.add(666);
}
}
Неограниченный подстановочный знак (>) можно адаптировать к любому ссылочному типу, и он кажется эквивалентным исходному типу, но все же отличается от исходного типа.Неограниченные подстановочные знаки указывают на то, что используются дженерики.. В то же время список List> не может добавить какой-либо тип, потому что он не знает, какой это тип на самом деле. Но список List может добавлять объекты любого типа, потому что он содержит объекты типа Object.
Подстановочный знак верхней границы
Использование подстановочных знаков вида , т.е.подстановочный знак верхней границы. Ключевое слово extends указывает, что параметры в этом универсальном элементе должны быть E или подклассом E, см. демонстрацию:
class apple extends Fruit{}
static int sumWeight1(List<? extends Fruit> fruits) {
int weight = 0;
for (Fruit fruit : fruits) {
weight += fruit.getWeigth();
}
return weight;
}
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
sumWeight1(apples);
}
Тем не менее, следующий кодНеисправностьиз:
static int sumWeight1(List<? extends Fruit> fruits){
//报错
fruits.add(new Fruit());
//报错
fruits.add(new Apple());
}
- существует
List<Fruit>
Только объекты класса Fruit и объекты их подклассов (такие как объекты Apple, объекты Oragne) могут быть добавлены кList<Apple>
К нему можно добавить только класс Apple и его подклассы. - мы знаем
List<Fruit>、List<Apple>
и т.д. все это список Подтип продлен фруктов>. Предположим, что исходный параметрList<Fruit> list
, два добавить не проблема, если дошлоList<Apple> list
, добавление не удается, и компилятор напрямую отключает функцию добавления, чтобы защитить себя. - На самом деле в List нельзя добавлять произвольные объекты, кроме null.
Подстановочный знак нижней границы
Использование подстановочных знаков вида , то естьподстановочный знак нижней границы. Ключевое слово super указывает, что параметр в этом универсальном типе должен быть указанного типа E или супертипа этого типа, вплоть до Object.
public class GenericTest {
private static <T> void test(List<? super T> dst, List<T> src){
for (T t : src) {
dst.add(t);
}
}
public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
List<Fruit> fruits = new ArrayList<>();
test(fruits, apples);
}
}
Можно обнаружить, что в добавлении List нет проблем, потому что подкласс может указывать на родительский класс, это не похоже на List, проблем с безопасностью не будет, так что это выполнимо .
В-четвертых, общее стирание
что такое стирание типа
что такое джаваОбщее стираниеШерстяная ткань? Давайте сначала посмотрим на демо:
Class c1 = new ArrayList<Integer>().getClass();
Class c2 = new ArrayList<String>().getClass();
System.out.println(c1 == c2);
/* Output
true
*/
ArrayList <Integer>
иArrayList <String>
Легко думать о разных типах. Но вывод здесь верен, потому что дженерики Java реализованы с использованием стирания, будь тоArrayList<Integer>()
все ещеnew ArrayList<String>()
, параметры типа в универсальном типе не включаются в байт-код, сгенерированный при компиляции, то есть все они стираются в ArrayList, то есть они стираются в «примитивный тип», который является универсальным стиранием.
Введите стирание нижнего слоя
Дженерики Java выполняются во время компиляции и реализуются компилятором. На самом деле, компилятор в основном делает следующие вещи:
- Проверка типов для метода set()
- Преобразование типов в get(), компилятор вставляет оператор проверки,
Посмотрите на пример:
public class GenericTest<T> {
private T t;
public T get() {
return t;
}
public void set(T t) {
this.t = t;
}
public static void main(String[] args) {
GenericTest<String> test = new GenericTest<String>();
test.set("jay@huaxiao");
String s = test.get();
System.out.println(s);
}
}
/* Output
jay@huaxiao
*/
javap -c GenericTest.class может декомпилировать класс GenericTest
public class generic.GenericTest<T> {
public generic.GenericTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public T get();
Code:
0: aload_0
1: getfield #2 // Field t:Ljava/lang/Object;
4: areturn
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field t:Ljava/lang/Object;
5: return
public static void main(java.lang.String[]);
Code:
0: new #3 // class generic/GenericTest
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String jay@huaxiao
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
25: aload_2
26: invokevirtual #10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29: return
}
- Посмотрите на № 11, в набор входит примитивный тип Object (# 6);
- См. Раздел 15, метод get также относится к типу Object (#7), что указывает на то, что тип был стерт.
- Посмотрите на 18-й, он выполняет операцию контрольного приведения, это тип String, и он приводится.
V. Ограничения и ограничения дженериков
Использование универсальных шаблонов Java требует рассмотрения следующих ограничений и ограничений, почти все из которых связаны с универсальным стиранием.
Не может привести к типу параметров в основных типах
不能用类型参数代替基本类型。 Следовательно, нетPair<double>
, ТолькоPair<Double>
. Конечно, причиной этого является стирание типа. После стирания класс Pair содержит поля типа Object, которые не могут хранить двойные значения.
Запросы типа времени выполнения работают только с примитивными типами
Например, методы getClass() и т. д. возвращают только примитивные типы, потому что JVM вообще не знает о дженериках, она знает только примитивные типы.
if(a instanceof Pair<String>) //ERROR,仅测试了a是否是任意类型的一个Pair,会看到编译器ERROR警告
if(a instanceof Pair<T>) //ERROR
Pair<String> p = (Pair<String>) a;//WARNING,仅测试a是否是一个Pair
Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if(stringPair.getClass() == employeePair.getClass()) //会得到true,因为两次调用getClass都将返回Pair.class
Невозможно создать массив параметризованного типа
Массивы параметризованных типов не могут быть созданы, например:
Pair<String>[] table = new Pair<String>[10]; // Error
не может создать экземпляр переменной типа
Вы не можете использовать переменные типа в таких выражениях, как new T(...), newT[...] или T.class. Например, следующееPair<T>
Конструкторы незаконны:
public Pair() { first = new T(); second = new T(); } // Error
При использовании универсальных интерфейсов необходимо избегать повторной реализации одного и того же интерфейса.
interface Swim<T> {}
class Duck implements Swim<Duck> {}
class UglyDuck extends Duck implements Swim<UglyDuck> {}
Может исключить проверки проверенных исключений
@SuppressWamings("unchecked")
public static <T extends Throwable〉void throwAs(Throwable e) throws T { throw (T) e; }
При определении возвращаемых сообщений API попробуйте использовать дженерики;
public class Response<T> extends BaseResponse {
private static final long serialVersionUID = -xxx;
private T data;
private String code;
public Response() {
}
public T getData() {
return this.data;
}
public void setData(T data,String code ) {
this.data = data;
this.code = code;
}
}
В-шестых, общие общие поверхностные вопросы по Java.
Распространенные вопросы на собеседовании по дженерикам Java
- Что такое дженерики в Java? Каковы преимущества использования дженериков? (Можно ответить на первый и второй подразделы)
- Как работают дженерики Java? Что такое стирание типов? (Можно ответить на четвертый подраздел)
- Что такое квалифицированные подстановочные знаки и неквалифицированные подстановочные знаки в дженериках? (ответ в третьем подразделе)
- В чем разница между List и List? (Ответ в третьем подразделе)
- Вы понимаете подстановочные знаки дженериков и ограничиваете их? (Третий раздел может ответить)
Ссылка и спасибо
- «Идеи программирования на Java»
- «Основная технология Java»
- Давайте поговорим - подстановочные знаки T, E, K, V, ? в дженериках JAVA
- Ограничения и опыт работы с Java Generics
- Тип стирания дженериков Java
Личный публичный аккаунт
- Если вы хороший ребенок, который любит учиться, вы можете подписаться на мой официальный аккаунт, чтобы учиться и обсуждать вместе.
- Если вы считаете, что в этой статье есть какие-либо неточности, вы можете прокомментировать или подписаться на мой официальный аккаунт, пообщаться со мной в частном порядке, и все смогут учиться и прогрессировать вместе.