Java泛型 - 快速指南



Java泛型 - 概述

如果我們能夠編寫一個單一的排序方法,用於排序整數陣列、字串陣列或任何支援排序型別的陣列元素,那就太好了。

Java泛型方法和泛型類使程式設計師能夠用單個方法宣告指定一組相關方法,或者用單個類宣告指定一組相關型別。

泛型還提供編譯時型別安全,允許程式設計師在編譯時捕獲無效型別。

使用Java泛型概念,我們可以編寫一個用於排序物件陣列的泛型方法,然後用整數陣列、雙精度陣列、字串陣列等呼叫泛型方法來排序陣列元素。

Java泛型 - 環境設定

本地環境設定

JUnit是一個Java框架,所以首要要求是在你的機器上安裝JDK。

系統需求

JDK 1.5或更高版本。
記憶體 無最低要求。
磁碟空間 無最低要求。
作業系統 無最低要求。

步驟1:驗證你的機器上是否安裝了Java

首先,開啟控制檯並根據你使用的作業系統執行java命令。

作業系統 任務 命令
Windows 開啟命令控制檯 c:\> java -version
Linux 開啟命令終端 $ java -version
Mac 開啟終端 machine:< joseph$ java -version

讓我們驗證所有作業系統的輸出:

作業系統 輸出
Windows

java version "1.6.0_21"

Java(TM) SE Runtime Environment (build 1.6.0_21-b07)

Java HotSpot(TM) Client VM (build 17.0-b17, mixed mode, sharing)

Linux

java version "1.6.0_21"

Java(TM) SE Runtime Environment (build 1.6.0_21-b07)

Java HotSpot(TM) Client VM (build 17.0-b17, mixed mode, sharing)

Mac

java version "1.6.0_21"

Java(TM) SE Runtime Environment (build 1.6.0_21-b07)

Java HotSpot(TM) 64-Bit Server VM (build 17.0-b17, mixed mode, sharing)

如果你的系統上沒有安裝Java,請從以下連結下載Java軟體開發工具包(SDK): https://www.oracle.com。在本教程中,我們假設安裝的版本為Java 1.6.0_21。

步驟2:設定JAVA環境

設定**JAVA_HOME**環境變數,使其指向Java安裝在你的機器上的基目錄位置。例如:

作業系統 輸出
Windows 將環境變數JAVA_HOME設定為C:\Program Files\Java\jdk1.6.0_21
Linux export JAVA_HOME = /usr/local/java-current
Mac export JAVA_HOME = /Library/Java/Home

將Java編譯器位置新增到系統路徑。

作業系統 輸出
Windows 在系統變數**Path**的末尾附加字串**C:\Program Files\Java\jdk1.6.0_21\bin**。
Linux export PATH = $PATH:$JAVA_HOME/bin/
Mac 無需此步驟

使用上面解釋的命令**java -version**驗證Java安裝。

Java泛型 - 類

泛型類宣告看起來像非泛型類宣告,只是類名後面跟著一個型別引數部分。

泛型類的型別引數部分可以包含一個或多個用逗號分隔的型別引數。這些類被稱為引數化類或引數化型別,因為它們接受一個或多個引數。

語法

public class Box<T> {
   private T t;
}

其中

  • **Box** − Box是一個泛型類。

  • **T** − 傳遞給泛型類的泛型型別引數。它可以接受任何物件。

  • **t** − 泛型型別T的例項。

描述

T是傳遞給泛型類Box的型別引數,建立Box物件時應該傳遞。

示例

使用你選擇的任何編輯器建立以下Java程式。

GenericsTester.java

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
      Box<String> stringBox = new Box<String>();

      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));

      System.out.printf("Integer Value :%d\n", integerBox.get());
      System.out.printf("String Value :%s\n", stringBox.get());
   }
}

class Box<T> {
   private T t;

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

   public T get() {
      return t;
   }   
}

這將產生以下結果。

輸出

Integer Value :10
String Value :Hello World

型別引數命名約定

按照約定,型別引數名稱被命名為單個大寫字母,以便可以輕鬆地區分型別引數和普通的類或介面名稱。以下是常用型別引數名稱的列表:

  • **E** − 元素,主要用於Java集合框架。

  • **K** − 鍵,主要用於表示對映鍵的引數型別。

  • **V** − 值,主要用於表示對映值的引數型別。

  • **N** − 數字,主要用於表示數字。

  • **T** − 型別,主要用於表示第一個泛型型別引數。

  • **S** − 型別,主要用於表示第二個泛型型別引數。

  • **U** − 型別,主要用於表示第三個泛型型別引數。

  • **V** − 型別,主要用於表示第四個泛型型別引數。

