Java Generics 简明教程

Java Generics - Quick Guide

Java Generics - Overview

如果能够编写一个单一的 sort 方法对 Integer 数组、String 数组或支持排序的任何类型的数组中的元素进行排序,那就太好了。

Java 泛型方法和泛型类使程序员能够使用单个方法声明指定一组相关方法,或者使用单个类声明指定一组相关类型。

泛型还提供编译时类型安全性,使程序员能够在编译时捕捉无效的类型。

使用 Java 泛型概念,我们可以为对象数组编写一个泛型方法,然后使用 Integer 数组、Double 数组、String 数组等调用泛型方法,对数组元素进行排序。

Java Generics - Environment Setup

Local Environment Setup

JUnit 是 Java 框架,因此最先决条件是在您的计算机中安装 JDK。

System Requirement

JDK

1.5 or above.

Memory

No minimum requirement.

Disk Space

No minimum requirement.

Operating System

No minimum requirement.

Step 1: Verify Java Installation in Your Machine

首先,打开控制台并根据您正在使用的操作系统执行 java 命令。

OS

Task

Command

Windows

Open Command Console

c:> java -version

Linux

Open Command Terminal

$ java -version

Mac

Open Terminal

machine:< joseph$ java -version

让我们验证所有操作系统的输出 −

OS

Output

Windows

java version "1.6.0_21"Java™ SE Runtime Environment (build 1.6.0_21-b07)Java HotSpot™ Client VM (build 17.0-b17, mixed mode, sharing)

Linux

java version "1.6.0_21"Java™ SE Runtime Environment (build 1.6.0_21-b07)Java HotSpot™ Client VM (build 17.0-b17, mixed mode, sharing)

Mac

java version "1.6.0_21"Java™ SE Runtime Environment (build 1.6.0_21-b07)Java HotSpot™64-Bit Server VM (build 17.0-b17, mixed mode, sharing)

如果您的系统上没有安装 Java,请从以下链接下载 Java 软件开发工具包 (SDK): https://www.oracle.com 。本教程假设已安装 Java 1.6.0_21 版本。

Step 2: Set JAVA Environment

JAVA_HOME 环境变量设置为您计算机中安装 Java 的基本目录位置。例如。

OS

Output

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 编译器位置。

OS

Output

Windows

在系统变量 Path 末尾追加字符串 C:\Program Files\Java\jdk1.6.0_21\bin

Linux

export PATH = $PATH:$JAVA_HOME/bin/

Mac

not required

如上所述,使用命令 java -version 验证 Java 安装。

Java Generics - Classes

泛型类声明看起来像非泛型类声明,除了类名后跟类型参数部分。

泛型类的类型参数部分可以包含一个或多个用逗号分隔的类型参数。这些类被称为参数化类或参数化类型,因为它们接受一个或多个参数。

Syntax

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

其中

  1. Box − 框是泛型类。

  2. T − 传递给泛型类的泛型类型参数。它可以获取任何对象。

  3. t − 泛型类型 T 的实例。

Description

T 是传递给泛型类框的类型参数,并且应该在创建框对象时传递。

Example

使用任意你选择的编辑器创建以下 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;
   }
}

这将产生以下结果。

Output

Integer Value :10
String Value :Hello World

Type Parameter Naming Conventions

根据惯例,类型参数名称会被命名为单个大写字母,这样可以很容易地将类型参数与普通类或接口名称区分开来。以下是常用的类型参数名称列表:

  1. E - Element,主要由 Java 集合框架使用。

  2. K - Key,主要用于表示地图键的参数类型。

  3. V - Value,主要用于表示地图值的参数类型。

  4. N - Number,主要用于表示数字。

  5. T - Type,主要用于表示第一个泛型类型参数。

  6. S - Type,主要用于表示第二个泛型类型参数。

  7. U - Type,主要用于表示第三个泛型类型参数。

  8. V - Type,主要用于表示第四个泛型类型参数。

以下示例将展示上述概念。

Example

使用任意你选择的编辑器创建以下 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);
   }
}

这将产生以下结果。

Output

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

Java Generics - Type Inference

类型推断表示 Java 编译器查看方法调用及其相应声明的能力,以检查和确定类型参数。推断算法检查参数的类型,如果可用,则返回已分配的类型。推断算法尝试找到一个特定类型,该类型可以满足所有类型参数。

如果未使用类型推断,编译器会生成未检查转换警告。

Syntax

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

其中

  1. Box − 框是泛型类。

  2. &lt;&gt; − 菱形运算符表示类型推断。

Description

使用菱形运算符,编译器确定参数的类型。此运算符在 Java SE 7 版本中可用。

Example

使用任意你选择的编辑器创建以下 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;
   }
}

这将产生以下结果。

Output

Integer Value :10
String Value :Hello World

