08_泛型程序设计

使用泛型

更易读,更安全

泛型类

引入一个类型变量,使用<>括起来,放在类名后面

public class Pair<T,U>{...}

类型变量可用于指定方法的返回类型和参数类型、字段和局部变量的类型

常用的类型变量表示法:E(集合的元素类型)、K / V(分别表示表的键和值的类型)、T / U / S(任意类型)

使用具体的类型替代类型变量,来实例化泛型类型

Pair<int,int>

泛型方法

可以在普通类中定义一个泛型方法

类型变量放在修饰符的后面,并放在返回类型的前面

public static <T> T getMiddle(T... a)

当调用泛型方法时,使用具体的类型替代类型变量,并放在方法名前面

若编译器可以推断出具体类型,甚至可以在调用中省略类型参数

类型变量的限定

使用extends关键字来限定类型变量是某个类的子类或实现了某个接口,

一个类型变量可以有多个限定,使用&分隔限定,而使用,分隔类型变量

但多个限定中,最多只有一个限定是类,且这个限定是限定列表中的第一个

public static <T extends Comparable & Serializable> T min(T[] a){...}

泛型代码和虚拟机

虚拟机中没有泛型类型对象,所有对象都属于普通类

类型擦除

泛型类型都会被自动提供一个原始类型,即去掉类型参数的泛型类型名

作为方法的返回类型和参数类型、字段和局部变量类型的类型变量会被擦除,替换为其限定类型,而无限定的替换为Object

转换泛型表达式

在一个表达式中,调用将类型变量作为返回类型的方法,或访问泛型字段,擦除类型变量后,编译器会在字节码中插入强制类型转换

转换泛型方法

普通类中的泛型方法也会有类型擦除

编译器生成一个桥方法用于保持多态性

public void setSecond (Object second) {setSecond ((LocalDate) second);}
public LocalDate getSecond() {return (LocalDate) super.getSecond();}

在桥方法中,对被擦除类型的参数进行强制类型转换,然后重载解析,调用匹配的方法

在桥方法中,对被擦除返回类型的方法的返回值进行强制类型转换,并作为新的返回值

调用遗留代码

将一个泛型对象赋给原始类型的变量,或由一个遗留类得到一个原始类型的对象,将它赋给一个泛型变量,将会得到警告

使用注解消除这个警告

@SupressWarnings("Unchecked")
Dictionary<Integer,Components> labelTable=slider.getlabelTable();

泛型类型的继承规则

对于原始类型相同、类型变量不同的泛型类,不管类型变量之间是否存在关系,对应泛型类之间没有任何关系

参数类型是原始类型的一个子类型,可以将参数类型转换为原始类型,但会抛出ClassCastException异常

泛型类可以扩展或实现其他泛型类

限制与局限性

大部分限制都是由于类型擦除

不能用基本类型实例化

擦除后的类型仍是一个类,而不是基本类型,无法储存基本类型的值

运行时查询只适用于原始类型

虚拟机中只对原始类型进行操作,instanceof操作符、强制类型转换都不能使用泛型类型,getClass方法也只能返回原始类型

不能创建参数化类型的数组

可以声明参数化类型数组的变量,但不能创建参数化类型的数组来初始化这个变量

Varags警告

向参数个数可变的方法传递泛型类型实例时,Java虚拟机必须建立一个参数化类型的数组,从而违背了规则

但规则有所放松,只对得到一个警告而不是错误,可以确保安全的前提下使用注解

可以为包含参数长度可变方法调用的方法使用注解@SupressWarnings(“Unchecked”)

或直接对参数长度可变方法使用@SafeVarags,但只能用于static、final、private的方法

不能实例化类型变量

不能使用new操作符创建类型变量T的对象

最好的方法是提供一个构造器引用

public static <T> Pair<T> makePair(Supplier<T> constr)
{
    return new Pair<>(constr.get(),constr.gte());
}
Pair<String> p = Pair.makePair(String::new);

传统的方法是使用反射

public static <T> Pair<T> makePair(Class<T> cl)
{
    try{
        return new Pair<>(cl.getConstructor().newInstance(),cl.getConstructor().newInstance());
    }
    catch (Exception e){return null;}
}
Pair<String> p = Pair.makePair(String.Class);

不能构造泛型数组?

不能使用new操作符创建元素类型为类型变量T的数组

