java泛型系列 | 二、泛型类型

泛型类型是通过类型参数化的泛型类或接口。

下面是个简单的例子,对任何类型的对象进行操作的非泛型 Box 类。 它只需要提供两个方法:set,它将一个Object添加到中 Box ,以及 get方法。

public class Box {
    private Object object;
​
    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

 

方法中接收和返回都是Object类型,你可以随意传入任何你想要的东西,只要它不是原始类型之一。在编译时无法验证类的使用方式。 一部分代码可能会在框中放置一个 Integer 并期望从中取出 Integers,

而另一部分代码可能会错误地传入一个 String,从而导致运行时错误。下面是一个带泛型的例子,通用类使用以下格式定义:

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

 

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

/**
 * 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 替换。类型变量可以是您指定的任何非原始类型:任何类类型、任何接口类型、任何数组类型,甚至另一个类型变量。可以应用相同的技术来创建通用接口。

1.、类型参数命名约定

按照惯例,类型参数名称是单个大写字母。如果没有这个约定,就很难区分类型变量和普通类或接口名称之间的区别。最常用的类型参数名称是:

  • E - Element (一般用在集合中)

  • K - Key

  • N - Number

  • T - Type

  • V - Value

  • S,U,V 等

2、调用和实例化泛型类型

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

Box<Integer> integerBox;

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

参数化类型(An invocation of a generic type is generally known as a parameterized type.)要实例化这个类,像往常一样使用 new 关键字,但将 <Integer> 放在类名和括号之间:

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

在 Java SE 7 及更高版本中,只要编译器可以从上下文中确定或推断类型参数,就可以将调用泛型类的构造函数所需的类型参数替换为空类型参数集 (<>) . 这对尖括号 <> 非正式地称为菱形。

例如,您可以使用以下语句创建 Box<Integer> 的实例:

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

3 、多类型参数

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

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 传递给类是有效的。

Java 编译器可以从 OrderedPair<String, Integer> 声明中推断出 K 和 V 类型,所以可以使用菱形表示法缩短这些语句

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

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

参数化类型,还可以用参数化类型(即 List<String>)替换 类型参数(即 K 或 V),也就是可以类型化参数嵌套。 例如,使用 OrderedPair<K, V> 示例:

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

4、原始类型

原始类型是没有任何类型参数的泛型类或接口的名称。 例如,给定通用 Box 类:

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

要创建 Box<T> 的参数化类型,请为形式类型参数 T 提供实际类型参数:

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

如果省略实际类型参数,则创建 Box<T> 的原始类型:

Box rawBox = new Box();

因此,Box 是泛型类型 Box<T> 的原始类型。 但是,非泛型类或接口类型不是原始类型。

原始类型出现在遗留代码中是因为许多 API 类(例如 Collections 类)在 JDK 5.0 之前不是通用的。 使用原始类型时,您基本上会获得预泛型行为——一个 Box 为您提供对象。

为了向后兼容,允许将参数化类型分配给它的原始类型:

Box<String> stringBox = new Box<>();
Box rawBox = stringBox;               // OK

但是,如果将原始类型分配给参数化类型,则会收到警告:

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

如果使用原始类型调用在相应泛型类型中定义的泛型方法,也会收到警告:

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

警告显示原始类型绕过泛型类型检查,将不安全代码的捕获推迟到运行时。 因此,您应该避免使用原始类型。

关于 Java 编译器如何使用原始类型, 类型擦除 部分有更多的信息。

未经检查的错误消息,如前所述,将遗留代码与通用代码混合时,您可能会遇到类似于以下内容的警告消息:

Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

使用对原始类型进行操作的旧 API 时可能会发生这种情况,如下例所示:

public class WarningDemo {
    public static void main(String[] args){
        Box<Integer> bi;
        bi = createBox();
    }
​
    static Box createBox(){
        return new Box();
    }
}

术语“未检查”意味着编译器没有足够的类型信息来执行确保类型安全所需的所有类型检查。 默认情况下,“unchecked”警告是禁用的,尽管编译器会给出提示。 要查看所有“未检查”警告,请使用 -Xlint: unchecked 重新编译。

使用 -Xlint: unchecked 重新编译前面的示例会显示以下附加信息:

WarningDemo.java:4: warning: [unchecked] unchecked conversion
found   : Box
required: Box<java.lang.Integer>
        bi = createBox();
                      ^
1 warning

 

要完全禁用未经检查的警告,请使用 -Xlint:-unchecked 标志。 @SuppressWarnings("unchecked") 注释抑制未经检查的警告。 如果您不熟悉 @SuppressWarnings 语法,请参阅注解。

 

posted @ 2021-09-16 15:53  meow_world  阅读(104)  评论(0)    收藏  举报