一文带你深入理解【Java基础】· 泛型
创始人
2024-03-16 12:26:41
0

写在前面


        Hello大家好, 我是【麟-小白】,一位软件工程专业的学生,喜好计算机知识。希望大家能够一起学习进步呀!本人是一名在读大学生,专业水平有限,如发现错误或不足之处,请多多指正!谢谢大家!!!

        如果小哥哥小姐姐们对我的文章感兴趣,请不要吝啬你们的小手,多多点赞加关注呀!❤❤❤ 爱你们!!!


目录

写在前面

1. 为什么要有泛型

1.1 为什么要有泛型

1.2 泛型的概念

1.3 在集合中使用泛型

1.4 泛型代码演示

 2. 自定义泛型结构

2.1 泛型结构

2.2 泛型类、泛型接口

2.3 泛型方法

2.4 自定义泛型类型代码演示

2.5 泛型在继承上的体现

3. 通配符的使用

3.1 使用通配符

3.2 注意点

3.3 有限制的通配符

3.4 通配符代码演示

4. 泛型应用举例

4.1 泛型嵌套

4.2 实际案例

结语


【往期回顾】

一文带你深入理解【Java基础】· Java集合(上)

一文带你深入理解【Java基础】· 注解

一文带你深入理解【Java基础】· 枚举类

一文带你深入理解【Java基础】· 常用类(上)

一文带你深入理解【Java基础】· 多线程(上)

一文带你深入理解【Java基础】· 异常处理


1. 为什么要有泛型


1.1 为什么要有泛型

  • 泛型:标签
  • 举例:
    • 中药店,每个抽屉外面贴着标签
    • 超市购物架上很多瓶子,每个瓶子装的是什么,有标签
  • 泛型的设计背景
  • 集合容器类在设计阶段/声明阶段不能确定这个容器到底实际存的是什么类型的对象,所以在JDK1.5之前只能把元素类型设计为Object,JDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection,List,ArrayList 这个就是类型参数,即泛型。

1.2 泛型的概念

  • 所谓泛型,就是允许在定义类、接口时通过一个标识表示类中某个属性的类型或者是某个方法的返回值及参数类型。这个类型参数将在使用时(例如,继承或实现这个接口,用这个类型声明变量、创建对象时)确定(即传入实际的类型参数,也称为类型实参)。
  • 从JDK1.5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。
  • JDK1.5改写了集合框架中的全部接口和类,为这些接口、类增加了泛型支持,从而可以在声明集合变量、创建集合对象时传入类型实参。

那么为什么要有泛型呢,直接Object不是也可以存储数据吗?

1. 解决元素存储的安全性问题,好比商品、药品标签,不会弄错。 2. 解决获取数据元素时,需要类型强制转换的问题,好比不用每回拿商品、药品都要辨别。

 

Java泛型可以保证如果程序在编译时没有发出警告,运行时就不会产生ClassCastException异常。同时,代码更加简洁、健壮。

1.3 在集合中使用泛型

ArrayList list = new ArrayList<>();//类型推断
list.add(78);
list.add(88);
list.add(77);
list.add(66);
//遍历方式一:
//for(Integer i : list) {//不需要强转//System.out.println(i);
//}
//遍历方式二:
Iterator iterator = list.iterator();
while(iterator.hasNext()) {System.out.println(iterator.next());
}
Map map = new HashMap();
map.put("Tom1",34);
map.put("Tom2",44);
map.put("Tom3",33);
map.put("Tom4",32);
//添加失败
//map.put(33, "Tom");
Set> entrySet = map.entrySet();
Iterator> iterator = entrySet.iterator();
while(iterator.hasNext()) {Entry entry = iterator.next();System.out.println(entry.getKey() + "--->" + entry.getValue());
}

1.4 泛型代码演示

