泛型概述
生活中的例子
- 举例1:中药店,每个抽屉外面贴着标签
- 举例2:超市购物架上很多瓶子,每个瓶子装的是什么,有标签
Java中的泛型,就类似于上述场景中的
标签
。
泛型的引入
在Java中,我们在声明方法时,当在完成方法功能时如果有未知的数据
需要参与,这些未知的数据需要在调用方法时才能确定,那么我们把这样的数据通过形参
表示。在方法体中,用这个形参名来代表那个未知的数据,而调用者在调用时,对应的传入实参
就可以了。
受以上启发,JDK1.5设计了泛型的概念。泛型即为“类型参数
”,这个类型参数在声明它的类、接口或方法中,代表未知的某种通用类型。
举例1:
集合类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK5.0之前只能把元素类型设计为Object,JDK5.0时Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时指定集合元素的类型。比如:List<String>
,这表明该List只能保存字符串类型的对象。
使用集合存储数据时,除了元素的类型不确定,其他部分是确定的(例如关于这个元素如何保存,如何管理等)。
举例2:
java.lang.Comparable
接口和java.util.Comparator
接口,是用于比较对象大小的接口。这两个接口只是限定了当一个对象大于另一个对象时返回正整数,小于返回负整数,等于返回0,但是并不确定是什么类型的对象比较大小。JDK5.0之前只能用Object类型表示,使用时既麻烦又不安全,因此 JDK5.0 给它们增加了泛型。
其中<T>
就是类型参数,即泛型。
所谓泛型,就是允许在定义类、接口时通过一个
标识
表示类中某个属性的类型
或者是某个方法的返回值或参数的类型
。这个类型参数将在使用时(例如,继承或实现这个接口、创建对象或调用方法时)确定(即传入实际的类型参数,也称为类型实参)。
使用泛型举例
自从JDK5.0引入泛型的概念之后,对之前核心类库中的API做了很大的修改,例如:JDK5.0改写了集合框架中的全部接口和类、java.lang.Comparable接口、java.util.Comparator接口、Class类等。为这些接口、类增加了泛型支持,从而可以在声明变量、创建对象时传入类型实参。
使用说明:集合框架声明接口和实现类时,使用了泛型(JDK5.0),在实例化集合对象时,如果没有使用泛型,则认为操作的是Object类型的数据。如果使用了泛型,则需要指明泛型的具体类型,一旦指明了泛型的具体类型,则在集合的相关方法中,凡是使用类的泛型的位置,都替换为具体的泛型类型。
集合中使用泛型
Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。即,把不安全的因素在编译期间就排除了,而不是运行期;既然通过了编译,那么类型一定是符合要求的,就避免了类型转换。
同时,代码更加简洁、健壮。
把一个集合中的内容限制为一个特定的数据类型,这就是generic背后的核心思想。
举例:
//泛型在List中的使用
@Test
public void test1(){
//举例:将学生成绩保存在ArrayList中
//标准写法:
//ArrayList<Integer> list = new ArrayList<Integer>();
//jdk7的新特性:类型推断
ArrayList<Integer> list = new ArrayList<>();
list.add(56); //自动装箱
list.add(76);
list.add(88);
list.add(89);
//当添加非Integer类型数据时,编译不通过
//list.add("Tom");//编译报错
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()){
//不需要强转,直接可以获取添加时的元素的数据类型
Integer score = iterator.next();
System.out.println(score);
}
}
举例:
//泛型在Map中的使用
@Test
public void test3(){
// HashMap<String,Integer> hashMap = new HashMap<String,Integer>();
//JDK7新特性 类型推断
HashMap<String,Integer> hashMap = new HashMap<>();
hashMap.put("David",87);
hashMap.put("Green",33);
hashMap.put("Jack",66);
hashMap.put("Kate",99);
Set<Map.Entry<String, Integer>> entrySet = hashMap.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entrySet.iterator();
while (iterator.hasNext()){
Map.Entry<String, Integer> entry = iterator.next();
String key = entry.getKey();
Integer value = entry.getValue();
System.out.println(key+"---"+value);
}
}
泛型在比较器中的应用:
MyDate类:
public class MyDate {
private int year;
private int month;
private int day;
public MyDate() {
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return day;
}
public void setDay(int day) {
this.day = day;
}
@Override
public String toString() {
return year + "年" + month + "月" + day + "日";
}
}
Employee类:
public class Employee implements Comparable<Employee> {
private String name;
private int age;
private MyDate birthday;
public Employee() {
}
public Employee(String name, int age, MyDate birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public MyDate getBirthday() {
return birthday;
}
public void setBirthday(MyDate birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", birthday=" + birthday +
'}';
}
//按name从A-Z排序
@Override
public int compareTo(Employee employee) {
return this.name.compareTo(employee.name);
}
}
测试类:
package genericity;
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
public class EmployeeTest {
@Test
public void test1() {
Employee employee1 = new Employee("Tom", 25, new MyDate(1999, 3, 4));
Employee employee2 = new Employee("Jack", 33, new MyDate(1991, 2, 26));
Employee employee3 = new Employee("David", 31, new MyDate(1993, 9, 2));
Employee employee4 = new Employee("Mask", 27, new MyDate(1997, 10, 1));
Employee employee5 = new Employee("Steven", 19, new MyDate(2005, 1, 1));
TreeSet<Employee> treeSet = new TreeSet<>();
treeSet.add(employee1);
treeSet.add(employee2);
treeSet.add(employee3);
treeSet.add(employee4);
treeSet.add(employee5);
Iterator<Employee> iterator = treeSet.iterator();
while (iterator.hasNext()) {
Employee employee = iterator.next();
System.out.println(employee);
}
}
//创建TreeSet时传入Comparator对象,按照生日日期的先后定制排序
@Test
public void test2() {
Employee employee1 = new Employee("Tom", 25, new MyDate(1999, 3, 4));
Employee employee2 = new Employee("Jack", 33, new MyDate(1991, 2, 26));
Employee employee3 = new Employee("David", 25, new MyDate(1999, 3, 2));
Employee employee4 = new Employee("Mask", 27, new MyDate(1997, 10, 1));
Employee employee5 = new Employee("Steven", 19, new MyDate(2005, 1, 1));
Comparator<Employee> comparator = new Comparator<Employee>() {
@Override
public int compare(Employee employee1, Employee employee2) {
int yearValue = employee1.getBirthday().getYear() - employee2.getBirthday().getYear();
if (yearValue != 0) {
return yearValue;
}
int monthValue = employee1.getBirthday().getMonth() - employee2.getBirthday().getMonth();
if (monthValue != 0) {
return monthValue;
}
return employee1.getBirthday().getDay() - employee2.getBirthday().getDay();
}
};
TreeSet<Employee> treeSet = new TreeSet<>(comparator);
treeSet.add(employee1);
treeSet.add(employee2);
treeSet.add(employee3);
treeSet.add(employee4);
treeSet.add(employee5);
Iterator<Employee> iterator = treeSet.iterator();
while (iterator.hasNext()) {
Employee employee = iterator.next();
System.out.println(employee);
}
}
}
相关使用说明
-
在创建集合对象的时候,可以指明泛型的类型。
具体格式为:List< Integer> list = new ArrayList< Integer>(); -
JDK7.0时,有新特性,可以简写为:
List< Integer> list = new ArrayList<>(); //类型推断 -
泛型,也称为泛型参数,即参数的类型,只能使用引用数据类型进行赋值。(不能使用基本数据类型,可以使用包装类替换)
-
集合声明时,声明泛型参数。在使用集合时,可以具体指明泛型的类型。一旦指明,类或接口内部,凡是使用泛型参数的位置,都指定为具体的参数类型。如果没有指明的话,看做是Object类型。
自定义泛型结构
泛型的基础说明
1、<类型>这种语法形式就叫泛型。
-
<类型>的形式我们称为类型参数,这里的"类型"习惯上使用T表示,是Type的缩写。即:< T>。
-
< T>:代表未知的数据类型,我们可以指定为< String>,< Integer>,< Circle>等。
- 类比方法的参数的概念,我们把< T>,称为类型形参,将< Circle>称为类型实参,有助于我们理解泛型
-
这里的T,可以替换成K,V等任意字母。
2、在哪里可以声明类型变量< T>
- 声明类或接口时,在类名或接口名后面声明泛型类型,我们把这样的类或接口称为
泛型类
或泛型接口
。
【修饰符】 class 类名<类型变量列表> 【extends 父类】 【implements 接口们】{
}
【修饰符】 interface 接口名<类型变量列表> 【implements 接口们】{
}
//例如:
public class ArrayList<E>
public interface Map<K,V>{
....
}
- 声明方法时,在【修饰符】与返回值类型之间声明类型变量,我们把声明了类型变量的方法,称为泛型方法。
[修饰符] <类型变量列表> 返回值类型 方法名([形参列表])[throws 异常列表]{
//...
}
//例如:java.util.Arrays类中的
public static <T> List<T> asList(T... a){
....
}
//例如:
//自定义泛型方法
public <E> E method(E e) {
return null;
}
自定义泛型类或泛型接口
当我们在类或接口中定义某个成员时,该成员的相关类型是不确定的,而这个类型需要在使用这个类或接口时才可以确定,那么我们可以使用泛型类、泛型接口。
说明
① 我们在声明完自定义泛型类以后,可以在类的内部(比如:属性、方法、构造器中)使用类的泛型。
② 我们在创建自定义泛型类的对象时,可以指明泛型参数类型。一旦指明,内部凡是使用类的泛型参数的位置,都具体化为指定的类的泛型类型。
③ 如果在创建自定义泛型类的对象时,没有指明泛型参数类型,那么泛型将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。
-经验:泛型要使用一路都用。要不用,一路都不要用。
④ 泛型的指定中必须使用引用数据类型。不能使用基本数据类型,此时只能使用包装类替换。
⑤ 除创建泛型类对象外,子类继承泛型类时、实现类实现泛型接口时,也可以确定泛型结构中的泛型参数。
如果我们在给泛型类提供子类时,子类也不确定泛型的类型,则可以继续使用泛型参数。
我们还可以在现有的父类的泛型参数的基础上,新增泛型参数。
注意
① 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
② JDK7.0 开始,泛型的简化操作:ArrayList flist = new ArrayList<>();
③ 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
④ 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。
⑤ 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,但不可以在静态方法中使用类的泛型。
⑥ 异常类不能是带泛型的。
//测试自定义泛型类
@Test
public void test4(){
// 实例化时,就可以指明类的泛型参数的类型
Order order = new Order();
Object obj = order.getT();
//泛型参数在指明时,不可以使用基本数据类型,但是可以使用包装类替换基本数据类型
// Order<int> order1 = new Order<int>();
//在实例化时,可以指明类的泛型参数的具体类型,一旦指明泛型类型,则在泛型类中凡是使用泛型
//参数的位置,都替换为指定的类型
Order<Integer> order2 = new Order<>();
Integer t = order2.getT();
Order<String> order3 = new Order<>();
order3.setT("AAA");
}
//测试自定义泛型类的子类
@Test
public void test5(){
SubOrder subOrder = new SubOrder();
Object obj = subOrder.getT();
//因为SubOrder不是泛型类,此处编译错误
// SubOrder<Integer> subOrder1 = new SubOrder<>();
SubOrder1 subOrder1 = new SubOrder1();
Integer integer = subOrder1.getT();
SubOrder2<String> subOrder2 = new SubOrder2<>();
subOrder2.show("ABC");
SubOrder3<String> subOrder3 = new SubOrder3<>();
Integer integer1 = subOrder3.getT();
String e = subOrder3.getE();
SubOrder4<String,Integer> subOrder4 = new SubOrder4<>();
String t = subOrder4.getT();
Integer integer2 = subOrder4.getE();
}
自定义泛型方法
如果我们定义类、接口时没有使用<泛型参数>,但是某个方法形参类型不确定时,这个方法可以单独定义<泛型参数>。
- 泛型方法的格式:
[访问权限] <泛型> 返回值类型 方法名([泛型标识 参数名称]) [抛出的异常]{
}
- 方法,也可以被泛型化,与其所在的类是否是泛型类没有关系。
- 泛型方法中的泛型参数在方法被调用时确定。
- 泛型方法可以根据需要,声明为static的。
泛型在继承上的体现
如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G< B >并不是G< A >的子类型!
比如:String是Object的子类,但是List< String >并不是List< Object >的子类。
类SuperA是类A的父类或接口,SuperA< G>与A< G>的关系:SuperA< G>与A< G>有继承的关系。即A< G>的实例可以赋值给SuperA< G>类型的引用(或变量)
比如:List< String>与ArrayList< String>
通配符的使用
当我们声明一个变量/形参时,这个变量/形参的类型是一个泛型类或泛型接口,例如:Comparator类型,但是我们仍然无法确定这个泛型类或泛型接口的类型变量的具体类型,此时我们考虑使用类型通配符 ? 。
//通配符的使用
@Test
public void test8() {
List<?> list = null;
List<String> list1 = new ArrayList<>();
list1.add("ABC");
list=list1;
//读取数据
Object obj = list.get(0);
System.out.println(obj);
//写入数据,操作失败
// list.add("CCC");
// 特例:可以将null写入集合中
list.add(null);
}
通配符的理解
使用类型通配符:?
比如:List<?>
,Map<?,?>
List<?>
是List<String>
、List<Object>
等各种泛型List的父类。
通配符的读与写
写操作:
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // 编译时错误
因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
唯一可以插入的元素是null,因为它是所有引用类型的默认值。
读操作:
另一方面,读取List<?>的对象list中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是Object。
使用注意点
注意点1:编译错误:不能用在泛型方法声明上,返回值类型前面<>不能使用?
public static <?> void test(ArrayList<?> list){
}
注意点2:编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{
}
注意点3:编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();
有限制的通配符
-
<?>
- 允许所有泛型的引用调用
-
通配符指定上限:
<? extends 类/接口 >
- 使用时指定的类型必须是继承某个类,或者实现某个接口,即<=(小于等于)
-
通配符指定下限:
<? super 类/接口 >
-
使用时指定的类型必须是操作的类或接口,或者是操作的类的父类或接口的父接口,即>=(大于等于)
-
说明:
<? extends Number> //(无穷小 , Number] //只允许泛型为Number及Number子类的引用调用 <? super Number> //[Number , 无穷大) //只允许泛型为Number及Number父类的引用调用 <? extends Comparable> //只允许泛型为实现Comparable接口的实现类的引用调用
// 有限制的通配符
@Test
public void test9() {
List<? extends Order> list = null;
List<Object> list1 = null;
List<Order> list2 = new ArrayList<>();
List<SubOrder> list3 = null;
list2.add(new Order("ABC",123));
// list = list1; 报错
list = list3;
list = list2;
//读取数据
Order order = list.get(0);
System.out.println("order:"+order);
//写入数据 不可写入
// list.add(new Order());
List<? super Order> list11 = null;
List<Object> list12 = null;
List<Order> list13 = new ArrayList<>();
List<SubOrder> list14 = null;
list11 = list12;
list11 = list13;
// list11 = list14; 报错
list13.add(new Order("111",123));
//读取数据
Object object = list11.get(0);
System.out.println("object:"+object);
//写入数据
// list11.add(new Object()); 报错
list11.add(new Order());
list11.add(new SubOrder());
}
【真题】
Java的泛型是什么?有什么好处和优点?JDK不同版本的泛型有什么区别?
泛型是程序中出现的不确定的类型。
以集合为例:把一个集合中的内容限制为一个特定的数据类型,就是泛型的核心思想。
JDK7新特性 类型推断:
ArrayList< String> list = new ArrayList<>();