Generics

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kiss_xiaojie/article/details/78409541

Generics

Why Use Generics?

概况的说,泛型使得一些类型(类、接口)可在定义 classes interfacesmethods 时被参数化。

泛型的作用:

  • 编译时更强大的类型检查
  • 消除部分强制转换
  • 书写更通用的代码

Generic Types

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

上述引用 The Java™ Tutorials 中对泛型类型的定义。也就是说,泛型类型是一个泛型类或接口,它有可参数指定的类型。

举个例子:假如需要设计这样一个类,该类持有一个对象,而这个对象的类型不确定。如何设计呢?之前的做法是,把该对象的类型设置为 Object 。这样确实可以。但是在使用的时往往需自己添加转型操作的代码,并且一些错误在运行时才能发现(比如,强转失败)。现在,可以使用泛型设计如下:

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

如何使用:比如要持有一个 Integer 类型变量,实例化如下:

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

Raw Types

A raw type is the name of a generic class or interface without any type arguments.However, a non-generic class or interface type is not a raw type.

概括的说,Raw Types 指的是没有指定 Type Parameter(上面类中的 T)的泛型类型。当然,非泛型类或接口不是 Raw Type。

Box rawBox = new Box(); // Box is the raw type of the generic type Box<T>
// ----------------------------------------------
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)

泛型是在 JDK 1.5 才发布的。Raw Types 的存在很大一部是为了兼容以前的代码。

细心的会发现,上面代码注释中存在 warning: unchecked ,表明编译器在编译的时候没有足够的类型信息检查操作是否安全。这里的警告是因为使用 Raw Types 去掉了 Type Parameter 信息。会有大部分警告是因为 Type Erasure 机制。

The term “unchecked” means that the compiler does not have enough type information to perform all type checks necessary to ensure type safety.

使用 @SuppressWarnings("unchecked") 注解可以压制 unchecked warnings

Generic Methods

泛型方法只强调如下:

For static generic methods, the type parameter section must appear before the method’s return type.

静态方法非静态方法 都允许使用泛型。但,它们是有区别的。对于静态泛型方法,它使用的 Type Parameter 必须是自己在头部声明的。也就是它不能使用所在类的 Type Parameter 。

注意静态方法是属于类的,对于一个泛型类往往在实例化时才指定 Type Parameter 。若,直接调用静态方法是拿不到所属类的 Type Parameter

Bounded Type Parameters

To declare a bounded type parameter, list the type parameter’s name, followed by the extends keyword, followed by its upper bound, which in this example is Number.

上面的定义很精炼,不再进行翻译。比如,下面对 U 的限制。

public <U extends Number> void inspect(U u){ /* ... */ }

Note:
1. 此处的关键字 extends 含有 “extends” (as in classes) or “implements” (as in interfaces) 的意义。
2. 注意 <U extends Number><? extends Number> 的区分。<U extends Number> 使用在泛型声明处,而 <? extends Number> 用在 the type of a parameter, field, or local variable; sometimes a return type

Bounded Type Parameters 的作用,见如下案例:

public class Box<T> {

    private T t;          

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

    public T get() {
        return t;
    }

    public <U extends Number> void inspect(U u){    // here
        System.out.println("T: " + t.getClass().getName());
        System.out.println("U: " + u.getClass().getName());
    }

    public static void main(String[] args) {
        Box<Integer> integerBox = new Box<Integer>();
        integerBox.set(new Integer(10));
        integerBox.inspect("some text"); // error: this is still String!
    }
}

还有,Bounded Type Parameters 可使 T 对象调用 upper bound 里的方法。比如,这里 t 可以调用 Number 类中方法。

Note: A type variable with multiple bounds is a subtype of all the types listed in the bound. If one of the bounds is a class, it must be specified first.

最后,这里可使用多次界定。但要求有且只有一个是 class ,且为 class 时,必须放在第一位置。

Generics, Inheritance, and Subtypes

根据 object-oriented 机制,很容易接受:

Box<Number> box = new Box<Number>();
box.add(new Integer(10));   // OK
box.add(new Double(10.1));  // OK

然而,考虑下面的方法,上面样的实参被接受?

public void boxTest(Box<Number> n) { /* ... */ }

注意:Box<Integer> Box<Double> 都不被接受,因为它们并不是 Box<Number> 的子类,并且它们之间没有任何关系(但,它们 Class 类信息是一样的)。

Generics-1

最后,如 ArrayList<E> implements List<E>, List<E> extends Collection<E>,只要保证它们的泛型参数一样,它们依然有 父子关系

Generics-2

Type Inference (类型推断)

类型推断是Java编译器能够查看每个方法调用和相应的声明,以使调用适用的类型参数(或参数)。

