1. 为什么要有泛型
1.1 什么是泛型
- 所谓泛型,就是允许在定义类,接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。
- 这个类型参数将在使用时(例如:继承或实现这个接口,用这个类型声明变量,创建对象时)确定(即传入实际的类型参数,也成为类型实参)。
- JDK1.5后,Java引入了 “参数化类型” 的概念,允许我们在创建集合时再指定集合元素的类型
1.2 泛型的好处
- 解决元素存储的安全性问题
- 解决在获取数据元素时,需要强制类型转换的问题
2. 在集合中使用泛型
在集合中使用泛型之前,会存在两个问题
public class Demo {
@Test
public void test() throws Exception {
ArrayList list = new ArrayList();
//需求:存放人物姓名
list.add("费渡");
list.add("沈清秋");
//问题一:类型不安全
list.add(90);
for (Object name : list) {
//问题二:强制类型转换时,有可能会出现类型转换异常 ClassCastException
String str = (String) name;
System.out.println(str);
}
}
}
在集合中使用泛型之后
public class Demo {
@Test
public void test() throws Exception {
ArrayList<String> list = new ArrayList<>();//JDK7新特性:类型推断,可以省略类型
list.add("费渡");
list.add("沈清秋");
//编译时,就会进行类型检查,保证数据的安全
//list.add(90);
//避免强制类型转换
//遍历方式一:
for (String s : list) {
String name = s;
System.out.println(name);
}
//遍历方式二:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
System.out.println(name);
}
Map<Integer, String> map = new HashMap<>();
map.put(1, "费渡");
map.put(2, "沈清秋");
//编译时,就会进行类型检查,保证数据的安全
//map.put(1, 100);
//遍历Map
Set<Map.Entry<Integer, String>> entrySet = map.entrySet();
Iterator<Map.Entry<Integer, String>> iterator1 = entrySet.iterator();
while (iterator1.hasNext()) {
Map.Entry<Integer, String> entry = iterator1.next();
Integer key = entry.getKey();
String value = entry.getValue();
System.out.println(key + ": " + value);
}
}
}
总结
- 集合接口或集合类在JDK5.0时都修改为带泛型的结构
- 在实例化集合类时可以指明具体的泛型类型
- 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(方法,构造器,属性等)使用到类的泛型的位置,都指定为实例化时的泛型类型( 比如:add(E e) ----> 实例化以后:add(Integer e) )
- 注意泛型的类型必须是类,不能是基本数据类型,如需使用,要使用包装类替换
- 如果实例化时没有指明泛型的类型,默认类型为java.lang.Object
3. 自定义泛型结构
3.1 自定义泛型类
定义泛型类
public class Order<T> {
private String orderName;
private Integer orderId;
private T orderT;
//省略构造器等结构的声明
}
使用泛型类
public class Demo {
@Test
public void test() throws Exception {
Order<Date> dateOrder = new Order<>();
dateOrder.setOrderT(new Date());
Date date = dateOrder.getOrderT();//此时无需类型转换
System.out.println(dateOrder);
System.out.println(date);
}
}
如果子类继承了带泛型的父类且父类指明的泛型类型,则在实例化子类对象时,不再需要指明泛型
public class SubOrder extends Order<Date>{
}
public class Demo {
@Test
public void test() throws Exception {
SubOrder subOrder = new SubOrder();//不再需要指明泛型
subOrder.setOrderT(new Date());
Date date = subOrder.getOrderT();
System.out.println(subOrder);
System.out.println(date);
}
}
如果子类继承了带泛型的父类但没有指明泛型类型,此时可以子类可以自己指定泛型类型
public class SubOrder1<T> extends Order<T>{
}
public class Demo {
@Test
public void test() throws Exception {
SubOrder1<Date> subOrder1 = new SubOrder1<>();// 子类自己指定泛型
subOrder1.setOrderT(new Date());
Date date2 = subOrder1.getOrderT();
System.out.println(subOrder1);
System.out.println(date2);
}
}
注意
- 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:
<E1, E2, E3>
- 泛型类的构造器声明时不带泛型:
public Order() {}
- 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致
- 泛型不同的引用不能相互赋值,比如:
List<String> 和 List<Integer>
- 泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object
- 如果泛型结构是一个接口或抽象类,则不可创建对象
- 泛型的指定中不能使用基本数据类型,可以使用包装类替换
- 静态方法中不能使用类的泛型(泛型在实例化的时候指定)
- 异常类不能是泛型的
- 不能使用
new E()
,但是可以E[] elements = (E[]) new Object[capacity]
3.2 自定义泛型方法
泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数无关
// 泛型方法可以声明为静态的,泛型参数是在调用方法时确定的,并非在实例化类时确定
public static <E> List<E> copyFromArrayToList(E[] arr) {
List<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
public void test() throws Exception {
Order<Date> dateOrder = new Order<>();
// 泛型方法在调用时,指明泛型参数的类型
List<Integer> list = dateOrder.copyFromArrayToList(new Integer[]{12, 56, 84});
System.out.println(list);
}
4. 泛型在继承上的体现
此时的 list1 和 list2 的类型不具有子父类关系,无法进行赋值,编译不通过
List<Object> list1 = null;
List<String> list2 = null;
list1 = list2;
此时的 list1 和 list2 的类型是子父类关系,可以进行赋值
List<String> list1 = null;
ArrayList<String> list2 = null;
list1 = list2;
5. 通配符(?)的使用
相当于作为通用父类
public void test() throws Exception {
List<Object> list1 = null;
List<String> list2 = null;
List<?> list = null;// 相当于作为通用父类
list = list1;
list = list2;
print(list1);
print(list2);
}
public void print(List<?> list) {
Iterator<?> iterator = list.iterator();
while(iterator.hasNext()){
Object o = iterator.next();
System.out.println(o);
}
}
使用通配符后,数据的添加与读取操作
public void test() throws Exception {
List<String> list1 = new ArrayList<>();
list1.add("feidu");
list1.add("jiansuiying");
List<?> list = null;
list = list1;
// 添加:对于 List<?> 就不能向其内部添加数据(除了添加 null 外)
list.add("guyun");// 编译不通过
list.add(null);
// 读取:允许读取数据,读取的数据类型为 Object
Object o = list.get(0);
System.out.println(o);// feidu
}
有限制条件的通配符的使用
public void test() throws Exception {
// Person 和 Person 的子类
List<? extends Person> list1 = null;
// Person 和 Person 的父类
List<? super Person> list2 = null;
List<Employee> list3 = new ArrayList<>();
List<Person> list4 = new ArrayList<>();
List<Object> list5 = new ArrayList<>();
list1 = list3;
list1 = list4;
list1 = list5;// 编译不通过
list2 = list3;// 编译不通过
list2 = list4;
list2 = list5;
}
extends 代表小于等于,super 代表大于等于
在获取数据时,需要使用最大的类型来接收;在添加数据时,只能使用小于最小的类型;