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

写在前面


        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之前只能把元素类型设计为ObjectJDK1.5之后使用泛型来解决。因为这个时候除了元素的类型不确定,其他的部分是确定的,例如关于这个元素如何保存,如何管理等是确定的,因此此时把元素的类型设计成一个参数,这个类型参数叫做泛型。Collection<E>List<E>ArrayList<E> 这个<E>就是类型参数,即泛型。

1.2 泛型的概念

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

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

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

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

1.3 在集合中使用泛型

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

    //在集合中使用泛型之前的情况:
    @Test
    public 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) {
            //问题二:强转时,可能出现ClassCastException
            int stuScore = (Integer) score;
            System.out.println(stuScore);
        }
    }

    //在集合中使用泛型的情况:以ArrayList为例
    @Test
    public void test2() {
        ArrayList<Integer> list = new ArrayList<Integer>();

        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<Integer> iterator = list.iterator();
        while (iterator.hasNext()) {
            int stuScore = iterator.next();
            System.out.println(stuScore);
        }
    }

    //在集合中使用泛型的情况:以HashMap为例
    @Test
    public void test3() {
        //Map<String,Integer> map = new HashMap<String,Integer>();
        //jdk7新特性:类型推断
        Map<String, Integer> map = new HashMap<>();

        map.put("Tom", 87);
        map.put("Jerry", 87);
        map.put("Jack", 67);

//      map.put(123,"ABC");
        //泛型的嵌套
        Set<Map.Entry<String, Integer>> entry = map.entrySet();
        Iterator<Map.Entry<String, Integer>> iterator = entry.iterator();

        while (iterator.hasNext()) {
            Map.Entry<String, Integer> e = iterator.next();
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key + "----" + value);
        }
    }
}

 2. 自定义泛型结构


2.1 泛型结构

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

2.2 泛型类、泛型接口

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


2.3 泛型方法

  • 方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
泛型方法的格式:
  • [访问权限] <泛型> 返回类型 方法名([泛型标识 参数名称]) 抛出的异常
  • 泛型方法声明泛型时也可以指定上限(12.5中讲)
