什么是泛型?
泛型其实就参数化类型,也就是说这个类型类似一个变量是可变的。
为什么会有泛型?
在没有泛型之前,java中是通过Object来实现泛型的功能。但是这样做有下面两个缺陷:
1 获取值的时候必须进行强转
2 没有错误检查
Object data[] = new Object[]{"one", "two", 1, new Object()};
String str = (String) data[0];
一般来说我们会把相同类型的数据放到一起,但是有没有发现如果使用object我们可以放入任意类型的数据,编译器也不会报错,这样在使用的时候就增加了类型转换异常的概率。
那么使用泛型呢?
List<String> strList = new ArrayList<>();
strList.add("one");
// 这句代码编译器就会提醒你不能这样使用
strList.add(1);
非static的内部类,在外部类加载的时候,并不会加载它,所以它里面不能有静态变量或者静态方法。
泛型擦除
泛型信息只存在于代码编译阶段,但是在java的运行期(已经生成字节码文件后)与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。
// 这个例子 最后输出的结果为 true class的结果为:java.util.ArrayList
public class Pair<T> {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
System.out.println(Objects.equals(integerList, stringList));
System.out.println(integerList.getClass());
System.out.println(stringList.getClass());
}
}
再看下面这个例子:
public class Pair<T> {
private T data;
public static void main(String[] args) {
Pair<String> stringPair = new Pair<>();
System.out.println(stringPair.getClass());
Field[] declaredFields = stringPair.getClass().getDeclaredFields();
Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
System.out.println("========================");
Pair<Integer> integerPair = new Pair<>();
System.out.println(integerPair.getClass());
Field[] fields = integerPair.getClass().getDeclaredFields();
Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
}
}
从运行结果我们可以证明在运行时类型已经被擦除为Object类型
在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如则会被转译成普通的Object 类型,如果指定了上限,如则类型参数就被替换成类型上限。
1 没有限定泛型的界限
public class Pair<T> {
private T first;
private T second;
}
擦除后,没有限定泛型的界限所以是Object类型:
public class Pair {
private Object first;
private Object second;
}
2 限定了泛型的界限
public class Pair<T extends Comparable> {
private T data;
public static void main(String[] args) {
Pair<String> stringPair = new Pair<>();
System.out.println(stringPair.getClass());
Field[] declaredFields = stringPair.getClass().getDeclaredFields();
Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
System.out.println("========================");
Pair<Integer> integerPair = new Pair<>();
System.out.println(integerPair.getClass());
Field[] fields = integerPair.getClass().getDeclaredFields();
Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
}
}
这就证明在擦除后:
public class Pair<T extends Comparable & Serializable> {
private Comparable first;
private Comparable second;
}
如果交换泛型的顺序: Pair<T extends Serializable & Comparable > 那么擦除以后的类型为Serializable,这个时候编译器会插入强制类型转换(也就是说我们获取Comparable 类型时候会强制转换),为了提高效率一般将标记接口往末尾放。
public class Pair<T extends Serializable & Comparable> {
private T data;
public static void main(String[] args) {
Pair<String> stringPair = new Pair<>();
System.out.println(stringPair.getClass());
Field[] declaredFields = stringPair.getClass().getDeclaredFields();
Arrays.stream(declaredFields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
System.out.println("========================");
Pair<Integer> integerPair = new Pair<>();
System.out.println(integerPair.getClass());
Field[] fields = integerPair.getClass().getDeclaredFields();
Arrays.stream(fields).forEach(f -> System.out.println(String.valueOf(f.getName() + " " + f.getType())));
}
}
所谓的插入强制类型转换,就是编译器在编译泛型表达式的时候会转化为两条指令:
- 对原始方法的调用得到Object
- 将返回的Object类型强制转换为具体的类型。
3 泛型方法的擦除
首先我们要区分一下泛型方法,泛型方法。只有声明了的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。如果单纯的在方法中使用了泛型它不是泛型方法。
泛型方法:
public <E> E convert(T t) {
}
非泛型方法:
public T getT(T t) {
}
public static <T extends Comparable> T min(T[] data) {
return null;
}
泛型方法擦除后:
public static Comparable min(Comparable [] data) {
return null;
}
需要注意的是泛型方法的多态是通过桥方法实现的
public class Pair<T> {
private T time;
public void setTime(T time) {
this.time = time;
}
}
// 被擦除以后
public void setTime(Object time) {
}
如果这个时候子类继承Pair,并指定了类型:
class DateInterVal extends Pair<LocalDate> {
@Override
public void setTime(LocalDate time) {
}
}
这个时候如果调用Pair的setTime方法,由于多态其实底层是这样来实现的:
setTime(setTime((LocalDate) time));
总结:1 虚拟机中没有类型,只有普通的类和方法 2 所有的类型参数都会替换为他们的限定类型 3 合成桥方法来保持多态 4为保证类型安全,必要时会插入强制类型转换
泛型方法
1 在类中的泛型方法
首先我们来区分几个定义方式,看注释部分。
public class Pair<T extends Comparable & Serializable> {
private T data;
// 编译无法通过 因为这个方法是静态方法,所以我们不能使用T类型,但是我们可以使用E类型,因为E类型是申明的
public static <E> E convert2E(T t) {
return null;
}
// 在非静态方法的情况下 可以使用上面的类中定义的泛型T
public <E> E convert2E(T t) {
return null;
}
// 注意这里我们在静态方方法申明了一个T类型,这个T和类上的T类型是没有关联的,是一个全新的类型
// 这个T可以和类的T是同一个类型,也可以不是同一个类型
public static <T> T accept(T t) {
return null;
}
}
2 泛型方法中的可变参数
public class Pair<T> {
static class Dot {
private int x;
private int y;
public Dot(int x, int y) {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return "Dot{" +
"x=" + x +
", y=" + y +
'}';
}
}
// 泛型方法可变参数
private static <T> void print(T ...es) {
for (T t : es) {
System.out.println(t + "");
}
}
public static void main(String[] args) {
Dot dot = new Dot(1, 1);
print(15000, "15000", dot);
}
}
2 静态方法与泛型
一个基本原则,如果要在静态方法使用泛型的话,这个方法必须为泛型方法。
// 也就是说必须申明
public static <T> T accept(T t) {
return null;
}
泛型缺陷
1 不能使用基本类型实例化类型参数
也就是说没有
Pair<int> Pair<double> 类型
因为泛型在擦除以后为object类型,但是原生类型不能直接赋值为object,而是要使用包装类。
2 不能实例化类型参数
本质也是因为类型擦除导致的
String string = new T();
// 类型擦除以后,很显然是存在问题的
String string = new Object();
但是我们可以通过反射来创建一个实例:
Pair<String> pair = Pair.class.newInstance();
3 运行时类型查询只适用于原始类型
下面这三条语句都是编译会报错的,因为虚拟中的对象总是一个特定的非泛型类型,所以类查询只能查询原始类型。
pair instanceof String;
pair instanceof Pair<String>;
pair instanceof Pair<T>;
// 这条语句是可以的 原始类型的查询
pair instanceof Pair
并且下面的语句也会返回true:
Pair<String> ps = new Pair<>();
Pair<Double> pd = new Pair<>();
System.out.println(ps.getClass() == pd.getClass());
4 不能创建参数化类型的数组
类型擦除以后变为Pair[] pars = new Pair[10]; 然后我们可以赋予pairs[0] = new Pair(); 没有编译错误,但存在运行时错误。
// 可以申明
Pair<String> [] pairs;
// 不可以实例化 也是一样的如果把String擦除 为Object 可能会导致运行时异常 不安全
Pair[] pairs = new Pair<String>[10];
// 如果是通配类型的 则可以 但是这样的话不是很安全因为里面可以存 Pair<String> 也可以存 Pair<Double>
// 在使用的时候可能类型转换异常
Pair[] pairs = new Pair<?>[10];
5 Varargs 警告
public static <T> void addAll(Collection<T> coll, T... ts){
// 这里其实创建了数组就违背了不能创建数组
for(T t : ts) {
coll.add(t);
}
}
public static void main(String[] args) {
Collection<Pair<String>> table = new ArrayList<>();
Pair<String> pair1 = new Pair<>();
Pair<String> pair2 = new Pair<>();
addAll(table, pair1, pair2);
}
static class Pair<T> {
}
6 不能实例化类型变量
T[] array = new T[10]
类型擦除后上述定义变为Object[] array = new Object[10]; 这样一来我们可以将任何类型赋予array[0], 比如array[0] = “1”; 编译器不会报错,但运行时在使用的时候就有可能会出错了。
// 编译不会通过
T t = new T();
// 编译不会通过
T[] array = new T[10]
这里也可以通过反射来进行:
public T[] demo() {
T data[] = (T[]) Array.newInstance(Pair.class, 2);
return data;
}
7 泛型类的静态上下文中类型变量无效
也就是静态不能和泛型一起使用,如果一定要一起使用的话,必须申明。
// 通不过
public static T t;
// 如果一定要使用则需要申明
public static <T> void addAll(Collection<T> coll, T... ts){
for(T t : ts) {
coll.add(t);
}
}
8 不能抛出或捕获泛型类的实例
public static <T extends Throwable> void doWork() {
try {
// 会产生编译错误
} catch (T e) {
}
}
泛型中的继承
继承泛型类时,必须对父类中的类型参数进行初始化
1 使用泛型初始化父类的泛型
public class Bar<T> extends Foo<T> {
}
2 使用具体的类型
public class Bar extends Foo<String> {
}
特别注意:
// 这里的继承关系是 Integer 和 Double 继承
Box<Number> box = new Box<Number>();
box.add(new Integer(10));
box.add(new Double(10.1));
// Box<Integer> Box<Number> 他们并不是继承关系 这一定要注意
原文链接
以 Collections 类为例,ArrayList 实现 List ,List 继承 Collection 。 所以 ArrayList 是 List 的一个子类型,它是 Collection 的一个子类型。 只要不改变类型参数,子类型关系在类型之间保留。
泛型中的限定
在通配符类型中,允许类型参数发生变化。
1 子类限定
// 类上
class Son<T extends Foo> {
}
// 方法上
public <T> T demo2(Bar<? extends Number> data) {
}
// 方法的申明
public <T extends Integer> T demo3(Bar<? super Integer> data) {
}
2 超类限定
// 方法上
public <T> T demo1(Bar<? super Integer> data) {
}