一、什么是泛型?
泛型是JDK5引入的一种特性,是一种类型安全检测机制,开发者在编译阶段发现类型相关的报错。
泛型即参数类型化,将操作的数据类型定义为参数,可定义在类、接口、方法中。
可以把类型参数看作是使用参数化类型时指定的类型的一个占位符,就像方法的形式参数是运行时传递的值的占位符一样。
二、为什么有泛型?
我们都知道java中泛型又称为“伪泛型”,为什么称为伪泛型呢?是因为泛型只存在编译阶段,编译完成之后泛型会被擦除,并没有作用在程序的执行阶段,所以他存在的目的就是“为了规范”。
1、类型安全
泛型的主要目标是提高 Java 程序的类型安全,保证程序的可读性和安全性。
2、消除强制类型转换
泛型的一个附带好处是,消除源代码中的许多强制类型转换。这使得代码更加可读,并且减少了出错机会。
3、潜在的性能收益
泛型为较大的优化带来可能。在泛型的初始实现中,编译器将强制类型转换(没有泛型的话,程序员会指定这些强制类型转换)插入生成的字节码中。但是更多类型信息可用于编译器这一事实,为未来版本的 JVM 的优化带来可能。
三、泛型的使用
1、泛型类、泛型接口、泛型方法
public class Test<T> {
public T test2(T params){}
}
public interface Test1<T>{}
2、泛型通配符
比如常见的?,T,K,V 就是通配符,比如其中的T换成A-Z其中任何一个都可以,java中是讲究约定的,就是大家共同约定,某个字符代表什么意思,这样也有利于代码维护。
- ? 表示不确定的 java 类型
- T (type) 表示具体的一个java类型
- K V (key value) 分别代表java键值中的Key Value
- E (element) 代表Element
3、上界通配符(<? extends T>)
通配符上界,可以限制传入的类型必须是上界这个类或者是这个类的子类
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
public class Test<T> {
public static void printList1(List<? extends Number> list) {
for (Object x:list) {
System.out.println(x);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
printList1(list); // ok
List<Double> list1 = new ArrayList<>();
list1.add(1.0D);
printList1(list1); // ok
List<String> list2 = new ArrayList<>();
list2.add("1");
printList1(list2); // compile error
List<? extends Number> list3 = list;
// get能用上界
Number o = list3.get(0);
// 不能add
list3.add(5); // compile error
list3.add(new Object()); // compile error
}
}
小结:
- 通配符上界? extends A, 表明所有的是A的类或者子类型可以传入,比如本例中的Integer和Double都是Number的子类,String不是。
- 通配符上界? extends A,确定了类型是A或者是A的子类,那么向集合容器get获取数据,肯定是它的上界类A,因为其他放的类都是A的子类,比如例子中的
Number o = list3.get(0);
- 如果向通配符上界集合中添加元素时,会失败。 List<? extends A>, 说明容器可以容纳的是A或者A的子类,但A的子类有很多,不确定放哪个,为了安全性,就直接不让你add,比如例子中的
list3.add(5);
,5虽然是Number的子类,依然不能add。
4、下界通配符(<? super T>)
上界通配符:用 super关键字声明,表示类型为T类型及T类型的父类
<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
public static void printList1(List<? super Integer> list) {
for (Object x:list) {
System.out.println(x);
}
}
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);
printList1(list); // ok
List<Double> list1 = new ArrayList<>();
list1.add(1.0D);
printList1(list1); // compile error
List<String> list2 = new ArrayList<>();
list2.add("1");
printList1(list2); // compile error
List<? super Integer> list3 = list;
// 不能用下界接收
Integer o = list3.get(0); // compile error
// 能add
list3.add(5); // ok
list3.add(new Number(5)); // compile error
}
- 通配符上界? super A, 表明所有的是A的类或者A的父类可以传入。
- 通配符上界? super A,确定了类型是A或者是A的父类,那么向集合容器get获取数据,无法确定是A还是A的某个父类,所以不能get,
Integer o = list3.get(0); // compile error
,比如例子中用Integer接收,万一list3中放的是Object类型,就凉凉了。 - 如果向通配符下界集合中添加元素时,只能添加下届类的子类。比如例子中的:
list3.add(5)
, list3的通配符是<? super Integer>
,说明该集合存放的是Integer或者Integer的子类,我只要向容器中放Integer和它的子类都是成立的。
源码中使用例子:
类Collections 中binarySearch方法 【List<? extends Comparable<? super T>> list】入参list中存储对象A或者A的父类是Comparable子类都可以。
public static <T>
int binarySearch(List<? extends Comparable<? super T>> list, T key) {
if (list instanceof RandomAccess || list.size()<BINARYSEARCH_THRESHOLD)
return Collections.indexedBinarySearch(list, key);
else
return Collections.iteratorBinarySearch(list, key);
}
可通过以下代码辅助理解 <T extends Comparable<T>> 与<T extends Comparable<? super T>>
public class Test2 {
public static <T extends Comparable<T>> void sort1(List<T> list) {
Collections.sort(list);
}
public static <T extends Comparable<? super T>> void sort2(List<T> list) {
Collections.sort(list);
}
public static void t1() {
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal(20));
animals.add(new Animal(30));
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog(5));
dogs.add(new Dog(10));
sort1(animals);
// sort1(dogs); 会报错
sort2(animals);
sort2(dogs);
}
}
class Animal implements Comparable<Animal> {
public int age;
public Animal(int age) {
this.age = age;
}
public int compareTo(Animal other) {
return this.age - other.age;
}
}
class Dog extends Animal {
public Dog(int age) {
super(age);
}
}
5、T 和 ?的区别
T 是一个确定的类型,通常用于泛型类和泛型方法的定义,?是一个不确定的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
四、泛型常见面试问题
1、泛型为什么不能使用基本数据类型
泛型在编译阶段会进行泛型擦除,擦除为原始类型,但是object并不是基本数据类型的父类。