Java范型学习笔记

泛型的目的

  • 通过引入类型参数,使得相同的代码可以被复用;传入不同的类型参数,就可以适用于不同的场景。
  • 通过编译器来避免代码中可能存在的错误,在编译阶段排除可能存在的错误。

两种泛型

  • 泛型类
  • 泛型方法

满足is-a关系的变量都可以作为方法的参数。
但是,A和B之间的关系,以及A的泛型类与B的泛型类之间的关系,是两种不同的关系。
泛型类之间的可以存在继承关系。

类型推断

  • 类型推断:泛型方法在调用时,无需指定类型参数,编译器会从调用的实参中推断出这个泛型参数。

  • 泛型类和非泛型类中的泛型构造器的类型推导

受限制类型参数

  • 上限:T extends UpperBound
    限定一个未知的类型为一个指定的类型,或者此类型的子类型。

  • 上限通配符
    ? extends UpperBound
    具体的类型未知,但此类型为一个指定的类型,或者此类型的子类型。

  • 下限通配符
    ? super LowerBound

  • 未限定通配符

    • 通过 ? 符号指定。
    • 可以用于参数的类型、域的类型、本地变量的类型,有时也可用做返回值的类型;但是不作为泛型方法调用的参数、泛型类的实例化,以及超类型。
    • 使用通配符进行子类化,例如:ListListList的超类型。

通配符捕获(Wildcard Captrue)

在编译阶段对表达式求值时,编译器可以从代码中推测出一种特定的类型,这种情况被称为通配符捕获。

一般我们不用关注这个过程,除非编译过程中出现通配符捕获相关的错误。

void foo(List i) {
        fooHelper(i);
    }


    // Helper method created so that the wildcard can be captured
    // through type inference.
    private  void fooHelper(List l) {
        l.set(0, l.get(2));
    }

public class WildcardErrorBad {

    void swapFirst(List l1, List l2) {
      Number temp = l1.get(0);
      l1.set(0, l2.get(0)); // expected a CAP#1 extends Number,
                            // got a CAP#2 extends Number;
                            // same bound, but different types
      l2.set(0, temp);      // expected a CAP#1 extends Number,
                            // got a Number
    }
}

通配符的使用规则

对于通配符的使用来说,一个很让人迷惑的地方在于何时使用extends,何时使用super。总体上看,可以通过把参数分为以下两类来决定使用哪种关键字。

  • 变量
    提供数据,供本代码使用。
  • 变量
    保存数据,供其它地方的代码使用。

例如,对于Collectionscopy方法:
public static void copy(List dest, List src)
dest是出变量,src是入变量。

基于这种分类,可以约定以下使用规则:

  • 入变量,使用extends关键字定义变量的上限。
  • 出变量,使用super关键字定义变量的下限。
  • 入变量,准备调用Object类的方法,使用未受限通配符。
  • 同时作为入变量和出变量,不使用通配符。

简单地说,就是入变量只能读,不能写;出变量只能写,不能读。

特别注意
通过定义的变量,可以被认为是一种只读的变量。例如: List的变量只能执行以下几种操作:

  • add(null)
  • 调用clear
  • 获取iterator,调用remove
  • 捕获通配符,往其中添加从中读出来的元素。

扩展思考:

  • 为什么上限通配符定义变量只能读,不能写?
    对于使用? extends T定义的变量参数,可以是T的任意子类。
    在执行读操作的时候,可以用T来引用读出来的元素,因为T是这些元素类型的父类。
    但是,如果执行写操作,编译器不知道写入的是T的哪个子类型。假设A、B均是T的子类,并且B继承A,即T <- A <-- B。编译器无法推断出写入的元素类型是T,还是A,还是B。

  • 为什么下限通配符定义的变量只能写,不能读?
    对于使用? super T定义的变量参数,可以是T的任意父类。
    在执行写操作的时候,无论实际是哪种类型,都是T的父类。
    执行读操作的时候,编译器无法推断出读出的元素类型,不知道该用什么类型去引用。

你可能感兴趣的:(Java范型学习笔记)