泛型
参与了孤尽大大的 DIY 班, 这一期的主题时泛型, 之前没有深入研究过泛型, 于是有了此篇的总结.
1. 简单的泛型类和接口
- public class GenericMemoryCell<AnyType>{
- public AnyType read(){
- return storedVal;
- }
- public void write(AnyType x){
- storedVal = x;
- }
- private AnyType storedVal;
- }
- public interface Comparable<>{
- public int comparaTo(AnyType other);
- }
从基础的泛型可以看出, 限制了类和接口的参数的类型. 这样一个直接的好处就是, 时以前只有在运行时报告的错误, 如今现在可以在编译时报错.
2. 菱形运算符
- // 通过 <> 可以省略, 自动匹配要生成的类的 type
- GenericMemoryCell<Integer> m=new GenericMemoryCell<>();
3. 带有限制的通配符
现在场景: totalArea 是一个计算图形面积的静态方法, Shape 是一个父类, Square,Circle 继承了 Shape 的父类.
- public static double TotalArea(Colletion<Shape> arr){
- double total=0;
- for(Shap s:arr){
- if(s!=null)
- total+=s.area();
- }
- return total;
- }
问题是, 如果是, 方法中传入, Colletion. 编译通过, 但是产生运行时错误. 为了解决这个问题, java5 采用了通配符表示参数类型的子类或者超类. Collection ,T IS-A Shape .
- public static double TotalArea(Colletion<?extends Shape> arr){
- double total=0;
- for(Shap s:arr){
- if(s!=null)
- total+=s.area();
- }
- return total;
- }
4. 泛型方法
定义泛型方法时, 必须在返回值前边加一个, 来声明这是一个泛型方法, 持有一个泛型 T, 然后才可以用 T 作为方法返回值
泛型方法要求的参数是 Class 类型, 而 Class.forName()方法的返回值也是 Class, 因此可以用 Class.forName()作为参数. 其中, forName()方法中的参数是何种类型, 返回的 Class 就是何种类型. 在本例中, forName()方法中传入的是 User 类的完整路径, 因此返回的是 Class 类型的对象, 因此调用泛型方法时, 变量 c 的类型就是 Class, 因此泛型方法中的泛型 T 就被指明为 User, 因此变量 obj 的类型为 User.
当然, 泛型方法不是仅仅可以有一个参数 Class, 可以根据需要添加其他参数.
为什么要使用泛型方法呢? 因为泛型类要在实例化的时候就指明类型, 如果想换一种类型, 不得不重新 new 一次, 可能不够灵活; 而泛型方法可以在调用的时候指明类型, 更加灵活.
- public static <T> boolean contains(T [] arr,T x){
- for(T val:arr){
- if(x.equals(val))
- return true;
- }
- return false;
- }
5. 类型界限
我们想编写一个 finMax 的程序, 比较的 AnyType 必须是实现 comparable 才可以比较. 下面的这种方法是不行的
- public static <AnyType> AnyType findMax(AnyType [] arr){
- int maxIndex=0;
- for(int i=0;i<arr.length;i++){
- if(arr[i].compareTo(arr[maxIndex]))
- maxIndex=i;
- }
- return arr[maxIndex];
- }
改写泛型, 需要比较的泛型是可以比较的. 所以就有
public static>
假设 Shape 实现了 Comparable,Square 继承了 Shape,Square 所继承的泛型是 Comparable, 并不是 Comparable, 但是这样也是可以的. 所以, 可以写成以下格式:
- public static <AnyType extends Comparable<? super AnyType>> AnyType findMax(AnyType [] arr){
- int maxIndex=0;
- for(int i=0;i<arr.length;i++){
- if(arr[i].compareTo(arr[maxIndex]))
- maxIndex=i;
- }
- return arr[maxIndex];
- }
6. 类型擦除
因为, 泛型是 java 语言中的成分, 而不是虚拟机中的结构. 所以泛型类需要经过虚拟机的类型擦出而变成非泛型类.
编译器生成一个与泛型类名相同的原始类. 类型变量使用它们原先的类型界限来代替.
7. 对于泛型的限制
由于类型擦除的存在, 所以必须遵从以下规则:
1. 基本类型不能做类型参数
2.instanceof 检测和类型转换工作只对原始类型进行.
这句话的理解, 就是编译时检测, 只对原始类型有用, 进行转换之后就不管用了.
- GenericMemoryCell<Integer> cell1=new GenericMemoryCell<>();
- cell1.write(4);
- Object cell=cell1;
- GenericMemoryCell<String> cell2=(GenericMemoryCell<String>) cell;
- String s=cell2.read;
3. 在一个泛型类中, static 方法和 static 域均不可引用类型变量, 因为再类型擦出后, 类型变量就不存在了.
4. 泛型类型的实例化
- // 非法, 如果 T 是一个类型变量
- T obj=new T();
- T [] arr=new T[10]
5. 参数化类型的数组
如下方案例, 正常情况, 存在类型检查, 但是类型擦除之后, cell 和数组 arr 里面的内容同样都是 GenericMemoryCell. 只有在实际存储的时候会出现, ClassCastException 错误.
- GenericMemoryCell<String> [] arr= new GenericMemoryCell<String> [10];
- GenericMemoryCell<Integer> cell1=new GenericMemoryCell<>();
- cell1.write(4);
- Object [] arr2=arr;
- arr2[0]=cell
- String s=arr2[0].read();
8. 函数对象
定义一个没有数据而只有一个方法的类, 并传递该类的一个实例. 事实上, 一个函数通过将其放在一个对象内部而被传递, 这样的对象叫做函数对象.
- public static <T> T findMax(T [] arr,Comparator<? super T> cmp){
- int maxIndex=0;
- for(int i=0;i<arr.length;i++){
- if(cmp.compare(arr[i], arr[maxIndex])>0)
- maxIndex=i;
- }
- return arr[maxIndex];
- }
- class CaseInsensitiveCompare implements Comparator<String>{
- public int compare(String lhs,String rhs){
- return lhs.compareToIgnoreCase(rhs);
- }
- }
- class TestProgram{
- public static void main(String [] args){
- String [] arr={"ZEBRA","ali","arc"}
- System.out.println(findMax(arr,new CaseInsensitiveCompare()));
- }
- }
- // 输出: ZEBRA 最后
来源: https://yq.aliyun.com/articles/694979