// [1] By target types
static <T> T pick(T a1, T a2) { return a2; }
Serializable s = pick("d", new ArrayList<String>());    // 通过 s 的类型,推算出 a2 需要为 Serializable 类型
// [2] By invocation arguments
public static <U> void addBox(U u, java.util.List<Box<U>> boxes) {
    Box<U> box = new Box<>();
    box.set(u);
    boxes.add(box);
}
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes);    // U is Integer
// [3] The diamond (The new features of Java SE7)
Map<String, List<String>> myMap = new HashMap<>();
// [4] Type Inference and Generic Constructors of Generic
class MyClass<X> {
    <T> MyClass(T t) {
        // ...
      }
}
MyClass<Integer> myObject = new MyClass<>(""); // X is Integer, T is String
// [5] obvious expected return type to infer type
static <T> List<T> emptyList();
List<String> listOne = Collections.emptyList(); // T is String
// [5.1] The new features of Java SE8
void processStringList(List<String> stringList) {
    // process stringList
}
processStringList(Collections.<String>emptyList());
processStringList(Collections.emptyList());         // 可以推测出为 String ,JSE8 才可以

It is important to note that the inference algorithm uses only invocation arguments, target types, and possibly an obvious expected return type to infer types. The inference algorithm does not use results from later in the program.

Wildcards

在泛型代码中, 被称为 wildcards ,代表未知 Type Parameter (类型参数)。它常被用作:函数参数、字段或局部变量的类型。有时作为一个返回类型(很少)。

Upper Bounded Wildcards

前面已讨论,下面方法不接受 List<Integer> or List<Double> 类型的参数。For generic, how to do?

public static double sumOfList(List<Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

Upper Bounded Wildcards 可把这几个参数联系起来,需把参数类型 List<Number> 改为 List<? extends Number> 。此时,它能够接受 List<Integer> or List<Double> 类型的参数。

注意:List<? extends Number> 仍表达一种未知,说明并不知道是哪种类型参数的 List 。这里,只是限制类型参数的父类必须是 Number 。所以,它不支持 写入 。比如,向集合里面添加一个 Double 数据。然而,集合的具体类型是 List<Integer> 。这是不允许的!

Unbounded Wildcards

仅有 ? ,比如 List<?> 。它有两个应用场景(作为方法的参数类型):
1. 所写的方法中,只需要 Object 类提供的功能;
2. 所写的方法中,不依赖类型参数。比如, List.size or List.clear 等。事实上,Class<?> 很常见,因为好多 Class<T> 不依赖 T

// Scenario 1
// 目的:让像 List<Integer>, List<String>, List<Double> 对象都可应用于该方法
// 但很可惜 List<Object> 形参只允许接受 List<Object> 的实参(原理见上文)
// 只需要把 List<Object> 形参改为 List<?> 即可
public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

因为对于任意类型 AList<A>List<?> 的子类。

注意:要区分 List<Object>List<?> 的不同, List<Object> 的集合可添加任意类型的对象,而 List<?> 只允许添加 null

Lower Bounded Wildcards

其形式为 <? super A>。从名称上看,它与 Upper Bounded Wildcards 相对应。

Upper Bounded Wildcards 中指定了 上界(父类) ,故,它可使用父类中的方法。也因此,它不能支持 写入。现在,Lower Bounded Wildcards 指定了 下界(某子类) 。故,它可支持 写入,但不能用子类的方法。

比如, List<? super Integer> list 形参,它允许接受 List<Integer> List<Number> List<Object> 类型的实参。因此,可以安全地往 list 集合添加 Integer 对象。因为,List<Object> List<Number> 泛型类都允许添加 Integer 类型变量(IntegerObject or Number 的子类)。

Wildcards and Subtyping

Generics, Inheritance, and Subtypes 中指明:尽管 IntegerNumber 的子类,但 List<Integer>List<Number> 没有任何关系。如何建立关系?

List<?>List<Number> and List<Integer> 的公共父类。

Generics-3

一幅图描述全部关系!

Generics-4

Guidelines for Wildcard Use

为了说明 通配符的使用原则,先定义两种变量(用于形参中):

  1. “In” 变量:为方法提供数据。
  2. “Out” 变量:把数据保存到该变量中。

Wildcard Guidelines:
- 声明一个“In 变量”时,使用 upper bounded wildcard ,使用 extends 关键字;
- 声明一个“Out 变量”时,使用 lower bounded wildcard ,使用 super 关键字;
- 即是”In 变量”又是“Out 变量”时,不使用通配符;
- 声明一个“In 变量”时,但方法中只使用 Object 中的方法,可使用 unbounded wildcard

Type Erasure

C++ 中的 Template (或称为泛型)相比, Java 中的泛型比较 笨拙 且功能没有 C++ 的强大。主要原因,泛型是 Java 后期发布的。为了不妨碍原类库的使用,不得不使用 Type Erasure(类型擦除) 机制实现泛型。是一个折中选择!

类型擦除机制:

  • Replace all type parameters in generic types with their bounds or Object if the type parameters are unbounded. The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.
  • Insert type casts if necessary to preserve type safety.
  • Generate bridge methods to preserve polymorphism in extended generic types.
  1. 编译器编译时会用 Object (无边界限制时,如 List<Number>)或 bounds (比如, List<T extends Number> 会使用 Number)代替类型参数。也就是,编译生成的字节码不包括类型参数。
  2. 编译器支持泛型的动作发生在 边界 处:++对传递进来的值进行额外的编译器检查,并自动插入对传递出去的值的转型++。
  3. 生成 bridge methods 用于解决类型擦除破坏多态的问题。(略)

    // 针对 1)代码说明
    public class Node

Non-Reifiable Types, Reifiable types

Non-Reifiable Types 指那些编译时被擦除类型参数信息的类型,为 invocations of generic types that are not defined as unbounded wildcards

Reifiable types 则在运行时含有类的全部信息,包括:primitives, non-generic types, raw types, and invocations of unbound wildcards

Restrictions on Generics

1. Cannot Instantiate Generic Types with Primitive Types

类似 List<int> List<double> 是不允许的,可以使用原始类型对应的类代替(比如,List<Integer>)。而且,有了 自动装箱、拆箱 后,还是比较方便的。

2. Cannot Create Instances of Type Parameters

public static <E> void append(List<E> list) {
    E elem = new E();  // compile-time error
    list.add(elem);
}

不能直接创建一个类型参数实例。how to do?

// 1)可以像下面一样注入注入 类 信息
public static <E> void append(List<E> list, Class<E> cls) throws Exception {
    E elem = cls.newInstance();   // OK
    list.add(elem);
}
//-------------------------------------------------
// 2)显示地传递工厂
    interface Factory<T> {
    T create();
}
class Foo<T>{
    private T x;
    public <F extends Factory<T>> Foo(F factory){
        x = factory.create();
    }
}
//-------------------------------------------------
// 3)使用 模板方法 设计
abstract class GenericWithCreate<T> {
    final T element;
    GenericWithCreate(){element = create();}
    abstract T create();
}
class X {}
class Creator extends GenericWithCreate<X>{
    @Override
    X create() {
        return new X();
    }
}

注意:2)3)方法都利用了一原理,类型擦除不会擦除那些结构化信息结构化信息 是指与类结构相关的信息,而不是与程序执行流程有关的。

