泛型的使用
理解
说明
-
泛型是 JDK5 新增的特性
-
在集合中使用泛型:
-
集合接口或集合类在 JDK5 时都修改为了带泛型的结构
-
在实例化集合类的时候指明具体的泛型类型
-
指明完类型后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型
比如:add(E e) -> 实例化以后:add(Integer e)
-
注意点:泛型的类型必须是类,不能是基本数据类型,所以我们需要用到基本数据类型时,可以使用它们的包装类
-
如果实例化时没有指明泛型的类型,默认类型为 java.lang.Object 类型
-
-
如何自定义泛型结构:泛型类、泛型接口、泛型方法
设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在 JDK1.5 之前只能把元素类型设计为 Object,JDK1.5 之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这些就是类型参数,即泛型。
概念
- 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
- 从 JDK1.5 以后,Java 引入了 “参数化类型(Parameterized type)” 的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该 List 只能保存字符串类型的对象。
- JDK1.5 改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持, 从而可以在声明集合变量、创建集合对象时传入类型实参。
为什么要有泛型?
- 解决元素存储的安全性问题,好比商品、药品标签等,不会弄错。
- 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。
在集合中没有泛型时:
在集合中有泛型时:
Java 泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生 ClassCastException 异常。同时,代码更加简洁、健壮。
在集合中使用泛型
之前的写法(不在集合中使用泛型的情况)
容易导致的问题:
- 类型不安全
- 强转时可能出现 ClassCastException
package com.laoyang.test.day1;
import org.junit.Test;
import java.util.*;
/**
* @ClassName GenericTest
* @Description: 泛型的使用
* @Author Laoyang
* @Date 2021/10/17 16:21
*/
public class GenericTest {
/**
* 不在集合中使用泛型的情况
*/
@Test
public void testOne() {
List list = new ArrayList();
// 需求:存放学生的成绩
list.add(81);
list.add(68);
list.add(97);
list.add(53);
// 问题一:类型不安全
// list.add("Tom");
for (Object o : list) {
// 问题二:强转时可能出现 ClassCastException
int score = (Integer) o;
System.out.println(score);
}
}
}
在集合中使用泛型的情况
这里使用 ArrayList 和 HashMap 进行举例
package com.laoyang.test.day1;
import org.junit.Test;
import java.util.*;
// import java.util.Map.*;
/**
* @ClassName GenericTest
* @Description: 泛型的使用
* @Author Laoyang
* @Date 2021/10/17 16:21
*/
public class GenericTest {
/**
* 在集合中使用泛型的情况
* 以 ArrayList 为例
*/
@Test
public void testTwo() {
// 指定泛型的类型,类型不能是基本数据类型(可以使用对应包装类)
List<Integer> list = new ArrayList<Integer>();
// 需求:存放学生的成绩
list.add(81);
list.add(68);
list.add(97);
list.add(53);
// 添加泛型以后,在编译时就会进行类型检查,保证数据的安全
// list.add("Tom");
// 使用 foreach 进行遍历
// for (Integer integer : list) {
// // 避免了强制操作,减少报错几率
// int score = integer;
// System.out.println(score);
// }
// 使用迭代器进行遍历
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
int score = iterator.next();
System.out.println(score);
}
}
/**
* 在集合中使用泛型的情况
* 以 HashMap 为例
*/
@Test
public void testThree() {
// Map<String, Integer> map = new HashMap<String, Integer>();
// JDK7 新特性:类型推断(简单说就是在前面指明泛型类型后,后面就可以省略,效果还是一样的)
Map<String, Integer> map = new HashMap<>();
map.put("Tom", 80);
map.put("Jack", 65);
map.put("Jerry", 77);
// map.put(99, "AA");
/*
加上 import java.util.Map.*; 这个包后可以不用写 “Map.”
可以直接写成:
Set<Entry<String, Integer>> entries = map.entrySet();
Iterator<Entry<String, Integer>> iterator = entries.iterator();
*/
Set<Map.Entry<String, Integer>> entries = map.entrySet();
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {
Map.Entry<String, Integer> next = iterator.next();
String key = next.getKey();
int value = next.getValue();
System.out.println(key + " = " + value);
}
}
}
自定义泛型结构
泛型的声明
接口:interface List< T >
类:class GenTest<K, V>
其中,T,K,V 不代表值,而是表示类型。这里使用任意字母都可以。常用 T 表示,是 Type 的缩写。
泛型的实例化
一定要在类名后面指定类型参数的值(类型)。如:
List< String> strList = new ArrayList< String>();
Iterator< Customer> iterator = customers.iterator();
- T 只能是类,不能用基本数据类型填充,但可以使用包装类填充。
- 把一个集合中的内容限制为一个特定的数据类型,这就是泛型(generics)背后的核心思想。
案例
自定义泛型类 - Order
package com.laoyang.test.day1;
/**
* @ClassName Order
* @Description: 自定义泛型类
* @Author Laoyang
* @Date 2021/10/17 17:24
*/
public class Order<T> {
String orderName;
int orderId;
// 类的内部结构就可以使用类的泛型
T orderT;
public Order() {
}
public Order(String orderName, int orderId, T orderT) {
this.orderName = orderName;
this.orderId = orderId;
this.orderT = orderT;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "Order{" +
"orderName='" + orderName + '\'' +
", orderId=" + orderId +
", orderT=" + orderT +
'}';
}
}
自定义泛型类的子类A - SubOrderA
package com.laoyang.test.day1;
/**
* @ClassName SubOrder
* @Description: 自定义泛型类的子类
* @Author Laoyang
* @Date 2021/10/17 17:35
*/
public class SubOrderA extends Order<Integer>{
/*
这里的 SubOrderA 不是泛型类
*/
}
自定义泛型类的子类B - SubOrderB
package com.laoyang.test.day1;
/**
* @ClassName SubOrderB
* @Description: 自定义泛型类的子类
* @Author Laoyang
* @Date 2021/10/17 17:39
*/
public class SubOrderB<T> extends Order<T> {
/*
这里的 SubOrderB 是泛型类
如果子类在继承的时候想让类型为 T(未指定某种类型)时,需要把自己也添加对应的泛型才能进行使用
*/
}
测试类 - GenericTests
package com.laoyang.test.day1;
import org.junit.Test;
/**
* @ClassName GenericTests
* @Description: 泛型结构的定义(类、接口、方法)
* @Author Laoyang
* @Date 2021/10/17 17:28
*/
public class GenericTests {
/**
* 自定义泛型类的使用
*/
@Test
public void testOne() {
/*
如果定义了泛型类,实例化的时候没有指明类的泛型,则会认为此泛型类型为 Object 类型
要求:如果大家定义的类是带泛型的,建议在实例化时要指明类的泛型
*/
Order orderA = new Order();
orderA.setOrderT(111);
orderA.setOrderT("AAA");
// 建议:实例化的时候指明泛型的类型
Order<String> orderB = new Order<>("orderA", 1001, "order:A");
orderB.setOrderT("A:hello");
orderB.setOrderT("B:world");
}
/**
* 自定义泛型类子类的使用
*/
@Test
public void testTwo() {
SubOrderA orderA = new SubOrderA();
// 由于子类在继承带泛型的类时,指明了泛型类型,所以实例化子类对象时,不需要在指明泛型
orderA.setOrderT(123);
SubOrderB<String> orderB = new SubOrderB<>();
orderB.setOrderT("ABC");
orderB.setOrderT("QQQ");
}
}
自定义泛型结构:泛型类、泛型接口
说明
-
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1, E2, E3>
-
泛型类的构造器如下:public GenericClass(){}。 这种写法是错误的:public GenericClass< E>(){}
-
实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
-
泛型不同的引用不能相互赋值。
尽管在编译时 ArrayList< String> 和 ArrayList< Integer> 是两种类型,但是,在运行时只有一个 ArrayList 被加载到 JVM 中。
-
泛型如果不指定,将被擦除,泛型对应的类型均按照 Object 处理,但不等价于 Object。
经验:泛型要使用一路都用。要不用,一路都不要用。
-
如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
-
jdk1.7,泛型的简化操作:ArrayList< Fruit> flist = new ArrayList<>();
-
泛型的指定中不能使用基本数据类型,可以使用包装类替换。
-
在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。
-
异常类不能是泛型的。
-
不能使用 new E[]。但是可以:E[] elements = (E[])new Object[capacity];
参考:ArrayList 源码中声明:Object[] elementData,而非泛型参数类型数组。
-
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
-
子类不保留父类的泛型:按需实现
√ 没有类型擦除
√ 具体类型
-
子类保留父类的泛型:泛型子类
√ 全部保留
√ 部分保留
-
结论:子类必须是 “富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型。
案例
package com.laoyang.test.day1;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName RuleTest
* @Description: 泛型结构的一些规则
* @Author Laoyang
* @Date 2021/10/18 10:34
*/
public class RuleTest {
/**
* 泛型不同的引用不能相互赋值
*/
@Test
public void testOne() {
ArrayList<String> listA = null;
ArrayList<Integer> listB = null;
// 泛型不同的引用不能相互赋值,编译不通过
// listA = listB;
}
/**
* 异常类不能为泛型
*/
@Test
public void testTwo() {
// try {
//
// }catch (T t) { // 异常类不能为泛型,编译不通过
//
// }
}
}
class User<T> {
private int id;
private String name;
// 类的内部结构可以使用类的泛型
T orderT;
public User() {
// 编译不通过,因为T类型还不确定,所以不能直接使用
// T[] arr = new T[10];
// 如果需要声明为泛型的数组,则需要new Object,并进行强转
T[] arr = (T[]) new Object[10];
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
/**
* 静态方法中不能使用类的泛型,编译不通过
* 如果没有使用类声明的泛型,则可以声明为泛型
*/
// public static void show(T orderT) {
// System.out.println(orderT);
// }
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", orderT=" + orderT +
'}';
}
}
/**
* 异常类不能声明为泛型,编译不通过
*/
// class MyException<T> extends RuntimeException { }
/**
* 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
* 子类不保留父类的泛型:按需实现
* 子类保留父类的泛型:泛型子类
*/
class Father<T1, T2> {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son1 extends Father {// 等价于class Son extends Father<Object,Object>{
}
// 2)具体类型
class Son2 extends Father<Integer, String> {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3<T1, T2> extends Father<T1, T2> {
}
// 2)部分保留
class Son4<T2> extends Father<Integer, T2> {
}
//class Father<T1, T2> {
//}
子类不保留父类的泛型
1)没有类型 擦除
//class Son<A, B> extends Father{//等价于class Son extends Father<Object,Object>{
//}
2)具体类型
//class Son2<A, B> extends Father<Integer, String> {
//}
子类保留父类的泛型
1)全部保留
//class Son3<T1, T2, A, B> extends Father<T1, T2> {
//}
2)部分保留
//class Son4<T2, A, B> extends Father<Integer, T2> {
//}
该案例只编写了部分自定义泛型结构的规则体现,更多的大家可以自行尝试
小练习
将之前的员工类和生日类,以及测试类修改为泛型结构
需求:① 根据员工姓名进行自然排序 ② 根据员工生日进行定制排序
员工类
package com.laoyang.test.day2;
/**
* @ClassName Employee
* @Description: 员工类
* @Author Laoyang
* @Date 2021/10/17 10:15
*/
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 +
'}';
}
/**
* 未指明泛型的写法
*/
// @Override
// public int compareTo(Object o) {
// if (o instanceof Employee) {
// Employee employee = (Employee) o;
// return this.name.compareTo(employee.name);
// }
// throw new RuntimeException("类型不一致");
// }
/**
* 指明泛型后的写法
*/
@Override
public int compareTo(Employee o) {
/*
指定类型后就省略了判断是否是对应类型的操作
如果只是判断 name 的话我们就可以直接进行返回了
*/
return this.name.compareTo(o.name);
}
}
指定类型后就省略了判断是否是对应类型的操作;如果只是判断 name 的话我们就可以直接进行返回了
生日类
package com.laoyang.test.day2;
/**
* @ClassName MyDate
* @Description: 生日类
* @Author Laoyang
* @Date 2021/10/17 10:16
*/
public class MyDate implements Comparable<MyDate> {
private int year;
private int month;
private int 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;
}
public MyDate(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public MyDate() {
}
@Override
public String toString() {
return "MyDate{" +
"year=" + year +
", month=" + month +
", day=" + day +
'}';
}
/**
* 使用泛型前的写法
*/
// @Override
// public int compareTo(Object o) {
// if (o instanceof MyDate) {
// MyDate myDate = (MyDate) o;
// // 判断年
// int compareA = Integer.compare(this.getYear(), myDate.getYear());
// if (compareA != 0) {
// return compareA;
// }
// // 判断月
// int compareB = Integer.compare(this.getMonth(), myDate.getMonth());
// if (compareB != 0) {
// return compareB;
// } else {
// // 判断日
// return Integer.compare(this.getDay(), myDate.getDay());
// }
// }
// throw new RuntimeException("类型不一致");
// }
/**
* 使用泛型后的写法
*/
@Override
public int compareTo(MyDate o) {
// 判断年
int compareA = Integer.compare(this.getYear(), o.getYear());
if (compareA != 0) {
return compareA;
}
// 判断月
int compareB = Integer.compare(this.getMonth(), o.getMonth());
if (compareB != 0) {
return compareB;
} else {
// 判断日
return Integer.compare(this.getDay(), o.getDay());
}
}
}
测试类
package com.laoyang.test.day2;
import org.junit.Test;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TreeSet;
/**
* @ClassName EmployeeTest
* @Description: 测试类
* @Author Laoyang
* @Date 2021/10/17 10:21
*/
public class EmployeeTest {
/**
* 自然排序
* 根据员工姓名进行排序
*/
@Test
public void testOne() {
Employee employeeA = new Employee("David",18,new MyDate(2003,2,20));
Employee employeeB = new Employee("Jerome",32,new MyDate(1988,5,18));
Employee employeeC = new Employee("Aaron",21,new MyDate(2000,3,11));
Employee employeeD = new Employee("Dennis",15,new MyDate(2006,6,1));
Employee employeeE = new Employee("Robinson",19,new MyDate(2002,10,6));
TreeSet<Employee> set = new TreeSet<>();
set.add(employeeA);
set.add(employeeB);
set.add(employeeC);
set.add(employeeD);
set.add(employeeE);
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()) {
Employee next = iterator.next();
System.out.println(next);
}
}
/**
* 定制排序
* 根据员工生日信息进行排序
*/
@Test
public void testTwo() {
Employee employeeA = new Employee("David",18,new MyDate(2003,2,20));
Employee employeeB = new Employee("Jerome",32,new MyDate(1988,5,18));
Employee employeeC = new Employee("Aaron",21,new MyDate(2000,3,11));
Employee employeeD = new Employee("Dennis",15,new MyDate(2006,6,1));
Employee employeeE = new Employee("Robinson",19,new MyDate(2003,10,6));
// 使用泛型前的写法
/*
Comparator comparator = new Comparator() {
@Override
public int compare(Object o1, Object o2) {
if (o1 instanceof Employee && o2 instanceof Employee) {
Employee employeeA = (Employee) o1;
Employee employeeB = (Employee) o2;
MyDate myDateA = employeeA.getBirthday();
MyDate myDateB = employeeB.getBirthday();
// 方式一:在对应方法方法中编写对应判断
// // 判断年
// int compareA = Integer.compare(myDateA.getYear(), myDateB.getYear());
// if (compareA != 0) {
// return compareA;
// }
// // 判断月
// int compareB = Integer.compare(myDateA.getMonth(), myDateB.getMonth());
// if (compareB != 0) {
// return compareB;
// } else {
// // 判断日
// return Integer.compare(myDateA.getDay(), myDateB.getDay());
// }
// 方式二:在MyDate类中进行判断,然后直接调用
return myDateA.compareTo(myDateB);
} else {
throw new RuntimeException("类型不一致");
}
}
};
*/
//使用泛型后的写法
Comparator<Employee> comparator = new Comparator<Employee>() {
@Override
public int compare(Employee o1, Employee o2) {
return o1.getBirthday().compareTo(o2.getBirthday());
}
};
TreeSet<Employee> set = new TreeSet<>(comparator);
set.add(employeeA);
set.add(employeeB);
set.add(employeeC);
set.add(employeeD);
set.add(employeeE);
Iterator<Employee> iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
}
}
我这里保留了之前没有使用泛型时的写法,大家可以进行参考
泛型方法
说明
- 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
- 泛型方法的格式: [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
- 泛型方法声明泛型时也可以指定上限
案例
package com.laoyang.test.day1;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
/**
* @ClassName RuleTest
* @Description: 泛型结构的一些规则
* @Author Laoyang
* @Date 2021/10/18 10:34
*/
public class RuleTest {
/**
* 泛型方法测试
*/
@Test
public void testThree() {
User<String> user = new User<>();
// 泛型方法在调用的时候指明泛型参数的类型(与泛型类指定的类型没有任何关系)
Integer[] arr = new Integer[]{1, 2, 3, 4};
List<Integer> integers = user.copyFromArrayToList(arr);
}
}
class User<T> {
private int id;
private String name;
// 类的内部结构可以使用类的泛型
T orderT;
public User() {
}
public User(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 下面的三个方法都不是泛型方法
*/
public T getOrderT() {
return orderT;
}
public void setOrderT(T orderT) {
this.orderT = orderT;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", orderT=" + orderT +
'}';
}
/**
* 泛型方法
* 1. 在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
* > 换句话说:泛型方法所属的类不管是不是泛型类都没有关系
* 2. 泛型方法可以声明为静态的。因为泛型参数是在调用方法的时候才确定的,并不是在实例化的时候确定
*/
public static <E> List<E> copyFromArrayToList(E[] arr) {
List<E> list = new ArrayList<>();
for (E e : arr) {
list.add(e);
}
return list;
}
}
在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系
换句话说:泛型方法所属的类不管是不是泛型类都没有关系
泛型方法可以声明为静态的。因为泛型参数是在调用方法的时候才确定的,并不是在实例化的时候确定
泛型在继承上的体现
说明
-
虽然类 A 是类 B 的父类,但是 G< A> 和 G< B> 二者不具备子父类的关系,二者是并列关系(结合 testOne 方法理解)
比如:String 是 Object 的子类,但是 List< String> 并不是 List< Object> 的子类。
-
类 A 是类 B 的父类,A< G> 是 B< G> 的父类(结合 testTwo 方法理解)
第二点中的 A< G> 和 B< G> 跟第一点中的 G< A> 和 G< B> 是没有任何关系的,请大家不要误解,最好是结合代码进行理解
案例
package com.laoyang.test.day4;
import org.junit.Test;
import java.util.*;
/**
* @ClassName GenericTest
* @Description: 泛型在继承方面的体现
* @Author Laoyang
* @Date 2021/10/18 16:42
*/
public class GenericATest {
/**
泛型在继承方面的体现
*/
@Test
public void testOne() {
/*
在之前使用引用数据类型和基本数据类型的时候可以直接把子类的对象赋值给父类进行使用
这是多态的体现
*/
Object object = null;
String str = null;
object = str;
Object[] objects = null;
String[] strings = null;
objects = strings;
// String 和 Date 不具备子父类关系,编译不通过
// Date date = new Date();
// str = date;
/*
G<A> 和 G<B> 不是子父类关系,而是并列关系
*/
List<Object> listA = null;
List<String> listB = new ArrayList<String>();
// 此时的listA和listB的类型不具备子父类关系,编译不通过
// listA = listB;
/*
证明一个东西的时候可以使用 “反证法”
反证法:
假设 listA = listB
listA.add(111); // 导致混入非 String 的数据,最后导致报错
*/
showA(listA);
// showA(listB); // 编译不通过,如果放入listB,就表示将String类型的泛型变为Object类型
showB(listB);
}
public void showA(List<Object> objects) { }
/**
* 如果想成功放入listB,则还需要额外编写一个方法
*/
public void showB(List<String> objects) { }
/**
* 类 A 是类 B 的父类,List<String> 是 ArrayList<String> 的父类
* AbstractList<String> 是 ArrayList<String> 的父类
*/
@Test
public void testTwo() {
List<String> listA = null;
AbstractList<String> listB = null;
ArrayList<String> listC = null;
listA = listC;
listB = listC;
}
}
通配符的使用
说明
-
使用类型通配符:?
比如:List<?> ,Map<?, ?>
List<?> 是 List< String>、List< Object> 等各种泛型 List 的父类。
-
读取 List<?> 的对象 list 中的元素时,永远是安全的,因为不管 list 的真实类型是什么,它包含的都是 Object。
-
写入 list 中的元素是不行的。因为我们不知道 c 的元素类型,我们不能向其中添加对象。
-
将任意元素加入到其中不是类型安全的:
Collection<?> c = new ArrayList<String>(); c.add(new Object()); // 编译时错误
-
因为我们不知道c的元素类型,所以我们不能向其中添加对象。add 方法有类型参数 E 作为集合的元素类型。我们传给 add 的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。
-
唯一的例外是 null,它是所有类型的成员。
-
另一方面,我们可以调用 get() 方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个 Object。
-
使用的注意点
-
编译错误:不能用在泛型方法声明上,返回值类型前面 <> 不能使用 “?”
public static <?> void test(ArrayList<?> list){ }
-
编译错误:不能用在泛型类的声明上
class GenericTypeClass<?>{ }
-
编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list = new ArrayList<?>();
案例
package com.laoyang.test.day4;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @ClassName GenericBTest
* @Description: 通配符的使用
* @Author Laoyang
* @Date 2021/10/18 17:14
*/
public class GenericBTest {
/**
通配符的使用
通配符:?
说明:类 A 是类 B 的父类,G<A> 和 G<B> 是没有关系的,二者共同的父类是 G<?>
*/
@Test
public void testOne() {
List<Object> listA = null;
List<String> listB = null;
List<?> list = null;
list = listA;
list = listB;
print(listA);
print(listB);
}
public void print(List<?> objects) {
Iterator<?> iterator = objects.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
}
/**
* 使用通配符后数据的读取和写入要求
*/
@Test
public void testTwo() {
List<?> list = null;
List<String> listC = new ArrayList<>();
listC.add("AA");
listC.add("BB");
list = listC;
/*
添加(写入):对于 List<?> 就不能向其内部添加数据
Ps:只能添加 null(但是我们一般加入数据的时候并不会去手动添加 null 值,所以添加 null 这个特点也可以忽略不记)
*/
// list.add("CC");
// list.add('?');
list.add(null);
/*
获取(读取):允许读取数据,读取的数据类型为 Object(读取到的时候是 ?通配符类型,但是不知道 ? 是什么类型所以最后就是 Object)
*/
Object o = list.get(1);
System.out.println(o); // BB
}
}
class Person { }
class Student extends Person { }
有限制的通配符
- <?>:允许所有泛型的引用调用
-
通配符指定上限
上限 extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即 <=
-
通配符指定下限
下限 super:使用时指定的类型不能小于操作的类,即 >=
-
举例:
<? extends Number> (无穷小 , Number]
只允许泛型为Number及Number子类的引用调用<? super Number> [Number , 无穷大)]
只允许泛型为Number及Number父类的引用调用<? extends Comparable>]
只允许泛型为实现Comparable接口的实现类的引用调用
案例
package com.laoyang.test.day4;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @ClassName GenericBTest
* @Description: 通配符的使用
* @Author Laoyang
* @Date 2021/10/18 17:14
*/
public class GenericBTest {
/**
有限制条件的通配符的使用
? extends A:G<? extends A> 可以作为 G<A> 和 G<B> 的父类,其中 B 是 A 的子类
? super A:G<? super A> 可以作为 G<A> 和 G<B> 的父类,其中 B 是 A 的父类
> 结合代码进行理解
*/
@Test
public void testThree() {
// 可以将 extends 看成是 “<=”(小于等于),其中 ? 是 Person 的子类
List<? extends Person> listA = null;
// 可以将 super 看成是 “>=”(大于等于),其中 ? 是 Person 的父类
List<? super Person> listB = null;
List<Student> listC = new ArrayList<Student>();
List<Person> listD = new ArrayList<Person>();
List<Object> listE = new ArrayList<Object>();
listA = listC;
listA = listD;
// listA = listE;
// listB = listC;
listB = listD;
listB = listE;
/*
读取数据
*/
listA = listC;
/*
举例:<? extends Person>
对象类型不能小于 Person,因为目前的最低上限就是 Person,所以可以使用 Object 和 Person 进行赋值,而不能使用 Student
简单理解:? <= Person
Ps:可以把 ? 看做对象类型
*/
Person personA = listA.get(1);
// 因为 Person > Student,所以编译不通过
// Student student = listA.get(1);
listB = listD;
/*
举例:<? super Person>
对象类型在读取数据的时候必须是Object(我有测试过给Person加个父类然后用父类进行读取,但是失败了......)
Ps:可以把 ? 看做对象类型
*/
Object object = listB.get(1);
// 因为对象类型必须是Object,所以编译不通过
// Person person = listB.get(1);
/*
写入数据
举例:<? extends Person>
? 表示负无穷,因为 Person 是最低上限,所以参数类型不能比 Person 小,而 Person 的父类(比它大的)不能赋值给子类,所以就会导致添加失败(感觉就是不能加数据)
简单理解:子类可以赋给父类,而父类不可以赋给子类
*/
// 编译不通过
// listA.add(new Person());
/*
举例:<? super Person>
? 表示正无穷,如果写入的数据是 Person 类及它的子类,那么是可以正常添加的;如果不是,则不可以添加
*/
listB.add(new Person());
listB.add(new Student());
// 编译不通过
// listB.add(new Object());
}
}
class Person { }
class Student extends Person { }
这里的注释解析可能会比较绕,大家可以多看几遍或者自己去尝试一下
泛型嵌套
package com.laoyang.test.day5;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
/**
* @ClassName GenericNestingTest
* @Description: 泛型嵌套
* @Author Laoyang
* @Date 2021/10/19 16:40
*/
public class GenericNestingTest {
public static void main(String[] args) {
HashMap<String, ArrayList<Citizen>> map = new HashMap<String, ArrayList<Citizen>>();
ArrayList<Citizen> list = new ArrayList<Citizen>();
list.add(new Citizen("刘恺威"));
list.add(new Citizen("杨幂"));
list.add(new Citizen("小糯米"));
map.put("刘恺威", list);
Set<Entry<String, ArrayList<Citizen>>> entrySet = map.entrySet();
Iterator<Entry<String, ArrayList<Citizen>>> iterator = entrySet.iterator();
while (iterator.hasNext()) {
Entry<String, ArrayList<Citizen>> entry = iterator.next();
String key = entry.getKey();
ArrayList<Citizen> value = entry.getValue();
System.out.println("户主:" + key);
System.out.println("家庭成员:" + value);
}
}
}
class Citizen {
public String name;
public Citizen(String name) {
this.name = name;
}
@Override
public String toString() {
return "Citizen{" +
"name='" + name + '\'' +
'}';
}
}
泛型引用举例:实际案例
用户在设计类的时候往往会使用类的关联关系
例如,一个人中可以定义一个信息的属性,但是一个人可能有各种各样的信息(如联系方式、基本信息等),所以此信息属性的类型就可以通过泛型进行声明,然后只要设计相应的信息类即可。
练习
-
定义个泛型类 DAO< T>,在其中定义一个 Map 成员变量,Map 的键为 String 类型,值为 T 类型。
分别创建以下方法:
public void save(String id,T entity): 保存 T 类型的对象到 Map 成员变量中
public T get(String id):从 map 中获取 id 对应的对象
public void update(String id,T entity):替换 map 中 key 为 id 的内容,改为 entity 对象
public List< T> list():返回 map 中存放的所有 T 对象
public void delete(String id):删除指定 id 对象
-
定义一个 User 类:
该类包含:private 成员变量(int 类型) id,age;(String 类型)name。
-
定义一个测试类:
创建 DAO 类的对象, 分别调用其 save、get、update、list、delete 方法来操作 User 对象,使用 Junit 单元测试类进行测试。
DAO类
package com.laoyang.test.day6;
import java.util.*;
/**
* @ClassName DAO
* @Description:
* @Author Laoyang
* @Date 2021/10/19 16:47
*/
public class DAO<T> {
/**
* 可以在此进行初始化,也可以编写一个 get/set 方法,在使用的时候进行初始化
* 注意:不进行初始化就使用,会报错的!!!
*/
private Map<String, T> map = new HashMap<>();
/**
* 保存 T 类型的对象到 Map 成员变量中
*
* @param id
* @param entry
*/
public void save(String id, T entry) {
map.put(id, entry);
}
/**
* 从 map 中获取 id 对应的对象
*
* @param id
* @return
*/
public T get(String id) {
return map.get(id);
}
/**
* 替换 map 中 key 为 id 的内容, 改为 entity 对象
* @param id
* @param entry
*/
public void update(String id, T entry) {
// 判读当前map中是否存在指定的id,如果有则进行修改,没有则不做任何操作
if (map.containsKey(id)) {
map.put(id, entry);
}
}
/**
* 返回 map 中存放的所有 T 对象
* @return
*/
public List<T> list() {
// 错误写法
// Collection<T> values = map.values();
// return (List<T>) values; // 可以简单理解为:父类不能赋值给子类
// 正确写法
List<T> list = new ArrayList<>();
Collection<T> values = map.values();
for (T value : values) {
list.add(value);
}
return list;
}
/**
* 删除指定 id 对象
* @param id
*/
public void delete(String id) {
map.remove(id);
}
}
User类
package com.laoyang.test.day6;
/**
* @ClassName User
* @Description:
* @Author Laoyang
* @Date 2021/10/19 16:52
*/
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
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;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试类
package com.laoyang.test.day6;
import org.junit.Test;
import java.util.List;
/**
* @ClassName DAOTest
* @Description: 练习
* @Author Laoyang
* @Date 2021/10/19 16:53
*/
public class DAOTest {
@Test
public void testOne() {
DAO<User> dao = new DAO<User>();
// 调用 save() 方法
dao.save("1001", new User(1001, "Tom", 18));
dao.save("1002", new User(1002, "Jerry", 50));
dao.save("1003", new User(1003, "Lisa", 23));
// 调用 get() 方法
User user = dao.get("1002");
System.out.println(user);
System.out.println("-------------------------------------------------");
// 调用 list() 方法
List<User> listA = dao.list();
listA.forEach(System.out :: println);
System.out.println("-------------------------------------------------");
// 调用 update() 方法
dao.update("1001", new User(1004, "Tom", 28));
List<User> listB = dao.list();
listB.forEach(System.out :: println);
System.out.println("-------------------------------------------------");
// 调用 delete() 方法
dao.delete("1004");
List<User> listC = dao.list();
listC.forEach(System.out :: println); // 打印 1001,1002,1003 的信息
System.out.println("**********");
dao.delete("1001");
List<User> listD = dao.list();
listD.forEach(System.out :: println); // 打印 1002,1003 的信息
}
}