泛型
泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数。使用泛型,意味着编写的代码可以被很多不同类型 的对象所重用。
泛型类
1 | public class ClassTest<T> { |
以上是一个简单的泛型类,T成为类型变量,一般使用大写字母命名。在Java中常用变量E表示集合的元素类型,K和V表示关键字与值的类型,T表示任意类型(约定俗成的用法,事实随便一个字母都行)。
当实例化泛型类型需要用具体类型替代类型变量
例如:
1 | ClassTest<String> one = new ClassTest<>(); |
泛型方法
泛型方法可以定义在普通类或泛型类中,与普通方法不同,泛型方法可以在调用它的时候定义类型变量。
例如 public static <T> T getStaticData(T data)
就是一个泛型方法,在方法的返回值前加上 <T>
,在调用时指定类型变量,如下:
1 | Integer staticData = ClassTest.getStaticData(9090); |
类型变量的限制
先看这个代码
1 | public static <T extends Comparable> boolean getMinData(T data){ |
之所以在定义泛型方法时给 <T>
继承 Comparable
接口,是因为 data
的类型无法确定,不能保证对象都有 compareTo
方法。
一个类型变量或通配符可以有多个限定,例如 T extends Comparable & Serializable
限定类型用“&”分隔,而逗号用来分隔类型变量。
在Java的继承中,可以根据需要拥有多个接口超类型,但限定中至多有一个类。如果用一个类作为限定,它必须是限定列表中的第一个。(core Java)
类型擦除
在虚拟机中没有泛型类型对象,所有对象都属于普通类。Java中的泛型基本上都是在编译器这个级别实现的,生成的字节码信息中是不包含泛型中的类型信息的。在定义一个泛型类型时, 都会提供一个删去类型参数后的原始类型,擦除类型变量,并替换为限定类型(无限定的变量用Object)。
例如以上的泛型类擦除类型后的原始类型:
1 | public class ClassTest { |
所以,不能存在如此两个方法,编译器会提示错误
1 | public String getFirtsFiled(T a){ |
通配符类型
在泛型操作中进行参数传递时泛型类型必须匹配才能传递,使用通配符来设置传递参数的类型
例子,其中Man是Peple的子类,不必关心实现:
1 | public class SubClass { |
当调用 sub.test(tt);
时发生错误,我们不能把一个 ClassTest<Man>
传递给这个方法, tt 的类型是 ClassTest<People>
,但定义 public void transfer(ClassTest<? extends Peple> p )
使用通配符后 sub.transfer(tt);
可以正确使用。
再看下面的两个错误,使用通配符后set方法和get方法显然为
1 | void setFirtsFiled(? extends Peple) |
编译器只知道要将 People
的子类型,但未具体指定,所有set方法会报错,而get方法就没这个问题,有点类似于多态的子类对象指定父类引用,返回一个 People
子类型没有问题。
通配符的超类限定
有 extends
来匹配子类,当然也有 super
来指定超类型限定,使用的意思刚好相反
1 | public class SubClass { |
transfer
方法允许使用通配符方式传进一个 ClassTest<Peple>
,因为 People
是 Man
的超类。下面的两个错误是因为此时不确定get方法返回的对象类型无法保证,只能把它赋给一个 Object ,而set方法可以使用任意 Man 对象或它的子类型调用它。