下面的例子將展示上面提到的概念。

示例

使用你選擇的任何編輯器建立以下Java程式。

GenericsTester.java

package com.tutorialspoint;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer, String> box = new Box<Integer, String>();
      box.add(Integer.valueOf(10),"Hello World");
      System.out.printf("Integer Value :%d\n", box.getFirst());
      System.out.printf("String Value :%s\n", box.getSecond());

      Pair<String, Integer> pair = new Pair<String, Integer>(); 
      pair.addKeyValue("1", Integer.valueOf(10));
      System.out.printf("(Pair)Integer Value :%d\n", pair.getValue("1"));

      CustomList<Box> list = new CustomList<Box>();
      list.addItem(box);
      System.out.printf("(CustomList)Integer Value :%d\n", list.getItem(0).getFirst());
   }
}

class Box<T, S> {
   private T t;
   private S s;

   public void add(T t, S s) {
      this.t = t;
      this.s = s;
   }

   public T getFirst() {
      return t;
   } 

   public S getSecond() {
      return s;
   } 
}

class Pair<K,V>{
   private Map<K,V> map = new HashMap<K,V>();

   public void addKeyValue(K key, V value) {
      map.put(key, value);
   }

   public V getValue(K key) {
      return map.get(key);
   }
}

class CustomList<E>{
   private List<E> list = new ArrayList<E>();

   public void addItem(E value) {
      list.add(value);
   }

   public E getItem(int index) {
      return list.get(index);
   }
}

這將產生以下結果。

輸出

Integer Value :10
String Value :Hello World
(Pair)Integer Value :10
(CustomList)Integer Value :10

Java泛型 - 型別推斷

型別推斷表示Java編譯器能夠檢視方法呼叫及其對應的宣告來檢查並確定型別引數的能力。推斷演算法檢查引數的型別,如果可用,則返回分配的型別。推斷演算法試圖找到一個可以滿足所有型別引數的特定型別。

如果未使用型別推斷,編譯器會生成未經檢查的轉換警告。

語法

Box<Integer> integerBox = new Box<>();

其中

  • **Box** − Box是一個泛型類。

  • **<>** −菱形運算子表示型別推斷。

描述

使用菱形運算子,編譯器確定引數的型別。此運算子從Java SE 7版本開始可用。

示例

使用你選擇的任何編輯器建立以下Java程式。

GenericsTester.java

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      //type inference   
      Box<Integer> integerBox = new Box<>();
      //unchecked conversion warning
      Box<String> stringBox = new Box<String>();

      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));

      System.out.printf("Integer Value :%d\n", integerBox.get());
      System.out.printf("String Value :%s\n", stringBox.get());
   }
}

class Box<T> {
   private T t;

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

   public T get() {
      return t;
   }   
}

這將產生以下結果。

輸出

Integer Value :10
String Value :Hello World

Java泛型 - 方法

你可以編寫單個泛型方法宣告,該宣告可以用不同型別的引數呼叫。根據傳遞給泛型方法的引數型別,編譯器會適當地處理每個方法呼叫。以下是定義泛型方法的規則:

  • 所有泛型方法宣告都有一個由尖括號(< 和 >)分隔的型別引數部分,該部分位於方法的返回型別之前(在下一個示例中為< E >)。

  • 每個型別引數部分包含一個或多個用逗號分隔的型別引數。型別引數,也稱為型別變數,是一個識別符號,它指定一個泛型型別名稱。

  • 型別引數可以用來宣告返回型別,並充當傳遞給泛型方法的引數型別的佔位符,這些引數被稱為實際型別引數。

  • 泛型方法的主體宣告與任何其他方法的宣告相同。請注意,型別引數只能表示引用型別,不能表示基本型別(如int、double和char)。

示例

以下示例說明了如何使用單個泛型方法列印不同型別的陣列:

public class GenericMethodTest {
   // generic method printArray
   public static < E > void printArray( E[] inputArray ) {
      // Display array elements
      for(E element : inputArray) {
         System.out.printf("%s ", element);
      }
      System.out.println();
   }

