Java泛型
为什么要使用泛型?
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
定义一个简单泛型类
public class Person <T>{
private T info;
public T getInfo() {
return info;
}
public void setInfo(T info) {
this.info = info;
}
}
可以看到好处 编译的时候检查类型安全 继续看下段代码
public static int addInt(int x, int y) {
return x + y;
}
public static Double addDouble(double x, double y) {
return x + y;
}
public static <T extends Number> T add(T x, T y) {
if (x instanceof Integer) {
return (T) Integer.valueOf(x.intValue() + y.intValue());
} else if (x instanceof Double) {
return (T) Double.valueOf(x.doubleValue() + y.doubleValue());
}
return (T) Integer.valueOf(-1);
}
看下使用
System.out.println(Utils.addInt(1,2));
System.out.println(Utils.addDouble(0.12,2));
System.out.println(Utils.add(0.13,2));
System.out.println(Utils.add(3,2));
结果输出
3
2.12
2.13
5
可以看的出 提高代码的重用率 看一下,下边的继承关系
public class Fruit
public class Orange extends Fruit
public class Apple extends Fruit
public class HongFuShi extends Apple
public static void print(GenericType<Fruit> p){
System.out.println(p.getData().getColor());
}
GenericType<Fruit> a = new GenericType<>();
print(a);
GenericType<Orange> b = new GenericType<>();
//print(b);
//这样会编译错误 虽然 Fruit是Orange的父类
//但是这个GenericType<Fruit>与GenericType<Orange> 是没有任何关系的
//因为泛型擦除的存在
于是提出了一个通配符类型 ?
? extends X 表示类型的上界,类型参数是 X 的子类
? super X 表示类型的下界,类型参数是 X 的超类
这两种 方式从名字上来看,特别是 super,很有迷惑性,下面我们来仔细辨析这两种方法。
? extends X
? extends X 表示传递给方法的参数,必须是 X 的子类(包括 X 本身)
public static void print2(GenericType<? extends Fruit> p){
System.out.println(p.getData().getColor());
}
GenericType<Fruit> a = new GenericType<>();
print2(a);
GenericType<Orange> b = new GenericType<>();
print2(b);
//只要是Fruit类和Fruit的子类都可以了
但是对泛型类 GenericType 来说,如果其中提供了 get 和 set 类型参数变量 的方法的话,set 方法是不允许被调用的,会出现编译错误,get 方法则没问题,会返回一个 Fruit 类型的值。
GenericType<? extends Fruit> c = new GenericType<>();
Apple apple = new Apple();
Fruit fruit = new Fruit();
//c.setData(apple);
//c.setData(fruit);
Fruit x = c.getData();
原因是 ? extends X 表示类型的上界,所以get的时候返回的一定是x的子类或者是其本身。主要用于安全的访问数据,可以访问x及其子类型,并且不能写入非null的数据。
? super X
表示传递的方法的参数,必须是X超类,包括X本身。
public static void printSuper(GenericType<? super Apple> p){
System.out.println(p.getData());
}
GenericType<Fruit> fruitGenericType = new GenericType<>();
GenericType<Apple> appleGenericType = new GenericType<>();
GenericType<HongFuShi> hongFuShiGenericType = new GenericType<>();
GenericType<Orange> orangeGenericType = new GenericType<>();
printSuper(fruitGenericType);
printSuper(appleGenericType);
//下边会检查报错 应为HongFuShi是Apple的子类 Orange是Fruit的子类
//printSuper(hongFuShiGenericType);
//printSuper(orangeGenericType);
但是对泛型类 GenericType 来说,如果其中提供了 get 和 set 类型参数变量 的方法的话,set 方法可以被调用的,且能传入的参数只能是 X 或者 X 的子类。
GenericType<? super Apple> x = new GenericType<>();
x.setData(new Apple());
x.setData(new HongFuShi());
//不能set Apple的父类Fruit 这里跟上边的不太一样
//x.setData(new Fruit());
Object data = x.getData();
? super X 表示类型的下界,类型参数是 X 的超类(包括 X 本身),那 么可以肯定的说,get 方法返回的一定是个 X 的超类,那么到底是哪个超类?不 知道,但是可以肯定的说,Object 一定是它的超类,所以 get 方法返回 Object。 编译器是可以确定知道的。对于 set 方法来说,编译器不知道它需要的确切类型, 但是 X 和 X 的子类可以安全的转型为 X。
主要用于安全地写入数据,可以写入 X 及其子类型。
泛型擦除
public void test(List<String> a){}
public void test(List<Integer> a){}
//这段代码会在编译器报错,就能体现出泛型擦除了。
上面这段代码是不能被编译的,因为参数 List<Integer>和 List<String>编 译之后都被擦除了,变成了一样的原生类型 List<E>,擦除动作导致这两种方法的特征签名变得一模一样。
虽然泛型会被擦除,但是在字节码中会有一个属性Signature存储泛型的信息,我们还可以得出结论,擦除法所谓的擦除, 仅仅是对方法的 Code 属性中的字节码进行擦除,实际上元数据中还是保留了泛 型信息,这也是我们能通过反射手段取得参数化类型的根本依据。
总结 泛型的使用的三种方式
泛型类:public class Test}{} T表示未知类型
泛型接口:public interface Test{} 和定义类一样
泛型方法:public void Test(T name){}