泛型
什么是泛型
1 | ArrayList是一种可变长数组,其内部使用的是Object类型 |
1 | 如果用上述ArrayList存储String类型,会有这么几个缺点: |
1 | ArrayList list = new ArrayList(); |
1 | 很容易出现ClassCastException,因为容易“误转型”: |
1 | 要解决上述问题,我们可以为String单独编写一种ArrayList: |
1 | 问题暂时解决。 |
1 | 为了解决新的问题,我们必须把ArrayList变成一种模板:ArrayList<T>,代码如下: |
1 | 因此,泛型就是定义一种模板,例如ArrayList<T>,然后在代码中为用到的类创建对应的ArrayList<类型>: |
向上转型
1 | 在Java标准库中的ArrayList<T>实现了List<T>接口,它可以向上转型为List<T>: |
1 | 要特别注意:不能把ArrayList<Integer>向上转型为ArrayList<Number>或List<Number>。 |
1 | 我们把一个ArrayList<Integer>转型为ArrayList<Number>类型后,这个ArrayList<Number>就可以接受Float类型,因为Float是Number的子类。但是,ArrayList<Number>实际上和ArrayList<Integer>是同一个对象,也就是ArrayList<Integer>类型,它不可能接受Float类型, 所以在获取Integer的时候将产生ClassCastException。 |
总结
1 | 泛型就是编写模板代码来适应任意类型; |
使用泛型
1 | 使用ArrayList时,如果不定义泛型类型时,泛型类型实际上就是Object: |
1 | 当我们定义泛型类型<String>后,List<T>的泛型接口变为强类型List<String>: |
1 | 当我们定义泛型类型<Number>后,List<T>的泛型接口变为强类型List<Number>: |
1 | 编译器如果能自动推断出泛型类型,就可以省略后面的泛型类型。例如,对于下面的代码: |
泛型接口
1 | 除了ArrayList<T>使用了泛型,还可以在接口中使用泛型。例如,Arrays.sort(Object[])可以对任意数组进行排序,但待排序的元素必须实现Comparable<T>这个泛型接口: |
1 | 可以直接对String数组进行排序: |
1 | 这是因为String本身已经实现了Comparable<String>接口。如果换成我们自定义的Person类型试试: |
1 | 运行程序,我们会得到ClassCastException,即无法将Person转型为Comparable。我们修改代码,让Person实现Comparable<T>接口: |
1 | 运行上述代码,可以正确实现按name进行排序。 |
总结
1 | 使用泛型时,把泛型参数<T>替换为需要的class类型,例如:ArrayList<String>,ArrayList<Number>等; |
编写泛型
1 | 编写泛型类比普通类要复杂。通常来说,泛型类一般用在集合类中,例如ArrayList<T>,我们很少需要编写泛型类。 |
1 | 然后,标记所有的特定类型,这里是String: |
静态方法
1 | 编写泛型类时,要特别注意,泛型类型<T>不能用于静态方法。例如: |
1 | 上述代码会导致编译错误,我们无法在静态方法create()的方法参数和返回类型上使用泛型类型T。 |
1 | 对于静态方法,我们可以单独改写为“泛型”方法,只需要使用另一个类型即可。对于上面的create()静态方法,我们应该把它改为另一种泛型类型,例如,<K>: |
多个泛型类型
1 | 泛型还可以定义多种类型。例如,我们希望Pair不总是存储两个类型一样的对象,就可以使用类型<T, K>: |
总结
1 | 编写泛型时,需要定义泛型类型<T>; |
擦拭法
1 | 泛型是一种类似”模板代码“的技术,不同语言的泛型实现方式不一定相同。 |
1 | 而虚拟机根本不知道泛型。这是虚拟机执行的代码: |
1 | 使用泛型的时候,我们编写的代码也是编译器看到的代码: |
1 | 所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。 |
1 | 局限二:无法取得带泛型的Class。观察以下代码: |
1 | 因为T是Object,我们对Pair<String>和Pair<Integer>类型获取Class时,获取到的是同一个Class,也就是Pair类的Class。 |
1 | 局限三:无法判断带泛型的类型: |
1 | 局限四:不能实例化T类型: |
1 | 要实例化T类型,我们必须借助额外的Class<T>参数: |