前言
泛型是 JAVA1.5中引入的新特性,其带来的好处如下:
- 编译时的类型检查安全;
- 所有的强制装换都是隐式和自动的;
- 参数化类型,将所操作的数据类型被指定为一个参数,提高代码的重用率;
1. 泛型与Object的比较
1.1 类型检查
对于Object的属性,编译器是不会有类型检查的,一些问题只有在运行的时候才会抛异常,这样本身就是一个安全隐患,例子如下:
public class ObjectEntity {
private Object pro;
... getter,setter
}
public static void main(String[] args) {
ObjectEntity objectEntity = new ObjectEntity();
objectEntity.setPro(new double[][]{});
Integer pro =(Integer) objectEntity.getPro();
}
如果将类型改成泛型类,那就会是另一种情况,在编译的时候就会进行类型检查。
泛型
public class GenericsEntity<T> {
private T pro;
... getter,setter
}
1.2 自动转换
对于Object的属性,预知的情况下取出值的时候还要进行一次转换
ObjectEntity objectEntity=new ObjectEntity();
objectEntity.setPro("str");
String str= (String) objectEntity.getPro();
对于泛型取出的值如果类型对应的上则无需手动转换
GenericsEntity<String> genericsEntity = new GenericsEntity<String>();
genericsEntity.setPro("str");
String str = genericsEntity.getPro();
总结一下
上面的示例使用了泛型的实体,强制转换,可以在编译时候检查类型安全。
2. T,E,K,V,?
2.1 T,E,K,V,?介绍
想必在平时的开发中会经常见到,这个几个泛型通配符 “T,E,K,V,?” ,那这些又是什么意思呢?
其实这个几个并没有什么区别,它们都是JAVA泛型通配符,之所有会经常见到这些通配符是因为代码的约定俗成,说白了这样写会增加代码的可阅读性,也就是说换成A-Z 之间的字母也都是可以的。
通常情况下T,E,K,V,?是这样约定的:
- ?:表示不确定的java类型;
- T(type):表示具体的Java类型;
- K V (key value) :表示Java中的 Key,Value;
- E(element):表示Element;
举个栗子
public class GenericsEntity<T> {
private T pro;
public T getPro() {
return pro;
}
public void setPro(T pro) {
this.pro = pro;
}
}
public class GenericsEntity<A> {
private A pro;
public A geAPro() {
return pro;
}
public void seAPro(A pro) {
this.pro = pro;
}
}
上面代码把T换成了A,效果是没有任何区别的,只不过T约定代表type,为了良好的可读性,所以还是按照约定规范将A替换成T会比较合适。
2.2 ? 无界通配符
在介绍,无界通配符前先看个例子。
有个超类Animal和几个子类,如狗,猫,鹦鹉,等…。
超类
public class Animal {
public void call(){
}
}
Dog类
public class Dog extends Animal{
@Override
public void call() {
System.out.println("汪汪汪");
}
}
Cat类
public class Cat extends Animal{
@Override
public void call() {
System.out.println("喵喵喵");
}
}
现在我需要一个动物的列表
通常的想法是:
List<Animal> listAnimals = new ArrayList<Animal>();
正确的做法:
List<? extends Animal> listAnimals
为什么要使用通配符而不是简单的泛型呢?通配符其实在声明局部变量时是没有什么意义的,但是当你为一个方法声明一个参数时,它是非常重要的。
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
// 不报错
test(dogs);
// 报错
test1(dogs);
}
static void test(List<? extends Animal> animals) {
for (Animal animal : animals) {
animal.call();
}
}
static void test1(List<Animal> animals) {
for (Animal animal : animals) {
animal.call();
}
}
当调用test1的时候会报如下的错误:
Error:(21, 15) java: 不兼容的类型: java.util.List<entity.Dog>无法转换为java.util.List<entity.Animal>
像 tes方法中,限定了上限,但是不关心具体类型是什么,所以对于传入的 Animal 的所有子类都可以支持,并且不会报错。而 tes1就不行。
2.3 上限通配符 < ? extends E>
上限:用 extends 关键字声明,表示参数化的类型可能是所指定的类型,或者是此类型的子类。
在类型参数中使用 extends 表示这个泛型中的参数必须是 E 或者 E 的子类,这样有两个好处:
- 如果传入的类型不是 E 或者 E 的子类,编译不成功
- 泛型中可以使用 E 的方法,要不然还得强转成 E 才能使用
static <T> void test(List<? extends T> animalSub, List<T> animals ){}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
test(dogs,animals);
}
这边当animals 作为参数被传进去的时候,animalSub参数类型以及被限定为animals的子类
2.4 下界通配符 < ? super E>
下限: 用 super 进行声明,表示参数化的类型可能是所指定的类型,或者是此类型的父类型,直至 Object;
在类型参数中使用 super 表示这个泛型中的参数必须是 E 或者 E 的父类。
static <T> void test(List<? super T> animalSub, List<T> animals ){}
public static void main(String[] args) {
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = new ArrayList<>();
test(animals ,dog);
}
2.5 ? 和 T 的区别
// 指定集合类型只能是T
List<T> arrayT=new ArrayList<T>();
// 集合中可以是任何类型,这种没有意义,一般在方法中,只是为了说用法
List<?> array=new ArrayList<?>();
?和 T 都表示不确定的类型,区别在于我们可以对 T 进行操作,但是对 ? 不行,比如如下这种 :
// 可以
T t = operate();
// 不可以
? car = operate();
简单总结下:
T 是一个 确定的 类型,通常用于泛型类和泛型方法的定义,?是一个 不确定 的类型,通常用于泛型方法的调用代码和形参,不能用于定义类和泛型方法。
2.5.1 通过 T 来 确保 泛型参数的一致性
通过 T 来 确保 泛型参数类型的一致性
static <T extends Number> void test(List<T> ids,List<T> num){}
public static void main(String[] args) {
// 可以保证,ids和num类型一致
List<Number> ids = new ArrayList<>();
List<Number> num = new ArrayList<>();
test(ids, num);
}
通配符是 不确定的,所以这个方法不能保证两个 List 具有相同的元素类型
static void test(List<? extends Number> ids,List<? extends Number> num){}
public static void main(String[] args) {
// 无法保证ids和num类型一致
List<Integer> ids = new ArrayList<>();
List<Double> num = new ArrayList<>();
test(ids, num);
}
2.5.2 类型参数可以多重限定而通配符不行
接口
public interface InterfaceA {};
public interface InterfaceB {};
实现类
public class ImplAB implements InterfaceA, InterfaceB {}
指定一个类型参数同时满足 InterfaceA 和 InterfaceB
static <T extends InterfaceA & InterfaceB> void test2(T t) {}
public static void main(String[] args) {
ImplAB implAB = new ImplAB();
test2(implAB);
}
使用 & 符号设定多重边界(Multi Bounds),指定泛型类型 T 必须是 InterfaceA和InterfaceB, 的共有子类型,此时变量 T 就具有了所有限定的方法和属性。对于通配符来说,因为它不是一个确定的类型,所以不能进行多重限定。
2.5.3通配符可以使用超类限定而类型参数不行
类型参数 T 只具有 一种 类型限定方式:
T extends A
但是通配符 ? 可以进行 两种限定:
? extends A
? super A
3.Class<T> 和 Class<?> 区别
前面介绍了 ? 和 T 的区别,那么对于,Class<T> 和 Class<?> 又有什么区别呢?
Class<T> 和 Class<?>
最常见的是在反射场景下的使用,这里以用一段反射的代码来说明下:
ImplAB implAB=(ImplAB)Class.forName("entity.ImplAB").newInstance();;
对于上述代码,在运行期,如果反射的类型不是 ImplAB类,那么一定会报 java.lang.ClassCastException 错误。
对于这种情况,则可以使用下面的代码来代替,使得在在编译期就能直接 检查到类型的问题:
static <T> T newInstance(Class<T> clazz) throws IllegalAccessException, InstantiationException {
return clazz.newInstance();
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
ImplAB implAB = newInstance(ImplAB.class);
}
Class 在实例化的时候,T 要替换成具体类。Class<?> 它是个通配泛型,? 可以代表任何类型,所以主要用于声明时的限制情况。
比如,我们可以这样做申明:
public class clazz {
// 可以
public Class<?> clazz;
// 不可以,因为 T 需要指定类型
public Class<T> clazzT;
}
那如果也想 public Class clazz; 这样的话,就必须让当前的类也指定 T
public class clazz<T> {
public Class<?> clazzPro;
// 不会报错
public Class<T> clazzTPro;
}