   public static void main(String args[]) {
      // Create arrays of Integer, Double and Character
      Integer[] intArray = { 1, 2, 3, 4, 5 };
      Double[] doubleArray = { 1.1, 2.2, 3.3, 4.4 };
      Character[] charArray = { 'H', 'E', 'L', 'L', 'O' };

      System.out.println("Array integerArray contains:");
      printArray(intArray);   // pass an Integer array

      System.out.println("\nArray doubleArray contains:");
      printArray(doubleArray);   // pass a Double array

      System.out.println("\nArray characterArray contains:");
      printArray(charArray);   // pass a Character array
   }
}

這將產生以下結果:

輸出

Array integerArray contains:
1 2 3 4 5 

Array doubleArray contains:
1.1 2.2 3.3 4.4 

Array characterArray contains:
H E L L O

Java泛型 - 多個型別引數

泛型類可以具有多個型別引數。以下示例將展示上面提到的概念。

示例

使用你選擇的任何編輯器建立以下Java程式。

GenericsTester.java

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer, String> box = new Box<Integer, String>();
      box.add(Integer.valueOf(10),"Hello World");
      System.out.printf("Integer Value :%d\n", box.getFirst());
      System.out.printf("String Value :%s\n", box.getSecond());

      Box<String, String> box1 = new Box<String, String>();
      box1.add("Message","Hello World");
      System.out.printf("String Value :%s\n", box1.getFirst());
      System.out.printf("String Value :%s\n", box1.getSecond());
   }
}

class Box<T, S> {
   private T t;
   private S s;

   public void add(T t, S s) {
      this.t = t;
      this.s = s;
   }

   public T getFirst() {
      return t;
   } 

   public S getSecond() {
      return s;
   } 
}

這將產生以下結果。

輸出

Integer Value :10
String Value :Hello World
String Value :Message
String Value :Hello World

Java泛型 - 引數化型別

泛型類可以具有引數化型別,其中型別引數可以用引數化型別替換。以下示例將展示上面提到的概念。

示例

使用你選擇的任何編輯器建立以下Java程式。

GenericsTester.java

package com.tutorialspoint;

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


public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer, List<String>> box
         = new Box<Integer, List<String>>();
      
      List<String> messages = new ArrayList<String>();
      
      messages.add("Hi");
      messages.add("Hello");
      messages.add("Bye");      
      
      box.add(Integer.valueOf(10),messages);
      System.out.printf("Integer Value :%d\n", box.getFirst());
      System.out.printf("String Value :%s\n", box.getSecond());

      
   }
}

class Box<T, S> {
   private T t;
   private S s;

   public void add(T t, S s) {
      this.t = t;
      this.s = s;
   }

   public T getFirst() {
      return t;
   } 

   public S getSecond() {
      return s;
   } 
}

這將產生以下結果。

輸出

Integer Value :10
String Value :[Hi, Hello, Bye]

Java泛型 - 原始型別

如果在建立泛型類或介面的物件時未傳遞型別引數,則原始型別是泛型類或介面的物件。以下示例將展示上面提到的概念。

示例

使用你選擇的任何編輯器建立以下Java程式。

GenericsTester.java

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> box = new Box<Integer>();
      
      box.set(Integer.valueOf(10));
      System.out.printf("Integer Value :%d\n", box.getData());
      
      
      Box rawBox = new Box();
      
      //No warning
      rawBox = box;
      System.out.printf("Integer Value :%d\n", rawBox.getData());
      
      //Warning for unchecked invocation to set(T)
      rawBox.set(Integer.valueOf(10));
      System.out.printf("Integer Value :%d\n", rawBox.getData());
      
      //Warning for unchecked conversion
      box = rawBox;
      System.out.printf("Integer Value :%d\n", box.getData());
   }
}

class Box<T> {
   private T t; 

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

   public T getData() {
      return t;
   } 
}

這將產生以下結果。

輸出

Integer Value :10
Integer Value :10
Integer Value :10
Integer Value :10

Java泛型 - 有界型別引數

有時你可能希望限制允許傳遞給型別引數的型別的種類。例如,對數字進行操作的方法可能只想接受Number或其子類的例項。這就是有界型別引數的用途。

要宣告有界型別引數,請列出型別引數的名稱,後跟extends關鍵字,然後是其上界。

示例

以下示例說明了如何以一般意義上使用extends來表示“擴充套件”(如在類中)或“實現”(如在介面中)。此示例是一個泛型方法,用於返回三個Comparable物件的較大者:

public class MaximumTest {
   // determines the largest of three Comparable objects
   
   public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
      T max = x;   // assume x is initially the largest
      
      if(y.compareTo(max) > 0) {
         max = y;   // y is the largest so far
      }
      
