溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java泛型的解析

發布時間:2021-06-05 09:48:33 來源:億速云 閱讀:225 作者:Leah 欄目:開發技術

這期內容當中小編將會給大家帶來有關Java泛型的解析,文章內容豐富且以專業的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

一、泛型簡介

1.1 泛型的概念

  • 所謂泛型,就是允許在定義類、接口時通過一個標識表示類中某個屬性的類型或者是某個方法的返 回值及參數類型。這個類型參數將在使用時(例如,繼承或實現這個接口,用這個類型聲明變量、 創建對象時確定(即傳入實際的類型參數,也稱為類型實參)。

  • 從JDK 5.0以后,Java引入了“參數化類型(Parameterized type)”的概念,允許我們在創建集合時再指定集合元素的類型,正如:List,這表明該List只能保存字符串類型的對象。

  • JDK 5.0改寫了集合框架中的全部接口和類,為這些接口、類增加了泛型支持,從而可以在聲明集合變量、創建集合對象時傳入類型實參。

1.2 泛型的引入背景

集合容器類在設計階段/聲明階段不能確定這個容器到底實際存的是什么類型的對象,所以在JDK1.5之前只能把元素類型設計為Object,JDK1.5之后使用泛型來解決。因為這個時候除了元素的類型不確定,其他的部分是確定的,例如關于這個元素如何保存,如何管理等是確定的,因此此時把元素的類型設計成一個參數,這個類型參數叫做泛型。Collection,List,ArrayList 這個就是類型參數,即泛型。

1.3 引入泛型的目的

1.解決元素存儲的安全性問題,好比商品、藥品標簽,不會弄錯。

2.解決獲取數據元素時,需要類型強制轉換的問題,好比不用每回拿商品、藥品都要辨別。

Java泛型可以保證如果程序在編譯時沒有發岀警告,運行時就不會產生 ClassCastException 異常。同時,代碼更加簡潔、健壯。

二、泛型在集合中的應用

2.1 在集合中沒有使用泛型的例子

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

    }

}

圖示:

Java泛型的解析

2.2 在集合中使用泛型的例子1

//在集合中使用泛型,以ArrayList為例
@Test
public void test1(){
    ArrayList<String> list = new ArrayList<>();
    list.add("AAA");
    list.add("BBB");
    list.add("FFF");
    list.add("EEE");
    list.add("CCC");
	//遍歷方式一:
    Iterator<String> iterator = list.iterator();
    while (iterator.hasNext()){
        System.out.println(iterator.next());
    }
    System.out.println("-------------");
    //便利方式二:
    for (String str:
         list) {
        System.out.println(str);
    }
}

圖示:

Java泛型的解析

2.3 在集合中使用泛型例子2

@Test
//在集合中使用泛型的情況:以HashMap為例
public void test2(){
    Map<String,Integer> map = new HashMap<>();//jdk7新特性:類型推斷
    map.put("Tom",26);
    map.put("Jarry",30);
    map.put("Bruce",28);
    map.put("Davie",60);
    //嵌套循環
    Set<Map.Entry<String, Integer>> entries = map.entrySet();
    Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();

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

}

2.4 集合中使用泛型總結:

① 集合接口或集合類在JDK 5.0時都修改為帶泛型的結構。

② 在實例化集合類時,可以指明具體的泛型類型

③ 指明完以后,在集合類或接口中凡是定義類或接口時,內部結構(比如:方法、構造器、屬性等)使用到類的泛型的位置,都指定為實例化的泛型類型。

比如:add(E e) —>實例化以后:add(Integer e)

④ 注意點:泛型的類型必須是類,不能是基本數據類型。需要用到基本數據類型的位置,拿包裝類替換

⑤ 如果實例化時,沒有指明泛型的類型。默認類型為 java.lang.Object 類型。

三、自定義泛型結構

泛型類、泛型接口、泛型方法

3.1 泛型的聲明

  • interface List<T>class GenTest<K,V> 其中,T,K,V,不代表值,而是表示類型。這里使用任意字母都可以。

  • 常用T表示,是Type的縮寫。

3.2 泛型的實例化:

一定要在類名后面指定類型參數的值(類型)。如:

List<String> strList =new ArrayList<String>();

Iterator<Customer> iterator = customers.iterator();

  • T只能是類,不能用基本數據類型填充。但可以使用包裝類填充

  • 把一個集合中的內容限制為一個特定的數據類型,這就是 generics背后的核心思想

