泛型-泛型的类型

泛型类型是通过类型参数化的泛型类或接口。下面的 Box 类将被修改以演示该概念。

一个简单的 Box 类

首先检查一个对任何类型的对象进行操作的非泛型 Box 类。它只需要提供两个方法:set,它添加一个对象到盒子,get,它检索它:

public class Box {
    private Object object;

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

因为它的方法接受或返回一个对象,所以你可以随意传入任何你想要的东西,只要它不是原始类型之一。在编译时无法验证类的使用方式。一部分代码可能会在框中放置一个 Integer 并期望从中取出 Integer,而另一部分代码可能会错误地传入一个 String,从而导致运行时错误。

Box 类的通用版本

泛型类使用以下格式定义:

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

由尖括号 (<>) 分隔的类型参数部分跟在类名之后。它指定类型参数(也称为类型变量)T1、T2、...和Tn。

要更新 Box 类以使用泛型,您可以通过将代码“public class Box”更改为“public class Box”来创建泛型类型声明。这引入了类型变量 T,它可以在类中的任何地方使用。

通过此更改,Box 类变为:

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

如您所见,所有出现的 Object 都被 T 替换。类型变量可以是您指定的任何非原始类型:任何类类型、任何接口类型、任何数组类型,甚至是另一个类型变量。

可以应用相同的技术来创建通用接口。

类型参数命名约定

按照惯例,类型参数名称是单个大写字母。这与您已经知道的变量命名约定形成鲜明对比,并且有充分的理由:如果没有这种约定,就很难区分类型变量和普通类或接口名称之间的区别。

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

  • E - Element (used extensively by the Java Collections Framework)
  • K - Key
  • N - Number
  • T - Type
  • V - Value
  • S,U,V etc. - 2nd, 3rd, 4th types

您将在整个 Java SE API 和本课的其余部分中看到这些名称。

调用和实例化泛型类型

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

Box<Integer> integerBox;

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

Type Parameter and Type Argument Terminology:许多开发人员互换使用术语“type parameter”和“type argument”,但这些术语并不相同。编码时,提供类型参数以创建参数化类型。因此,Foo 中的 T 是 type parameter ,而 Foo f 中的 String 是 type argument。本课在使用这些术语时遵守此定义。

与任何其他变量声明一样,此代码实际上并不创建新的 Box 对象。它只是声明 integerBox 将持有对“整数框”的引用,这就是 Box 的读取方式。

泛型类型的调用通常称为 parameterized type

要实例化这个类,像往常一样使用 new 关键字,但将 放在类名和括号之间:

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

菱形符号<>

在 Java SE 7 及更高版本中,您可以将调用泛型类的构造函数所需的类型参数替换为一组空的类型参数 (<>),只要编译器可以从上下文中确定或推断类型参数.这对尖括号 <> 被非正式地称为菱形。例如,您可以使用以下语句创建 Box 的实例:

有关菱形符号和类型推断的更多信息,请参阅 Type Inference.

多个类型参数

如前所述,一个泛型类可以有多个类型参数。例如,实现通用 Pair 接口的通用 OrderedPair 类:

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 类的两个实例:

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

代码 new OrderedPair<String, Integer> 将 K 实例化为字符串,将 V 实例化为整数。因此,OrderedPair 的构造函数的参数类型分别为 String 和 Integer。由于自动装箱,将 String 和 int 传递给类是有效的。

正如 The Diamond 中所提到的,因为 Java 编译器可以从声明 OrderedPair<String, Integer> 中推断出 K 和 V 类型,所以可以使用菱形表示法来缩短这些语句:

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

要创建泛型接口,请遵循与创建泛型类相同的约定。

Parameterized Types

您还可以将类型参数(即 K 或 V)替换为参数化类型(即 List)。例如,使用 OrderedPair<K, V> 示例:

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));
posted @ 2022-04-09 13:51  碧水蓝天白云  阅读(111)  评论(0)    收藏  举报