概述
集合可以存储任意类型的对象,对象存入集合以后都被提升为Object类型。当我从集合中取出对象的时候都需要进行强制转型来后续操作对象。
public static void main(String[] args) {
ArrayList list = new ArrayList();
list.add("a11");
list.add(1);
list.add("1");
for (Object o : list) {
String str = (String) o;
System.out.println(str);
}
}
这段简单的演示代码就会出现题java.lang.ClassCastException
的运行时期异常。通常遇到这种情况可以使用instanceof
来判断是否为某种类型或是其子类型,但这样会增加代码复杂度,且不能彻底解决问题,毕竟再怎么考虑终会有所遗漏的类型不能完成正常转换。
JDK1.5之后引入了**泛型(Generic)**语法。这样我们在使用设计API的时候可以指定类或者方法甚至接口的泛型,设计出的API也更为简洁,并且也能在编译时期查出异常。
使用泛型的好处
- 避免强制类型转换问题
- 将运行时异常提前到编译时期,减少了后期修改bug的工作量
- 一旦指定泛型,数据类型将被统一
- 实现代码的模块化,把数据类型当做参数
泛型的定义和使用
1、泛型类的定义
(1)格式
public class 类名<泛型变量>{
...
}
泛型类中的泛型一旦被指定,整个类除了泛型方法,全部带有泛型的类型占位符的地方都会被替换为指定的类型。
(2)使用范例
public class MyClass<T> {
private T t;
public MyClass() {
}
public MyClass(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
}
public static void main(String[] args) {
// 空参构造创造对象
MyClass<String> mc1 = new MyClass<>();
mc1.setT("Hello");
String t = mc1.getT();
System.out.println(t);
System.out.println("-----------------------------------");
// 满参构造,尖括号指定了什么类型就传递什么类型数据
MyClass<Integer> mc2 = new MyClass<>(1);
String t = mc1.getT();
System.out.println(t);
}
2、泛型方法的定义
在泛型类使用中一般默认直接把泛型占位符替换为统一指定的类型,但有时候需要方法定义自己特定的泛型。
(1)格式
修饰符 <T> 返回值类型 方法名称(T t,参数列表...){
方法体...
}
注意事项:
- 前面
<T>
在定义方法的泛型 - 后面方法参数:
T t
使用的是方法自己的的泛型
(2)使用范例
public class MyClass02<T> {
// 不是泛型方法
public void print(T t){
System.out.println(t);
}
// 泛型方法,定义了自己的泛型,使用占位符E表示泛型
public <E> void show(E e){
System.out.println(e);
}
}
public static void main(String[] args) {
MyClass02<String> mc = new MyClass02<>();
mc.print("Hello");
// print 使用的是类上的泛型,已经确定为String类型了
//错误:mc.print(10000);
mc.show(18);
}
3、泛型接口的定义
(1)格式
public interface 接口名称<T> {
...
}
(2)接口上定义的泛型,什么时间确定具体类型
- 定义实现类时,直接确定接口上泛型的具体类型
public interface MyInter<T> {
public abstract void show(T t);
}
/*
定义实现类时,直接确定接口上泛型的具体类型
*/
public class MyInterImpl implements MyInter<String> {
@Override
public void show(String s) {
System.out.println(s);
}
}
- 定义实现类时不确定接口上泛型的具体类型,那么该实现类必须定义为泛型类,而且实现类上的泛型变量要和接口上的泛型变量名称一致——创建实现类对象时,<>中要写是什么类型,泛型就是什么类型。这也叫做泛型的传递。
public interface MyInter<T> {
public abstract void show(T t);
}
/*
定义实现类时不确定接口的类型
*/
public class MyInterImplB<T> implements MyInter<T> {
@Override
public void show(T t) {
System.out.println(t);
}
}
泛型的通配符
代表任意的一种引用类型,只能用来匹配泛型,不能用来定义泛型。
(1)注意事项:
- 泛型是不存在多态的,创建集合对象时,左右两边的<>内容要保持一致
- ArrayList< ? > list 可以接受什么?
可以接受ArrayList的任意类型对象(只要在创建ArrayList集合对象中<>写上一种引用类型,都是可以的)
(2)示例
public static void main(String[] args) {
ArrayList<String> l1 = new ArrayList<>();
Collections.addAll(l1,"123-123qwe-qwe-qwe-fhj-ghj-yjw-wlt-gfd-iop".split("-"));
ArrayList<Integer> l2 = new ArrayList<>();
Collections.addAll(l2,4,5,6,13,123,123,123,45,6,7,12,890,7,2,3,30);
print(l1);
print(l2);
}
public static void print(ArrayList<?> arrayList){
System.out.println(arrayList);
}
泛型的上下限
使用泛型的通配符,我们可以接受任何类型的参数,但有时候我们需要更加严格的限制传递的类型,比如,我们需要传递的参数是某个类型或者它的子类,又或是某个类型或者它的父类。此时,我们就需要使用泛型的上下限来做约束。
1、泛型的上限:
格式-类型名称<? extends E>
:表示E类型或者E类型任意子类
2、泛型的下限:
格式-类型名称<? super E>
:表示E类型或者E类型的任意父类