JAVA—基礎之“泛型”

什麼是泛型:

在瞭解一個技術點的時候最有效的辦法就是先看看官方文檔解釋,OK,官網給出的泛型定義:
泛型是 Java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定爲一個參數。
這種參數類型可以用在類、接口和方法的創建中,分別稱爲泛型類、泛型接口、泛型方法。

簡單來說:泛型就是“參數化類型”。衆所周知參數分爲“形參、實參”,這裏的”參數化類型”亦是如此,在定義泛型的時候就是“形參”,調用時傳入具體的參數即“實參”。



泛型出現在哪:

好多朋友就問了,在哪會出現泛型?在平時的擼碼中,咱們最經常接觸的泛型就是集合了(Collection),咱們先來看看現有的代碼。

這裏以 ArrayList 舉例。
在這裏插入圖片描述

接着看一下 ArrayList 的類
在這裏插入圖片描述
圖中紅色款內有一個E,這個就是泛型 (形參)。在創建的ArrayList的時候,你可以給出任何類型:基本數據類、引用數據類型。它可以接受任何傳入的具體的類型實參,並且在該類或接口中的所有E均爲該傳入的具體類型。我們常見還有T、E、K、V等形式的參數都是表示泛型形參。



爲何使用泛型:

ITEM-1:

編碼時的約束。如下圖,我們的 ArrayList 給的類型爲 String ,在我們進行 add 操作時,如果添加的元素不是 String 類型時,你的IDEA 就會給你一個紅槓槓,不能進行編譯,因爲使用了泛型,就是直接給出了限定類型。
在這裏插入圖片描述

ITEM-2:

在擼碼的時候,ArrayList也可以不給出具體的類型,這個沒問題,但是從ArrayList取元素的時候該元素就爲 Object 類型了,這個時候需要進行強轉,這樣就很容易出現 類型轉換異常/ java.lang.ClassCastException,使用泛型既可以儘量避免這樣的異常,同時也無需強轉,取值時IDEA已經能夠確認該值的類型。
在這裏插入圖片描述
在這裏插入圖片描述

ITEM-3:

減少類的開發,增強類的通用性。我們在開發中,會出現很多的實體類,這個時候有一些公用的方法需要整理,需要根據不同的類來進行相同的操作(類似Base Service),這個時候如果我們每個實體類都創建一個這樣的公共類,這樣的工作量是龐大的,而泛型就恰好幫我們解決了這樣的問題。



怎麼使用泛型:

長篇大論的東西都瞭解完之後,咱們就真槍實彈的運用一下。用一個經典的例子:學校、老師、學生。在學校裏的人既有學生還有老師。

/**
 * 創建一個 student 的類型
 */
public class Student {
    private String name;
    private String age;
    ...			// 省略 get set 方法
}
/**
 * 創建一個 teacher 的類型
 */
public class Teacher {
    private String name;
    private Integer age;
    ...			// 省略 get set 方法
}
/**
 * 創建一個 school 的類型,並且給 school 一個 泛型
 * 在該類裏所有用到 E 的地方都表示調用時傳入的具體參數類型
 * @param <E>
 */
public class School<E> {
    private E person;

    public E getPerson() {
        return person;
    }

    public void setPerson(E person) {
        this.person = person;
    }
}

接下來就是寫一個測試類,爲了不讓代碼看花眼,咱們一個一個來測試。首先咱們先來測試學生類

public class SchoolDemo {
    public static void main(String[] args) {
        // 創建一個學生對象
        Student student = new Student();
        student.setName("張三同學");
        student.setAge(15);
        // 創建學校對象,將 Student 類型放入
        School<Student> school_stu = new School<>();
        // 將 Student 對象放入 School
        school_stu.setPerson(student);
        // 這裏我們調用 get 方法就可以直接獲取到對應傳入的具體對象類型
        Student person_stu = school_stu.getPerson();
        // 打印一下內容
        System.out.println(person_stu.toString());
    }
}

打印出來的結果:
在這裏插入圖片描述

接着咱們一樣的套路,一樣的方法,來測試一下教師類

public class SchoolDemo {
    public static void main(String[] args) {
         // 創建一個老師對象
        Teacher teacher = new Teacher();
        teacher.setName("王美麗老師");
        teacher.setAge(25);
        // 創建學校對象,將 Teacher 類型放入
        School<Teacher> school_tea = new School<>();
        // 將 Teacher 對象放入 School
        school_tea.setPerson(teacher);
        // 這裏我們調用 get 方法就可以直接獲取到對應傳入的具體對象類型
        Teacher person_tea = school_tea.getPerson();
        // 打印一下內容
        System.out.println(person_tea.toString());
    }
}

打印出來的結果:
在這裏插入圖片描述
這樣就不用創建多個 School 類來去操作這兩各類了,我們只不過在創建 School 的時候放入的具體參數不同,set、get方法也會約束你的參數類型,減少出錯率,方便取值。



關於生成的實例類型:

