1.泛型概念的提出(为什么需要泛型)?
(1)当我们将一个对象放入集合中,集合不会记住此对象的类型,当再次从集合中取出此对象时,改对象的编译类型变成了Object类型,但其运行时类型任然为其本身类型。
(2)因此,//1处取出集合元素时需要人为的强制类型转化到具体的目标类型,且很容易出现“java.lang.ClassCastException”异常。
List list = new ArrayList();
list.add(1);
list.add(2);
list.add("五号");//一不小心插入了String
for (Object object : list){
//取出“五号”时报ClassCastException
Integert = (Integer)object;
}
/** * 上面是一个集合没有使用泛型时出现的错误,那么当我们使用泛型时,该错误就会避免,例如: */
List<Integer>list = newArrayList<Integer>();
list.add(1);
list.add(2);
list.add("五号");//插入String,引起编译错误
2.认识泛型
(1)Java的参数化类型被称为泛型,即允许我们在创建集合时就指定集合元素的类型,该集合只能保存其指定类型的元素。
(2)泛型允许在定义类、接口、方法时使用类型形参,这个类型形参将在声明变量、创建对象、调用方法时动态地指定。例如:
// 定义一个接口
interface Money<E>{
Eget(intindex);
boolean add(E e);
}
// 定义一个类
public classApple<T>{
private T info;
public Apple(T info) {
this.info = info;
}
public T getInfo(){
return this.info;
}
public void setInfo(T info){
this.info = info;
}
public static void main(String[] args) {
Apple<String>ap1 = newApple<String>("小苹果");
System.out.println(ap1.getInfo());
Apple<Double>ap2 = newApple<Double>(1.23);
System.out.println(ap2.getInfo());
}
}
(3)注意:在静态方法、静态初始化块或者静态变量的声明和初始化中不允许使用类型形参。因为不管为泛型的类型形参传入哪一种类型实参,对于Java来说,它们依然被当成同一个类处理,在内存中也只占用一块内存空间。不管泛型的实际类型参数是什么,它们在运行时总有同样的类(class),例如下面的程序将输出true。
List<String> l1 = new ArrayList<String>();
List<Double> l2 = new ArrayList<Double>();
System.out.println(l1.getClass() == l2.getClass());
3.类型通配符
(1)如果我们想定义一个方法,这个方法的参数是一个集合形参,但是集合形参的元素类型是不确定的。可能会想到下面两种定义方法:
/** * test1可以使用,但是会报泛型警告; */
public void test1(List l){ }
/** * test2在传入非List<Object>时无法使用,会报“test2(List<Object>)对于参数(List<String>)不适用”; */
public void test2(List<Object> l){ }
/** * 为了表示各种泛型List的父类,我们需要使用类型通配符,类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作List<?>,那么我们就可以这样定义上面的方法: */
public void test3(List<?> l){ }
此时我们就可以传入以任何元素类型为集合形参的List了,就算是自定义类也可以。
(2)通配符形参时上限问题
①因为不知道这个受限制通配符的具体类型,所以不能把Shape对象或其子类的对象加入这个泛型集合中,例如下面的代码会报编译错误:
public void drawAll(List<? extends Shape> shapes){
//此处会报编译错误
shapes.add(new Rectangle());
}
②Java泛型不仅允许在使用通配符形参时设定上限,而且可以在定义类型形参时设定上限,用于表示传给该类型形参的实际类型要么是该上限类型,要么是该上限类型的子类,例如:
public class Apple<T extends Number> {
T colT;
public static void main(String[]args) {
Apple<Integer> ai = new Apple<Integer>();
Apple<Double> ad = new Apple<Double>();
//下面代码将引发编译错误,因为String类不是Number的子类型
Apple<String> as = new Apple<String>();
}
}
4.泛型方法
所谓泛型方法,就是在声明方法时定义一个或多个类型形参。泛型方法的用法格式如下:
修饰符<T,S> 返回值类型 方法名(形参列表){
//方法体
}
static <T> void fromArrayToCollection(Collection<T> a,Collection<T> b){
for (T t : a) {
b.add(t);
}
}
static <T> void fromArrayToCollection(Collection<? extends T> a, Collection<T> b){
for (T t : a) {
b.add(t);
}
}
public static void main(String[]args) {
List<String> a = new ArrayList<String>();
List<Object> b = new ArrayList<Object>();
fromArrayToCollection(a, b);
}
5.泛型方法和类型通配符的区别
(1)大多数时候都可以使用泛型方法来代替类型通配符。例如对于Java的Collection接口中两个方法的定义:
public interface Collection<E>{
boolean containsAll(Collection<?>c);
booleanaddAll(Collection<? extends E> c);
...
}
(2)上面集合中的两个方法的形参都采用了类型通配符的形式,也可以采用泛型方法的形式,如下所示:
public interface Collection<E>{
<T> boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
...
}
(3)上面两个方法中类型形参T只使用了一次,类型形参T产生的唯一效果是可以在不同的调用点传入不同的实际类型。对于这种情况,应该使用通配符:通配符就是被设计用来支持灵活的子类化的。
(4)泛型方法允许类型形参被用来表示方法的一个或多个参数之间的类型依赖关系,或者方法返回值与参数之间的类型依赖关系。如果没有这样的类型依赖关系,就不应该使用泛型方法。