1、Java的泛型是类型擦除的
Java中的泛型是在编译期间有效的,在运行期间将会被删除,也就是所有泛型参数类型在编译后都会被清除掉.请看以下例子
public static void test(List<Integer> testParameter) { }
public static void test(List<String> testParameter) { }
以上是一个最最简单的重载例子,在编译后泛型类型是会被擦除的,所以无论是List<Integer>或List<String>都会变成List<E>,所以参数相同,重载不成立,无法编译通过,而且读者可以尝试将不同泛型的类getClass()看看,他们的Class是一样的
2、不能初始化泛型参数和数组
请观察以下代码:
class Test<T> {
private T t = new T(); //编译不通过
private T[] tArray = new T[5]; //编译不通过
private List<T> list = new ArrayList<T>(); //编译通过
}
Java语言是一门强类型,编译型的安全语言,它需要确保运行期的稳定性和安全性,所以在编译时编译器需要严格的检查我们所声明的类型,在编译期间编译器需要获取T类型,但泛型在编译期间已经被擦除了,所以new T()和new T[5]都会出现编译错误,而为什么ArrayList却能成功初始化?这是因为ArrayList表面是泛型,但在编译期间已经由ArrayList内部转型成为了Object,有兴趣的读者可以看一下ArrayList的源码,源码中容纳元素的是private transient Object[] elementData,是Object类型的数组
3、强制声明泛型的实际类型
在使用泛型时,在大多数情况下应该声明泛型的类型,例如该集合是存放User对象的,就应该使用List<User>来声明,这样可以尽量减少类型的转换,若只使用List而不声明泛型,则该集合等同于List<Object>
4、注意泛型没有继承说
public static void main(String[] args) {
List<Object> list = new ArrayList<Integer>(); // 编译不通过
List<Object> list = new ArrayList<Double>(); //编译不通过
}
上面代码的业务逻辑为有一组元素,但我不确定元素时整形还是浮点型,当我在确定后创建对应的泛型实现类,刚接触泛型的读者有可能会犯以上错误,理解为泛型之间是可以继承的,例如声明Object的泛型实际上创建Integer泛型,错误认为泛型之间也存在继承关系,但这是不正确的,泛型是帮助开发者编译期间类型检查安全.我们可以换种方式实现以上业务逻辑
public static void main(String[] args) {
List<Number> numberList = new ArrayList<Number>(); //Number 为Integer 和 Double 的公共父类
List<? extends Number> numberList2 = new ArrayList<Integer>(); //使用通配符,代表实际类型是Number类型的子类
List<? extends Number> numberList3 = new ArrayList<Double>(); //or
}
5.不同场景使用不同的通配符
泛型当中支持通配符,可以单独使用'?' 来表示任意类型,也可以使用刚才上面例子中的extends关键字表示传入参数是某一个类或接口的子类,也可以使用super关键字表示接受某一个类型的父类型,extends和super的写法一样,extends是限定上界,super为限定下界
6.建议采用顺序为List<T> List<?> List<Object>
以上三者都可以容纳所有的对象,但使用的顺序应该是首选List<T>,然后List<?>,最后才使用List<Object>,原因是List<T>是确定为某一类型的,安全的,也是Java所推荐的,而List<?>代表为任意类型,与List<T>类似,而List<Object>中全部为Object类型,这与不使用泛型无异,而List<T>是可读可写的,因为已经明确了T类型,而其他两者在读取时需要进行向下转型,造成了不必要的转型
7.使用多重边界限定
现在有一个业务需求,收钱时需要是我们本公司的员工(继承User),同时亦需要具备收银员的功能(实现Cashier),此时我们当然可以想到接受1个User然后判断User是否实现了Cashier接口,但在泛型的世界中,我们可以怎么做?请看以下例子
public static <T extends User & Cashier> void CollectMoney(T t){ }
以上例子就表明,限定了上界,只要是User和Cashier的子类才可以作为参数传递到方法当中