import org.junit.Test;import java.util.*;/*** 泛型的使用* 1.jdk 5.0新增的特性** 2.在集合中使用泛型:*  总结:*  ① 集合接口或集合类在jdk5.0时都修改为带泛型的结构。*  ② 在实例化集合类时,可以指明具体的泛型类型*  ③ 指明完以后,在集合类或接口中凡是定义类或接口时,内部结构(比如:方法、构造器、属性等)使用到类的泛型的位置,都指定为实例化的泛型类型。*    比如:add(E e)  --->实例化以后:add(Integer e)*  ④ 注意点:泛型的类型必须是类,不能是基本数据类型。需要用到基本数据类型的位置,拿包装类替换*  ⑤ 如果实例化时,没有指明泛型的类型。默认类型为java.lang.Object类型。** 3.如何自定义泛型结构:泛型类、泛型接口;泛型方法。见 GenericTest1.java*/
public class GenericTest {//在集合中使用泛型之前的情况:@Testpublic void test1() {ArrayList list = new ArrayList();//需求:存放学生的成绩list.add(78);list.add(76);list.add(89);list.add(88);//问题一:类型不安全//list.add("Tom");for (Object score : list) {//问题二:强转时,可能出现ClassCastExceptionint stuScore = (Integer) score;System.out.println(stuScore);}}//在集合中使用泛型的情况:以ArrayList为例@Testpublic void test2() {ArrayList list = new ArrayList();list.add(78);list.add(87);list.add(99);list.add(65);//编译时,就会进行类型检查,保证数据的安全
//        list.add("Tom");//方式一:
//        for(Integer score : list) {
//            //避免了强转操作
//            int stuScore = score;
//            System.out.println(stuScore);
//        }//方式二:Iterator iterator = list.iterator();while (iterator.hasNext()) {int stuScore = iterator.next();System.out.println(stuScore);}}//在集合中使用泛型的情况:以HashMap为例@Testpublic void test3() {//Map map = new HashMap();//jdk7新特性:类型推断Map map = new HashMap<>();map.put("Tom", 87);map.put("Jerry", 87);map.put("Jack", 67);//      map.put(123,"ABC");//泛型的嵌套Set> entry = map.entrySet();Iterator> iterator = entry.iterator();while (iterator.hasNext()) {Map.Entry e = iterator.next();String key = e.getKey();Integer value = e.getValue();System.out.println(key + "----" + value);}}
}

 2. 自定义泛型结构


2.1 泛型结构

1.泛型的声明
  • interface List 和 class GenTest
  • 其中,T,K,V不代表值,而是表示类型。这里使用任意字母都可以。
  • 常用T表示,是Type的缩写。
2.泛型的实例化:
  • 一定要在类名后面指定类型参数的值(类型)。如:
    • List strList = new ArrayList();
    • Iterator iterator = customers.iterator();
  • T只能是类,不能用基本数据类型填充。但可以使用包装类填充
  • 把一个集合中的内容限制为一个特定的数据类型,这就是generics背后的核心思想
  •  体会:使用泛型的主要优点是能够在编译时而不是在运行时检测错误。

2.2 泛型类、泛型接口

1. 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如: 2. 泛型类的构造器如下:public GenericClass(){}。     而下面是错误的:public GenericClass(){} 3. 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。 4. 泛型不同的引用不能相互赋值。尽管在编译时ArrayList和ArrayList是两种类型,但是,在运行时只有一个ArrayList被加载到JVM中。 5. 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。 6. 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。 7. jdk1.7,泛型的简化操作:ArrayList flist = new ArrayList<>(); 8. 泛型的指定中不能使用基本数据类型,可以使用包装类替换。 9. 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型。 10. 异常类不能是泛型的 11. 不能使用new E[]。但是可以:E[] elements = (E[])new Object[capacity]; 参考:ArrayList源码中声明:Object[] elementData,而非泛型参数类型数组。 12.父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型:
  • 子类不保留父类的泛型:按需实现
    • 没有类型 擦除
    • 具体类型
  • 子类保留父类的泛型:泛型子类
    • 全部保留
    • 部分保留
