Java Generics - 快速指南
Java Generics - Overview
如果我们可以编写一个单独的排序方法,可以对Integer数组,String数组或任何支持排序的类型的数组进行排序,那将是很好的。
Java通用方法和泛型类使程序员能够使用单个方法声明,一组相关方法或单个类声明来指定一组相关类型。
泛型还提供编译时类型安全性,允许程序员在编译时捕获无效类型。
使用Java Generic概念,我们可能会编写一个用于对对象数组进行排序的泛型方法,然后使用Integer数组,Double数组,String数组等调用泛型方法来对数组元素进行排序。
Java Generics - Environment Setup
本地环境设置 (Local Environment Setup)
JUnit是Java的框架,因此第一个要求是在您的机器中安装JDK。
系统要求
JDK | 1.5或以上。 |
---|---|
记忆 | 没有最低要求。 |
磁盘空间 | 没有最低要求。 |
操作系统 | 没有最低要求。 |
步骤1:验证计算机中的Java安装
首先,打开控制台并根据您正在使用的操作系统执行java命令。
OS | 任务 | 命令 |
---|---|---|
Windows | 打开命令控制台 | c:\> java -version |
Linux | 打开命令终端 | $ java -version |
Mac | 开放式终端 | machine: |
让我们验证所有操作系统的输出 -
OS | output |
---|---|
Windows | java版“1.6.0_21” Java(TM)SE运行时环境(版本1.6.0_21-b07) Java HotSpot(TM)客户端VM(版本17.0-b17,混合模式,共享) |
Linux | java版“1.6.0_21” Java(TM)SE运行时环境(版本1.6.0_21-b07) Java HotSpot(TM)客户端VM(版本17.0-b17,混合模式,共享) |
Mac | java版“1.6.0_21” Java(TM)SE运行时环境(版本1.6.0_21-b07) Java HotSpot(TM)64位服务器VM(内置17.0-b17,混合模式,共享) |
如果您的系统上没有安装Java,请从以下链接https://www.oracle.com下载Java软件开发工具包(SDK)。 我们假设Java 1.6.0_21是本教程的已安装版本。
第2步:设置JAVA环境
将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 | 不需要 |
如上所述,使用命令java -version验证Java安装。
Java Generics - Classes
泛型类声明看起来像非泛型类声明,除了类名后跟一个类型参数部分。
泛型类的类型参数部分可以有一个或多个用逗号分隔的类型参数。 这些类称为参数化类或参数化类型,因为它们接受一个或多个参数。
语法 (Syntax)
public class Box<T> {
private T t;
}
哪里
Box - Box是一个通用类。
T - 传递给泛型类的泛型类型参数。 它可以采取任何对象。
t - 泛型类型T的实例
描述 (Description)
T是传递给泛型类Box的类型参数,应在创建Box对象时传递。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
GenericsTester.java
package com.iowiki;
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
按照惯例,类型参数名称被命名为单个大写字母,因此可以使用普通的类或接口名称轻松区分类型参数。 以下是常用类型参数名称列表 -
E - Element,主要由Java Collections框架使用。
K - Key,主要用于表示地图键的参数类型。
V - Value,主要用于表示地图值的参数类型。
N - 数字,主要用于表示数字。
T - Type,主要用于表示第一个泛型类型参数。
S - Type,主要用于表示第二个泛型类型参数。
U - Type,主要用于表示第三个泛型类型参数。
V - Type,主要用于表示第四个泛型类型参数。
以下示例将展示上述概念。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
GenericsTester.java
package com.iowiki;
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<>();
哪里
Box - Box是一个通用类。
《》 - 菱形算子表示类型推断。
描述 (Description)
使用菱形运算符,编译器确定参数的类型。 从Java SE 7版开始,此运算符可以使用。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
GenericsTester.java
package com.iowiki;
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
您可以编写一个可以使用不同类型的参数调用的通用方法声明。 根据传递给泛型方法的参数类型,编译器会适当地处理每个方法调用。 以下是定义通用方法的规则 -
所有泛型方法声明都有一个由尖括号()分隔的类型参数部分,它位于方法的返回类型之前(下一个示例中为
)。 每个类型参数部分包含一个或多个以逗号分隔的类型参数。 类型参数(也称为类型变量)是指定泛型类型名称的标识符。
类型参数可用于声明返回类型,并充当传递给泛型方法的参数类型的占位符,这些参数称为实际类型参数。
泛型方法的主体声明为任何其他方法的主体。 请注意,类型参数只能表示引用类型,而不能表示基本类型(如int,double和char)。
例子 (Example)
以下示例说明了如何使用单个Generic方法打印不同类型的数组 -
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.iowiki;
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
Generic类可以具有参数化类型,其中类型参数可以用参数化类型替换。 以下示例将展示上述概念。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
GenericsTester.java
package com.iowiki;
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
如果类型参数在创建期间传递了npt,则原始类型是泛型类或接口的对象。 以下示例将展示上述概念。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
GenericsTester.java
package com.iowiki;
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)
下面的示例说明了扩展在一般意义上如何用于表示“扩展”(如在类中)或“实现”(如在接口中)。 此示例是返回三个Comparable对象中最大的对象的Generic方法 -
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)
哪里
maximum - maximum是一种通用方法。
T - 传递给泛型方法的泛型类型参数。 它可以采取任何对象。
描述 (Description)
T是传递给泛型类Box的类型参数,应该是Number类的子类型,并且必须包含Comparable接口。 如果一个类以绑定方式传递,则应在接口之前首先传递它,否则将发生编译时错误。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
package com.iowiki;
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
Java在List接口中提供了通用支持。
语法 (Syntax)
List<T> list = new ArrayList<T>();
哪里
list - List接口的对象。
T - 列表声明期间传递的泛型类型参数。
描述 (Description)
T是传递给通用接口List及其实现类ArrayList的类型参数。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
package com.iowiki;
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>();
哪里
set - Set Interface的对象。
T - 在set声明期间传递的泛型类型参数。
描述 (Description)
T是传递给通用接口Set及其实现类HashSet的类型参数。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
package com.iowiki;
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>();
哪里
set - Set Interface的对象。
T - 在set声明期间传递的泛型类型参数。
描述 (Description)
T是传递给通用接口Set及其实现类HashSet的类型参数。
例子 (Example)
使用您选择的任何编辑器创建以下Java程序。
package com.iowiki;
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
问号(?)代表通配符,代表泛型中的未知类型。 有时您可能希望限制允许传递给类型参数的类型。 例如,对数字进行操作的方法可能只想接受Number或其子类的实例。
要声明一个上限的Wildcard参数,请列出?,后跟extends关键字,后跟其上限。
例子 (Example)
以下示例说明了如何使用extends来指定上限通配符。
package com.iowiki;
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
问号(?)代表通配符,代表泛型中的未知类型。 有时可以使用Object类中提供的功能实现方法时,或者当代码独立于类型参数时,可以使用任何对象。
要声明Unbounded Wildcard参数,请列出? 只要。
例子 (Example)
以下示例说明了如何使用extends来指定无界通配符。
package com.iowiki;
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)的实例。
要声明一个较低的有界Wildcard参数,请列出?,然后是super关键字,后跟其下限。
例子 (Example)
以下示例说明了如何使用super来指定下限通配符。
package com.iowiki;
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
通配符可以三种方式使用 -
Upper bound Wildcard - ? 扩展类型。
Lower bound Wildcard - ? 超级型。
Unbounded Wildcard - ?
为了确定哪种类型的通配符最适合该条件,让我们首先将传递给方法的参数类型分类为in和out参数。
in variable - in in变量为代码提供数据。 例如,copy(src,dest)。 这里src充当变量是要复制的数据。
out variable - out变量保存由代码更新的数据。 例如,copy(src,dest)。 这里dest作为具有复制数据的变量。
通配符指南。
Upper bound wildcard - 如果变量属于类别,请使用带有通配符的extends关键字。
Lower bound wildcard - 如果变量属于out category,则使用带有通配符的super关键字。
Unbounded wildcard - 如果可以使用Object类方法访问变量,则使用未绑定的通配符。
No wildcard - 如果代码在进入和out类别中访问变量,则不要使用通配符。
例子 (Example)
以下示例说明了上述概念。
package com.iowiki;
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编译器应用类型擦除。 类型擦除是编译器用实际的类或桥接方法替换泛型参数的过程。 在类型擦除中,编译器确保不会创建额外的类,也不会有运行时开销。
键入Erasure规则
如果使用有界类型参数,则使用其绑定替换泛型类型中的类型参数。
如果使用无界类型参数,则使用Object替换泛型类型参数。
插入类型转换以保持类型安全。
生成桥接方法以保持扩展泛型类型中的多态性。
Java Generics - Bound Types Erasure
如果使用有界类型参数,Java Compiler将使用其绑定替换泛型类型中的类型参数。
例子 (Example)
package com.iowiki;
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.iowiki;
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 Compiler将使用Object替换泛型类型中的类型参数。
例子 (Example)
package com.iowiki;
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.iowiki;
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 Compiler将使用Object替换泛型类型参数,如果绑定参数用作方法参数,则使用类型替换。
例子 (Example)
package com.iowiki;
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.iowiki;
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基本类型传递给box类,那么编译器会抱怨。 为了缓解这种情况,我们需要传递Integer对象而不是int基本类型。
例子 (Example)
package com.iowiki;
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.iowiki;
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.iowiki;
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 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
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];
由于编译器使用类型擦除,因此type参数将替换为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) { }
}