目录
一、为什么要有泛型
1. 泛型的设计背景
集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>,List<E>,ArrayList<E> 这个<E>就是类型参数,即泛型。
2. 泛型的概念
-
所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
-
从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List<String>,这表明该List只能保存字符串类型的对象。
-
JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。
3. 泛型的作用
那么为什么要有泛型呢,直接Object不是也可以存储数据吗?
-
解决元素存储的安全性问题,好比商品、药品标签,不会弄错。
-
解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。
二、在集合中使用泛型
在集合中使用泛型之前的情况 : JDK 1.5之前
/*
在集合中使用泛型之前的情况
*/
@Test
public void test01(){
ArrayList list = new ArrayList();
//需求 : 存放学生的成绩
list.add(75);
list.add(78);
list.add(85);
list.add(95);
//问题一 : 类型不安全 什么都能放
// list.add("Tom");
for (Object obj : list){
//问题二 : 强转时,可能出现ClassCastException
int score = (Integer) obj;
System.out.println(score);
}
}
在集合中使用泛型的情况 : 以ArrayList为例
/*
在集合中使用泛型的情况 : 以ArrayList为例
*/
@Test
public void test02(){
ArrayList<Integer> list = new ArrayList<Integer>();//泛型的类型不能是基本数据类型
list.add(13);
list.add(53);
list.add(89);
list.add(98);
//编译时,就会进行类型检查,保证数据的安全
// list.add("Tom");
//方式一 : 增强for循环
for (Integer score : list){
//避免了强转操作
int stScore = score;
System.out.println(stScore);
}
//方式二 : 迭代器
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
Integer stScore = it.next();
System.out.println(stScore);
}
}
在集合中使用泛型的情况 : 以HashMap为例
/*
在集合中使用泛型的情况 : 以HashMap为例
*/
@Test
public void test03(){
Map<String, Integer> map = new HashMap<String, Integer>();
map.put("Tom", 18);
map.put("Jerry", 19);
map.put("Jack", 28);
map.put("Peter", 8);
//泛型的嵌套
Set<Map.Entry<String, Integer>> entrySet = map.entrySet();
Iterator<Map.Entry<String, Integer>> it = entrySet.iterator();
while(it.hasNext()){
Map.Entry<String, Integer> entry = it.next();
Integer value = entry.getValue();
String key = entry.getKey();
System.out.println(key + "-->" + value);
}
}
JDK 7新特性 : 类型推断
HashMap<String, Integer> map = new HashMap<>();//后面不用写具体类型 只需要加尖括号即可
总结 :
-
集合接口或集合类在JDK 1.5时都修改为带泛型的结构
-
在实例化集合类时,可以指明具体的泛型类型
-
指明完以后,在集合类或接口中凡是定义类或接口时,内部结构 (比如 : 方法、构造器、属性等) 使用到类的泛型的位置,都指定为实例化时的泛型类型。
例如 : add(E e) Integer实例化之后变为 : add(Integer e)
-
注意点 : 泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换。
-
如果实例化时,没有指明泛型的类型,默认类型为java.lang.Object类型
三、自定义泛型结构
1. 自定义泛型类和接口
-
泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<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,而非泛型参数类型数组。
-
父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
子类不保留父类的泛型:按需实现
-
没有类型 擦除
-
具体类型
子类保留父类的泛型:泛型子类
-
全部保留
-
部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
-
public class Order<T> {
private String orderName;
private int orderId;
//类的内部结构就可以使用类的泛型
private 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 +
'}';
}
}
自定义泛型类的测试 :
@Test
public void test01(){
//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
//要求 : 如果定义了类是带泛型的,建议在实例化时要指明泛型的类型
Order order = new Order();
Object orderT = order.getOrderT();
order.setOrderT(123);
order.setOrderT("Tom");
//建议 : 在实例化时要指明泛型的类型
Order<String> o = new Order<>();
o.setOrderT("Tom");
String str = o.getOrderT();
}
泛型不同的引用不能相互赋值 :
@Test
public void test02(){
ArrayList<String> list1 = new ArrayList<>();
ArrayList<Integer> list2 = new ArrayList<>();
// list1 = list2;
}
静态方法中不能使用类的泛型 : 因为泛型的指定是在实例化对象的时候才指明的,静态结构早于对象的创建,所以不能用使用类的泛型。
//Non-static method 'setOrderT(T)' cannot be referenced from a static context
public static void show(T orderT){
setOrderT(orderT);
System.out.println(orderT);
}
异常类不能声明为泛型类 :
//Generic class may not extend 'java.lang.Throwable'
public class MyException<T> extends Exception {
}
public void show(){
try{
//编译不通过
} catch (T t){
}
}
声明定义泛型类型数组 :
public Order(){
//编译不通过 Type parameter 'T' cannot be instantiated directly
// T[] arrT = new T[10];//由于T还不确定是什么类型 所以不能new 只有是具体的类才可以new
//编译通过
T[] arrT = (T[]) new Object[10];//这时候只能放T或其T的子类对象
}
问题 : 我们知道Object类型的对象不能强制转换为其子类对象,只有new的时候是Object的子类对象时才可以采用向下转型,否则会报ClassCastException异常,但是这里将Object类型数组强制转换为T类型的数组却没有报异常。这是因为,java中的泛型采用的是擦除方案,在编译过后T被替换成了Object类型了,所以在运行不会报ClassCastException类型转换错误。
关于自定义泛型类的继承情况 :
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> {
}
2. 自定义泛型方法
区分泛型方法和使用了泛型类的方法
泛型方法是指在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系,换句话说,泛型方法所属的类是不是泛型类都没有关系。
泛型方法,可以声明为静态的。原因 : 泛型参数是在调用方法时确定的。并非在实例化类时确定。
//自定义泛型方法
public <E> List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr){
list.add(e);
}
return list;
}
//可以是静态的
public <E> static List<E> copyFromArrayToList(E[] arr){
ArrayList<E> list = new ArrayList<>();
for (E e : arr){
list.add(e);
}
return list;
}
/*
测试自定义泛型方法
*/
@Test
public void test03(){
Integer[] arr = new Integer[]{1, 2, 3, 4};
Order<String> order = new Order<>();
//泛型方法在调用时 可以通过传入的参数获取泛型参数的类型 而与类的泛型无关
List<Integer> list = order.copyFromArrayToList(arr);
System.out.println(list);
}
3. 自定义泛型的使用场景
在操作数据库时,会定义一个DAO类 : data(base) access object (数据访问对象),在这个类中定义一些操作数据库的通用操作(一些curd方法)。由于一张表会对应一个类(ORM),所以这个DAO类不能对任何一张表都适用。所以我们让每张表对应的操作类都去继承这个基础的DAO类,可以使用这个类中的基础的操作,进而这个DAO类使用自定义泛型类实现。
public class DAO <T> {//表的共性操作的DAO
//添加一条记录
public void add(T t){}
//删除一条记录
public boolean remove(){return false;}
//修改一条记录
public void update(int index, T t){}
//查询一条记录
public T getIndex(int index){return null;}
//查询多条记录
public List<T> getForList(int index){return null;}
}
public class Customer{}
public class CustomerDAO extends DAO<Customer>{}//某一张具体表的DAO
@Test
public void test01(){
CustomerDAO dao = new CustomerDAO();
List<Customer> forList = dao.getForList(1);
Customer index = dao.getIndex(1);
boolean remove = dao.remove();
}
四、泛型在继承上的体现
虽然类A是类B的父类,但是G<A> 和 G<B> 二者不具有子父类关系,二者是并列关系。
@Test
public void test01(){
Object obj = new Object();
String str = "Tom";
obj = str;//子父类多态的体现
Object[] arrObj = new Object[10];
String[] arrStr = new String[10];
arrObj = arrStr;
//编译不通过
ArrayList<Object> listObj = new ArrayList<>();
ArrayList<String> listStr = new ArrayList<>();
//此时的list1和list2的类型不具有子父关系
// listObj = listStr;
//相当于 :
// Date date = new Date();
// String s = new String();
// date = s;
}
反证法 : 如果 listObj = listStr 可以,那么listObj指向了ArrayList<String>,这时调用listObj中的方法,可以往其添加非String的数据,就会出错。
弊端,导致方法使用的局限性 :
@Test
public void test03(){
ArrayList<Object> objectArrayList = new ArrayList<>();
ArrayList<String> stringArrayList = new ArrayList<>();
show(objectArrayList);
// show(stringArrayList);
show1(stringArrayList);
}
public void show1(ArrayList<String> arrayList){}
public void show(ArrayList<Object> arrayList){}
Java泛型是在编译期有效,在运行期删除,也就是说所有的泛型参数类型在编译后都会被清除掉。所以以上两个方法如果同名不能看做是重载,因为两个方法中的泛型在编译器编译后会有相同的擦除,不能构成重载。
补充 : 类A是类B的父类,A<G> 和 B<G> 具有子父类的关系
@Test
public void test02(){
List<String> list = null;
AbstractList<String> abstractList = null;
ArrayList<String> arrayList = new ArrayList<>();
abstractList = arrayList;
list = arrayList;
//常用的
List<String> list = new ArrayList<>();
}
五、通配符的使用
1. 通配符的使用情境
通配符就是来解决以上情况的局限性的,由于没有子父类关系不能使用多态,则就需要写许多重载的方法,但是通配符的出现解决了这一问题。
通配符 : ?
虽然类A是类B的父类,但是G<A> 和 G<B> 二者不具有子父类关系,二者是并列关系。G<A> 和 G<B> 的共同父类 是 G<?>
@Test
public void test04(){
List<Object> objectList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
print(objectList);
print(stringList);
stringList.add("AA");
stringList.add("BB");
stringList.add("CC");
stringList.add("DD");
List<?> list = stringList;
//添加(写入) : 对于 List<?> 不能向其添加数据 (除了添加null之外)
list.add(null);
//list.add("AA");
//获取(读取) : 允许读取数据 读取的数据类型为Object
Object o = list.get(0);
System.out.println(o);
}
public void print(List<?> list){
Iterator<?> it = list.iterator();
while(it.hasNext()){
Object obj = it.next();
System.out.println(obj);
}
}
2. 有条件限制的通配符
? extends A : G <? extend A> 可以作为 G<A> 和 G<B> 的父类,其中B是A的子类
? super A : G<? super A> 可以作为 G<A> 和 G<B> 的父类,其中B是A的父类
public class Test {
public static void main(String[] args) {
List<? extends Person> list1 = new ArrayList<>();
List<? super Person> list2 = new ArrayList<>();
List<Object> objectList = new ArrayList<>();
List<Person> personList = new ArrayList<>();
List<Student> studentList = new ArrayList<>();
//? extends A : G <? extend A> 可以作为 G<A> 和 G<B> 的父类,其中B是A的子类
// list1 = objectList;
list1 = personList;
list1 = studentList;
//? super A : G<? super A> 可以作为 G<A> 和 G<B> 的父类,其中B是A的父类
list2 = objectList;
list2 = personList;
// list2 = studentList;
//获取数据
Person person = list1.get(0);//最小是Person类型
//编译不通过
// Student studnet = list1.get(0);
Object object = list2.get(0);
//编译不通过
// Person person1 = list2.get(0);
//添加数据
// list1.add(new Student());
// list1.add(new Person());
//由于是 ? extends Person 表明是Person或Person的子类 如果是其子类 添加一个Person那么肯定添加失败
list2.add(new Person());
list2.add(new Student());
// list2.add(new Object());
//由于 ? super Person 表明是Person及其父类 这时无论真正的是什么 都可以添加Person及其子类对象
}
}
class Person{}
class Student extends Person{}
六、泛型应用举例
定义个泛型类 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 对象,
public class DAO <T> {
private Map<String, T> map = new HashMap<>();
//保存 T 类型的对象到 Map 成员变量中
public void save(String id, T entity){
map.put(id, entity);
}
//从 map 中获取 id 对应的对象
public T get(String id){
return map.get(id);
}
//替换 map 中 key 为 id 的内容,改为 entity 对象
public void update(String id,T entity){
if (map.containsKey(id))
map.put(id, entity);
}
//返回 map 中存放的所有 T 对象
public List<T> list(){
ArrayList<T> list = new ArrayList<>();
Collection<T> values = map.values();
for (T t : values){
list.add(t);
}
return list;
}
//删除指定 id 对象
public void delete(String id){
map.remove(id);
}
}
public class User {
private String name;
private int age;
public User(){}
public User(String name, int age){
this.age = age;
this.name = name;
}
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{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
if (age != user.age) return false;
return name != null ? name.equals(user.name) : user.name == null;
}
@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}
}
public class DAOTest {
public static void main(String[] args) {
DAO<User> dao = new DAO<>();
dao.save("1001", new User("Tom", 18));
dao.save("1003", new User("Jerry", 19));
dao.save("1002", new User("Jack", 20));
List<User> list = dao.list();
list.forEach(System.out::println);
//User{name='Jerry', age=19}
//User{name='Jack', age=20}
//User{name='Tom', age=18}
User user = dao.get("1001");
System.out.println(user);//User{name='Tom', age=18}
dao.update("1002", new User("John", 22));
List<User> list1 = dao.list();
list1.forEach(System.out::println);
//User{name='Jerry', age=19}
//User{name='John', age=22}
//User{name='Tom', age=18}
dao.delete("1003");
List<User> list2 = dao.list();
list2.forEach(System.out::println);
//User{name='John', age=22}
//User{name='Tom', age=18}
}
}