泛型类声明:在普通类声明后跟一个类型参数(type parameter)。
泛型类的类型参数部分可以有一个或多个用逗号分隔的类型参数。 这些类称为参数化类(parameterized classes)或参数化类型(parameterized types),因为它们接受一个或多个参数。
public class Box<T> {
private T t;
}
其中:
T是传递给泛型类Box的类型参数,应在创建Box对象时传递。
com.theten52.tutarial.generics.a0.GenericsTester
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
Box<String> stringBox = new Box<String>();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Integer Value :10
String Value :Hello World
按照惯例,类型参数名称被命名为单个大写字母,因此可以轻松区分一个名称是类型参数还是普通的类或接口。 以下是常用类型参数名称列表:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, String> box = new Box<Integer, String>();
box.add(Integer.valueOf(10),"Hello World");
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
Pair<String, Integer> pair = new Pair<String, Integer>();
pair.addKeyValue("1", Integer.valueOf(10));
System.out.printf("(Pair)Integer Value :%d\n", pair.getValue("1"));
CustomList<Box> list = new CustomList<Box>();
list.addItem(box);
System.out.printf("(CustomList)Integer Value :%d\n", list.getItem(0).getFirst());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
class Pair<K,V>{
private Map<K,V> map = new HashMap<K,V>();
public void addKeyValue(K key, V value) {
map.put(key, value);
}
public V getValue(K key) {
return map.get(key);
}
}
class CustomList<E>{
private List<E> list = new ArrayList<E>();
public void addItem(E value) {
list.add(value);
}
public E getItem(int index) {
return list.get(index);
}
}
Integer Value :10
String Value :Hello World
(Pair)Integer Value :10
(CustomList)Integer Value :10
类型推断表示Java编译器有能力去查看方法调用及其相应的声明,以检查和确定类型参数。 类型推断算法检查参数的类型,如果可用,则返回分配的类型。 类型推断算法尝试查找可以填充所有类型参数的特定类型。
如果不使用类型推断,则编译器会生成未经检查的转换警告。
Box<Integer> integerBox = new Box<>();
其中:
使用菱形运算符,编译器确定参数的类型。 从Java SE 7版开始,此运算符可以使用。
public class GenericsTester {
public static void main(String[] args) {
//类型推断
Box<Integer> integerBox = new Box<>();
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;
}
}
您可以编写一个可以使用不同类型的参数调用的泛型方法声明。 根据传递给泛型方法的参数类型,编译器会适当地处理每个方法调用。 以下是定义泛型方法的规则:
public class GenericMethodTest {
// 泛型方法 printArray
public static <E> void printArray( E[] inputArray ) {
// 打印数组元素
for(E element : inputArray) {
System.out.printf("%s ", element);
}
System.out.println();
}
public static void main(String args[]) {
// 创建 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); // 传入一个 Integer 数组
System.out.println("\nArray doubleArray contains:");
printArray(doubleArray); // 传入一个 Double 数组
System.out.println("\nArray characterArray contains:");
printArray(charArray); // 传入一个 Character 数组
}
}
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
泛型类可以具有多个类型参数。以下示例将展示上述概念。
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, String> box = new Box<Integer, String>();
box.add(Integer.valueOf(10),"Hello World");
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
Box<String, String> box1 = new Box<String, String>();
box1.add("Message","Hello World");
System.out.printf("String Value :%s\n", box1.getFirst());
System.out.printf("String Value :%s\n", box1.getSecond());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
Integer Value :10
String Value :Hello World
String Value :Message
String Value :Hello World
泛型类可以具有参数化类型,其中类型参数可以替换为参数化类型。以下示例将展示上述概念。
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
public static void main(String[] args) {
Box<Integer, List<String>> box = new Box<Integer, List<String>>();
List<String> messages = new ArrayList<String>();
messages.add("Hi");
messages.add("Hello");
messages.add("Bye");
box.add(Integer.valueOf(10),messages);
System.out.printf("Integer Value :%d\n", box.getFirst());
System.out.printf("String Value :%s\n", box.getSecond());
}
}
class Box<T, S> {
private T t;
private S s;
public void add(T t, S s) {
this.t = t;
this.s = s;
}
public T getFirst() {
return t;
}
public S getSecond() {
return s;
}
}
Integer Value :10
String Value :[Hi, Hello, Bye]
原始类型:一个泛型类虽然定义了类型参数,但是使用时并未给其赋值。此时我们称此类型成为原始类型。
如果原始类型的类型参数在创建过程中未传递,则原始类型是通用类或接口的对象。以下示例将展示上述概念。
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();
//不会有编译警告
rawBox = box;
System.out.printf("Integer Value :%d\n", rawBox.getData());
//set(T) 方法会有一个 Unchecked call 的 Warning
rawBox.set(Integer.valueOf(10));
System.out.printf("Integer Value :%d\n", rawBox.getData());
//此行代码会有一个 Unchecked assignment 的 Warning
box = rawBox;
System.out.printf("Integer Value :%d\n", box.getData());
}
}
class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T getData() {
return t;
}
}
Integer Value :10
Integer Value :10
Integer Value :10
Integer Value :10
有时您可能想限制允许传递给类型参数的类型的种类。例如,对Number进行操作的方法可能只希望接受Number或其子类的实例。这就是有界类型参数的用途。
要声明一个有界的类型参数,请列出类型参数的名称,然后列出extends关键字,然后列出其上限。
public class MaximumTest {
// 确定三个可比较对象中的最大对象
public static <T extends Comparable<T>> T maximum(T x, T y, T z) {
T max = x; // assume x is initially the largest
if(y.compareTo(max) > 0) {
max = y; // y is the largest so far
}
if(z.compareTo(max) > 0) {
max = z; // z is the largest now
}
return max; // returns the largest object
}
public static void main(String args[]) {
System.out.printf("Max of %d, %d and %d is %d\n\n",
3, 4, 5, maximum( 3, 4, 5 ));
System.out.printf("Max of %.1f,%.1f and %.1f is %.1f\n\n",
6.6, 8.8, 7.7, maximum( 6.6, 8.8, 7.7 ));
System.out.printf("Max of %s, %s and %s is %s\n","pear",
"apple", "orange", maximum("pear", "apple", "orange"));
}
}
Max of 3, 4 and 5 is 5
Max of 6.6,8.8 and 7.7 is 8.8
Max of pear, apple and orange is pear
类型参数可以具有多个边界。
public static <T extends Number & Comparable<T>> T maximum(T x, T y, T z)
其中
T是传递给泛型类Box的类型参数,并且应该是Number类的子类型,并且必须包含Comparable接口。如果将一个类作为有界传递,则应在接口之前首先传递它,否则会发生编译时错误(先声明类,再在&后声明接口)。
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;
}
// 一下声明会导致编译错误
/* public static <T extends Comparable<T> & Number> T maximum1(T x, T y, T z) {
T max = x;
if(y.compareTo(max) > 0) {
max = y;
}
if(z.compareTo(max) > 0) {
max = z;
}
return max;
}*/
}
输出
Max of 3, 4 and 5 is 5
Max of 6.6,8.8 and 7.7 is 8.8
List<T> list = new ArrayList<T>();
Set<T> set = new HashSet<T>();
Set<T> set = new HashSet<T>();
问号(?)代表通配符,代表泛型中的未知类型。有时您可能想限制允许传递给类型参数的类型的种类。例如,对Number进行操作的方法可能只希望接受Number或其子类的实例。
要声明一个上界通配符参数,先声明“?”,其次是extends关键字,最后是其上限。
import java.util.Arrays;
import java.util.List;
public class GenericsTester {
public static double sum(List<? extends Number> numberlist) {
double sum = 0.0;
for (Number n : numberlist) sum += n.doubleValue();
return sum;
}
public static void main(String args[]) {
List<Integer> integerList = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sum(integerList));
List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sum(doubleList));
}
}
sum = 6.0
sum = 7.0
问号(?)代表通配符,代表泛型中的未知类型。有时可以使用Object类中提供的功能来实现方法,或者代码独立于类型参数时,可以使用任何对象。
public class GenericsTester {
public static void printAll(List<?> list) {
for (Object item : list)
System.out.println(item + " ");
}
public static void main(String args[]) {
List<Integer> integerList = Arrays.asList(1, 2, 3);
printAll(integerList);
List<Double> doubleList = Arrays.asList(1.2, 2.3, 3.5);
printAll(doubleList);
}
}
1
2
3
1.2
2.3
3.5
问号(?)代表通配符,代表泛型中的未知类型。有时您可能想限制允许传递给类型参数的类型的种类。例如,对数字进行操作的方法可能只希望接受Integer或其超类(例如Number)的实例。
要声明下界通配符参数,先声明?,后跟super关键字,然后是其下界。
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);
//编译错误,不能添加Cat的子类RedCat到list中
//addCat(redCatList);
//编译错误,不能添加Animal的子类Dog到Cat类的list中
//addCat.addMethod(dogList);
}
}
class Animal {}
class Cat extends Animal {}
class RedCat extends Cat {}
class Dog extends Animal {}
Cat Added
Cat Added
通配符有三种方式可以使用:
为了确定最适合某些条件下的通配符类型,我们首先将传递给方法的参数类型分类为in变量和out变量。
import java.util.ArrayList;
import java.util.List;
public class GenericsTester {
//in变量:上界通配符
public static void deleteCat(List<? extends Cat> catList, Cat cat) {
catList.remove(cat);
System.out.println("Cat Removed");
}
//out变量:下界通配符
public static void addCat(List<? super RedCat> catList) {
catList.add(new RedCat("Red Cat"));
System.out.println("Cat Added");
}
//无界通配符
//使用 Object 的 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编译器应用了类型擦除。类型擦除是编译器用**实际的类(actual class )或桥接方法(bridge method)**替换泛型参数的过程。在类型擦除中,编译器确保不创建额外的类,并且没有运行时开销。
如果使用有界的类型参数,Java编译器会用其界限类替换泛型类的类型参数。
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类,并且在类型擦除之后,编译器将为以下代码生成字节码。
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box doubleBox = new Box();
integerBox.add(new Integer(10));
doubleBox.add(new Double(10.0));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("Double Value :%s\n", doubleBox.get());
}
}
class Box {
private Number t;
public void add(Number t) {
this.t = t;
}
public Number get() {
return t;
}
}
在两种情况下,结果都是相同的。
Integer Value :10
Double Value :10.0
如果使用无界的类型参数,则Java编译器会将泛型类的类型参数替换为Object。
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,并且在类型擦除之后,编译器将为以下代码生成字节码。
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box stringBox = new Box();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
System.out.printf("Integer Value :%d\n", integerBox.get());
System.out.printf("String Value :%s\n", stringBox.get());
}
}
class Box {
private Object t;
public void add(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
在两种情况下,结果都是相同的。
Integer Value :10
String Value :Hello World
如果使用无界的类型参数,则Java编译器将泛型类的类型参数替换为对象,如果使用有界的类型参数用作方法参数,则Java编译器将使用其界限类替换类型参数。
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;
}
}
类型擦除之后,编译器将为以下代码生成字节码。
public class GenericsTester {
public static void main(String[] args) {
Box integerBox = new Box();
Box stringBox = new Box();
integerBox.add(new Integer(10));
stringBox.add(new String("Hello World"));
printBox(integerBox);
printBox1(stringBox);
}
//Bounded Types Erasure
private static void printBox(Box box) {
System.out.println("Integer Value :" + box.get());
}
//Unbounded Types Erasure
private static void printBox1(Object box) {
System.out.println("String Value :" + ((Box)box).get());
}
}
class Box {
private Object t;
public void add(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
在两种情况下,结果都是相同的。
Integer Value :10
String Value :Hello World
使用泛型,基本类型不能作为类型参数传递。在下面给出的示例中,如果将int基本类型传递给box类,则编译器将生成一个警告。为了避免这种情况,我们需要传递Integer对象而不是int基本类型。
public class GenericsTester {
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
//编译错误
//ReferenceType
//- Syntax error, insert "Dimensions" to complete
//ReferenceType
//Box<int> stringBox = new Box<int>();
integerBox.add(new Integer(10));
printBox(integerBox);
}
private static void printBox(Box box) {
System.out.println("Value: " + box.get());
}
}
class Box<T> {
private T t;
public void add(T t) {
this.t = t;
}
public T get() {
return t;
}
}
Value: 10
类型参数不能用于实例化方法内部的对象。
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.");
}
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.
使用泛型时,类型参数不允许为静态。 由于静态变量在对象之间共享,因此编译器无法确定要使用哪种类型。 如果允许使用静态类型参数,请考虑以下示例。
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都有一个带有标记的静态类型变量,因此无法确定其类型。 因此,不允许使用静态类型参数。
除非强制使用无界通配符对其进行参数化,否则不允许强制转换为参数化类型。
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;
}
由于编译器使用类型擦除,因此运行时不跟踪类型参数,因此在运行时,无法使用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>) { }
不允许使用参数化类型的数组。
//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>();
//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) {
// ...
}
}
class Box<T extends Exception> {
private int t;
public void add(int t) throws T {
this.t = t;
}
public int get() {
return t;
}
}
一个类不允许具有两个在类型擦除后可以具有相同签名的重载方法。
class Box {
//Compiler error
//两个print方法在方法擦除后具有同一个方法签名
public void print(List<String> stringList) { }
public void print(List<Integer> integerList) { }
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。