The Java™ Tutorials —— Generics 导读

The Java™ Tutorials —— Generics 导读

笔者在此将此前通篇翻译的Oracle官方文档泛型部分加以整理如下:

The Java™ Tutorials —— Generics:前言

  • 泛型可以增强编译时错误检测,减少因类型问题引发的运行时异常

The Java™ Tutorials — Generics :Why Use Generics? 为什么使用泛型

  • 泛型具有更强的类型检查
  • 泛型可以避免类型转换
  • 泛型可以泛型算法,增加代码复用性

The Java™ Tutorials — Generics :Generic Types 泛型

  • 如果利用Object来达到通用存储的目的,那么可能引起运行时错误
  • 泛型类格式:class name<T1, T2, ..., Tn>
  • 常见类型变量名:E(元素)、K(键)、N(数字)、T(类型)、V(值)、S(第二类型参数)、U(第三类型参数)
    *“类型参数”与“类型变量”的不同
    • Foo<T>中的T为类型参数
    • Foo<String>中的String为类型变量
  • 钻石运算符的使用

The Java™ Tutorials — Generics :Raw Types 原始类型

  • 定义:
    • 缺少实际类型变量的泛型就是一个原始类型
    • 举例:
class Box<T>{} 
Box b = new Box(); //这个Box就是Box<T>的原始类型 
  • 实际行为:获取和返回的类型都为Object
  • 缺点:无法进行编译时类型检查,将异常的捕获推迟到了运行时,还可能会收到unchecked警告。
  • 对unchecked警告的处理
    • @SuppressWarnings(“unchecked”)
    • -Xlint:unchecked

The Java™ Tutorials — Generics :Generic Methods 泛型方法

  • 定义格式:private <K,V> boolean compare(Pair<K,V> p1, Pair<K,V> p2)
  • 调用格式:Util.compare(p1,p2)

The Java™ Tutorials — Generics :Bounded Type Parameters 受限的类型参数

  • 功能:对泛型变量的范围作出限制
  • 格式:
    • 单一限制:<U extends Number>
    • 多种限制:<U extends A & B & C>
  • extends表达的意义:这里指的是广义上“扩展”,兼有“类继承”和“接口实现”之意
  • 多种限制下的格式语法要求:如果上限类型是一个类,必须第一位标出,否则编译错误
    • 题外问题:如果多种限制中的A和B同时为类该何如?
      • 答:编译错误,这违反了Java不许多重继承的原则

The Java™ Tutorials — Generics :Generic Methods and Bounded Type Parameters 泛型方法和受限类型参数

  • 泛型算法实现的关键:利用受限类型参数

The Java™ Tutorials — Generics :Generics, Inheritance, and Subtypes 泛型,继承和子类型

  • 泛型间父子关系

The Java™ Tutorials — Generics :Type Inference 类型推断

  • 理解编译器是如何利用目标类型来推算泛型变量的值
  • 注意下面的代码:在Java7中无法编译通过,而在Java8中却可以。Java8的编译器可以通过方法形参类型对泛型变量进行推断
static <T> List<T> emptyList();
void processStringList(List<String> stringList) {
    // process stringList
}
processStringList(Collections.emptyList());

The Java™ Tutorials — Generics :Wildcards 通配符

  • 通配符的适用范围:
    • 参数类型
    • 字段类型
    • 局部变量类型
    • 返回值类型(但返回一个具体类型的值更好)

The Java™ Tutorials — Generics :Upper Bounded Wildcards 受上限控制的通配符

  • 语法格式:

The Java™ Tutorials — Generics :Unbounded Wildcards 非受限通配符

  • 两个关键使用场合:
    • 写一个方法,而这方法的实现可以利用Object类中提供的功能时
    • 泛型类中的方法不依赖类型参数时
      • 如List.size()方法,它并不关心List中元素的具体类型
  • List<XXX>List<?>的一个子类型
  • 理解List<Object>List<?>的不同:差在NULL处理,前者不支持,而后者却可接受一个null入表

The Java™ Tutorials — Generics :Lower Bounded Wildcards 有下限通配符

  • 功能:限定了类型的下限,也就它必须为某类型的父类
  • 格式:<? super A>

The Java™ Tutorials — Generics :Wildcards and Subtyping 泛型和子类

The Java™ Tutorials — Generics :Wildcard Capture and Helper Methods 通配符匹配和辅助方法

  • 利用辅助方法解决通配符类型推断问题:
    • 原理:利用泛型的类型推断
public class WildcardFixed {

    void foo(List<?> i) {
        fooHelper(i);
    }


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

}
  • 辅助方法格式:“所辅助的方法的名称+Helper”