//JDK 5.0以前
Comparable c = new Date();
System.out.println(c.comparaTo("red");

//JDK 5.0以后
Comparable <Date> c = new Date();
System.out.println(c.comparaTo("red");

總結:使用泛型的主要優點在于能夠在編譯時而不是在運行時檢測錯誤

3.3 注意點

1.泛型類可能有多個參數,此時應將多個參數一起放在尖括號內。比如<E1,E2,E3>

2.泛型類的構造器如下: public GenericClass(){}

而下面是錯誤的: public GenericClass<E>{}

3.實例化后,操作原來泛型位置的結構必須與指定的泛型類型一致。

4.泛型不同的引用不能相互賦值。

盡管在編譯時 ArrayList和ArrayList是兩種類型,但是,在運行時只有一個ArrayList被加載到JVM中。

5.泛型如果不指定,將被擦除,泛型對應的類型均按照Object處理,但不等價于Object。

建議:泛型要使用一路都用。要不用,一路都不要用。

6.如果泛型結構是一個接口或抽象類,則不可創建泛型類的對象。

7.JDK 7.0,泛型的簡化操作: ArrayList<Fruit>first= new ArrayList<>();(類型推斷)

8.泛型的指定中不能使用基本數據類型,可以使用包裝類替換。

9.在類/接口上聲明的泛型,在本類或本接口中即代表某種類型,可以作為非靜態屬性的類型、非靜態方法的參數類型、非靜態方法的返回值類型。但在靜態方法中不能使用類的泛型。

10.異常類不能是泛型的。

11.不能使用 new E[]。但是可以:E[] elements= (E[])new Object[capacity];

> 參考:ArrayList源碼中聲明:`Object[] elementData`,而非泛型參數類型數組。

12.父類有泛型,子類可以選擇保留泛型也可以選擇指定泛型類型:

-   子類不保留父類的泛型:按需實現
    -   沒有類型---擦除
    -   具體類型
-   子類保留父類的泛型:泛型子類
    -   全部保留
    -   部分保留
-   結論:子類必須是“富二代”,子類除了指定或保留父類的泛型,還可以增加自己的泛型

代碼示例:

class Father<T1, T2> {
}

/**
 * 定義泛型子類Son
 * 情況一:繼承泛型父類后不保留父類的泛型
 */
//1.沒有指明類型  擦除
class Son1<A, B> extends Father {//等價于class Son1 extends Father<Object,Odject>{}
}

//2.指定具體類型
class Son2<A, B> extends Father<Integer, String> {
}

/**
 * 定義泛型子類Son
 * 情況二:繼承泛型父類后保留泛型類型
 */
//1.全部保留
class Son3<T1, T2, A, B> extends Father<T1, T2> {
}

//2.部分保留
class Son4<T2, A, B> extends Father<Integer,T2>{
}

3.4 自定義泛型結構

1.自定義泛型類

代碼示例:

/**
 * 自定義泛型類Order
 */
class Order<T> {
    private String orderName;
    private int orderId;
    //使用T類型定義變量
    private T orderT;

    public Order() {
    }
    //使用T類型定義構造器
    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);
//    }

//    //try-catch中不能是泛型的。
//    public void show(){
//        try {
//
//        }catch (T t){
//
//        }
//    }

    //泛型方法:在方法中出現了泛型的結構,泛型參數與類的泛型參數沒有任何關系。
    //換句話說,泛型方法所屬的類是不是泛型類都沒有關系。
    //泛型方法,可以聲明為靜態的。
    // 原因:泛型參數是在調用方法時確定的。并非在實例化類時確定。
    public static <E> List<E> copyFromArryToList(E[] arr) {
        ArrayList<E> list = new ArrayList<>();
        for (E e :
                list) {
            list.add(e);
        }
        return list;
    }
}

自定義泛型類Order的使用

@Test
public void test1() {
    //如果定義了泛型類,實例化沒有指明類的泛型,則認為此泛型類型為Object類型
    //要求:如果大家定義了類是帶泛型的,建議在實例化時要指明類的泛型。
    Order order = new Order();
    order.setOrderT(123);
    System.out.println(order.getOrderT());

    order.setOrderT("abc");
    System.out.println(order.getOrderT());

    //建議:實例化時指明類的泛型
    Order<String> order1 = new Order<>("Tom", 16, "male");
    order1.setOrderT("AA:BBB");
    System.out.println(order1.getOrderT());
}

@Test
//調用泛型方法
public void test2(){
    Order<String> order = new Order<>();
    Integer [] arr = new Integer[]{1,2,3,4,5,6};

    List<Integer> list = order.copyFromArryToList(arr);
    System.out.println(list);
}

2.自定義泛型接口

代碼示例:

/**
 * 自定義泛型接口
 */
public interface DemoInterface <T> {
    void show();
    int size();
}

//實現泛型接口
public class Demo implements DemoInterface {
    @Override
    public void show() {
        System.out.println("hello");
    }

    @Override
    public int size() {
        return 0;
    }
}

@Test
//測試泛型接口
public void test3(){
    Demo demo = new Demo();
    demo.show();
}

3.自定義泛型方法

1.方法,也可以被泛型化,不管此時定義在其中的類是不是泛型類。在泛型方法中可以定義泛型參數,此時,參數的類型就是傳入數據的類型。

2.泛型方法的格式: [訪問權限]<泛型>返回類型 方法名(泛型標識 參數名稱])拋出的異常

