为什么需要泛型
首先,我们来看一段代码:
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList animal = new ArrayList();
animal.add(new Dog());
animal.add(new Cat());
Dog dog = (Dog) animal.get(0);
Cat cat = (Cat) animal.get(1);
Cat cat = (Dog) animal.get(0);//报错提示:Type mismatch: cannot convert from Dog to Cat
}
}
不使用泛型带来的问题:
Dog 和 Cat 都是Animal的子类
ArrayList 默认接受Object类型的对象,所以所有对象都可以放进ArrayList中
所以get(0) 返回的类型是Object
接着,需要进行强制转换才可以得到 Dog 类型或者 Cat 类型。
如果软件开发人员记忆比较好,能记得哪个是哪个,还是可以的。 但是开发人员会犯错误,比如最后一行代码,会记错,把第0个对象转换为 Cat ,这样就会出现类型转换异常 ( java.lang.ClassCastException )
那么有没有什么办法可以使集合能够记住集合内元素各类型,且能够达到只要编译时不出现问题,运行时就不会出现“java.lang.ClassCastException”异常呢?答案就是使用泛型。
什么是泛型?
泛型,即“参数化类型”。一提到参数,最熟悉的就是定义方法时有形参,然后调用此方法时传递实参。那么参数化类型怎么理解呢?顾名思义,就是将类型由原来的具体的类型参数化,类似于方法中的变量参数,此时类型也定义成参数形式(可以称之为类型形参),然后在使用/调用时传入具体的类型(类型实参)。
使用泛型的好处:
泛型的用法是在容器后面添加<Type>
Type可以是类,抽象类,接口
泛型表示这种容器,只能存放Dog,Cat就放不进去了。
代码如下:
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<Dog> animal = new ArrayList<Dog>();
//只有Dog可以放进去
animal.add(new Dog());
//Cat甚至放不进去
animal.add(new Cat());//报错提示:The method add(Dog) in the type ArrayList<Dog> is not applicable for the arguments (Cat)
//获取的时候也不需要进行转型,因为取出来一定是Dog
Dog dog = animal.get(0);
}
}
设置泛型里存放的子类对象
假设容器的泛型是Animal,那么 Animal 的子类Dog,Cat都可以放进去
和Animal无关的类型Human还是放不进去
代码如下:
import java.util.ArrayList;
public class GenericTest {
public static void main(String[] args) {
ArrayList<Animal> animal = new ArrayList<Animal>();
// 只有作为animal的子类可以放进去
animal.add(new Dog());
animal.add(new Cat());
// 和Animal无关的类型Human还是放不进去
animal.add(new Human());
// 报错提示:The method add(Animal) in the type ArrayList<Animal> is not applicable for the arguments (Human)
}
}
泛型的简写
为了不使编译器出现警告,需要前后都使用泛型,像这样:
ArrayList<Animal> animal= new ArrayList<Animal>();
不过JDK7提供了一个可以略微减少代码量的泛型简写方式
ArrayList< Animal > animal2 = new ArrayList<>();
后面的泛型可以用<>来代替
支持泛型的类
设计一个支持泛型的 GenericTest 类
设计这个类的时候,在类的声明上,加上一个<T>,表示该类支持泛型。
T是type的缩写,也可以使用任何其他的合法的变量,比如A,B,X都可以,但是一般约定成俗使用T,代表类型。
示例代码如下:
public class GenericTest<T> {
ArrayList<T> objects = new ArrayList<T>();
public void add(T t){
objects.add(t);
}
public static void main(String[] args) {
//在声明这个类的时候,使用泛型<Dog>就表示该类只能放Dog
GenericTest<Dog> dog = new GenericTest<>();
dog.add(new Dog());//✔
dog.add(new Cat());//报错提示:The method add(Dog) in the type GenericTest<Dog> is not applicable for the arguments (Cat)
//在声明这个类的时候,使用泛型<Animal>就表示该类只能放Animal,或者其子类
GenericTest<Animal> animal = new GenericTest<>();
animal.add(new Dog());//✔
//不能放与Animal无关的Human类
animal.add(new Human());//报错提示:The method add(Animal) in the type GenericTest<Animal> is not applicable for the arguments (Human)
}
}
? extends
ArrayList animals<? extends Animal> 表示这是一个Animal 泛型或者其子类泛型
animals 的泛型可能是 Animal
animals 的泛型可能是 Dog
animals 的泛型可能是 Cat
所以 可以确凿的是,从 animals 取出来的对象,一定是可以转型成 Animal 的
但是,不能往里面放东西,因为
放 Dog 不满足 < Cat >
放 Cat 也不满足< Dog >
示例代码如下:
import java.util.ArrayList;
public class GenericTest<T> {
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<Cat>();
cats.add(new Cat());
ArrayList<? extends Animal> animals = cats;
// ? extends Animal 表示这是一个Animal泛型的子类泛型
// animals 的泛型可以使Animal
// animals 的泛型可以使Cat
// animals 的泛型可以使Dog
// 可以确凿的是,从animals取出来的对象,一定是可以转型成Animal的
Animal animal = animals.get(0);
// 但是,不能往里面放东西
animals.add(new Dog()); // 编译错误,因为 animals 的泛型 有可能是Cat
//错误提示:The method add(capture#2-of ? extends Animal) in the type ArrayList<capture#2-of ? extends Animal> is not applicable for the arguments (Dog)
}
}
? super
ArrayList animals<? super Animal> 表示这是一个Animal泛型或者其父类泛型
Animal 的泛型可能是 Animal
Animal 的泛型可能是 Object
可以往里面插入 Animal 以及 Animal 的子类
但是取出来有风险,因为不确定取出来是 Animal 还是Object
示例代码如下:
import java.util.ArrayList;
public class GenericTest<T> {
public static void main(String[] args) {
ArrayList<? super Animal> animals = new ArrayList<Object>();
//? super Animal 表示 animals的泛型是Animal或者其父类泛型
//animals 的泛型可以是Animal
//animals 的泛型可以是Object
//所以就可以插入Animal
animals.add(new Animal());
//也可以插入Animal的子类
animals.add(new Cat());
animals.add(new Dog());
//但是,不能从里面取数据出来,因为其泛型可能是Object,而Object是强转Animal会失败
Animal animal= animals.get(0);//报错提示: Type mismatch: cannot convert from capture#4-of ? super Animal to Animal
}
}
泛型通配符 ?
泛型通配符? 代表任意泛型
既然?代表任意泛型,那么换句话说,这个容器什么泛型都有可能
所以只能以Object的形式取出来
并且不能往里面放对象,因为不知道到底是一个什么泛型的容器
import java.util.ArrayList;
public class GenericTest<T> {
public static void main(String[] args) {
ArrayList<Cat> cats = new ArrayList<Cat>();
// ?泛型通配符,表示任意泛型
ArrayList<?> generalList = cats;
// ?的缺陷1: 既然?代表任意泛型,那么换句话说,你就不知道这个容器里面是什么类型
// 所以只能以Object的形式取出来
Object o = generalList.get(0);
// ?的缺陷2: 既然?代表任意泛型,那么既有可能是Animal,也有可能是Human
// 所以,放哪种对象进去,都有风险,结果就什么什么类型的对象,都不能放进去
generalList.add(new Human()); // 编译错误 因为?代表任意泛型,很有可能不是Human
generalList.add(new Animal()); // 编译错误 因为?代表任意泛型,很有可能不是Animal
generalList.add(new Cat()); // 编译错误 因为?代表任意泛型,很有可能不是Cat
}
}
总结
如果希望只取出,不插入,就使用? extends
如果希望只插入,不取出,就使用? super
如果希望,又能插入,又能取出,就不要用通配符?
PS:
子类泛型不可以转换为父类泛型
父类泛型不可以转型为子类泛型