Java Generics

417 阅读6分钟

读书笔记,from:docs.oracle.com/javase/tuto…

泛型(Generics)在J2SE 5.0引入,也叫泛化类型,就是把类型参数化的意思。

为什么要把类型参数化,它有什么作用?

  1. 增加编译期类型检查,减少运行时错误;
  2. 提升代码可读性,消除显式casts;
  3. 实现泛型算法(重要应用场景,一套代码作用于不同类型,如容器类);

类型参数命名约定

类型参数的命名没有严格要求,一般按如下约定使用:

  • E —— 容器元素
  • K —— Key
  • V —— Value
  • N —— Number
  • T —— Type
  • S, U, V etc —— 其他类型参数命名

泛型类型(类、接口)

generic type is a generic class or interface that is parameterized over types.

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

Raw Types

raw type is the name of a generic class or interface without any type arguments. If the actual type argument is omitted, you create a raw type of Box<T>:

Box rawBox = new Box();

Therefore, Box is the raw type of the generic type Box<T>.
Raw Types 一般出现在JDK5.0 之前的遗留代码中,兼容性考虑,我们可以将泛型类型与raw types相互赋值,但是编译器检查会触发unchecked warning,即编译器无法对raw types进行类型安全检查。

泛型方法

Generic methods are methods that introduce their own type parameters. Static and non-static generic methods are allowed, as well as generic class constructors.
泛型方法返回值前的尖括号内包含泛型类型列表。

public class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
               p1.getValue().equals(p2.getValue());
    }
}

public class Pair<K, V> {

    private K key;
    private V value;

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

    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey()   { return key; }
    public V getValue() { return value; }
}

受限类型参数

泛型在使用时,如果你想限制传入的真实类型,可以使用受限类型参数。

<T extends B1 & B2 & B3>

A type variable with multiple bounds is a subtype of all the types listed in the bound.

泛型方法与受限参数

Bounded type parameters are key to the implementation of generic algorithms.

public interface Comparable<T> {
    public int compareTo(T o);
}

泛型方法:

public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
    int count = 0;
    for (T e : anArray)
        if (e.compareTo(elem) > 0)
            ++count;
    return count;
}

T为实现Comparable<T>的子类。

泛型、继承与子类

一个常见的泛型误解
image.png
Box<Integer> is not a subtype of Box<Number> even though Integer is a subtype of Number.
Note:  Given two concrete types A and B (for example, Number and Integer), MyClass<A> has no relationship to MyClass<B>, regardless of whether or not A and B are related. The common parent of MyClass<A> and MyClass<B> is Object.

正确的泛型子类
Using the Collections classes as an example, ArrayList<E> implements List<E>, and List<E> extends Collection<E>. So ArrayList<String> is a subtype of List<String>, which is a subtype of Collection<String>. So long as you do not vary the type argument, the subtyping relationship is preserved between the types.

image.png

类型推断

Java编译器中的类型推断算法会尽力推断类型参数最精确的类型。

public class BoxDemo {

  public static <U> void addBox(U u, 
      java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
  }
}

以下两种写法是等价的,Java编译器会推断泛型参数为Integer

BoxDemo. <Integer> addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(10), listOfIntegerBoxes);

通配符 Wildcards

In generic code, the question mark (?), called the wildcard, represents an unknown type.

Upper Bounded Wildcards

To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword, followed by its upper bound. Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

To write the method that works on lists of Number and the subtypes of Number, such as IntegerDouble, and Float, you would specify List<? extends Number>. The term List<Number> is more restrictive than List<? extends Number> because the former matches a list of type Number only, whereas the latter matches a list of type Number or any of its subclasses.

public static void process(List<? extends Foo> list) {
    for (Foo elem : list) {
        // ...
    }
}

Unbounded Wildcards

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type.
无限制通配符通常用于以下场景:

  • If you are writing a method that can be implemented using functionality provided in the Object class.
  • When the code is using methods in the generic class that don't depend on the type parameter. For example, List.size or List.clear. In fact, Class<?> is so often used because most of the methods in Class<T> do not depend on T.

上界<? extends T>不能往里存,只能往外取

因为?表示未知类型,所以通配符列表如List<?>无法添加具体的元素除了null,因为具体元素都有明确类型,而不是未知类型。一般来说,通配符类用于方法入参,用于承载多种类型元素,在方法实现中,做只读操作,不依赖于具体类型,比如List.size