Java Generics - Methods

你可以编写一个可以针对不同类型参数调用的单个泛型方法声明。根据传递给泛型方法的参数类型,编译器会分别处理每个方法调用。以下是定义泛型方法的规则:

  1. 所有泛型方法声明都具有一个类型参数部分,该部分用尖括号(< 和 >)分隔,位于方法的返回类型之前(在下一个示例中为 < E >)。

  2. 每个类型参数部分包含一个或多个以逗号分隔的类型参数。类型参数(也称为类型变量)是一个指定泛型类型名称的标识符。

  3. 类型参数可用于声明返回类型并充当传递给泛型方法的参数类型的占位符,这些参数被称为实际类型参数。

  4. 泛型方法的 body 的声明方式与其他方法相同。请注意,类型参数只能表示引用类型,不能表示基本类型(如 int、double 和 char)。

Example

以下示例说明了如何使用单个泛型方法打印不同类型的数组 -

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

这会产生以下结果 −

Output

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 Generics - Multiple Type Parameters

泛型类可以有多个类型参数。以下示例將展示上述概念。

Example

使用任意你选择的编辑器创建以下 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;
   }
}

这将产生以下结果。

Output

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

Java Generics - Parameterized Types

泛型类可以有参数化类型,其中类型参数可以用参数化类型替换。以下示例将展示上述概念。

Example

使用任意你选择的编辑器创建以下 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;
   }
}

这将产生以下结果。

Output

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

Java Generics - Raw Types

如果在创建过程中没有传递类型参数,则原始类型是泛型类或接口的对象。以下示例將展示上述概念。

Example

使用任意你选择的编辑器创建以下 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;
   }
}

这将产生以下结果。

Output

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

Java Generics - Bounded Type Parameters

有时您可能希望限制允许传递给类型参数的类型种类。例如,对数字进行操作的方法可能只希望接受 Number 或其子类的实例。这就是有界类型参数的作用。

要声明有界类型参数,请列出类型参数的名称,后跟 extends 关键字,后跟其上限。

Example

以下示例说明了如何以常规意义使用 extends 表示“extends”(如在类中)或“implements”(如在接口中)。此示例是用于返回三个 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"));
   }
}

这会产生以下结果 −

Output

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 Generics - Multiple Bounds

类型参数可以有多个界限。

Syntax

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

其中

  1. maximum - maximum 是一个泛型方法。

  2. T - 传递给泛型方法的泛型类型参数。它能接受任何对象。

Description

传递给泛型类 Box 的 T 是一个类型参数,并且应当是 Number 类的子类型,还必须实现 Comparable 接口。如果类作为界限传递,则应当在接口之前传递,否则会发生编译时错误。

Example

使用任意你选择的编辑器创建以下 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;
   }*/
}

这会产生以下结果 −

Output

Max of 3, 4 and 5 is 5

Max of 6.6,8.8 and 7.7 is 8.8

Java Generics - List

List 接口中,Java 提供了泛型支持。

Syntax

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

其中

  1. ` list ` − List 接口的对象。

  2. ` T ` − 列表声明期间传递的泛型类型参数。

Description

T 是传递给泛型接口 List 及其实现类 ArrayList 的类型参数。

Example

使用任意你选择的编辑器创建以下 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());
      }
   }
}

这会产生以下结果 −

Output

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

Java Generics - Set

Java 已在 Set 接口中提供了泛型支持。

Syntax

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

其中

  1. set − 设置接口对象。

  2. T − 在设置声明期间传递的泛型类型参数。

Description

T 是传递给 Set 泛型接口及其实现类 HashSet 的类型参数。

Example

使用任意你选择的编辑器创建以下 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());
      }
   }
}

这会产生以下结果 −

Output

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

Java Generics - Map

Java 在 Map 接口中提供了泛型支持。

Syntax

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

其中

  1. set − 设置接口对象。

  2. T − 在设置声明期间传递的泛型类型参数。

Description

T 是传递给 Set 泛型接口及其实现类 HashSet 的类型参数。

Example

使用任意你选择的编辑器创建以下 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());
      }
   }
}

这会产生以下结果 −

Output

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

Java Generics - Upper Bounded Wildcards

问号 (?) 代表通配符,在泛型中表示未知类型。有时,您可能希望限制可传递到类型参数的类型种类。例如,运算数字的方法可能只希望接受 Integer 实例或其超类,如 Number。

要声明上限通配符参数,请按顺序列出 ? 及 extends 关键字,后跟其上限。

Example

以下示例说明了如何使用 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));
   }
}

这会产生以下结果 −

Output

sum = 6.0
sum = 7.0

Java Generics - Unbounded Wildcards

问号 (?) 表示通配符,代表泛型中的未知类型。当可以通过使用对象类中提供的功能实现方法或当代码独立于类型参数时,可能会使用任何对象。