public class DAO {
    public <E> E get(int id, E e) {
        E result = null;
        return result;
    }
}
public static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o);
    }
}
public static void main(String[] args) {
    Object[] ao = new Object[100];
    Collection<Object> co = new ArrayList<Object>();
    fromArrayToCollection(ao, co);
    String[] sa = new String[20];
    Collection<String> cs = new ArrayList<>();
    fromArrayToCollection(sa, cs);
    Collection<Double> 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 <T extends Person> 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 {

    @Test
    public void test1() {
        //如果定义了泛型类,实例化没有指明类的泛型,则认为此泛型类型为Object类型
        //要求:如果大家定义了类是带泛型的,建议在实例化时要指明类的泛型。
        Order order = new Order();
        order.setOrderT(123);
        order.setOrderT("ABC");

        //建议:实例化时指明类的泛型
        Order<String> order1 = new Order<String>("orderAA", 1001, "order:AA");
        order1.setOrderT("AA:hello");
    }

    @Test
    public void test2() {
        SubOrder sub1 = new SubOrder();
        //由于子类在继承带泛型的父类时,指明了泛型类型。则实例化子类对象时,不再需要指明泛型。
        sub1.setOrderT(1122);

        SubOrder1<String> sub2 = new SubOrder1<>();
        sub2.setOrderT("order2...");
    }

    @Test
    public void test3() {
        ArrayList<String> list1 = null;
        ArrayList<Integer> list2 = new ArrayList<Integer>();
        //泛型不同的引用不能相互赋值。
        //list1 = list2;

        Person p1 = null;
        Person p2 = null;
        p1 = p2;
    }

    //测试泛型方法
    @Test
    public void test4() {
        Order<String> order = new Order<>();
        Integer[] arr = new Integer[]{1, 2, 3, 4};
        //泛型方法在调用时,指明泛型参数的类型。
        List<Integer> list = order.copyFromArrayToList(arr);

        System.out.println(list);
    }
}
import java.util.ArrayList;
import java.util.List;

/**
 * 自定义泛型类
 */
public class Order<T> {
    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;
    }

    @Override
    public 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 <E> List<E> copyFromArrayToList(E[] arr) {
        ArrayList<E> 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<Integer> {//SubOrder:不是泛型类
    public static <E> List<E> copyFromArrayToList(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e : arr) {
            list.add(e);
        }
        return list;
    }
}
public class SubOrder1<T> extends Order<T> {//SubOrder1<T>:仍然是泛型类
}

2.5 泛型在继承上的体现

  • 如果BA的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,G<B>并不是G<A>的子类型!
  • 比如:StringObject的子类,但是List<String >并不是List<Object>的子类。
public void testGenericAndSubClass() {
    Person[] persons = null;
    Man[] mans = null;
    // 而 Person[] 是 Man[] 的父类.
    persons = mans;
    Person p = mans[0];
    // 在泛型的集合上
    List<Person> personList = null;
    List<Man> manList = null;
    // personList = manList;(报错)
}

3. 通配符的使用


3.1 使用通配符

1.使用类型通配符:?

  • 比如:List<?> Map<?,?>
  • List<?>List<String>List<Object>等各种泛型List的父类。

2.读取List<?>的对象list中的元素时,永远是安全的,因为不管list的真实类型是什么,它包含的都是Object

3.写入list中的元素时,不行。因为我们不知道c的元素类型,我们不能向其中添加对象。

  • 唯一的例外是null,它是所有类型的成员。

将任意元素加入到其中不是类型安全的

  • Collection<?> c = new ArrayList<String>();
  • c.add(new Object()); // 编译时错误
  • 因为我们不知道c的元素类型,我们不能向其中添加对象。add方法有类型参数E作为集合的元素类型。我们传给add的任何参数都必须是一个未知类型的子类。因为我们不知道那是什么类型,所以我们无法传任何东西进去。

唯一的例外的是null,它是所有类型的成员。

另一方面,我们可以调用get()方法并使用其返回值。返回值是一个未知的类型,但是我们知道,它总是一个Object

public static void main(String[] args) {
    List<?> list = null;
    list = new ArrayList<String>();
    list = new ArrayList<Double>();
    // list.add(3);//编译不通过
    list.add(null);
    List<String> l1 = new ArrayList<String>();
    List<Integer> l2 = new ArrayList<Integer>();
    l1.add("尚硅谷");
    l2.add(15);
    read(l1);
    read(l2);
}
public static void read(List<?> list) {
    for (Object o : list) {
        System.out.println(o);
    }
}

3.2 注意点

注意点 1 :编译错误:不能用在泛型方法声明上,返回值类型前面 <> 不能使用 ?
public static <?> void test(ArrayList<?> list) {
}
注意点 2 :编译错误:不能用在泛型类的声明上
class GenericTypeClass<?> {
}
注意点 3 :编译错误:不能用在创建对象上,右边属于创建集合对象
ArrayList<?> list2 = new ArrayList<?>();

3.3 有限制的通配符

<?>
  • 允许所有泛型的引用调用
通配符指定上限
  • 上限extends:使用时指定的类型必须是继承某个类,或者实现某个接口,即<=
通配符指定下限
  • 下限super:使用时指定的类型不能小于操作的类,即>=
举例:
<? extends Number> ( 无穷小 , Number]
  • 只允许泛型为NumberNumber子类的引用调用
<? super Number> [Number , 无穷大 )
  • 只允许泛型为NumberNumber父类的引用调用
<? extends Comparable>
  • 只允许泛型为实现Comparable接口的实现类的引用调用
public static void printCollection3(Collection<? extends Person> coll) {
    //Iterator只能用Iterator<?>或Iterator<? extends Person>.why?
    Iterator<?> iterator = coll.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}
public static void printCollection4(Collection<? super Person> coll) {
    //Iterator只能用Iterator<?>或Iterator<? super Person>.why?
    Iterator<?> iterator = coll.iterator();
    while (iterator.hasNext()) {
        System.out.println(iterator.next());
    }
}

3.4 通配符代码演示

import org.junit.Test;

import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * 1. 泛型在继承方面的体现
 * 2. 通配符的使用
 */
public class GenericTest {
    /*
    1. 泛型在继承方面的体现
       虽然类A是类B的父类,但是G<A> 和G<B>二者不具备子父类关系,二者是并列关系。
       补充:类A是类B的父类,A<G> 是 B<G> 的父类
     */
    @Test
    public void test1() {
        Object obj = null;
        String str = null;
        obj = str;

        Object[] arr1 = null;
        String[] arr2 = null;
        arr1 = arr2;
//      编译不通过
//      Date date = new Date();
//      str = date;
        List<Object> list1 = null;
        List<String> list2 = new ArrayList<String>();
        //此时的list1和list2的类型不具有子父类关系
        //编译不通过
        //list1 = list2;
        /*
        反证法:
        假设list1 = list2;
           list1.add(123);导致混入非String的数据。出错。
         */
        show(list1);
        show1(list2);
    }

    public void show1(List<String> list) {

    }

    public void show(List<Object> list) {

    }

    @Test
    public void test2() {
        AbstractList<String> list1 = null;
        List<String> list2 = null;
        ArrayList<String> list3 = null;

        list1 = list3;
        list2 = list3;

        List<String> list4 = new ArrayList<>();
    }

    /*
    2. 通配符的使用
       通配符:?
       类A是类B的父类,G<A>和G<B>是没有关系的,二者共同的父类是:G<?>
     */

    @Test
    public void test3() {
        List<Object> list1 = null;
        List<String> list2 = null;

        List<?> list = null;

        list = list1;
        list = list2;
        //编译通过
        //print(list1);
        //print(list2);

        List<String> list3 = new ArrayList<>();
        list3.add("AA");
        list3.add("BB");
        list3.add("CC");
        list = list3;
        //添加(写入):对于List<?>就不能向其内部添加数据。
        //除了添加null之外。
        //list.add("DD");
        //list.add('?');

        list.add(null);

        //获取(读取):允许读取数据,读取的数据类型为Object。
        Object o = list.get(0);
        System.out.println(o);
    }

    public void print(List<?> list) {
        Iterator<?> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object obj = iterator.next();
            System.out.println(obj);
        }
    }

    /*
    3.有限制条件的通配符的使用。
        ? 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 test4() {
        List<? extends Person> list1 = null;
        List<? super Person> list2 = null;

        List<Student> list3 = new ArrayList<Student>();
        List<Person> list4 = new ArrayList<Person>();
        List<Object> list5 = new ArrayList<Object>();

        list1 = list3;
        list1 = list4;
        //list1 = list5;

        //list2 = list3;
        list2 = list4;
        list2 = list5;

        //读取数据:
        list1 = list3;
        Person p = list1.get(0);
        //编译不通过
        //Student s = list1.get(0);

        list2 = list4;
        Object obj = list2.get(0);
        //编译不通过
        //Person obj = list2.get(0);

        //写入数据:
        //编译不通过
        //list1.add(new Student());

        //编译通过
        list2.add(new Person());
        list2.add(new Student());
    }
}

4. 泛型应用举例


4.1 泛型嵌套

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);
    }
}

4.2 实际案例

用户在设计类的时候往往会使用类的关联关系,例如,一个人中可以定义一个信息的属性,但是一个人可能有各种各样的信息(如联系方式、基本信息等),所以此信息属性的类型就可以通过泛型进行声明,然后只要设计相应的信息类即可。


结语


本人会持续更新文章的哦!希望大家一键三连,你们的鼓励就是作者不断更新的动力

猜你喜欢

转载自blog.csdn.net/qq_34025246/article/details/128164961