Java Generics 简明教程

Java Generics - Quick Guide

Java Generics - Overview

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

It would be nice if we could write a single sort method that could sort the elements in an Integer array, a String array, or an array of any type that supports ordering.

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

Java Generic methods and generic classes enable programmers to specify, with a single method declaration, a set of related methods, or with a single class declaration, a set of related types, respectively.

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

Generics also provide compile-time type safety that allows programmers to catch invalid types at compile time.

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

Using Java Generic concept, we might write a generic method for sorting an array of objects, then invoke the generic method with Integer arrays, Double arrays, String arrays and so on, to sort the array elements.

Java Generics - Environment Setup

Local Environment Setup

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

JUnit is a framework for Java, so the very first requirement is to have JDK installed in your machine.

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 命令。

First of all, open the console and execute a java command based on the operating system you are working on.

OS

Task

Command

Windows

Open Command Console

c:> java -version

Linux

Open Command Terminal

$ java -version

Mac

Open Terminal

machine:< joseph$ java -version

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

Let’s verify the output for all the operating systems −

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 版本。

If you do not have Java installed on your system, then download the Java Software Development Kit (SDK) from the following link https://www.oracle.com. We are assuming Java 1.6.0_21 as the installed version for this tutorial.

Step 2: Set JAVA Environment

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

Set the JAVA_HOME environment variable to point to the base directory location where Java is installed on your machine. For example.

OS

Output

Windows

Set the environment variable JAVA_HOME to C:\Program Files\Java\jdk1.6.0_21

Linux

export JAVA_HOME = /usr/local/java-current

Mac

export JAVA_HOME = /Library/Java/Home

在系统路径中追加 Java 编译器位置。

Append Java compiler location to the System Path.

OS

Output

Windows

Append the string C:\Program Files\Java\jdk1.6.0_21\bin at the end of the system variable, Path.

Linux

export PATH = $PATH:$JAVA_HOME/bin/

Mac

not required

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

Verify Java installation using the command java -version as explained above.

Java Generics - Classes

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

A generic class declaration looks like a non-generic class declaration, except that the class name is followed by a type parameter section.

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

The type parameter section of a generic class can have one or more type parameters separated by commas. These classes are known as parameterized classes or parameterized types because they accept one or more parameters.

Syntax

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

其中

Where

  1. Box − Box is a generic class.

  2. T − The generic type parameter passed to generic class. It can take any Object.

  3. t − Instance of generic type T.

Description

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

The T is a type parameter passed to the generic class Box and should be passed when a Box object is created.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这将产生以下结果。

This will produce the following result.

Output

Integer Value :10
String Value :Hello World

Type Parameter Naming Conventions

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

By convention, type parameter names are named as single, uppercase letters so that a type parameter can be distinguished easily with an ordinary class or interface name. Following is the list of commonly used type parameter names −

  1. E − Element, and is mainly used by Java Collections framework.

  2. K − Key, and is mainly used to represent parameter type of key of a map.

  3. V − Value, and is mainly used to represent parameter type of value of a map.

  4. N − Number, and is mainly used to represent numbers.

  5. T − Type, and is mainly used to represent first generic type parameter.

  6. S − Type, and is mainly used to represent second generic type parameter.

  7. U − Type, and is mainly used to represent third generic type parameter.

  8. V − Type, and is mainly used to represent fourth generic type parameter.

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

Following example will showcase above mentioned concept.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这将产生以下结果。

This will produce the following result.

Output

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

Java Generics - Type Inference

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

Type inference represents the Java compiler’s ability to look at a method invocation and its corresponding declaration to check and determine the type argument(s). The inference algorithm checks the types of the arguments and, if available, assigned type is returned. Inference algorithms tries to find a specific type which can fullfill all type parameters.

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

Compiler generates unchecked conversion warning in-case type inference is not used.

Syntax

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

其中

Where

  1. Box − Box is a generic class.

  2. <> − The diamond operator denotes type inference.

Description

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

Using diamond operator, compiler determines the type of the parameter. This operator is avalilable from Java SE 7 version onwards.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这将产生以下结果。

This will produce the following result.

Output

Integer Value :10
String Value :Hello World

Java Generics - Methods

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

You can write a single generic method declaration that can be called with arguments of different types. Based on the types of the arguments passed to the generic method, the compiler handles each method call appropriately. Following are the rules to define Generic Methods −

  1. All generic method declarations have a type parameter section delimited by angle brackets (< and >) that precedes the method’s return type ( < E > in the next example).

  2. Each type parameter section contains one or more type parameters separated by commas. A type parameter, also known as a type variable, is an identifier that specifies a generic type name.

  3. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.

  4. A generic method’s body is declared like that of any other method. Note that type parameters can represent only reference types, not primitive types (like int, double and char).