      if(z.compareTo(max) > 0) {
         max = z;   // z is the largest now                 
      }
      return max;   // returns the largest object   
   }
   
   public static void main(String args[]) {
      System.out.printf("Max of %d, %d and %d is %d\n\n", 
         3, 4, 5, maximum( 3, 4, 5 ));

      System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
         6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));

      System.out.printf("Max of %s, %s and %s is %s\n","pear",
         "apple", "orange", maximum("pear", "apple", "orange"));
   }
}

這將產生以下結果:

輸出

Max of 3, 4 and 5 is 5

Max of 6.6,8.8 and 7.7 is 8.8

Max of pear, apple and orange is pear

Java泛型 - 多重界限

一個型別引數可以有多個界限。

語法

public static <T extends Number & Comparable<T>> T maximum(T x, T y, T z)

其中

  • **maximum** − maximum是一個泛型方法。

  • **T** − 傳遞給泛型方法的泛型型別引數。它可以接受任何物件。

描述

T是傳遞給泛型類Box的型別引數,並且應該是Number類的子型別,並且必須實現Comparable介面。如果傳遞類作為界限,則應該在介面之前傳遞,否則將發生編譯時錯誤。

示例

使用你選擇的任何編輯器建立以下Java程式。

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      System.out.printf("Max of %d, %d and %d is %d\n\n", 
         3, 4, 5, maximum( 3, 4, 5 ));

      System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
         6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
   }

   public static <T extends Number 
      & Comparable<T>> T maximum(T x, T y, T z) {
      T max = x;      
      if(y.compareTo(max) > 0) {
         max = y;   
      }

      if(z.compareTo(max) > 0) {
         max = z;                    
      }
      return max;      
   }

   // Compiler throws error in case of below declaration
   /* public static <T extends Comparable<T> 
      & Number> T maximum1(T x, T y, T z) {
      T max = x;      
      if(y.compareTo(max) > 0) {
         max = y;   
      }

      if(z.compareTo(max) > 0) {
         max = z;                    
      }
      return max;   
   }*/
}

這將產生以下結果:

輸出

Max of 3, 4 and 5 is 5

Max of 6.6,8.8 and 7.7 is 8.8

Java泛型 - 列表

Java在List介面中提供了泛型支援。

語法

List<T> list = new ArrayList<T>();

其中

  • **list** − List介面的物件。

  • **T** − 在列表宣告期間傳遞的泛型型別引數。

描述

T是傳遞給泛型介面List及其實現類ArrayList的型別引數。

示例

使用你選擇的任何編輯器建立以下Java程式。

package com.tutorialspoint;

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

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

      List<Integer> integerList = new ArrayList<Integer>();
  
      integerList.add(Integer.valueOf(10));
      integerList.add(Integer.valueOf(11));

      List<String> stringList = new ArrayList<String>();
  
      stringList.add("Hello World");
      stringList.add("Hi World");
 

      System.out.printf("Integer Value :%d\n", integerList.get(0));
      System.out.printf("String Value :%s\n", stringList.get(0));

      for(Integer data: integerList) {
         System.out.printf("Integer Value :%d\n", data);
      }

      Iterator<String> stringIterator = stringList.iterator();

      while(stringIterator.hasNext()) {
         System.out.printf("String Value :%s\n", stringIterator.next());
      }
   }  
}

這將產生以下結果:

輸出

Integer Value :10
String Value :Hello World
Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World

Java泛型 - 集合

Java在Set介面中提供了泛型支援。

語法

Set<T> set = new HashSet<T>();

其中

  • **set** − Set介面的物件。

  • **T** − 在集合宣告期間傳遞的泛型型別引數。

描述

T是傳遞給泛型介面Set及其實現類HashSet的型別引數。

示例

使用你選擇的任何編輯器建立以下Java程式。

package com.tutorialspoint;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

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

      Set<Integer> integerSet = new HashSet<Integer>();
  
      integerSet.add(Integer.valueOf(10));
      integerSet.add(Integer.valueOf(11));

      Set<String> stringSet = new HashSet<String>();
  
      stringSet.add("Hello World");
      stringSet.add("Hi World");
 

      for(Integer data: integerSet) {
         System.out.printf("Integer Value :%d\n", data);
      }

      Iterator<String> stringIterator = stringSet.iterator();

      while(stringIterator.hasNext()) {
         System.out.printf("String Value :%s\n", stringIterator.next());
      }
   }  
}

這將產生以下結果:

輸出

