第15章:泛型

Catalogue   

一般的类和方法,只能使用具体的类型,要么是基本类型,要么是自定义的类,如果要编写可以应用于
多种类型的代码,这种刻板的限制对代码的束缚就会很大。

泛型实现了参数化类型的概念,使代码可以应用于多种类型。

容器类是引入泛型的一个重要原因

Java中,return语句只允许返回单个对象,通过泛型,则可以返回多个对象。这个概念成为元祖(tuple),它将一组对象
直接打包存储于其中一个单一对象。

元祖可以具有任意长度,任意类型。

泛型方法

使用泛型方法时,会进行类型参数推断,如果传入基本类型,则会自动打包

1
2
3
4
5
6
7
8
9
10
11
public class GenericMethods {
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}

/*
public static T f(T a){ //编译不通过
return a;
}
*/
}

普通static方法无法访问泛型类的类型参数,如果要是使用泛型就要定义成泛型静态方法。
类的泛型要在创建对象时才确定,而类内的静态方法,静态域在类加载时初始化,因此如果使用类的泛型类型则初始化时无法知道具体类型是什么,
此时使用泛型方法这样就和类的泛型无关了,这样静态方法初始化时类型只和自身的泛型相关。

泛型方法返回值是泛型,那么就返回一个泛型,不能是具体类型,反之亦然。

1
2
3
public static <T> Set<T> set(){
// return new HashSet<String>(); //不能返回具体类型
}

泛型的一个重要好处是能够简单而安全的创建复杂的模型

可变参数泛型方法

擦除

  • jvm并不认识泛型因此需要将泛型擦除。
  • ArrayList 和 ArrayList很容易被认为是不同类型。因为他们有不同的行为,但程序却认为他们是相同的,正是因为擦除的存在。
  • 擦除的结果就是把一个对象变为它的原生类
  • 泛型只是用来检查类型正确性,泛型部分的代码不会参与运行,这是由于泛型的擦除作用。
  • 泛型代码内部无法获得有关泛型参数的类型的信息。

泛型擦除到第一个边界

  • 上界 意思就是T 只能为HasF或者其子类。
  • 泛型只是在静态类型检查期间出现来验证类型正确性,程序一但运行后泛型都将被擦除,替换成他们的非泛型上界,
    如List被擦除为List,List被擦除为List, 擦除为

擦除动机

擦除使得泛化的代码变得具体,因此泛化客户端可以使用非泛化类库,非泛化客户端也可以使用泛化类库。

擦除的代价

泛型不能当做一个类去操作,如Foo<Cat> cat不能代表Cat这个类,因为它会被擦除为Object.

边界处的动作

  • 边界就是对象进入和离开方法的地方,编译期执行类型检查和插入转型代码就是在边界处。
  • 编译期执行了类型检查确保了数据一致性,在离开方法时由编译器为你插入转型代码执行转型,此时转型是安全的。
  • 由于擦除kind实际被存储为Class,因此创建数组无法后知道要转型成什么类型,因此必须强转。但创建容器就不需要强转了,编译期可以保证类型的一致性,如果类型不一致不通过编译。

边界

强制泛型可以使用什么类型
按边界类型调用方法其方法,无边界的只能调用从Objec继承的方法。

通配符

通配符可以允许某种类型向上转型,与普通边界对比:

List first = new ArrayList(); 只能使用T
List<? extends Fruit> first = new ArrayList(); //可以使用各种Fruit的子类。
List<? extends Fruit> 读作具有任何从Fruit继承的类型列表。

上界 ?是Fruit 的子类,但具体是什么不知道,因此当调用get方法时返回的对象可以赋值给Fruit引用,而add添加对象时由于不清楚具体要添加什么子类所以无法使用add方法。 超类型通配符 下界 也称 逆变?是Apple的父类,但具体是什么类型不得而知,因此当调用add方法添加对象时可以添加Apple和其子类对象,但调用get方法时无法确定要返回什么类型,因此不能调用get方法返回具体类型,只能返回Object。 无界通配符 与上下界之间的区别 - 一个方法的参数的类型如是 List ,List

,则可以接收任何形式的List参数,参数是不是泛型无所谓。

  • 参数的类型如果是List<? extends/super A > ,则只能接收泛型的List参数.
  • 如果参数的类型是 <?> 或者 <? extends A>,则该方法无法调用
  • 可以向上转型
  • 多个泛型参数下只有全为?时编译器无法与原生类区分,但只要有过一个参数不是?就会有所区分如Map<String, ?>必须传入map<String,?>类型的参数,而Map可以传入new HashMap();