Example

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

Following example illustrates how we can print an array of different type using a single Generic method −

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

这会产生以下结果 −

This will produce the following result −

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

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

A Generic class can have muliple type parameters. Following example will showcase above mentioned concept.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这将产生以下结果。

This will produce the following result.

Output

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

Java Generics - Parameterized Types

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

A Generic class can have parameterized types where a type parameter can be substituted with a parameterized type. Following example will showcase above mentioned concept.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这将产生以下结果。

This will produce the following result.

Output

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

Java Generics - Raw Types

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

A raw type is an object of a generic class or interface if its type arguments are not passed during its creation. Following example will showcase above mentioned concept.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这将产生以下结果。

This will produce the following result.

Output

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

Java Generics - Bounded Type Parameters

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

There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses. This is what bounded type parameters are for.

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

To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound.

Example

以下示例说明了如何以常规意义使用 extends 表示“extends”(如在类中)或“implements”(如在接口中)。此示例是用于返回三个 Comparable 对象中最大的对象的泛型方法 -

Following example illustrates how extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces). This example is Generic method to return the largest of three Comparable objects −

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

这会产生以下结果 −

This will produce the following result −

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

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

A type parameter can have multiple bounds.

Syntax

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

其中

Where

  1. maximum − maximum is a generic method.

  2. T − The generic type parameter passed to generic method. It can take any Object.

Description

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

The T is a type parameter passed to the generic class Box and should be subtype of Number class and must implments Comparable interface. In case a class is passed as bound, it should be passed first before interface otherwise compile time error will occur.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这会产生以下结果 −

This will produce the following result −

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 提供了泛型支持。

Java has provided generic support in List interface.

Syntax

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

其中

Where

  1. list − object of List interface.

  2. T − The generic type parameter passed during list declaration.

Description

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

The T is a type parameter passed to the generic interface List and its implemenation class ArrayList.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这会产生以下结果 −

This will produce the following result −

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 接口中提供了泛型支持。

Java has provided generic support in Set interface.

Syntax

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

其中

Where

  1. set − object of Set Interface.

  2. T − The generic type parameter passed during set declaration.

Description

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

The T is a type parameter passed to the generic interface Set and its implemenation class HashSet.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这会产生以下结果 −

This will produce the following result −

Output

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

Java Generics - Map

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

Java has provided generic support in Map interface.

Syntax

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

其中

Where

  1. set − object of Set Interface.

  2. T − The generic type parameter passed during set declaration.

Description

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

The T is a type parameter passed to the generic interface Set and its implemenation class HashSet.

Example

使用任意你选择的编辑器创建以下 Java 程序。

Create the following java program using any editor of your choice.

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

这会产生以下结果 −

This will produce the following result −

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。

The question mark (?), represents the wildcard, stands for unknown type in generics. There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Number or its subclasses.

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

To declare a upper bounded Wildcard parameter, list the ?, followed by the extends keyword, followed by its upper bound.

Example

以下示例说明了如何使用 extends 指定上限通配符。

Following example illustrates how extends is used to specify an upper bound wildcard.

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

这会产生以下结果 −

This will produce the following result −

Output

sum = 6.0
sum = 7.0

Java Generics - Unbounded Wildcards

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

The question mark (?), represents the wildcard, stands for unknown type in generics. There may be times when any object can be used when a method can be implemented using functionality provided in the Object class or When the code is independent of the type parameter.

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

To declare a Unbounded Wildcard parameter, list the ? only.

Example

以下示例展示了如何使用 extends 指定无界通配符。

Following example illustrates how extends is used to specify an unbounded wildcard.

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

这会产生以下结果 −

This will produce the following result −

Output

1
2
3
1.2
2.3
3.5

Java Generics - Lower Bounded Wildcards

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

The question mark (?), represents the wildcard, stands for unknown type in generics. There may be times when you’ll want to restrict the kinds of types that are allowed to be passed to a type parameter. For example, a method that operates on numbers might only want to accept instances of Integer or its superclasses like Number.

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

To declare a lower bounded Wildcard parameter, list the ?, followed by the super keyword, followed by its lower bound.

Example

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

Following example illustrates how super is used to specify an lower bound wildcard.

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

这会产生以下结果 −

This will produce the following result −

Cat Added
Cat Added

Java Generics - Guidelines for Wildcard Use

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

Wildcards can be used in three ways −

  1. Upper bound Wildcard − ? extends Type.

  2. Lower bound Wildcard − ? super Type.

  3. Unbounded Wildcard − ?

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