Integer Value :10
Integer Value :11
String Value :Hello World
String Value :Hi World

Java泛型 - 對映

Java在Map介面中提供了泛型支援。

語法

Set<T> set = new HashSet<T>();

其中

  • **set** − Set介面的物件。

  • **T** − 在集合宣告期間傳遞的泛型型別引數。

描述

T是傳遞給泛型介面Set及其實現類HashSet的型別引數。

示例

使用你選擇的任何編輯器建立以下Java程式。

package com.tutorialspoint;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

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

      Map<Integer,Integer> integerMap 
         = new HashMap<Integer,Integer>();
  
      integerMap.put(1, 10);
      integerMap.put(2, 11);

      Map<String,String> stringMap = new HashMap<String,String>();
    
      stringMap.put("1", "Hello World");
      stringMap.put("2","Hi World");
 

      System.out.printf("Integer Value :%d\n", integerMap.get(1));
      System.out.printf("String Value :%s\n", stringMap.get("1"));

      // iterate keys.
      Iterator<Integer> integerIterator   = integerMap.keySet().iterator();

      while(integerIterator.hasNext()) {
         System.out.printf("Integer Value :%d\n", integerIterator.next());
      }

      // iterate values.
      Iterator<String> stringIterator   = stringMap.values().iterator();

      while(stringIterator.hasNext()) {
         System.out.printf("String Value :%s\n", stringIterator.next());
      }
   }  
}

這將產生以下結果:

輸出

Integer Value :10
String Value :Hello World
Integer Value :1
Integer Value :2
String Value :Hello World
String Value :Hi World

Java泛型 - 上界萬用字元

問號(?),表示萬用字元,在泛型中代表未知型別。有時你可能希望限制允許傳遞給型別引數的型別的種類。例如,對數字進行操作的方法可能只想接受Number或其子類的例項。

要宣告上界萬用字元引數,請列出?,後跟extends關鍵字,然後是其上界。

示例

以下示例說明了如何使用extends指定上界萬用字元。

package com.tutorialspoint;

import java.util.Arrays;
import java.util.List;

public class GenericsTester {

   public static double sum(List<? extends Number> numberlist) {
      double sum = 0.0;
      for (Number n : numberlist) sum += n.doubleValue();
      return sum;
   }

   public static void main(String args[]) {
      List<Integer> integerList = Arrays.asList(1, 2, 3);
      System.out.println("sum = " + sum(integerList));

      List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
      System.out.println("sum = " + sum(doubleList));
   }
}

這將產生以下結果:

輸出

sum = 6.0
sum = 7.0

Java泛型 - 無界萬用字元

問號 (?) 代表萬用字元,在泛型中代表未知型別。當方法可以使用 `Object` 類提供的功能實現,或者程式碼獨立於型別引數時,可以使用任何物件。

要宣告一個無界萬用字元引數,只需列出 ?。

示例

下面的示例演示瞭如何使用 `extends` 指定無界萬用字元。

package com.tutorialspoint;

import java.util.Arrays;
import java.util.List;

public class GenericsTester {
   public static void printAll(List<?> list) {
      for (Object item : list)
         System.out.println(item + " ");
   }

   public static void main(String args[]) {
      List<Integer> integerList = Arrays.asList(1, 2, 3);
      printAll(integerList);
      List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
      printAll(doubleList);
   }
}

這將產生以下結果:

輸出

1 
2 
3 
1.2 
2.3 
3.5 

Java泛型 - 下界萬用字元

問號 (?) 代表萬用字元,在泛型中代表未知型別。有時您可能希望限制允許傳遞給型別引數的型別的種類。例如,對數字進行操作的方法可能只想接受 `Integer` 或其超類(如 `Number`)的例項。

要宣告一個下界萬用字元引數,請列出 ?,然後是 `super` 關鍵字,然後是其下界。

示例

下面的示例演示瞭如何使用 `super` 指定下界萬用字元。

package com.tutorialspoint;

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

public class GenericsTester {

   public static void addCat(List<? super Cat> catList) {
      catList.add(new RedCat());
      System.out.println("Cat Added");
   }

   public static void main(String[] args) {
      List<Animal> animalList= new ArrayList<Animal>();
      List<Cat> catList= new ArrayList<Cat>();
      List<RedCat> redCatList= new ArrayList<RedCat>();
      List<Dog> dogList= new ArrayList<Dog>();

      //add list of super class Animal of Cat class
      addCat(animalList);

      //add list of Cat class
      addCat(catList);

      //compile time error
      //can not add list of subclass RedCat of Cat class
      //addCat(redCatList);

      //compile time error
      //can not add list of subclass Dog of Superclass Animal of Cat class
      //addCat.addMethod(dogList); 
   }
}
class Animal {}

class Cat extends Animal {}

class RedCat extends Cat {}

class Dog extends Animal {}

這將產生以下結果:

Cat Added
Cat Added

Java泛型 - 萬用字元使用指南

萬用字元可以三種方式使用:

  • 上界萬用字元 − ? extends Type。

  • 下界萬用字元 − ? super Type。

  • 無界萬用字元 − ?

為了確定哪種型別的萬用字元最適合條件,讓我們首先將傳遞給方法的引數型別分類為輸入輸出引數。

  • 輸入變數 − 輸入變數向程式碼提供資料。例如,`copy(src, dest)`。這裡 `src` 充當輸入變數,是被複制的資料。

  • 輸出變數 − 輸出變數儲存程式碼更新的資料。例如,`copy(src, dest)`。這裡 `dest` 充當輸出變數,包含已複製的資料。

萬用字元指南。

  • 上界萬用字元 − 如果變數屬於輸入類別,則使用 `extends` 關鍵字與萬用字元一起使用。

  • 下界萬用字元 − 如果變數屬於輸出類別,則使用 `super` 關鍵字與萬用字元一起使用。

  • 無界萬用字元 − 如果可以使用 `Object` 類方法訪問變數,則使用無界萬用字元。

  • 無萬用字元 − 如果程式碼同時訪問輸入輸出類別的變數,則不要使用萬用字元。

示例

下面的示例演示了上述概念。

package com.tutorialspoint;

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

public class GenericsTester {

   //Upper bound wildcard
   //in category
   public static void deleteCat(List<? extends Cat> catList, Cat cat) {
      catList.remove(cat);
      System.out.println("Cat Removed");
   }

   //Lower bound wildcard
   //out category
   public static void addCat(List<? super RedCat> catList) {
      catList.add(new RedCat("Red Cat"));
      System.out.println("Cat Added");
   }

   //Unbounded wildcard
   //Using Object method toString()
   public static void printAll(List<?> list) {
      for (Object item : list)
         System.out.println(item + " ");
   }

   public static void main(String[] args) {

      List<Animal> animalList= new ArrayList<Animal>();
      List<RedCat> redCatList= new ArrayList<RedCat>();

      //add list of super class Animal of Cat class
      addCat(animalList);
      //add list of Cat class
      addCat(redCatList);  
      addCat(redCatList);  

      //print all animals
      printAll(animalList);
      printAll(redCatList);

      Cat cat = redCatList.get(0);
      //delete cat
      deleteCat(redCatList, cat);
      printAll(redCatList); 
   }
}

class Animal {
   String name;
   Animal(String name) { 
      this.name = name;
   }
   public String toString() { 
      return name;
   }
}

class Cat extends Animal { 
   Cat(String name) {
      super(name);
   }
}

class RedCat extends Cat {
   RedCat(String name) {
      super(name);
   }
}

class Dog extends Animal {
   Dog(String name) {
      super(name);
   }
}

這將產生以下結果:

Cat Added
Cat Added
Cat Added
Red Cat 
Red Cat 
Red Cat 
Cat Removed
Red Cat 

Java泛型 - 型別擦除

泛型用於在編譯時進行更嚴格的型別檢查,並提供泛型程式設計。為了實現泛型行為,Java編譯器應用型別擦除。型別擦除是一個過程,編譯器用實際類或橋接方法替換泛型引數。在型別擦除中,編譯器確保不會建立額外的類,並且沒有執行時開銷。

型別擦除規則

  • 如果使用了有界型別引數,則用其界替換泛型型別中的型別引數。

  • 如果使用了無界型別引數,則用 `Object` 替換泛型型別中的型別引數。

  • 插入型別轉換以保持型別安全。

  • 生成橋接方法以保持擴充套件泛型型別中的多型性。

Java泛型 - 有界型別擦除

如果使用了有界型別引數,Java編譯器將用其界替換泛型型別中的型別引數。

示例

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
      Box<Double> doubleBox = new Box<Double>();

      integerBox.add(new Integer(10));
      doubleBox.add(new Double(10.0));

      System.out.printf("Integer Value :%d\n", integerBox.get());
      System.out.printf("Double Value :%s\n", doubleBox.get());
   }
}

class Box<T extends Number> {
   private T t;

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

