CodeGym /Java Blog /Toto sisi /Java 中的泛型
John Squirrels
等級 41
San Francisco

Java 中的泛型

在 Toto sisi 群組發布
你好!我們將討論 Java 泛型。我必須說你會學到很多東西!不僅這節課,而且接下來的幾節課,都將專門討論泛型。所以,如果您對泛型感興趣,那麼今天是您的幸運日:您將學到很多關於泛型特性的知識。如果沒有,請辭職並放鬆!:) 這是一個非常重要的主題,您需要了解它。讓我們從簡單的開始:“什麼”和“為什麼”。

什麼是 Java 泛型?

泛型是具有參數的類型。創建泛型類型時,您不僅要指定類型,還要指定它將使用的數據類型。我猜你已經想到了最明顯的例子:ArrayList!這就是我們通常在程序中創建一個的方式:

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
正如您可能猜到的那樣,這個列表的一個特點是我們不能把所有東西都塞進去:它只適用於String對象。現在讓我們稍微偏離一下 Java 的歷史並嘗試回答“為什麼?”這個問題。為此,我們將編寫自己的 ArrayList 類的簡化版本。我們的列表只知道如何向內部數組添加數據和從中檢索數據:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
假設我們希望我們的列表只存儲Integer s。我們沒有使用通用類型。我們不想在add()方法中包含顯式的“instanceof Integer ”檢查。如果這樣做,我們的整個類將只適用於Integer,我們將不得不為世界上所有其他數據類型編寫一個類似的類!我們將依賴我們的程序員,並在代碼中留下註釋以確保他們不會添加我們不想要的任何內容:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
一位程序員錯過了這條評論,無意中將幾個字符串放入數字列表中,然後計算它們的總和:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
控制台輸出:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
這種情況最糟糕的部分是什麼?當然不是程序員的粗心。最糟糕的是,不正確的代碼最終出現在我們程序的重要位置並成功編譯。現在我們不會在編寫代碼時遇到錯誤,而只會在測試期間遇到錯誤(這是最好的情況!)。在開發的後期階段修復錯誤的成本要高得多——無論是金錢還是時間。這正是泛型給我們帶來好處的地方:泛型類可以讓不幸的程序員立即檢測到錯誤。該程序根本無法編譯!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
程序員立即意識到他或她的錯誤並立即變得更好。順便說一句,我們不必創建自己的List類來查看此類錯誤。只需從普通 ArrayList 中 刪除尖括號和類型 ( <Integer> )!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
控制台輸出:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
換句話說,即使使用 Java 的“本機”機制,我們也可能會犯這種錯誤並創建不安全的集合。但是,如果我們將此代碼粘貼到 IDE 中,我們會收到警告:“未經檢查的調用 add(E) 作為 java.util.List 原始類型的成員”我們被告知添加項目時可能會出錯到缺少通用類型的集合。但是“原始類型”這個短語是什麼意思呢?原始類型是其類型已被刪除的泛型類。換句話說,List myList1原始類型與原始類型相反的是泛型類型——帶有參數化類型指示的泛型類。例如,List<String> myList1. 您可能會問為什麼該語言允許使用原始類型?原因很簡單。Java 的創建者在語言中保留了對原始類型的支持,以避免產生兼容性問題。到 Java 5.0 發佈時(泛型首次出現在這個版本中),已經有很多代碼是使用原始類型編寫的。因此,該機制至今仍受支持。我們在課程中多次提到了Joshua Bloch的經典著作《Effective Java》。作為該語言的創造者之一,他在書中 並沒有跳過原始類型泛型類型。Java 中的泛型是什麼? - 2該書的第 23 章有一個非常雄辯的標題:“不要在新代碼中使用原始類型”,這是您需要記住的。使用泛型類時,切勿將泛型類型轉換為原始類型

通用方法

Java 允許您通過創建所謂的泛型方法來參數化各個方法。這些方法有何幫助?最重要的是,它們的幫助在於它們讓您可以使用不同類型的方法參數。如果可以將相同的邏輯安全地應用於不同的類型,那麼泛型方法可能是一個很好的解決方案。考慮這是一個非常簡單的例子:假設我們有一個名為myList1的列表。我們想從列表中刪除所有值並用新值填充所有空白區域。這是我們的類使用泛型方法的樣子:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
注意語法。它看起來有點不尋常:

public static <T> void fill(List<T> list, T val)
我們在返回類型之前寫上<T>。這表明我們正在處理一個泛型方法。在這種情況下,該方法接受 2 個參數作為輸入:T 對象列表和另一個單獨的 T 對象。通過使用 <T>,我們參數化了方法的參數類型:我們不能傳入一個字符串列表和一個整數。一個字符串列表和一個字符串、一個整數列表和一個整數列表、一個我們自己的Cat對象列表和另一個Cat對象——這就是我們需要做的。main ()方法說明瞭如何使用fill()方法輕鬆處理不同類型的數據。首先,我們使用帶有字符串列表和字符串作為輸入的方法,然後使用整數列表和整數。控制台輸出:

[New String, New String, New String] [888, 888, 888]
想像一下,如果我們沒有泛型方法並且需要30 個不同類的fill()方法的邏輯。我們將不得不為不同的數據類型編寫相同的方法 30 次!但是多虧了泛型方法,我們可以重用我們的代碼!:)

通用類

您不限於標準 Java 庫中提供的通用類 — 您可以創建自己的類!這是一個簡單的例子:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
我們的Box<T>類是一個泛型類。一旦我們在創建過程中分配了一個數據類型(<T>),我們就不能再在其中放置其他類型的對象。這可以在示例中看到。在創建我們的對象時,我們指出它可以與字符串一起使用:

Box<String> stringBox = new Box<>();
在最後一行代碼中,當我們嘗試將數字 12345 放入框中時,出現編譯錯誤!就這麼簡單!我們已經創建了自己的通用類!:) 至此,今天的課程就結束了。但我們並沒有與泛型說再見!在接下來的課程中,我們將討論更多高級功能,所以不要走開!) 為了鞏固您所學的知識,我們建議您觀看我們的 Java 課程中的視頻課程
祝你學業成功!:)
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION