1、概述
- 增加泛型其中一个重要原因是为了让集合能记住其元素的数据类型,防止从集合取出对象时,强转类型容易引起ClassCastExeception异常
- 泛型将运行时异常转移至编译时异常:
List list = new ArrayList();
list.add("123");
list.add(123); //运行时会异常
List<String> list = new ArrayList<String>();
list.add("123");
list.add(123); //编译时会异常
- Java7可以写成:
List<String> list = new ArrayList<>();
- 泛型的实质:允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用
2、从泛型派生子类
- 当创建了带泛型声明的接口、父类之后,可以为该接口创建实现类,或从该父类派生子类,当使用这些接口、父类时不能再包含类型形参,下面是错误的:
public class A extends Apple<T>{}
- 定义方法时可以声明数据形参,调用方法时必须为这些数据形参传入实际的数据;而定义类、接口、方法时可以声明类型形参,使用类、接口、方法时应该为类型形参传入实际的类型
public class A extends Apple{}
- 也可以不为类型形参传入实际的类型参数
public class A extends Apple{}
- 如果从Apple类派生子类,则在Apple类中所有使用T类型形参的地方都将被替换成String类型,重写父类的方法,就必须注意这一点
3、并不存在泛型类
- ArrayList类像一种特殊的ArrayList类,但实际上,系统并没有为ArrayList生成新的class文件,也不会把它当作新类来处理
- 不管泛型的实际类型参数是什么,它们在运行时总有同样的类,都会被当成同一个类处理,在内存中也只占用一块内存空间,因此在静态方法、静态初始化块活着静态变量的声明和初始化中不允许使用类型形参
错误示范:
public class R<t>{
static T info;
T age;
public void foo(T msg){}
public static void bar(T msg){}
}
- 由于系统中并不会真正生成泛型类,所以instanceof运算符后不能使用泛型类,错误示范:
List<String> list = new ArrayList<String>();
if(list instanceof java.util.ArrayList<String>){...}
4、类型通配符
- Java早期设计中,允许Integer[]数组赋值给Number[]变量存在缺陷,因此在泛型设计时进行了改进,不再允许把List对象赋值给List变量
- 类型通配符是一个问号(?),将一个问号作为类型实参传给List集合,写作List
public void test(List<?> c){
for(int i=0;i<c.size();i++){
System.out.println(c.get(i))
}
}
- 带通配符的List仅表示它是各种泛型List的父类,并不能把元素加入到其中,错误示范:
List<?> c = new ArrayList<String>();
// 下面会引起编译时错误
c.add(new Object());
但null是例外,它是所有引用类型的实例
- 通配符的上限:如果只希望它代表某一类泛型List的父类,可以写成
public class Canvas{
public void drawAll(List<? extends Sharp> sharps){
for(Shape s:shapes){
s.draw(this);
}
}
}
// 但以下是错误的,因为仅仅知道是Sharp的子类,并没有指明具体是哪个子类
public void addRectangle(List<? extends Sharp> sharpes){
sharpes.add(new Rectangle());
}
- 设定类型形参的上限
public class Apple<T extends Number>{
T col;
}
- 多重形参上限
public class Apple<T extends Number && java.io.Serializable>{...}
5、泛型方法
static <T> void arrayToCollection(T[]a, Collection<T> c){
for(T o:a){
c.add(o);
}
}
- 上面的方法无法使用通配符,因为Java不允许把对象放进一个未知类型的集合中
- 而上面的方法统一了数组和集合的类型,所以可以
- 与接口、类声明中定义的类型形参不同,方法声明中定义的形参只能在该方法中使用
- 泛型方法中使用通配符
static <T> void test(Collection<? extends T> from,Collection<T> to){
for(T ele:from){
to.add(ele);
}
}
public static main(String[] args){
test(new ArrayList<Object>(),new ArrayList<String>());
}
6、泛型方法和类型通配符的区别
- 大多数时候可以使用泛型方法来替代类型通配符:
public interface Collection<E>{
boolean containsAll(Collection<?> c);
boolean addAll(Collection<? extends E> c);
}
public interface Collection<E>{
<T> boolean containsAll(Collection<T> c);
<T extends E> boolean addAll(Collection<T> c);
}
- 如果没有形参之间的依赖关系或者方法返回值与参数之间的类型没有依赖关系,则不应该使用泛型,以下情况需要使用:
public class Collections{
public static <T , S extends T> void copy(List<T> dest,List<S> src){...}
}
7、通配符下限
- 错误示范:
public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
T last = null;
for(T ele:src){
last = ele;
dest.add(ele);
}
return last;
}
// 问题在于src的类型不确定,当调用的时候由于参数类型不同会报错:
copy(new ArrayList<Number>(),new ArrayList<Integer>())
- 正确示范
public static <T> T copy(Collection<? super T> dest,Collection<T> src){
T last = null;
for(T ele:src){
last = ele;
dest.add(ele);
}
return last;
}