The Java™ Tutorials — Generics :Guidelines for Wildcard Use 通配符使用指南

  • “输入”和“输出”变量的定义
    • 输入:负责添加数据到代码中,类比一个入口
    • 输出:负责接收输入的数据,并将其传到需要的地方,类比一个出口
  • 通配符准则:
    • 一个“输入”变量是通过有上限通配符定义的,它使用了extends关键字
    • 一个“输出”变量是通过有下限通配符定义的,它使用了super关键字
    • 当“输入”变量可通过Object类中的方法获取时,那就使用一个无限制通配符
    • 当代码需要获取一个兼具“输入”和“输出”功能的变量时,就不要使用通配符了
  • List

The Java™ Tutorials — Generics :Type Erasure 类型消除

  • 功能:保证了泛型不在运行时出现
  • 类型消除应用的场合:
    • 编译器会把泛型类型中所有的类型参数替换为它们的上(下)限,如果没有对类型参数做出限制,那么就替换为Object类型。因此,编译出的字节码仅仅包含了常规类,接口和方法。
    • 在必要时插入类型转换以保持类型安全。
    • 生成桥方法以在扩展泛型时保持多态性。

The Java™ Tutorials — Generics :Erasure of Generic Methods 泛型方法的类型擦除

  • 消除方法:同对泛型类的处理
    • 无限制:替换为Object
    • 有限制:替换为第一受限类型

The Java™ Tutorials — Generics :Effects of Type Erasure and Bridge Methods 类型擦除的影响以及桥方法

  • 补充阅读:http://www.cnblogs.com/ggjucheng/p/3352519.html
  • 桥方法的功能:防止类型擦除后子类无法override父类的方法,保护泛型的多态性

The Java™ Tutorials — Generics :Non-Reifiable Types 不可具体化类型

  • 可具体化类型和不可具体化类型的定义:
    • 可具体化类型:就是一个可在整个运行时可知其类型信息的类型。
      • 包括:基本类型、非泛型类型、原始类型和调用的非受限通配符。
    • 不可具体化类型:无法整个运行时可知其类型信息的类型,其类型信息已在编译时被擦除:
      • 例如:List<String>List<Number>,JVM无法在运行时分辨这两者
  • 堆污染:
    • 发生时机:当一个参数化类型变量引用了一个对象,而这个对象并非此变量的参数化类型时,堆污染就会发生。
    • 分模块对代码进行分别编译,那就很难检测出潜在的堆污染,应该同时编译
  • 带泛型的可变参数问题:
    • T…将会被翻译为T[],根据类型擦除,进一步会被处理为Object[],这样就可能造成潜在的堆污染
  • 避免堆污染警告
    • @SafeVarargs:当你确定操作不会带来堆污染时,使用此注释关闭警告
    • @SuppressWarnings({“unchecked”, “varargs”}):强制关闭警告弹出(不建议这么做)

The Java™ Tutorials — Generics :Restrictions on Generics 泛型约束

  • 无法利用原始类型来创建泛型
    • 解决方法:使用它们的包装类
  • 无法创建类型参数的实例
    • 变通方案:利用反射就是可以
  • 无法创建参数化类型的静态变量
    • 原因:静态变量是类所有,共享决定了其必须能确定。但多个类型作为参数传入此类的多个实例时,就会无法确定究竟赋予此静态变量哪个实例对象所传入的参数了
  • 无法对参数化类型使用转换或者instanceof关键字
    • 但需要注意通配符的情况
  • 无法创建参数化类型的数组
  • 无法创建、捕获或是抛出参数化类型对象
    • 但却可以在throw后使用类型参数
  • 当一个方法的所有重载方法的形参类型擦除后,如果它们具有了相同的原始类型,那么此方法不可重载
    • 原因:此情境下,类型擦除会产生两个同签名的方法

拓展阅读

Can I create an array whose component type is a concrete parameterized type? 我可以创建一个元素类型为具体的参数化类型的数组嘛(我可以创建一个泛型数组嘛)?

泛型数组被禁止的原因:

类型擦除后会导致数组元素类型不确定性,举例

Pair<Integer,Integer>[] intPairArr = new Pair<Integer,Integer>[10]; // illegal 
Object[] objArr = intPairArr;  
objArr[0] = new Pair<String,String>("",""); // should fail

我们期望intPairArr仅仅存储Pair

Pair[] intPairArr = new Pair[10];

数组仅保留了其原始类型(raw type)。这样一来,数组就会接受任何类型的键值对了,如Pair

Node<?>[] nodes = new Node[10]; //这是可以的

你可能感兴趣的:(java,泛型)