   public T get() {
      return t;
   }   
}

在這種情況下,Java編譯器將用 `Number` 類替換 `T`,並且在型別擦除後,編譯器將為以下程式碼生成位元組碼。

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box integerBox = new Box();
      Box doubleBox = new Box();

      integerBox.add(new Integer(10));
      doubleBox.add(new Double(10.0));

      System.out.printf("Integer Value :%d\n", integerBox.get());
      System.out.printf("Double Value :%s\n", doubleBox.get());
   }
}

class Box {
   private Number t;

   public void add(Number t) {
      this.t = t;
   }

   public Number get() {
      return t;
   }   
}

在這兩種情況下,結果都是相同的:

輸出

Integer Value :10
Double Value :10.0

Java泛型 - 無界型別擦除

如果使用了無界型別引數,Java編譯器將用 `Object` 替換泛型型別中的型別引數。

示例

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
      Box<String> stringBox = new Box<String>();

      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));

      System.out.printf("Integer Value :%d\n", integerBox.get());
      System.out.printf("String Value :%s\n", stringBox.get());
   }
}

class Box<T> {
   private T t;

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

   public T get() {
      return t;
   }   
}

在這種情況下,Java編譯器將用 `Object` 類替換 `T`,並且在型別擦除後,編譯器將為以下程式碼生成位元組碼。

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box integerBox = new Box();
      Box stringBox = new Box();

      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));

      System.out.printf("Integer Value :%d\n", integerBox.get());
      System.out.printf("String Value :%s\n", stringBox.get());
   }
}

class Box {
   private Object t;

   public void add(Object t) {
      this.t = t;
   }

   public Object get() {
      return t;
   }   
}

在這兩種情況下,結果都是相同的:

輸出

Integer Value :10
String Value :Hello World

Java泛型 - 泛型方法擦除

如果使用了無界型別引數,Java編譯器將用 `Object` 替換泛型型別中的型別引數;如果使用了有界引數作為方法引數,則用型別替換。

示例

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
      Box<String> stringBox = new Box<String>();

      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));
      
      printBox(integerBox);
      printBox1(stringBox);
   }
   
   private static <T extends Box> void printBox(T box) {
      System.out.println("Integer Value :" + box.get());
   }
   
   private static <T> void printBox1(T box) {
      System.out.println("String Value :" + ((Box)box).get());
   }
}

class Box<T> {
   private T t;

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

   public T get() {
      return t;
   }   
}

在這種情況下,Java編譯器將用 `Object` 類替換 `T`,並且在型別擦除後,編譯器將為以下程式碼生成位元組碼。

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box integerBox = new Box();
      Box stringBox = new Box();

      integerBox.add(new Integer(10));
      stringBox.add(new String("Hello World"));
      
      printBox(integerBox);
      printBox1(stringBox);
   }
	
   //Bounded Types Erasure
   private static void printBox(Box box) {
      System.out.println("Integer Value :" + box.get());
   }
	
   //Unbounded Types Erasure
   private static void printBox1(Object box) {
      System.out.println("String Value :" + ((Box)box).get());
   }
}

class Box {
   private Object t;

   public void add(Object t) {
      this.t = t;
   }

   public Object get() {
      return t;
   }   
}

在這兩種情況下,結果都是相同的:

輸出

Integer Value :10
String Value :Hello World

Java泛型 - 不支援基本型別

使用泛型,不能將基本型別作為型別引數傳遞。在下面的示例中,如果我們將 `int` 基本型別傳遞給 `box` 類,則編譯器將報錯。為了解決這個問題,我們需要傳遞 `Integer` 物件而不是 `int` 基本型別。

示例

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();

      //compiler errror
      //ReferenceType
      //- Syntax error, insert "Dimensions" to complete
      ReferenceType
      //Box<int> stringBox = new Box<int>();

      integerBox.add(new Integer(10));
      printBox(integerBox);
   }

   private static void printBox(Box box) {
      System.out.println("Value: " + box.get());
   }  
}

class Box<T> {
   private T t;

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

   public T get() {
      return t;
   }   
}

這將產生以下結果:

輸出

Value: 10

Java泛型 - 不支援例項化

不能使用型別引數在方法內部例項化其物件。

public static <T> void add(Box<T> box) {
   //compiler error
   //Cannot instantiate the type T
   //T item = new T();  
   //box.add(item);
}

要實現此功能,請使用反射。

public static <T> void add(Box<T> box, Class<T> clazz) 
   throws InstantiationException, IllegalAccessException{
   T item = clazz.newInstance();   // OK
   box.add(item);
   System.out.println("Item added.");
}

