1. 程式人生 > 其它 >Java 新特性之泛型

Java 新特性之泛型

技術標籤:JavaJava泛型

1)文筆有限,如果發現部落格有書寫有誤的地方懇請讀者直言不諱,我一定會第一時間改正。
2)程式碼的具體實現可以參考程式碼中的註釋,如果由於註釋不清楚而不明白相應原理,可以與作者私聊。碼字不易,有興趣的小夥伴點個讚唄,大家相互學習。
3)本篇部落格為Java新特性之 泛型,如需瞭解 Java 的其它部分,歡迎點選連結。

  1. Java 系列之基礎程式設計
  2. Java 系列之類與物件
  3. Java高階特性之 IO流
  4. Java高階特性之 集合
  5. Java高階特性之 多執行緒
  6. Java新特性之 列舉
  7. Java新特性之 泛型

傳送門:

1 概述

1.1 泛型的定義

所謂泛型(Generic),就是允許在定義類、介面時通過一個標識表示 類中某個屬性的型別或者是某個方法的返回值及引數型別。

這個型別引數將在使用時確定。例如當繼承或者實現某個介面時,通過在宣告或者建立物件時,傳入實際的型別引數來確定

1.2 為什麼需要泛型

我們用在集合中新增元素舉例。

當集合中沒有泛型時:
在這裡插入圖片描述

    /**
     * 需求:存放學生的成績
     * 1 當集合中不使用泛型時。集合中的任何元素都會預設用 Object 來對待
     */
@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); } }

當集合中使用泛型時:
在這裡插入圖片描述

    /**
     * 需求:存放學生的成績
     * 2 在集合中使用泛型的情況:以ArrayList為例
     */
    @Test
    public void test2(){
        ArrayList<Integer> list =  new ArrayList<Integer>();

        list.add(78);
        list.add(87);
        list.add(99);
        list.add(65);
        //編譯時,就會進行型別檢查,保證資料的安全。因為在例項化時規定了泛型是Integer
        //型,所以list中無法加入String型別的"Tom"
        //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);
        }
    }

重點

  1. 泛型只能使用包裝資料型別,而不能使用基本資料型別;
  2. 如果例項化時,沒指明泛型的型別。預設型別為 java.lang.Object 型別;
  3. 把一 個集合中的內容限制為一個特定的資料型別,這就是泛型背後的核心思想;
  4. 泛型在例項化之後,後面的集合就一定要放例項化時固定的型別了。

2 泛型在集合中的使用

    /**
     * 需求:存放學生的成績
     * 1 在集合中使用泛型的情況:以ArrayList為例
     */
    @Test
    public void test2(){
        // 如果沒有<Integer>,那麼就表示ArrayList中變數型別是 Object 型別的。
        ArrayList<Integer> list = new ArrayList<>();

        list.add(78);
        list.add(87);
        list.add(99);
        list.add(65);

        Iterator<Integer> iterator = list.iterator();
        while(iterator.hasNext()){
            int stuScore = iterator.next();
            System.out.println(stuScore);
        }
    }


    /**
     * 需求:存放學生的成績
     * 2 在集合中使用泛型的情況:以HashMap為例
     */
    @Test
    public void test3(){
        Map<String,Integer> map = new HashMap<>();

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

        //泛型的巢狀
        Set<Map.Entry<String,Integer>> entry = map.entrySet();

        for (Map.Entry<String, Integer> e : entry) {
            String key = e.getKey();
            Integer value = e.getValue();
            System.out.println(key + "----" + value);
        }
    }

3 自定義泛型結構

3.1 自定義泛型類、介面

  1. 泛型類可能有多個引數,此時應將多個引數一起放在尖括號內 。比如:<E1,E2,E3>;
  2. 泛 型類的構造器為 public GenericClass(){} ,而非public GenericClass<E>(){}
  3. T 不能用基本資料型別填充,但可以使用包裝類填充;
  4. 靜態方法中不能使用類的泛型;
  5. 異常類是不能泛型的;
  6. 不能使用 new E[] ;但是可以 E [] elements = (E[])new Object[capacity];
  7. 父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型型別。
/**
 * 自定義泛型類
 * 1. <T>中的 T 表示的是什麼型別;
 * 2. 泛型類並不是表示這個類是什麼型別,而是表示類中的屬性是什麼型別,且這些型別在構造物件的時候確定;
 * 3. 類名中的 <T> 是為了宣告這個類是泛型類。
 */
public class Order<T> {

    String orderName;
    int orderId;

    //類的內部結構就可以使用類的泛型
    T order;

    public Order(){
        // 在建構函式中必須使用下面一種方法
        // T[] arr = new T[10];
        T[] arr = (T[]) new Object[10];
    }

    public Order(String orderName,int orderId,T order){
        this.orderName = orderName;
        this.orderId = orderId;
        this.order = order;
    }

    //如下的三個方法都不是泛型方法
    public T getOrder(){
        return order;
    }

    public void setOrder(T order){
        this.order = order;
    }