结论:子类必须是“富二代”,子类除了指定或保留父类的泛型,还可以增加自己的泛型
class Father {
}
// 子类不保留父类的泛型
// 1)没有类型 擦除
class Son extends Father {//等价于class Son extends Father {
}
// 2)具体类型
class Son2 extends Father {
}
// 子类保留父类的泛型
// 1)全部保留
class Son3 extends Father {
}
// 2)部分保留
class Son4 extends Father {
}    
class Person {// 使用T类型定义变量private T info;// 使用T类型定义一般方法public T getInfo() {return info;
}
public void setInfo(T info) {this.info = info;
}
// 使用T类型定义构造器
public Person() {
}
public Person(T info) {this.info = info;
}
// static的方法中不能声明泛型
//public static void show(T t) {
//
//}
// 不能在try-catch中使用泛型定义
//public void test() {
//try {
//
//} catch (MyException ex) {
//
//}
//}
//}


2.3 泛型方法

  • 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法的格式:
  • [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
  • 泛型方法声明泛型时也可以指定上限(在12.5中讲)
public class DAO {public  E get(int id, E e) {E result = null;return result;}
}
public static  void fromArrayToCollection(T[] a, Collection c) {for (T o : a) {c.add(o);}
}
public static void main(String[] args) {Object[] ao = new Object[100];Collection co = new ArrayList();fromArrayToCollection(ao, co);String[] sa = new String[20];Collection cs = new ArrayList<>();fromArrayToCollection(sa, cs);Collection cd = new ArrayList<>();// 下面代码中T是Double类,但sa是String类型,编译错误。// fromArrayToCollection(sa, cd);// 下面代码中T是Object类型,sa是String类型,可以赋值成功。fromArrayToCollection(sa, co);
} 
class Creature{}
class Person extends Creature{}
class Man extends Person{}
class PersonTest {public static  void test(T t) {System.out.println(t);}public static void main(String[] args) {test(new Person());test(new Man());//The method test(T) in the type PersonTest is not //applicable for the arguments (Creature)test(new Creature());}
}

2.4 自定义泛型类型代码演示

import org.junit.Test;import java.util.ArrayList;
import java.util.List;/** 如何自定义泛型结构:泛型类、泛型接口;泛型方法。** 1. 关于自定义泛型类、泛型接口:*/
public class GenericTest1 {@Testpublic void test1() {//如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型//要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。Order order = new Order();order.setOrderT(123);order.setOrderT("ABC");//建议:实例化时指明类的泛型Order order1 = new Order("orderAA", 1001, "order:AA");order1.setOrderT("AA:hello");}@Testpublic void test2() {SubOrder sub1 = new SubOrder();//由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。sub1.setOrderT(1122);SubOrder1 sub2 = new SubOrder1<>();sub2.setOrderT("order2...");}@Testpublic void test3() {ArrayList list1 = null;ArrayList list2 = new ArrayList();//泛型不同的引用不能相互赋值。//list1 = list2;Person p1 = null;Person p2 = null;p1 = p2;}//测试泛型方法@Testpublic void test4() {Order order = new Order<>();Integer[] arr = new Integer[]{1, 2, 3, 4};//泛型方法在调用时,指明泛型参数的类型。List list = order.copyFromArrayToList(arr);System.out.println(list);}
}
import java.util.ArrayList;
import java.util.List;/*** 自定义泛型类*/
public class Order {String orderName;int orderId;//类的内部结构就可以使用类的泛型T orderT;public Order() {//编译不通过//T[] arr = new T[10];//编译通过T[] arr = (T[]) new Object[10];}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;}@Overridepublic String toString() {return "Order{" +"orderName='" + orderName + '\'' +", orderId=" + orderId +", orderT=" + orderT +'}';}
//    静态方法中不能使用类的泛型。
//    public static void show(T orderT) {
//        System.out.println(orderT);
//    }public void show() {//编译不通过
//        try{
//
//        } catch(T t) {
//
//        }}//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没有任何关系。//换句话说,泛型方法所属的类是不是泛型类都没有关系。//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。public static  List copyFromArrayToList(E[] arr) {ArrayList list = new ArrayList<>();for (E e : arr) {list.add(e);}return list;}
}
import java.util.ArrayList;
import java.util.List;public class SubOrder extends Order {//SubOrder:不是泛型类public static  List copyFromArrayToList(E[] arr) {ArrayList list = new ArrayList<>();for (E e : arr) {list.add(e);}return list;}
}
public class SubOrder1 extends Order {//SubOrder1:仍然是泛型类
}

2.5 泛型在继承上的体现