示例

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) 
      throws InstantiationException, IllegalAccessException {
      Box<String> stringBox = new Box<String>();
      add(stringBox, String.class);
   }  

   public static <T> void add(Box<T> box) {
      //compiler error
      //Cannot instantiate the type T
      //T item = new T();  
      //box.add(item);
   }

   public static <T> void add(Box<T> box, Class<T> clazz) 
      throws InstantiationException, IllegalAccessException{
      T item = clazz.newInstance();   // OK
      box.add(item);
      System.out.println("Item added.");
   }   
}

class Box<T> {
   private T t;

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

   public T get() {
      return t;
   }   
}

這將產生以下結果:

Item added.

Java泛型 - 不支援靜態欄位

使用泛型,型別引數不允許是靜態的。由於靜態變數在物件之間共享,因此編譯器無法確定使用哪種型別。如果允許靜態型別引數,請考慮以下示例:

示例

package com.tutorialspoint;

public class GenericsTester {
   public static void main(String[] args) {
      Box<Integer> integerBox = new Box<Integer>();
	  Box<String> stringBox = new Box<String>();
	  
      integerBox.add(new Integer(10));
      printBox(integerBox);
   }

   private static void printBox(Box box) {
      System.out.println("Value: " + box.get());
   }  
}

class Box<T> {
   //compiler error
   private static T t;

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

   public T get() {
      return t;
   }   
}

由於 `stringBox` 和 `integerBox` 都具有一個共享的靜態型別變數,因此無法確定其型別。因此,不允許使用靜態型別引數。

Java泛型 - 不支援強制型別轉換

不允許轉換為引數化型別,除非它由無界萬用字元引數化。

Box<Integer> integerBox = new Box<Integer>();
Box<Number> numberBox = new Box<Number>();
//Compiler Error: Cannot cast from Box<Number> to Box<Integer>
integerBox = (Box<Integer>)numberBox;

要實現相同的功能,可以使用無界萬用字元。

private static void add(Box<?> box) {
   Box<Integer> integerBox = (Box<Integer>)box;
}

Java泛型 - 不支援 instanceof

因為編譯器使用型別擦除,執行時不會跟蹤型別引數,所以執行時無法使用 `instanceOf` 運算子驗證 `Box` 和 `Box` 之間的區別。

Box<Integer> integerBox = new Box<Integer>();

//Compiler Error:
//Cannot perform instanceof check against 
//parameterized type Box<Integer>. 
//Use the form Box<?> instead since further 
//generic type information will be erased at runtime
if(integerBox instanceof Box<Integer>) { }

Java泛型 - 不支援陣列

不允許使用引數化型別的陣列。

//Cannot create a generic array of Box<Integer>
Box<Integer>[] arrayOfLists = new Box<Integer>[2]; 

因為編譯器使用型別擦除,型別引數被替換為 `Object`,使用者可以向陣列中新增任何型別的物件。在執行時,程式碼將無法丟擲 `ArrayStoreException`。

// compiler error, but if it is allowed
Object[] stringBoxes = new Box<String>[];
  
// OK
stringBoxes[0] = new Box<String>();  

// An ArrayStoreException should be thrown,
//but the runtime can't detect it.
stringBoxes[1] = new Box<Integer>();  

Java泛型 - 不支援異常

泛型類不允許直接或間接擴充套件 `Throwable` 類。

//The generic class Box<T> may not subclass java.lang.Throwable
class Box<T> extends Exception {}

//The generic class Box<T> may not subclass java.lang.Throwable
class Box1<T> extends Throwable {}

方法不允許捕獲型別引數的例項。

public static <T extends Exception, J> 
   void execute(List<J> jobs) {
      try {
         for (J job : jobs) {}
  
         // compile-time error
         //Cannot use the type parameter T in a catch block
      } catch (T e) { 
         // ...
   }
} 

型別引數允許在 `throws` 子句中使用。

class Box<T extends Exception>  {
   private int t;

   public void add(int t) throws T {
      this.t = t;
   }

   public int get() {
      return t;
   }   
}

Java泛型 - 不支援過載

類不允許有兩個過載方法,這些方法在型別擦除後可以具有相同的簽名。

class Box  {
   //Compiler error
   //Erasure of method print(List<String>) 
   //is the same as another method in type Box
   public void print(List<String> stringList) { }
   public void print(List<Integer> integerList) { }
}
廣告