如需声明无界通配符参数,只列出 ?

Example

以下示例展示了如何使用 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);
   }
}

这会产生以下结果 −

Output

1
2
3
1.2
2.3
3.5

Java Generics - Lower Bounded Wildcards

问号 (?) 代表通配符,在泛型中表示未知类型。有时,您可能希望限制可传递到类型参数的类型种类。例如,运算数字的方法可能只希望接受 Integer 实例或其超类,如 Number。

要声明下限通配符参数,请按顺序列出 ? 及 super 关键字,后跟其下限。

Example

以下示例说明如何使用超类型来指定下边界通配符。

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 Generics - Guidelines for Wildcard Use

通配符可以用三种方式使用 −

  1. Upper bound Wildcard − ? extends Type。

  2. Lower bound Wildcard − ? super Type。

  3. Unbounded Wildcard − ?

为了确定哪种类型的通配符最适合该条件,首先让我们将传递给方法的类型参数分类为 inout 参数。

  1. in variable − 一个 in 变量为代码提供数据。例如,copy(src, dest)。此处,src 充当 in 变量,是要复制的数据。

  2. out variable − 一个 out 变量保存由代码更新的数据。例如,copy(src, dest)。此处,dest 充当 in 变量,已复制数据。

Guidelines for Wildcards.

  1. Upper bound wildcard − 如果一个变量属于 in 范畴,请在通配符中使用 extends 关键词。

  2. Lower bound wildcard − 如果一个变量属于 out 范畴,请在通配符中使用 super 关键词。

  3. Unbounded wildcard − 如果可以使用 Object 类方法访问一个变量,请使用一个非限定通配符。

  4. No wildcard − 如果代码在 inout 范畴中均访问变量,请不要使用通配符。

Example

以下示例阐明了上述概念。

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 Generics - Type Erasure

泛型用于在编译时进行更严格的类型检查并提供泛型编程。为了实现泛型行为,Java 编译器应用类型擦除。类型擦除是一个编译器将泛型参数替换为实际类或桥接方法的过程。在类型擦除中,编译器确保不创建额外的类且没有运行时开销。

Type Erasure rules

  1. 如果使用了有界类型参数,则用其界限替换泛型类型中的类型参数。

  2. 如果使用了无界类型参数,则用 Object 替换泛型类型中的类型参数。

  3. 插入类型转换以保持类型安全性。

  4. 生成桥接方法以在扩展泛型类型中保持多态性。

Java Generics - Bound Types Erasure

如果使用有界类型参数,Java 编译器将用其界限替换泛型类型中的类型参数。

Example

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 编译器将 T 替换为 Number 类,在类型擦除之后,编译器将为以下代码生成字节码。

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

在两种情况下,结果都是相同的 −

Output

Integer Value :10
Double Value :10.0

Java Generics - Unbounded Types Erasure

Java 编译器在使用非限定类型参数时,将泛型类型中的类型参数替换为 Object。

Example

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

在两种情况下,结果都是相同的 −

Output

Integer Value :10
String Value :Hello World

Java Generics - Generic Methods Erasure

如果使用了无界类型参数,Java 编译器将用对象替换泛型类型中的类型参数,如果使用了有界参数,则用类型替换方法参数。

Example

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

在两种情况下,结果都是相同的 −

Output

Integer Value :10
String Value :Hello World

Java Generics - No Primitive Types

使用泛型,不能将基本类型作为类型参数传递。在下面给出的示例中,如果我们将 int 基本类型传递给框类,则编译器会抱怨。为了缓解这种情况,我们需要传递 Integer 对象,而不是 int 基本类型。

Example

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

这会产生以下结果 −

Output

Value: 10

Java Generics - No Instance

不能在方法内部使用类型参数实例化其对象。

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.");
}

Example

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 Generics - No Static field

使用泛型时,不允许类型参数为静态类型。由于静态变量在对象之间共享,所以编译器无法确定要使用哪种类型。假设允许静态类型参数,请考虑以下示例。

Example

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

由于 stringBoxintegerBox 都具有共享的静态类型变量,因此无法确定其类型。因此,不允许使用静态类型参数。

Java Generics - No Cast

不允许强制转换为参数化类型,除非该类型由无界通配符参数化。

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 Generics - No instanceOf

由于编译器使用类型擦除,因此运行时不会跟踪类型参数,因此在运行时,使用 instanceOf 运算符不能验证 Box<Integer> 和 Box<String> 之间的差异。

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 Generics - No Array

不允许有参数化类型的数组。

//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 Generics - No Exception

不允许泛型类直接或间接扩展 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 Generics - No Overload

不允许类在类型擦除后具有两个具有相同签名的重载方法。

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) { }
}