Generic Types

本文最后更新于 2024年3月19日 上午

A generic type is a generic class or interface that is parameterized over types. The following Box class will be modified to demonstrate the concept.

A Simple Box Class

Begin by examining a non-generic Box class that operates on objects of any type. It needs only to provide two methods: set, which adds an object to the box, and get, which retrieves it:

1
2
3
4
5
6
public class Box {
private Object object;

public void set(Object object) { this.object = object; }
public Object get() { return object; }
}

Since its methods accept or return an Object, you are free to pass in whatever you want, provided that it is not one of the primitive types. There is no way to verify, at compile time, how the class is used. One part of the code may place an Integer in the box and expect to get Integers out of it, while another part of the code may mistakenly pass in a String, resulting in a runtime error.

A Generic Version of the Box Class

泛型类按以下格式定义:

class name<T1, T2, ..., Tn> { /* ... */ }

The type parameter section, delimited by angle brackets (<>), follows the class name. It specifies the type parameters (also called type variables) T1, T2, …, and Tn.

将泛型参数应用于 Box 类:

1
2
3
4
5
6
7
8
9
10
11
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;

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

As you can see, all occurrences of Object are replaced by T. A type variable can be any non-primitive type you specify: any class type, any interface type, any array type, or even another type variable.

同样的技术可用于创建通用接口。

Type Parameter Naming Conventions

按照约定,类型参数名称是单个大写字母。

最常用的类型参数名称是:

  • E - Element (used extensively by the Java CollectionsFramework)
  • E - Element
  • K - Key
  • N - Number
  • T - Type
  • V - Value

Invoking and Instantiating a Generic Type

要从代码中引用泛型 Box 类,必须执行泛型类型调用,它将 T 替换为一些具体值,例如 Integer

Box<Integer> integerBox;

可以将泛型类型调用视为与普通方法调用类似,但不是将参数传递给方法,而是将类型参数(在本例中为 Integer )传递给 Box 类本身。

Type Parameter and Type Argument Terminology: Many developers use the terms “type parameter” and “type argument” interchangeably, but these terms are not the same. When coding, one provides type arguments in order to create a parameterized type. Therefore, the T in Foo<T> is a type parameter and the String in Foo<String> f is a type argument.

Like any other variable declaration, this code does not actually create a new Box object. It simply declares that integerBox will hold a reference to a “Box of Integer”, which is how Box<Integer> is read.

An invocation of a generic type is generally known as a parameterized type.

To instantiate this class, use the new keyword, as usual, but place <Integer> between the class name and the parenthesis:

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

The Diamond

In Java SE 7 and later, you can replace the type arguments required to invoke the constructor of a generic class with an empty set of type arguments (<>) as long as the compiler can determine, or infer, the type arguments from the context. This pair of angle brackets, <>, is informally called the diamond. For example, you can create an instance of Box<Integer> with the following statement:

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

Multiple Type Parameters

如前所述,泛型类可以有多个类型参数(type parameters)。例如,通用 OrderedPair 类,它实现通用 Pair 接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public interface Pair<K, V> {
public K getKey();
public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

private K key;
private V value;

public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}

public K getKey() { return key; }
public V getValue() { return value; }
}

以下语句创建 OrderedPair 类的两个实例:

1
2
Pair<String, Integer> p1 = new OrderedPair<>("Even", 8);
Pair<String, String> p2 = new OrderedPair<>("hello", "world");

代码 new OrderedPair<String, Integer>K 实例化为 String ,将 V 实例化为 Integer 。因此,OrderedPair 构造函数的参数类型分别为 StringInteger

要创建通用接口,遵循与创建通用类相同的约定。

Parameterized Types

You can also substitute a type parameter (that is, K or V) with a parameterized type (that is, List<String>). For example, using the OrderedPair<K, V> example:

1
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

Raw Types

A raw type is the name of a generic class or interface without any type arguments. For example, given the generic Box class:

1
2
3
4
public class Box<T> {
public void set(T t) { /* ... */ }
// ...
}

To create a parameterized type of Box<T>, you supply an actual type argument for the formal type parameter T:

1
Box<Integer> intBox = new Box<>();

If the actual type argument is omitted, you create a raw type of Box<T>:

1
Box rawBox = new Box();

Therefore, Box is the raw type of the generic type Box<T>. However, a non-generic class or interface type is not a raw type.

Raw types show up in legacy code because lots of API classes (such as the Collections classes) were not generic prior to JDK 5.0. When using raw types, you essentially get pre-generics behavior — a Box gives you Objects. For backward compatibility, assigning a parameterized type to its raw type is allowed:

1
2
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;

But if you assign a raw type to a parameterized type, you get a warning:

1
2
Box rawBox = new Box();           // rawBox is a raw type of Box<T>
Box<Integer> intBox = rawBox; // warning: unchecked conversion

You also get a warning if you use a raw type to invoke generic methods defined in the corresponding generic type:

1
2
3
Box<String> stringBox = new Box<>();
Box rawBox = stringBox;
rawBox.set(8); // warning: unchecked invocation to set(T)

The warning shows that raw types bypass generic type checks, deferring the catch of unsafe code to runtime. Therefore, you should avoid using raw types.


Generic Types
https://stein283036.github.io/2024/03/18/Generic-Types/
作者
倪京龙
发布于
2024年3月18日
更新于
2024年3月19日
许可协议