上面的例子,我們就會思考,在創建 School 的時候,我們傳入的是不同的實力參數,那麼生成的 School 是否一樣的吶?
答案是:一樣的

public class SchoolDemo {
    public static void main(String[] args) {

        // 創建一個學生對象
        Student student = new Student();
        School<Student> school_stu = new School<>(student);


        // 創建一個老師對象
        Teacher teacher = new Teacher();
        School<Teacher> school_tea = new School<>(teacher);

        // 我們打印一下 類,看看是否爲true
        System.out.println("school_stu Class:" + school_stu.getClass());
        System.out.println("school_tea Class:" + school_tea.getClass());
        System.out.println(school_stu.getClass() == school_tea.getClass());

    }
}

打印的結果:
在這裏插入圖片描述
通過上面的例子,我們雖然傳入了不同的泛型實參,但並不沒有真正的生成不同的類型,也就是說我們傳入不同的參數實參,但在內存上卻只存在一個,就是原來的基本類型(這裏爲:School)。但我們在邏輯上是可理解爲多個不同的類型。
這是因爲,Java提出泛型概念的時候,只不過是作用在編碼階段,一旦進入編譯階段後,會先對其泛型進行校驗,完成校驗之後會將泛型的相關信息抹掉,成功編譯後的 class 文件中是不包含任何泛型信息的,更不會進入運行階段了。

泛型類型在邏輯上我們可以看爲不同的多個類型,在實際運行時,都是相同的基本類型



類型通配符:

泛型的基本用法咱們 Get 好了之後,來看看進階用法。上面的 Student、Teacher我們可以給他們一個父類(Person),同時讓其繼承這個父類,我們來改造一下代碼

/**
 * 創建父類 Person
 */
public class Person {
    private String name;
    private Integer age;
    
    ...			// 省略 get set 方法
}
/**
 * 創建一個 student 的類型,繼承 Person
 */
public class Student extends Person{
    
}
/**
 * 創建一個 teacher 的類型,繼承 Person
 */
public class Teacher extends Person{
    
}

重點在測試類裏面:

public class SchoolDemo {
    public static void main(String[] args) {

        // 創建一個學生對象
        Student student = new Student();
        School<Student> school_stu = new School<>(student);
        getPerson(school_stu);		// 1

        // 創建一個老師對象
        Teacher teacher = new Teacher();
        School<Teacher> school_tea = new School<>(teacher);
        getPerson(school_tea);		// 2
    }

    // 我們在這裏添加一個 getPerson 方法,參數爲 School<Person>
    public static void getPerson(School<Person> person){
    	System.out.println(person.getPerson().toString());
    }
}

上面這段代碼分別會在1、2兩處報紅,不會讓你編譯的。這就奇怪了,我們都繼承了Person類,爲什麼還不能編譯通過?我們想一下:這裏我們參數雖然是父類,但我們在 getPerson 的時候,返回的是 Student?還是 Teacher,而且在編譯過程中順序是不可控的,在必要的時候必須進行類型判斷,且進行強轉,這就與泛型的理念相背離了。因此在邏輯上我們並不能將 Person 看爲 Student、Teacher 的父類

這個時候我們就需要藉助通配符來讓其在邏輯上可以來表示爲一個父類的引用類型,類型通配符也就因此而生。

類型通配符我們基本使用問號(?)來代替具體的類型實參。值得注意的是,這裏的 ?是類型實參,而非形參!邏輯上就是所有 School<實參>的父類,一起來看看代碼

public class SchoolDemo {
    public static void main(String[] args) {

        // 創建一個學生對象
        Student student = new Student();
        School<Student> school_stu = new School<>(student);
        getPerson(school_stu);		// 1

        // 創建一個老師對象
        Teacher teacher = new Teacher();
        School<Teacher> school_tea = new School<>(teacher);
        getPerson(school_tea);		// 2

		// 我們在學校類裏面 放一個 String類型
		School<String> school_str = new School(new String());
        getPerson(school_str);		// 3
    }

    // 我們在這裏添加一個 getPerson 方法,參數爲 School<?>
    public static void getPerson(School<?> person){
        System.out.println(person.getPerson().toString());
    }
}

這樣1、2就可以進行編譯了,新的問題又出現了,我在3的這個位置放入的是一個 String 類型的學校。編譯器是不會編譯出錯的,因爲我們使用的是通配符,這時我們想對參數進行一定的限制,必須爲 Person的子類的時候怎麼處理?

// 我們在這裏添加一個 getPerson 方法,參數爲 School<? extends Person>
    public static void getPerson(School<? extends Person> person){
        System.out.println(person.getPerson().toString());
    }

使用類型通配符上線就解決啦,我們讓通配符去繼承某個父類,這樣就可以對參數進行一定的約束了。

在平時開發中,合理的運用泛型會給我們的開發帶了便利和代碼量的減少。~~OK,以上就是對泛型相關的知識了,如有不對請指出,多多海涵。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章