In order to decide which type of wildcard best suits the condition, let’s first classify the type of parameters passed to a method as in and out parameter.

  1. in variable − An in variable provides data to the code. For example, copy(src, dest). Here src acts as in variable being data to be copied.

  2. out variable − An out variable holds data updated by the code. For example, copy(src, dest). Here dest acts as in variable having copied data.

Guidelines for Wildcards.

  1. Upper bound wildcard − If a variable is of in category, use extends keyword with wildcard.

  2. Lower bound wildcard − If a variable is of out category, use super keyword with wildcard.

  3. Unbounded wildcard − If a variable can be accessed using Object class method then use an unbound wildcard.

  4. No wildcard − If code is accessing variable in both in and out category then do not use wildcards.

Example

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

Following example illustrates the above mentioned concepts.

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

这会产生以下结果 −

This will produce the following result −

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

Java Generics - Type Erasure

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

Generics are used for tighter type checks at compile time and to provide a generic programming. To implement generic behaviour, java compiler apply type erasure. Type erasure is a process in which compiler replaces a generic parameter with actual class or bridge method. In type erasure, compiler ensures that no extra classes are created and there is no runtime overhead.

Type Erasure rules

  1. Replace type parameters in generic type with their bound if bounded type parameters are used.

  2. Replace type parameters in generic type with Object if unbounded type parameters are used.

  3. Insert type casts to preserve type safety.

  4. Generate bridge methods to keep polymorphism in extended generic types.

Java Generics - Bound Types Erasure

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

Java Compiler replaces type parameters in generic type with their bound if bounded type parameters are used.

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

In this case, java compiler will replace T with Number class and after type erasure,compiler will generate bytecode for the following code.

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

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

In both case, result is same −

Output

Integer Value :10
Double Value :10.0

Java Generics - Unbounded Types Erasure

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

Java Compiler replaces type parameters in generic type with Object if unbounded type parameters are used.

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

In this case, java compiler will replace T with Object class and after type erasure,compiler will generate bytecode for the following code.

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

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

In both case, result is same −

Output

Integer Value :10
String Value :Hello World

Java Generics - Generic Methods Erasure

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

Java Compiler replaces type parameters in generic type with Object if unbounded type parameters are used, and with type if bound parameters are used as method parameters.

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

In this case, java compiler will replace T with Object class and after type erasure,compiler will generate bytecode for the following code.

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

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

In both case, result is same −

Output

Integer Value :10
String Value :Hello World

Java Generics - No Primitive Types

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

Using generics, primitive types can not be passed as type parameters. In the example given below, if we pass int primitive type to box class, then compiler will complain. To mitigate the same, we need to pass the Integer object instead of int primitive type.

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

这会产生以下结果 −

This will produce the following result −

Output

Value: 10

Java Generics - No Instance

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

A type parameter cannot be used to instantiate its object inside a method.

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

如需实现这种功能,请使用反射。

To achieve such functionality, use reflection.

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

这会产生以下结果 −

This will produce the following result −

Item added.

Java Generics - No Static field

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

Using generics, type parameters are not allowed to be static. As static variable is shared among object so compiler can not determine which type to used. Consider the following example if static type parameters were allowed.

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 都具有共享的静态类型变量,因此无法确定其类型。因此,不允许使用静态类型参数。

As stringBox and integerBox both have a stared static type variable, its type can not be determined. Hence static type parameters are not allowed.

Java Generics - No Cast

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

Casting to a parameterized type is not allowed unless it is parameterized by unbounded wildcards.

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;

要达到同样的效果,可以使用无界通配符。

To achive the same, unbounded wildcards can be used.

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

Java Generics - No instanceOf

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

Because compiler uses type erasure, the runtime does not keep track of type parameters, so at runtime difference between Box<Integer> and Box<String> cannot be verified using instanceOf operator.

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

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

Arrays of parameterized types are not allowed.

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

由于编译器使用类型擦除,因此类型参数将替换为 Object,并且用户可以向数组中添加任何类型的对象。并且在运行时,代码将不能抛出 ArrayStoreException。

Because compiler uses type erasure, the type parameter is replaced with Object and user can add any type of object to the array. And at runtime, code will not able to throw 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 类。

A generic class is not allowed to extend the Throwable class directly or indirectly.

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

不允许方法捕获类型参数的实例。

A method is not allowed to catch an instance of a type parameter.

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 子句中。

Type parameters are allowed in a throws clause.

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

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

A class is not allowed to have two overloaded methods that can have the same signature after type erasure.

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