3. Cannot Use Casts or instanceof With Parameterized Types

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

像上面 instanceof 操作是不被允许的。因为,ArrayList<Integer> 是一个 Non-Reifiable Types 。注意:unbound wildcardsReifiable Types。所以,下面是正确的:

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

转型的情况如下:

List<Integer> li = new ArrayList<>();
List<Number>  ln = (List<Number>) li;  // compile-time error
// ---------------------------------------------
List<String> l1 = ...;
ArrayList<String> l2 = (ArrayList<String>)l1;  // OK

4. Cannot Create Arrays of Parameterized Types

不允许创建泛型数组。下面是不允许的(解释见注释):

List<Integer>[] arrayOfLists = new List<Integer>[2];  // compile-time error
// 解释
Object[] strings = new String[2];
strings[0] = "hi";   // OK
strings[1] = 100;    // An ArrayStoreException is thrown.
// If you try the same thing with a generic list, there would be a problem:
Object[] stringLists = new List<String>[];  // compiler error, but pretend it's allowed
stringLists[0] = new ArrayList<String>();   // OK
stringLists[1] = new ArrayList<Integer>();  // An ArrayStoreException should be thrown, but the runtime can't detect it.
// 若允许创建泛型数组,则此时由于受类型擦除机制影响,并不能抛出 ArrayStoreException 。

How to do?

  • 在任何想使用泛型数组的地方使用 ArrayList;
  • 成功创建泛型数组的唯一方式是:创建它对应的 Raw Type 数组然后转型(可以不转型);

    List[] listArray = (List[]) new ArrayList[5];
    listArray[0] = new ArrayList<>();

  • 针对 T[] ,使用 Object[] 代替;

  • 针对 T[] ,注入 Class 信息,使用 T[] array = (T[]) Array.newInstance(class,SIZE); 创建(推荐);

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

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

print(...) 方法签名中,虽有类型参数不同,但不能视它们为重载。

6. Cannot Create, Catch, or Throw Objects of Parameterized Types

猜你喜欢

转载自blog.csdn.net/kiss_xiaojie/article/details/78409541