3.泛型方法聲明泛型時也可以指定上限

代碼示例:

//泛型方法:在方法中出現了泛型的結構,泛型參數與類的泛型參數沒有任何關系。
//換句話說,泛型方法所屬的類是不是泛型類都沒有關系。
//泛型方法,可以聲明為靜態的。
// 原因:泛型參數是在調用方法時確定的。并非在實例化類時確定。
public static <E> List<E> copyFromArryToList(E[] arr) {
    ArrayList<E> list = new ArrayList<>();
    for (E e :
         list) {
        list.add(e);
    }
    return list;
}

4.總結:

  •  泛型實際上就是標簽,聲明時不知道類型,再使用時指明

  • 定義泛型結構,即:泛型類、接口、方法、構造器時貼上泛型的標簽

  • 用泛型定義類或借口是放到類名或接口名后面,定義泛型方法時在方法名前加上

3.5 泛型的應用場景

DAO.java:定義了操作數據庫中的表的通用操作。 ORM思想(數據庫中的表和Java中的類對應)

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

}

CustomerDAO.java:

public class CustomerDAO extends DAO<Customer>{//只能操作某一個表的DAO
}

StudentDAO.java:

public class StudentDAO extends DAO<Student> {//只能操作某一個表的DAO
}

四、泛型在繼承上的體現

泛型在繼承方面的體現:

雖然類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<>();

}

五、通配符

5.1 通配符的使用

  • 使用類型通配符:?

比如:List<?>,Map<?,?>

List<?>List<String>、List<Object> 等各種泛型 List 的父類。

  • 讀取 List<?> 的對象list中的元素時,永遠是安全的,因為不管list的真實類型是什么,它包含的都是Object

  • 寫入list中的元素時,不可以。因為我們不知道c的元素類型,我們不能向其中添加對象。 除了添加null之外。

說明:

  • 將任意元素加入到其中不是類型安全的

Collection<?> c = new ArrayList<String>()

c.add(new Object());//編譯時錯誤

因為我們不知道c的元素類型,我們不能向其中添加對象。add 方法有類型參數 E 作為集合的元素類型。我們傳給add的任何參數都必須是一個已知類型的子類。因為我們不知道那是什么類型,所以我們無法傳任何東西進去。

  • 唯一的例外的是 null,它是所有類型的成員。

  • 我們可以調用 get() 方法并使用其返回值。返回值是一個未知的類型,但是我們知道,它總是一個Object。

代碼示例:

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

5.2 注意點

//注意點1:編譯錯誤:不能用在泛型方法聲明上,返回值類型前面<>不能使用?
public static <?> void test(ArrayList<?> list){

}

//注意點2:編譯錯誤:不能用在泛型類的聲明上
class GenericTypeClass<?>{

}

//注意點3:編譯錯誤:不能用在創建對象上,右邊屬于創建集合對象
ArrayList<> list2 new ArrayList<?>();

5.3 有限制的通配符

  • <?>:允許所有泛型的引用調用

  • 通配符指定上限

上限 extends:使用時指定的類型必須是繼承某個類,或者實現某個接口,即 <=

  • 通配符指定下限

下限 super:使用時指定的類型不能小于操作的類,即 >=

舉例:

  • <?extends Number>(無窮小, Number\]

只允許泛型為Number及Number子類的引用調用

  • <?super Number>\[Number,無窮大)

只允許泛型為Number及Number父類的引用調用

  • <? extends Comparable>

只允許泛型為實現 Comparable接口的實現類的引用調用

代碼示例:

@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());

}

上述就是小編為大家分享的Java泛型的解析了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

亚洲午夜精品一区二区_中文无码日韩欧免_久久香蕉精品视频_欧美主播一区二区三区美女