擦除
java泛型是使用擦除来实现的
原始类型 就是删去类型参数后的泛型类型名。
擦除类型变量,并替换为限定类型(边界类型)
若无显示的边界 则默认以类型Object为边界
在泛型代码内部 无法获取任何有关泛型类型的信息,
可以知道类型参数的标识符 和 泛型类型边界,却无法知道用来创建特定实例的实际类型参数
使用Class<T> 是获取运行时所需的类型信息的唯一的方式 让用户显示的传入一个Class引用 通常是作为一个方法中的参数传入 之后 使用反射来显示地引入该类并创建实例
- 不能用基本类型实例化类型参数 如: ArrayList<int>
- 不能用instanceof中操作测试泛型类型 a instanceof pair<String> //Error instanceof 语法规则 : A instanceof B ;//A必须可以被解析为解析为变量 比如常量 对象 局部变量 // B必须可以被解析为类型,比如类 接口 枚举
- 不能创建参数化类型的数组
- Pair <String> [] table = new Pair<String>[10];
-
可以这样创建 Pair <String> [] table = new Pair[10]; 或者 Pair <String> [] table = (Pair<String>[]) new Pair<?>[10]; 比较推荐的方法 ArrayList<Pair<String>>
- 实际上可以向方法传入不定数参数
public void p(T...a){ for (T c:a) { System.out.println(c.toString()); } }
4.不能实例化类型变量 不能这样使用 :new T(..) , new T[..] 或T.class
解决:在泛型类中构造一个 方法提供 -
// 调用 让用户提供一个构造器表达式 Pair.makePair(String::new) public static <T> Pair<T> makePair (Supplier<T> constr){ try{return new Pair<>(constr.get(),constr.get());} catch (Exception e){return null;} } //调用 Pair.makePair(String.class) public static <T> Pair<T> makePair(Class<T> c1){ try{ return new Pair<>(c1.newInstance(),c1.newInstance()); } catch(Exception e) {return null;} }
5.不能构造泛型数组
如public static <T extends Comparable> T[] minmax(T[] a){ T[] mm=new T[2];return a;}//Error 不能创建泛型数组 -
import java.lang.reflect.Array; import java.util.function.IntFunction; public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T...a){ T[] mm=constr.apply(2);...} public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T...a){ T[] mm=(T[])Array.newInstance(a.getClass().getComponentType(),2);...}
6.泛型类的静态上下文中类型变量无效,即不能在静态域中使用类型变量,但static可以修饰泛型方法
7.不能抛出或捕获泛型类的实例(),泛型类扩展Throwable都是不合法的泛型类不能扩展Throwable
public static <T extends Throwable> void doWork(Class<T> t){ try{ do work} catch(T e) { Logger.global.info(...)} }
List<String> v = new ArrayList<String>();
System.out.println(v instanceof List<String>);
//编译器报错:不能对参数化类型列表执行instanceof检查
//使用 List<?> 代替 因为进一步的泛型类型信息将在运行时被删除
//就是说可以改为 v instanceof List<?>
不能让类实现两个看上去不同的泛型接口
public abstract class Test implements List<String> , List<Date>{ }
//编译器报错:不能使用不同的参数 List<Date> 和 List<String> 来多次实现接口 List
//原因由于泛型是使用擦除来实现的 它们在运行时是相同的
原始类型:每个泛型都有一个原始类型 比如List<String> List<Integer> ,List就是原始类型
参数化类型 与 原始类型 的相互赋值
//原始类型 泛型类型
List list = new ArrayList<Date>();//警告 List 是原始类型。应该将对通用类型 List<E> 的引用参数化
//泛型类型 原始类型
List<Date> dates = new ArrayList();//未受检警告 类型安全:类型 ArrayList 的表达式需要进行未经检查的转换以符合
//不允许 明显不兼容的内容赋值
List<Date> dates = new ArrayList<String>();
//报错 类型不匹配:不能从 ArrayList<String> 转换为List<Date>
泛型与继承
继承只适用于"基本"泛型类型,而不适合参数类型
仅有两个泛型类型都是基于完全相同的参数类型进行实例化的时候 赋值才适用
//前提 1.List接口是COllection接口的子类
// 2.List<x> Collocation<y> x完全等于y
Collection<Date> cd;
List<Date> ld = new ArrayList<Date>();
cd = ld ; //ok
Collection<Date> cd = new ArrayList<Date>();
List<Date> ld ;
// ld=cd ; //编译器报错 但可以强制类型转换
ld=(List<Date>) cd ; //ok
Collection<Object> cd;
List<Date> ld = new ArrayList<Date>();
cd = ld ;//Error 类型不匹配
将数组引用类型与实例类型不匹配造成的运行时错误移到编译期
class Fruit { }
class Apple extends Fruit {} // Fruit
class Jonathan extends Apple { } // Apple //Orange
class Orange extends Fruit { } // Jonathan
public class CovarianArrays {
public static void main(String [] args){
Fruit [] fruit = new Apple [10];
fruit[0] = new Apple();
fruit[1] = new Jonathan();
System.out.println(fruit.getClass().getName()); //[LApple;
try{
fruit[0] = new Fruit();
}catch(Exception e) {
System.out.println(e);//ArrayStoreException
}
try{
fruit[0] = new Orange();
}catch(Exception e) {
System.out.println(e);//ArrayStoreException
}
}
}
将Fruit数组的引用指向 Apple数组的实例
编译期 因为实际上实例是Apple[] 所以可以放入Apple Jonathan
又因为因为引用类型为Fruit类型所以 可以接受Fruit Orange
但运行时的数组机制知道其类型为Apple[] 所以不允许放入Fruit 和Orange类型
使用泛型将这种错误检查移入编译期
List<Fruit> flist = new ArrayList<Apple>();
//前面提到过 编译器会报错,不允许这种情况
泛型数组也存在类似的引用类型"欺骗编译器的情况"
Collection<Date> cd = new TreeSet<Date>();
List<Date> ld ;
ld=(List<Date>) cd ;
//运行错误 Exception in thread "main" java.lang.ClassCastException: java.util.TreeSet cannot be cast to java.util.List
数组类型的通配符
-
数组必须是无界限通配符类型 因为数组的无界限通配符实例化中的每一个元素都可以保存任何类型 在运行时 并不一定要对类型的泛型部分进行运行检查 , ArrayList的任何实例对于ArrayList<?>都是可以赋值的
-
因此只有原始类型检查是必须的
来看一下Enum的声明
Enum <E extends Enum<E> >{..} //java不允许我们扩展Enum类型
class Foo e xtends Enum<Foo> 1.满足Enum只能有自己的子类来实例化
2.引用了类型变量E的父类Enum类的任何方法 现在都必须引用子类型
E是某个参数化的Enum的一个子类