可以将数组的元素类型声明为擦除的类型,并在使用强制类型转换

private Object[] elements;
...
@SupressWarnings("Unchecked")
public E get(int i) {return (E) elements[i];}

private E[] elements;
...
public ArrayList() {elements = (E[]) new Object[10];}

强制类型转换E[ ]是一个假象,而类型擦除使其无法察觉

与实例化类型变量的方法一样,最好提供一个构造器引用

或是使用反射

泛型类的静态上下文中类型变量无效

不能在静态字段或方法中引用类型变量

不能抛出或捕获泛型类的实例

泛型类不能扩展Throwable类,泛型变量可以

catch子句中不能使用类型变量

可以取消对检查型异常的检查

利用泛型将检查型异常转换为非检查型异常

注意擦除后的冲突

考虑一个Pair< String >,它有两个equals方法,一个是Pair< T >中定义的equals(String),一个是从Object类中继承的equals(Object),然而equals(String)擦除为equals(Object)后,便会产生冲突,而不是覆盖

重新命名

若两个接口是同一泛型接口的不同参数化,若是存在继承关系,则擦除后方法合成的桥方法会与超类的桥方法产生冲突,而不是覆盖

class Employee implements Comparable<Employee>{
	public int compareTo(Object other){return compareTo((Employee) other);}
}

class Manager extends Employee implements Comparable<Manager>{
	public int compareTo(Object other){return compareTo((Manager) other);}
}

通配符类型

通配符概念

使用?通配符,允许类型参数发生变化

可表示类型变量符合限定的所有的泛型

通配符的类型限定

使用extends关键字指定一个子类型限定,而使用super关键字指定一个超类型限定

继承关系<-<-<-<-<-
super限定PairPair<?>Pair<? super Manager>Pair< Employee >
Pair< Object >
extends限定PairPair<?>Pair<? extends Employee>Pair< Employee >
Pair< Manager >

方法的返回类型可以是返回值的超类型,方法参数的类型可以是接受的参数的子类型

public Pair<? extends Employee> getFirst(){...}

extends限定的通配符类型可以作为返回类型,但不能用于方法参数

因为编译器知道通配符的超类型的具体类型,但无法得知通配符的子类型的具体类型

public void setFirst(Pair<? super Employee> p){...}

super限定的通配符类型不能用于返回类型,但可以用于方法参数

因为编译器无法得知通配符的超类型的具体类型,但知道通配符的子类型的具体类型

即带有超类型限定的通配符允许写入一个泛型对象,子类型限定的通配符允许读取一个泛型对象

无限定通配符

public Pair<?> getFirst(){...}
public void setFirst(Pair<?> p){...}

无限定通配符作为返回类型,返回值只能赋给一个Object

无限定通配符用于方法参数,将不能被调用,只能用于静态方法

public static boolean hasNull(Pair<?> p){...}
public static <T> boolean hasNull(Pair<T> p){...}

通配符捕获

通配符不是类型变量,因此不能将?作为一种类型

可以使用类型参数捕获通配符,虽然不知道通配符指示的是什么类型,但确实是个明确的类型

public static <T> void swapHelper(Pair<T> p) {...}
public static     void swapHelper(Pair<?> p) {swapHelper(p);}  //捕获

只有在能够保证通配符表示单个确定的类型时,通配符捕获才是合法的。如ArrayList<Pair<?>>保存多个Pair< ? >元素,但?不同

反射与泛型

泛型Class类

String.Class实际上是一个Class< String >类的唯一对象

使用Class< T >参数进行类型匹配

public static <T> Pair<T> makePair(Class<T> c){...}

则调用

Pair.makePair(Employee.Class)
//Pair.<Employee>makePair(Employee.Class)

Employee类将自动与类型参数T匹配

虚拟机中的泛型类型信息

对于java.lang.reflect包中的接口Type,有以下子类型

  • Class类,描述类型
  • TypeVariable接口,描述类型变量
  • WildcardType接口,描述通配符
  • ParameterizedType接口,描述泛型类或接口类型
  • GeneicArrayType接口,描述泛型数组

类型字面量

无法从一个对象得到泛型类型

字段和方法参数的泛型类型还留存在虚拟机中

CDI和Guice等注入框架使用类型字面量来控制泛型类型的注入

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值