Java 中的泛型提供了一種創建可以處理不同類型數據的可重用代碼的方法。它允許用戶定義可操作各種數據類型的類、接口和方法,而無需犧牲類型安全性。在 Java 5 中引入的泛型已經成爲 Java 編程語言的一個基本特性。
在 Java 引入泛型之前,它使用原始類型來允許將各種類型的對象存儲在集合中。然而,這種做法存在着類型安全性不足的問題,經常導致運行時錯誤,也讓代碼變得更加難以理解和維護。泛型的出現解決了這些問題,它通過在編譯時進行類型檢查和類型推斷來確保類型安全性,讓代碼更加可靠、清晰並且易於維護。
以下是 Java 中與泛型相關的一些關鍵概念:
- 類型參數:泛型使用類型參數,這些參數是使用泛型類、接口或方法時指定的類型的佔位符。類型參數括在尖括號( <> 符號)中,並且可以隨意命名。常見約定包括使用單個大寫字母(例如,E、T、K、V)。
- 泛型類和接口:可以通過在其聲明中包含類型參數來定義泛型類或接口。然後,這些參數可以用作類或接口中的字段類型、方法參數和返回類型。創建泛型類或接口的實例時,將提供類型參數來指定所使用的實際類型。
- 類型界限:可以通過指定類型界限來約束可用作泛型類或接口的參數的類型。類型界限可以是特定的類或接口,它們確保只有擴展指定類或實現指定接口的類型才能用作類型參數。
- 通配符:通配符在處理未知類型時提供了靈活性。Java 中有兩種通配符類型:上界通配符 (
? extends Type
) 和下界通配符 (? super Type
)。上界通配符允許作爲指定類型的子類型的任何類型,而下界通配符允許作爲指定類型的超類型的任何類型。 - 泛型方法:除了泛型類和接口之外,Java 還支持泛型方法。這些方法有自己的類型參數,可用於指定其參數的類型並獨立於封閉類或接口返回值。
泛型帶來了許多好處,比如提高了類型安全性、促進了代碼重用,並且能讓代碼更加簡潔。它們使您能夠編寫更通用的算法和數據結構,可以處理多種類型,同時保持了編譯時的類型檢查。藉助泛型,您能夠創建更爲健壯且易於維護的 Java 代碼。
Java 泛型的優點
Java 泛型提供了多個優點,有助於編寫更安全、更靈活和更可重用的代碼。以下是 Java 泛型的一些主要優點:
- 類型安全:泛型的主要好處之一是提高類型安全性。通過泛型,開發者可以指定類、接口或方法可以使用的元素類型。這使得編譯器能夠在編譯時執行類型檢查,防止與類型相關的錯誤並促進更可靠的代碼。它消除了顯式類型轉換的需要,並降低了運行時 ClassCastException 的風險。
- 代碼可重用性:泛型允許開發者編寫可在不同類型上運行的可重用代碼。通過使用類型參數對類、接口和方法進行參數化,可以創建可與各種數據類型一起使用的組件。這可以促進代碼重用,因爲開發者不必爲不同類型重寫類似的代碼。相反可以創建適用於多種類型的通用算法和數據結構。
- 編譯時類型檢查:使用泛型使編譯器能夠執行編譯時類型檢查,在代碼執行之前捕獲類型錯誤。這有助於及早發現類型不匹配,從而更輕鬆地在開發過程中識別和修復問題。通過在編譯時識別與類型相關的錯誤,可以降低在運行時遇到與類型相關的錯誤的可能性。
- 增強的可讀性和可維護性:泛型通過明確指示預期類型來提高代碼可讀性。通過使用類型參數,開發者可以向其他開發人員傳達代碼的期望,從而使其更易於理解和維護。它還減少了對註釋或文檔來解釋變量、參數和返回值的目的和預期類型的需要。
- 性能優化:Java 中的泛型是使用類型擦除來實現的。這意味着類型信息在運行時被刪除,編譯後的代碼可以使用原始類型。因此,不會因泛型而產生運行時開銷。這允許編寫通用代碼而不犧牲性能。
- 集合安全:泛型大大增強了ArrayList、LinkedList、HashMap等集合的安全性和完整性。使用泛型,開發者可以指定存儲在集合中的對象的類型,並且編譯器確保僅插入或檢索指定類型的對象。這可以防止運行時錯誤,提高代碼可靠性,並避免在使用集合時進行顯式類型轉換。
總的來說,Java 泛型在多個方面都帶來了明顯的優勢,包括類型安全、代碼重用、可讀性、可維護性以及集合安全性。它們讓能夠編寫健壯且靈活的代碼,減少了與類型相關的錯誤,並且有助於促進更優秀的軟件工程實踐。
Java泛型示例程序
下面是一個示例程序,演示了 Java 中泛型的使用:
/**
* GenericDemo類, 用於演示泛型的使用
* @param <F>
*/
public class GenericDemo<F> {
private F value;
public GenericDemo(F value) {
this.value = value;
}
public F getValue() {
return value;
}
public void setValue(F value) {
this.value = value;
}
public static void main(String[] args) {
GenericDemo<String> stringDemo = new GenericDemo<>("Hello, FunTester!");
System.out.println("String demo: " + stringDemo.getValue());
GenericDemo<Integer> integerDemo = new GenericDemo<>(42);
System.out.println("Integer demo: " + integerDemo.getValue());
}
}
在此示例中,我們有一個名爲 的泛型類GenericExample
,它可以與任何類型一起使用T
。它有一個value
類型爲 的私有字段T
,以及用於操作該值的構造函數、getter 和 setter 方法。
在該main
方法中,我們創建了兩個實例GenericExample
:一個具有類型參數String
,另一個具有類型參數Integer
。我們使用不同的值初始化它們,然後使用該getValue
方法檢索並打印這些值。
當我們運行程序時,它會輸出:
String demo: Hello, FunTester!
Integer demo: 88
該類GenericExample
是用不同的類型(String
和Integer
)實例化的,並且無論類型如何,代碼都保持不變。這演示了泛型如何允許我們編寫可用於不同類型的可重用代碼。
使用不同類型的 Java 泛型示例
以下是一些展示 Java 中不同類型泛型的示例:
多種類型的通用方法
public static <K, V> void printMap(Map<K, V> map) {
for (Map.Entry<K, V> entry : map.entrySet()) {
System.out.println(entry.getKey() + " : " + entry.getValue());
}
}
public static void main(String[] args) {
Map<String, Integer> users = new HashMap<>();
users.put("張三", 27);
users.put("李四", 30);
users.put("王武", 33);
printMap(users);
}
在此示例中,我們有一個通用方法printMap
,可以採用Map
任何鍵值類型。該方法迭代映射條目並打印它們。在該main
方法中,我們創建一個Map
包含String
鍵和Integer
值的對象並將其傳遞給該printMap
方法。
有界限的通用類
public class NumerGeneric<F extends Number> {
private F value;
public NumerGeneric(F value) {
this.value = value;
}
public double square() {
double num = value.doubleValue();
return num * num;
}
public static void main(String[] args) {
NumerGeneric<Integer> intBox = new NumerGeneric<>(5);
System.out.println("Square of Integer: " + intBox.square());
NumerGeneric<Double> doubleBox = new NumerGeneric<>(3.14);
System.out.println("Square of Double: " + doubleBox.square());
}
}
在此示例中,我們有一個泛型類NumerGeneric
,它僅接受擴展的類型Number
。它有一個初始化值的構造函數和一個square
計算值平方的方法。在該main
方法中,我們創建NumerGeneric
withInteger
和Double
types 的實例,然後調用該square
方法。
通用接口
/**
* 數組實現的棧
* @param <F>
*/
public class ArrayStack<F> extends Stack<F> {
private List<F> stack = new ArrayList<>();
/**
* push一個元素到棧頂
* @param element the item to be pushed onto this stack.
*/
public void push(F element) {
stack.add(element);
}
/**
* pop棧頂元素
* @return
*/
public F pop() {
if (isEmpty()) {
throw new NoSuchElementException("Stack 爲空");
}
return stack.remove(stack.size() - 1);
}
/**
* 返回棧頂元素
* @return
*/
public boolean isEmpty() {
return stack.isEmpty();
}
public static void main(String[] args) {
Stack<String> stringStack = new ArrayStack<>();
stringStack.push("Java");
stringStack.push("is");
stringStack.push("Fun");
while (!stringStack.isEmpty()) {
System.out.println(stringStack.pop());
}
}
}
Stack
在此示例中,我們使用方法push
、pop
和定義通用接口isEmpty
。然後,我們使用一個ArrayStack
使用泛型List
來存儲元素的類來實現該接口。在該main
方法中,我們創建一個ArrayStack
withString
類型的實例,並在堆棧上執行壓入和彈出操作。
這些示例演示了 Java 中泛型的多功能性,允許您以泛型和類型安全的方式處理不同的類型。
Java 泛型中的通配符
Java泛型中的通配符提供了一種指定未知類型或一系列類型的方法。它們在使用泛型類、接口和方法時提高了靈活性。通配符有兩種類型:上限通配符 ( ? extends Type
) 和下限通配符 ( ? super Type
)。
- 上限通配符(
? extends Type
):上限通配符將未知類型限制爲特定類型或其任何子類型。它允許您指定參數可以是擴展或實現特定類或接口的任何類型。
public static double sumOfList(List<? extends Number> numbers) {
double sum = 0.0;
for (Number number : numbers) {
sum += number.doubleValue();
}
return sum;
}
public static void main(String[] args) {
List<Integer> integers = Arrays.asList(1, 2, 3);
double sum = sumOfList(integers);
System.out.println("Sum: " + sum);
}
在此示例中,該方法sumOfList
接受List
帶有上限通配符的a <? extends Number>
。這意味着它可以接受擴展的任何類型的列表Number
,例如Integer
、Double
或Float
。該方法迭代列表並計算數字的總和。
- 下界通配符(
? super Type
):下界通配符將未知類型限制爲特定類型或其任何超類型。它允許您指定參數可以是特定類或接口的超類或超接口的任何類型。
public static void printElements(List<? super Integer> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20L);
numbers.add(30.5);
printElements(numbers);
}
在此示例中,該方法printElements
接受List
帶有下限通配符的a <? super Integer>
。這意味着它可以接受任何類型的超類列表Integer
,例如Number
或Object
。該方法迭代列表並打印每個元素。
當您有需要對未知類型或一系列類型進行操作的通用代碼時,通配符可以提供靈活性。它們允許您通過容納不同的類型來編寫更通用和可重用的代碼,而無需犧牲類型安全性。
- 無界通配符(
?
):Java 泛型中的無界通配符,僅用問號表示?
,通過接受任何類型來實現最大的靈活性。當你想要使用泛型類、接口或方法而不指定任何類型限制時,它非常有用。下面是一個演示無界通配符用法的示例:
public static void printList(List<?> list) {
for (Object element : list) {
System.out.println(element);
}
}
public static void main(String[] args) {
List<Integer> integers = new ArrayList<>();
integers.add(1);
integers.add(2);
integers.add(3);
List<String> strings = new ArrayList<>();
strings.add("Hello");
strings.add("World");
printList(integers);
printList(strings);
}
在此示例中,我們有一個名爲 的方法printList
,它接受List
帶有無界通配符的a List<?>
。這意味着該方法可以接受List
任何類型的 a。
在該main
方法中,我們創建兩個List
實例 - 一個具有Integer
類型,另一個具有String
類型。然後我們調用該printList
方法並傳入這些列表。該方法迭代列表的元素並打印它們。
通過使用無界通配符,該printList
方法變得通用並且可以處理List
任何類型的實例。這允許最大的靈活性,因爲它接受和處理列表而對元素類型沒有任何限制。
總體而言,Java泛型爲開發者帶來了顯著的優勢,使得代碼更加安全、靈活、可維護,並促進了更好的軟件工程實踐。