阿特我自己
[email protected]
Hello WvT
第 9 章 – 泛型
第 9 章 – 泛型

泛型允许在定义类、接口、方法时使用类型形参,该形参在声明变量、创建对象、调用方法时动态地指定

一、泛型(Generic)

1. 定义泛型接口、类

泛型允许在定义接口、类时声明类型形参,类型形参在整个接口、类体内可当成类型使用,几乎所有可使用普通类型的地方都可以使用这种类型形参

需要指出的是,类型形参不能定义为基本数据类型,且类型参数不能用于定义静态成员的类型

类型形参可以设定上限,表示该泛型只接受其上限类型或上限类型的子类型,不指定上限则默认为 Object
还可以定义多个上限,表示该类型必须是其上限类型或其子类型,并实现多个上限接口
接口上限必须定义在类上限之后

2. 使用泛型类、接口

创建泛型类实例的示例如下

从 Java7 开始,Java 允许在构造出后不需要带完整的泛型信息,只需要给出一对“<>”即可,Java 可以根据变量类型自动推断

3. Java 泛型的实质

Java 泛型只存在于编译期间,提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型

Java 泛型的设计原则是:只要代码在编译时没有出现警告,就不会遇到运行时 ClassCastException 异常

在编译后,Java 会把类型形参当作其上限类型并为所有使用了泛型类型的地方加上强制类型转换,这也是泛型类型不能定义为基本数据类型的原因

例如,上方代码在编译后会被替换成以下代码

以下代码的两段代码的运行方式完全相同

Java 允许不指定泛型信息,如果不指定泛型类型,Java 会把类型形参当作上限类型,相当于 G<上限类须>

将 带有泛型信息的泛型类实例 赋给 不带有泛型信息的引用类型变量 时同上

不能将泛型类赋给一个泛型类型不同的引用类型变量,如 G<Class1>  g = new G<Class2> 会发生编译错误

4. 从泛型类派生子类

当创建了带泛型声明的接口、父类后,可以为其创建实现类或派生子类

继承泛型接口、父类时则不能再包含类型形参必须显式指定父类的泛型类型
当指定了父类泛型类型后,父类中所有使用类型形参的地方都会被替换成实际的类型

如果继承泛型类时不指定泛型类型,则父类的类型形参都会被当成上限类型,同时 Java 编译器会发出泛型检查警告:使用了未经检查或不安全的操作

5. 并不存在泛型类

系统并不会为不同泛型类型的类生成新 class 文件,也不会把它当作新类处理,泛型类型只是泛型类实例在编译时的特性,在运行时总是同样的类

也因此,在静态方法、静态初始化块或静态变量的声明和初始化中不允许使用类型形参,instanceof 运算符后也不能指定实际泛型类型,但可以使用 ? 类型通配符

二、类型通配符

类型通配符是泛型对多态的支持,在使用类型通配符前,先看一下数组与多态

假设定义了 DogAnimal 类,Dog 是 Animal 的子类,Dog[] 类型将被当作 Animal[] 类型的子类型

为避免这种缺陷,Java 在设计泛型时进行了改进,对于泛型来说,如果 Dog 是 Animal 的一个子类型,而 G 是具有泛型声明的类或接口,G<Dog> 并不是 G<Animal> 的子类型

针对这种情况,可以使用类型通配符

1. 使用类型通配符

类型通配符(?)用于表示其泛型类型不确定在逻辑上是各种已确定泛型类型的父类型(物理上并不存在)

例如:G<?> 在逻辑上是 G<String>, G<Inetger> 甚至 G<Object> 的父类型
因此可以使用 G<?> list = new G<String>();

当使用 ? 作为泛型信息定义变量或方法形参后

  • 当调用方法形参为未知类型的方法时将会引起编译错误,因为此时泛型信息未知
  • 但可以访问未知类型的实例变量,或调用未知类型返回值的方法,此时未知类型将被强制替换为 通配符的上限类型
    因为虽然类型形参未知,但它一定是 通配符的上限类型 类型或其子类型

如下代码:

2. 设定类型通配符上限