    @Override
    public String toString() {
        return "Order{" +
                "orderName='" + orderName + '\'' +
                ", orderId=" + orderId +
                ", order=" + order +
                '}';
    }

    //靜態方法中不能使用類的泛型,因為泛型的建立是在類的例項化時,而靜態方法是屬於類本身的。
//    public static void show(T order){
//        System.out.println(order);
//    }

}

3.2 自定義泛型方法

public class Order<T> {

    String orderName;
    int orderId;
    T order;

    public Order(){
        T[] arr = (T[]) new Object[10];
    }

    //泛型方法:在方法中出現了泛型的結構,泛型引數與類的泛型引數沒有任何關係。
    //         換句話說,泛型方法所屬的類是不是泛型類都沒有關係。
    //泛型方法是可以宣告為靜態的,原因是泛型方法的泛型引數是在呼叫方法時確定的,並非在例項化類時確定。
    public static <E>  List<E> copyFromArrayToList(E[] arr){

        ArrayList<E> list = new ArrayList<>();

        for(E e : arr){
            list.add(e);
        }
        return list;
    }
}

3.3 自定義泛型在資料庫上的使用

在 Java 對資料庫的操作中,我們常採用 ORM 思想,即資料庫中的一張表對應一個 JavaBean 物件。

基本思想:對於不同表的很多操作都是相同的,這些操作,唯一不同的是被操作的物件。所以我們可以定義一個 DAO.java 來定義操作資料庫中的表的通用操作,然後利用泛型來約定不同的物件。

1)定義基本操作的Dao類

/**
 * DAO:data(base) access object
 */
public class DAO<T> {//表的共性操作的DAO

    //新增一條記錄
    public void add(T t){

    }

    //刪除一條記錄
    public boolean remove(int index){
        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 <E> E getValue(){
        return null;
    }
}

2)資料庫中的某一張表的 JavaBean物件

/**
 * 此類對應資料庫中的 customers 表
 */
public class Customer { 
}

3)資料庫中的另一張表的 JavaBean物件

/**
 * 此類對應資料庫中的 students 表
 */
public class Student {
}

4)操作 customs 表的 DAO

/**
 * 只能操作 customs 表的 DAO
 */
public class CustomerDAO extends DAO<Customer>{
}

5)操作 students 表的 DAO

/**
 * 只能操作 students 表的 DAO
 */
public class StudentDAO extends DAO<Student> {
}

6)測試類

public class DAOTest {

    @Test
    public void test1(){
        //對 customs 表進行操作
        CustomerDAO dao1 = new CustomerDAO();
        dao1.add(new Customer());
        List<Customer> list = dao1.getForList(10);

        //對 students 表進行操作
        StudentDAO dao2 = new StudentDAO();
        Student student = dao2.getIndex(1);
    }
}

如果沒有多型的話,我們需要寫很多過載的方法。這是一個重點。

4 泛型在繼承上的體現

現在有三個類,分別為類A、類B和類G,其中類A為類B的父類,那麼:

  • G<A>G <B>二者不是子父類關係,二者是並列關係;
  • 但是A<G>B<G> 的父類。

程式碼說明

    /**
     * `G<A>` 和`G <B>`二者不是子父類關係,二者是並列關係;所以不能賦值。
     */
    @Test
    public void test1(){

        //多型的體現
        Object obj = null;
        String str = null;

        obj = str;
        Object[] arr1 = null;
        String[] arr2 = null;
        arr1 = arr2;

        // 編譯不通過,因為String 並不是 Data 的父類。
        //Date date = new Date();
        //str = date;

        List<Object> list1 = null;
        List<String> list2 = new ArrayList<String>();

        //編譯不通過,此時的list1和list2的型別不具有子父類關係
        //list1 = list2;
    }
    /**
     * `A<G>` 是 `B<G>` 的父類。所以可以賦值。
     */
    @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<>()
    }

5 萬用字元的使用

由第四章可知,儘管類A是類B的父類,但是G<A>G<B>是沒關係的,不過二者共同的父類是:G<?>。其中的?在泛型中便是萬用字元。

?在資料庫中叫做佔位符。

5.1 普通萬用字元

    @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;
        //1 新增(寫入):對於List<?>就不能向其內部新增資料。
        //除了新增null之外。
//        list.add("DD");
//        list.add('?');
        list.add(null);

        //2 獲取(讀取):允許讀取資料,讀取的資料型別為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);
        }
    }

5.2 有限制條件的萬用字元的使用

帶限制條件的萬用字元作用
<? extends A>只允許泛型為 A 及 A 子類的引用呼叫
<? super A>只允許泛型為 A 及 A 父類的引用呼叫
<? extends Comparable>只允許泛型為實現 Comparable 介面的實現類的引用呼叫

程式碼例項

public class Person {
}
public class Student extends Person {
}
    @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;
        //list5無法賦值給list1
        //list1 = list5;
        //list3無法賦值給list2
        //list2 = list3;
        list2 = list4;
        list2 = list5;
    }