首先,肘子假设各位读者朋友已经了解了泛型的基本概念了。
同样以Head First Java的习题开头(P576练习)
下面哪些语句可以通过编译么?
//Dog是Animal的子类
//[1]
ArrayList<Dog> dogs1 = new ArrayList<Animal>();
//[2]
ArrayList<Animal> animals1 = new ArrayList<Dog>();
//[3]
List<Animal> list = new ArrayList<Animal>();
//[4]
ArrayList<Dog> dogs = new ArrayList<Dog>();
//[5]
ArrayList<Dog> dogs = new ArrayList<Dog>();
ArrayList<Animal> animals = dogs;
//[6]
ArrayList<Dog> dogs = new ArrayList<Dog>();
List<Dog> dogList = dogs;
//[7]
ArrayList<Object> objects = new ArrayList<Object>();
List<Object> objList = objects;
//[8]
ArrayList<Object> objs = new ArrayList<Dog>();
想要解决问题需要理解好一下几个概念:
泛型,继承与子类型
如果class Dog extends class Animal,即类Dog继承类Animal,那么Dog对象可以赋值给Animal对象。比如String是Object的子类,Integer,Float是Number的子类,所以以下的赋值是合法的:
String str = "1234";
Object obj = str;
public void foo(Number n) {}
foo(new Integer(6));
foo(new Float(10.001));
且来看看ArrayList的源码:
public class ArrayList<E> extends AbstractList<E>
implements List<E>, ...
{
...
}
//所以ArrayList<E>是AbstractList<E>,List<E>的子类
//ArrayList<Dog>的父类是AbstractList<Dog>和List<Dog>,而ArrayList<Dog>与ArrayList<Cat>都不是ArrayList<Animal>的子类,ArrayList<Dog>与ArrayList<Cat>的父类是Object
那么现在看回文章开头的习题:
所以能够通过编译的是[3][4][6][7][8]。
泛型命名规范
为了与java关键字区别开来,java泛型参数只是使用一个大写字母来定义。各种常用泛型参数的意义如下:
- E — Element,常用在java Collection里,如:List,Iterator,Set
- K,V — Key,Value,代表Map的键值对
- N — Number,数字
- T — Type,类型,如String,Integer等等
- S,U,V etc. - 2nd, 3rd, 4th 类型,和T的用法一样
参考:java泛型编程
泛型不协变(covariant)
说白了,就是数组与ArrayList的对比。
Java 语言中的数组是协变的(covariant),比如, Integer extends Number,那么不仅 Integer是 Number,而且 Integer[]也是 Number[],在要求 Number[]的地方完全可以传递或者赋予 Integer[]。(更正式地说,如果 Number是 Integer的超类型,那么 Number[]也是 Integer[]的超类型)。您也许认为这一原理同样适用于泛型类型 ——
List< Number>是 List< Integer>的超类型,那么可以在需要 List< Number>的地方传递 List< Integer>。不幸的是,情况并非如此。
不允许这样做有一个很充分的理由:这样做将破坏要提供的类型安全泛型。如果能够将 List< Integer>赋给 List< Number>。那么下面的代码就允许将非 Integer的内容放入 List< Integer>:
List<Integer> li = new ArrayList<Integer>();
List<Number> ln = li; // illegal
ln.add(new Float(3.1415));
//ln是 List< Number>,所以向其添加 Float似乎是完全合法的。但是如果 ln是 li的别名,那么这就破坏了蕴含在 li定义中的类型安全承诺 —— 它是一个整数列表,这就是泛型类型不能协变的原因。
这也就很好解释下面的代码为什么编译不过了:
class Test {
public void takeAnimals(Animal[] animals) {}
public void takeAnimals(ArrayList<Animal> animals){}
public void go() {
Dog[] dogs = {new Dog(), new Dog(), new Dog()};
Animal[] animals = {new Animal(), new Dog(), new Cat()};
takeAnimals(dogs);
takeAnimals(animals);
ArrayList<Animal> animals2 = new ArrayList<>();
animals.add(new Animal());
animals.add(new Dog());
animals.add(new Cat());
takeAnimals(animals2);
ArrayList<Dog> dogs2 = new ArrayList<>();
dogs2.add(new Dog());
dogs2.add(new Dog());
takeAnimals(dogs2); //编译不过哦o(0-0)o
}
}
如何写泛型
网络上有很详细的讲解了,肘子觉得写得比较清楚的是
Java泛型:泛型类,泛型接口,泛型方法
但是肘子觉得比较实用的是在输出一些信息会用到泛型方法:
public class Test {
public static <T> void print(T t) {
System.out.println(t);
}
public static <T> void print(T... t) {
for(T it: t)
System.out.println(it);
}
public static void main(String[] args){
print("hello world");
print(123);
print(12.09);
print(false);
print(123456, "hahhahah", true, 145.098);
}
}
这个在写小程序的时候,要输出一些primitive数据类型的变量时是不是有点方便呢?
以上。
争取这周把书啃完。