如果希望泛型信息只是某一类或其子类,可以使用 extends 关键字限定其类型上限,类型通配符的上限默认为 Object

3. 设定类型通配符的下限

使用 super 关键字可以限定通配符的下限,例如 <? super Type>,表示泛型类型必须是 Type 本身,或是 Type 的父类

三、泛型方法

1. 定义泛型方法

定义泛型方法的语法如下

方法的泛型信息与类无关,因此泛型方法可以是静态方法

在声明了类型形参后,可以用该类型形参作为方法形参返回值或方法局部变量类型

方法里定义的类型形参名称不能与类、接口中定义的相同

泛型方法与泛型类一样,可以设定类型形参的上限

系统会在程序调用泛型方法时自动推断类型形参的值,为确保编译器能准确推断出类型形参,不要制造迷惑

以下是自动推断类型的规则

P 代表 实际传入的方法形参类型(如果该形参是泛型类,则代表其泛型类型)
V 代表 接受方法返回值的引用变量(如果该形参是泛型类,则代表其泛型类型)
”定义“前省略了”使用类型形参“

  1. 只定义返回值类型:类型形参被当作 V 的类型,如果调用该方法时没有定义 V,则类型形参被当作 Object 类型
  2. 只定义了一个方法形参类型:类型形参会被推断成 P 的类型
  3. 定义了一个方法形参类型一个返回值类型:类型形参会被推断成 P 的类型
    也有可能被推断成 P 和 V 的最小公父类
  4. 定义了多个方法形参类型:类型形参会被推断成多个 P 的最小公父类
  5. 定义了返回值类型和多个方法形参类型:类型形参会被推断成多个 P 的最小公父类

2. 泛型方法和类型通配符的区别

大多数时候都可以使用泛型方法来代替类型通配符

3. 泛型构造器

构造器是一个特殊的方法,构造器也可以声明泛型信息

构造器定义泛型信息的语法如下

与泛型方法一样,泛型构造器业可以自动推断类型形参,但构造器还可以手动指定类型形参,语法如下

如果程序显式指定了泛型构造器的类型形参,则不能使用菱形语法自动推断泛型类的类型形参,如下

4. 泛型方法的方法重载

/* 泛型方法与普通方法一样都可以可以进行方法重载,只要形参列表不同即可

但由于泛型允许设置通配符上下限,因此可能出现两个方法形参列表不同,但在调用方法时,可以同时匹配两个方法的情况,如下代码 */

、、、

由于泛型是使用擦除实现的,如果两个方法的形参列表定义了泛型类,只有泛型类的泛型信息不同,这两个方法不构成方法重载,会发生编译错误:both method have the same erasure,如下代码:

5. Java 8 改进的类型推断

五、擦除和转换

在严格的泛型代码中,带泛型声明的类总应该带着类型参数。但为了与老的 Java 代码保持一致,也允许在使用带泛型声明的类时不指定实际的类型参数

如果没有为这个泛型类指定实际的类型参数,则该类型参数被称为 raw type(原始类型),默认是声明该类型参数时指定的第一个上限类型,如果没有指定上限类型则为 Object

如果将一个 有泛型信息的对象 给另一个 没有泛型信息的变量 时,所有在尖括号内的泛型信息将会被丢失,变成原始类型,这个过程叫擦除(erasure)

如果将一个 没有泛型信息的对象 给另一个 具有泛型信息的变量 时,不会引起编译错误,但会提示“[unchecked] 未经检查的转换“警告

如果将一个被擦除了泛型信息的对象赋给另一个具有泛型信息,且泛型信息不同的变量时,不会引起编译错误,但在运行时可能会发生 ClassCastException 异常,如下代码所示

六、泛型与数组

 

赞赏

发表评论

textsms
account_circle
email

Hello WvT

第 9 章 – 泛型
泛型允许在定义类、接口、方法时使用类型形参,该形参在声明变量、创建对象、调用方法时动态地指定 一、泛型(Generic) 1. 定义泛型接口、类 泛型允许在定义接口、类时声明类型形参,…
扫描二维码继续阅读
2019-08-16


没有激活的小工具