12.1 为什么要有泛型
- 泛型:标签
- 举例:
- 药店中,每个药瓶都有不同的名字
- 超市购物架上很多瓶子,每个瓶子装的是什么,有标签
- 泛型的设计背景 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的 对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时
把元素的类型设计成一个参数,这个类型参数叫做泛型
。Collection,List,ArrayList 这个就是类型参数,即泛型
12.1.1 泛型的概念
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时确定。
- 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念, 允许我们在创建集合时再指定集合元素的类型,正如:List,这表明 该List只能保存字符串类型的对象。
- JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持, 从而可以在声明集合变量、创建集合对象时传入类型实参。
集合中使用泛型:
ArrayList<String> arrayList = new ArrayList<>();
类中使用泛型
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
接口上使用泛型:
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
参数类型、返回值类型使用泛型
public E set(int index, E element) {
rangeCheck(index);
E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
12.1.2 泛型的作用
- 在JDK1.5之前没有泛型时不也一样使用集合、接口了吗?泛型的作用是什么?
- 解决元素存储的安全性问题
- 解决获取元素时的类型转换的问题
12.2 自定义泛型结构
泛型的声明
interface List<E> 和class Demo<K , V>
- 其中E、K、V不代表值,而是表示某种引用数据类型,任意的单个字母的大写都可以。
泛型的实例化
- 在类名后面指定类型参数的类型,如:
List<String> list = new ArrayList<>() ;
Map<String , String> map = new HashMap<>() ;
- E只能是引用数据类型的类名,不能使用基本数据类型填充。
- 其实泛型的作用就是约束,约束集合内存储的元素的数据类型。
12.2.1 泛型类
格式:
public class 类名<泛型> {
//在此类中多了一种 泛型类型 供此类使用
//只有在具体创建此类对象时才确定泛型的具体数据类型
}
举例:ArrayList集合类、HashMap<K,V>集合类等
- 定义一个Student泛型类
public class Student<T> {
private String name ;
private T age ;
public Student(String name, T age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getAge() {
return age;
}
public void setAge(T age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
- 实例化Student对象
public class Demo2 {
public static void main(String[] args) {
// 创建泛型类Student的实例化对象,并将其泛型类型设置为Integer
// 即age属性接收的类型为Integer
Student<Integer> stu1 = new Student<>();
// 设置属性
stu1.setName("张三");
stu1.setAge(21);
System.out.println("stu1 = " + stu1);
// 创建泛型类Student的实例化对象,并将其泛型类型设置为String类型
// 即age属性接收的类型为String
Student<String> stu2 = new Student<>();
// 设置属性
stu2.setName("张三");
stu2.setAge("23");
System.out.println("stu2 = " + stu2);
}
}
- 在泛型类Student中,对于泛型T,可以直接作为一种数据类型来使用,而在创建类对象时指定的数据类型会自动替换T
12.2.2 泛型接口
- 格式:
public interface 接口名<泛型> {
//在此接口中多了一种 泛型类型 供此其使用
//只有在具体创建此接口的具体实现类对象时才确定泛型的具体数据类型
}
举例:List接口、Collection接口
12.2.3 泛型方法
- 格式:
权限修饰符 状态修饰符 <泛型> 返回值类型 方法名(形参列表) {
// 在方法体中可以使用 泛型类型
}
示例:
public class Demo3 {
public static void main(String[] args) {
method(new Integer(5));
method("abcd");
}
public static <T> void method(T t) {
System.out.println("t = " + t);
System.out.println("泛型T的具体类型为" + t.getClass());
}
}
t = 5
泛型T的具体类型为class java.lang.Integer
t = abcd
泛型T的具体类型为class java.lang.String
12.2.4 泛型使用在类、接口上时的特点
- 泛型类可能有多个参数,此时应将多个参数一起放到尖括号内
class Cat<T , K , V> {
}
- 泛型类的构造器仍与普通类相同
public Cat() {}
- 泛型类被实例化后,操作原来泛型位置的结构必须与指定的泛型类型相同
- 泛型不同的引用不能相互赋值
- 如果不指定具体的泛型类型,会默认为Object类型
- 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态 属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法 中不能使用类的泛型。
- 异常类不能定义为泛型的
- 不能使用new E[]。但是可以:
E[] elements = (E[])new Object[10];
泛型的继承问题:
- 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
- 子类不保留父类的泛型:按需实现
- 没有类型
- 具体类型
- 子类保留父类的泛型:泛型子类
- 全部保留
- 部分保留
class Father<T , E> {
}
// 子类不保留父泛型
// 1、没有类型,此时等价于class Son1 extends Father<Object , Object> {}
class Son1 extends Father {
}
// 2、将泛型类型替换为具体类型
class Son2 extends Father<String , String> {
}
// 之类保留父类泛型
// 1、全部保留
class Son3<T , E> extends Father<T , E> {
}
// 部分保留
class Son4<E> extends Father<String , E> {
}
12.3 通配符的使用
- 泛型中的通配符为
?
- 如List<?> 、Map<? , ?>
- List<?> 是List、List等各类泛型List的父类
public class Demo6 {
public static void main(String[] args) {
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Object> list2 = new ArrayList<>();
System.out.println(list1 instanceof ArrayList<?>); // true
System.out.println(list2 instanceof ArrayList<?>); // true
}
}
-
读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object。
-
不能向集合中写入元素。因为我们不知道ArrayList<?>的元素类型,我们不能向其中添加对象。
- 唯一的例外是null,它是所有类型的成员
-
注意点
-
泛型的通配符不能使用在方法的声明上
public static <?> void test() {}
编译报错 -
不能使用在泛型类的声明上
public class Demo<?> {}
编译报错 -
不能用于创建对象
ArrayList<?> list = new ArrayList<?>() ;
编译报错
-
- 使用通配符指定泛型的上下限
- 通配符指定上限
- 上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
- 通配符指定下限
- 下限super:使用时指定的类型不能小于操作的类,即>=
- 举例:
<? extends Number>
(无穷小 , Number],只允许泛型为Number及Number子类的引用调用<? super Number>
[Number , 无穷大),只允许泛型为Number及Number父类的引用调用<? extends Comparable>
,只允许泛型为实现Comparable接口的实现类的引用调用