Java泛型理解
概述
在 Java5 以前,普通的类和方法只能使用特定的类型:基本数据类型或类类型,如果编写的代码需要应用于多种类型,这种严苛的限制对代码的束缚就会很大。
Java5 的一个重大变化就是引入泛型,泛型实现了参数化类型,使得你编写的组件(通常是集合)可以适用于多种类型。泛型的初衷是通过解耦类或方法与所使用的类型之间的约束,使得类或方法具备最宽泛的表达力。然而很快你就会发现,Java 中的泛型并没有你想的那么完美,甚至存在一些令人迷惑的实现
泛型类
促成泛型出现的最主要动机之一就是为了创建集合类,集合用于存放要使用到的对象。现有一个只能持有单个对象的类:
如果没有泛型,那么就必须明确指定其持有的对象的类型,会导致该复用性不高,它无法持有其他类型的对 象,我们当然不希望为每个类型都编写一个新类。
在 Java5 以前,为了解决这个问题,我们可以让这个类直接持有 Object 类型的对象,这样就可以持有多种不同类型的对象了。但通常而言,我们只会用集合存储同一类型的对象。泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。
所以,与其使用 Object,我们更希望先指定一个类型占位符,稍后再决定具体使用什么类型。由此我们需要使用类型参数,用尖括号括住,放在类名后面。然后在使用这个类时,再用实际的类型替换此类型参数
元组类库
有时一个方法需要能返回多个对象,而 return语句只能返回单个对象,解决的方法就是创建一个对象,用它来打包想要返回的多个对象。元组的概念正是基于此,元组将一组对象直接打包存储于单一对象中,可以从该对象读取其中元素,却不允许向其中存储新对象(这个概念也称数据传输对象或信使)
元组可以具有任意长度,元组中的对象可以是不同类型的,我们希望能为每个对象指明类型,这时泛型就派上用场了。例如下面是一个可以存储两个对象的元组:
使用 final 修饰成员变量可以保证其不被修改,如果用户想存储不同的元素,那么就必须创建新的 Tuple 对象。当然也可以允许用户重新对 a1、a2 赋值,但无疑前一种形式会更加安全
泛型方法
到目前为止,我们已经研究了参数化整个类,其实还可以参数化类中的方法。类本身是否是泛型,与它的方法是否是泛型并没有什么直接关系。我们应该尽可能使用泛型方法,通常将单个方法泛型化要比将整个类泛型化要更加清晰易懂
使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型,这称为类型参数推断,因此,对 f() 的调用看起来像普通的方法调用,而且像是被重载了无数次一样。
边界和通配符
由于擦除会删除类型信息,因此唯一可用于无限制泛型参数的方法是那些 Object 可用的方法。边界允许我们对泛型使用的参数类型施以类型,将参数限制为某类型的子集,那么就可以调用该子集中的方法。为了应用约束,Java 泛型使用了 extends 关键字
引入通配符可以在泛型实例化时更加灵活地控制,也可以在方法中控制方法的参数,具体语法如下:? extends T:表示 T 或 T 的子类
? super T:表示 T 或 T 的父类
?:表示可以是任意类型