Lower Bounded Wildcards

lower bounded wildcard restricts the unknown type to be a specific type or a super type of that type.
A lower bounded wildcard is expressed using the wildcard character ('?'), following by the super keyword, followed by its lower bound<? super A>.

下界<? super T>不影响往里存,但往外取只能放在Object对象里

通配符与子类

image.png

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

Type Erasure 类型擦除

为了保持向后兼容性,Java采用类型擦除的方式实现泛型,编译过程中执行类型擦除。

  • 把源码中所有泛型替换为泛型上下限,如果泛型不受限则替换为Object。编译后的字节码,只存在普通的类、接口和方法。
  • 为保证类型安全,插入显式类型转换casts
  • 在泛型子类中,生成桥接方法,确保多态性。

泛型类擦除

During the type erasure process, the Java compiler erases all type parameters and replaces each with its first bound if the type parameter is bounded, or Object if the type parameter is unbounded.

In the following example, the generic Node class uses a bounded type parameter:

public class Node<T extends Comparable<T>> {

    private T data;
    private Node<T> next;

    public Node(T data, Node<T> next) {
        this.data = data;
        this.next = next;
    }

    public T getData() { return data; }
    // ...
}

The Java compiler replaces the bounded type parameter T with the first bound class, Comparable:

public class Node {

    private Comparable data;
    private Node next;

    public Node(Comparable data, Node next) {
        this.data = data;
        this.next = next;
    }

    public Comparable getData() { return data; }
    // ...
}

泛型方法擦除

The Java compiler also erases type parameters in generic method arguments.

// Counts the number of occurrences of elem in anArray.
//
public static <T> int count(T[] anArray, T elem) {
    int cnt = 0;
    for (T e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

Because T is unbounded, the Java compiler replaces it with Object:

public static int count(Object[] anArray, Object elem) {
    int cnt = 0;
    for (Object e : anArray)
        if (e.equals(elem))
            ++cnt;
        return cnt;
}

类型擦除的影响及桥接方法

泛型的限制

不能使用原始类型创建泛型

Pair<int, char> p = new Pair<>(8, 'a');  // compile-time error

You can substitute only non-primitive types for the type parameters K and V:

Pair<Integer, Character> p = new Pair<>(8, 'a');

不能使用泛型参数创建对象

不能在静态属性中使用类型参数

静态属性是类级别的,如果通过不同类型创建泛型类,就无法确定静态属性的明确的类型。

不能在参数化类型上使用casts或instanceof

泛型代码经过编译器擦除后,字节码中不再存在参数化类型,运行时期间就无法比较参数化类型。

public static <E> void rtti(List<E> list) {
    if (list instanceof ArrayList<Integer>) {  // compile-time error
        // ...
    }
}

The runtime does not keep track of type parameters, so it cannot tell the difference between an ArrayList<Integer> and an ArrayList<String>. The most you can do is to use an unbounded wildcard to verify that the list is an ArrayList:

public static void rtti(List<?> list) {
    if (list instanceof ArrayList<?>) {  // OK; instanceof requires a reifiable type
        // ...
    }
}

不能创建泛型数组

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error

Cannot Create, Catch, or Throw Objects of Parameterized Types

A generic class cannot extend the Throwable class directly or indirectly.

// Extends Throwable indirectly
class MathException<T> extends Exception { /* ... */ }    // compile-time error

// Extends Throwable directly
class QueueFullException<T> extends Throwable { /* ... */ // compile-time error

A method cannot catch an instance of a type parameter:

public static <T extends Exception, J> void execute(List<J> jobs) {
    try {
        for (J job : jobs)
            // ...
    } catch (T e) {   // compile-time error
        // ...
    }
}

You can, however, use a type parameter in a throws clause:

class Parser<T extends Exception> {
    public void parse(File file) throws T {     // OK
        // ...
    }
}

Cannot Overload a Method Where the Formal Parameter Types of Each Overload Erase to the Same Raw Type

A class cannot have two overloaded methods that will have the same signature after type erasure.

public class Example {
    public void print(Set<String> strSet) { }
    public void print(Set<Integer> intSet) { }
}

The overloads would all share the same classfile representation and will generate a compile-time error.