0
点赞
收藏
分享

微信扫一扫

轻松掌握泛型

unadlib 2022-03-11 阅读 28

泛型

概念

在《Java编程思想》当中是这样说的:一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。也就是说,泛型是适用于多种类型的。把泛型作为了参数进行传递。

实例

泛型是将数据类型参数化,进行传递。可以将数据类型参数化,编译时自动进行类型检查和转换。

通过 Object 放入多种数据类型

在之前创建对象的时候,创建的 class 类只能放一种数据,如果使用泛型的话,就能放多种数据。也就是把类写成 Object 类型。代码如下:

class MyArray1 {
    public Object[] objects = new Object[10];

    public void set(int pos, Object val) {
        objects[pos] = val;
    }
    public Object get(int pos) {
        return objects[pos];
    }
}
public static void main(String[] args) {
    MyArray1 myArray = new MyArray1();
    myArray.set(0,"hello");
    myArray.set(1,10);
    //什么都能存进去,但是取出的时候要强制转换
    String str = (String) myArray.get(0);
}

如上面的代码,在放入数据的时候,什么都能放。但是取出的时候,就需要强制转换才可以,因为 get 拿到的是 Object 类型,所以要强制转换。

泛型写法

通过在 class 类后面加一个参数,来实现每次对数据的指定。代码如下:

class MyArray<T> {
    public T[] objects = (T[]) new Object[10];
    public void set(int pos, T val) {
        objects[pos] = val;
    }
    public T get(int pos) {
        return objects[pos];
    }
    public T[] getArray() {
        return objects;
    }
}
public static void main(String[] args) {
    MyArray<String> myArray = new MyArray();
    myArray.set(0,"hello");
    String str = myArray.get(0);
}

类名后的 <T> 代表占位符,来表示当前是一个泛型类,这里指定类型是 String ,也就是只能存放 String 类型的数据。但是简单类型(基本类型)不能做泛型的参数。输出的时候就不需要强制转换了,编译器会自动完成。如果放非 String 类型的话,就会报错:
在这里插入图片描述
通过观察编译的过程,可以发现所有的 <T> 都变成了 Object 数组,通过字节码文件查看。如下图:
在这里插入图片描述
也就是说,在编译过程当中,将所有的 T 替换为 Object 这种机制,就是:擦除机制。

泛型的上界

泛型可以继承,泛型所继承的就是泛型的上界。extends 又叫拓展。如果没有指定的话,泛型的上界就是 Object 。

class MyArray2<T extends Number> {
    public T[] objects = (T[]) new Object[10];
    public void set(int pos, T val) {
        objects[pos] = val;
    }
    public T get(int pos) {
        return objects[pos];
    }
    public T[] getArray() {
        return objects;
    }
}
public static void main(String[] args) {
    MyArray2<Integer> array1 = new MyArray2<>();
    MyArray2<Number> array2 = new MyArray2<>();
    //MyArray2<String> array3 = new MyArray2<>();//报错
    array1.set(0,10);
    //array1.set(1,12.5);//报错
    array2.set(0,10);
    array2.set(1,12.5);
}

这里 MyArray2 的上界就是 Number 所以,数组类型只能写 Number 类的,写 String 就会报错:
在这里插入图片描述
因为 array1 是 Integer 类型,所以存放小数的时候也会报错:
在这里插入图片描述

写泛型类,求出数组当中的最大值

在求数组最大值的时候,因为是泛型类,所以在传递的时候都是传的引用类型,所以要实现 Comparable 接口才可以进行比较大小。这里通过 Comparable 接口来实现上界进行比较。代码如下:

class Alg<T extends Comparable<T>> {
    public T findMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}
public static void main(String[] args) {
    Alg<Integer> alg1 = new Alg<>();
    Integer[] array = {1,2,3,4};
    System.out.println(alg1.findMax(array));
}

运行结果如下:
在这里插入图片描述

静态泛型方法

实现泛型的静态方法,通过静态方法来直接找到最大值。:

class Alg2 {
    public static  <T extends Comparable> T findMax(T[] array) {
        T max = array[0];
        for (int i = 0; i < array.length; i++) {
            if (max.compareTo(array[i]) < 0) {
                max = array[i];
            }
        }
        return max;
    }
}
public static void main9(String[] args) {
    Integer[] array = {1,2,3,4};
    System.out.println(Alg2.<Integer>findMax(array));
}

这里要注意的是,因为比较的时候,不是简单类型,是 Integer 类型,所以在定义数组的时候,不能用 int 这样的简单类型。因为是 static 修饰的,所以这里可以直接调用方法。运行结果如下:
在这里插入图片描述

泛型当中的父子类关系

理论上来说 Object 是所有类的父类,这些东西在编译完成的时候,都被擦除掉了,所以在 JVM 当中,也就不存在父子类关系了。

通配符

通配符 ? 用在泛型当中,是为了解决泛型无法完成协变的问题的。协变:指的就是如果 Student 是 Person 的子类,那么 List<Student> 也应该是 List<Person> 的子类。但是泛型是不支持这样的父子类关系的。如下示例,输出 list 当中的数据:
代码一

public static<T> void printList1(ArrayList<T> list) {
    for (T x:list) {
        System.out.println(x);
    }
}

此时代码的参数是 T ,此时的 T 是指定的一个泛型参数。
代码二

public static void printList2(ArrayList<?> list) {
    for (Object x:list) {
        System.out.println(x);
    }
}

代码二当中,使用了通配符 ? ,此时传入 printList2 的,具体是什么数据类型,是不知道的。就像如下代码,通过通配符来输出 list :

class Alg3 {
    public static void print2(ArrayList<?> list) {
        for (Object x:list) {
            System.out.println(x);
        }
    }
}
public static void main(String[] args) {
    ArrayList<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    Alg3.print2(list);
}

这里就是,不管传的是什么类型,输出那里都能匹配上。输出结果如下:
在这里插入图片描述

通配符的上界

通过通配符来获取到上界,也就是可以引用自己的子类。如下代码:

class up {
    ArrayList<Integer> arrayList1 = new ArrayList<>();
    ArrayList<Double> arrayList2 = new ArrayList<>();
    List<? extends Number> list = arrayList1;
    Number a = list.get(0);
}

因为 Number 是 Integer 和 Double 的父类,所以这里可以直接引用 arrayList1 .但是通配符的上界不适合写入。适合读取。 因为此时的 list 可以引用的子类对象很多,编译器无法确定具体的类型。编译器为了安全起见,只允许进行读取。

通配符的下界

使用 super 就是可以引用 “父类” 。

  • MyArrayList<? super Integer> 是 MyArrayList的父类类型
  • MyArrayList<?> 是 MyArrayList<? super Integer>的父类类型

下界是不允许读取的。允许写入。如果要一定要去读取的话,通过 Object 来读取。,代码示例:

class Person {

}
class Student extends Person{

}
class C extends Student {

}
public static void main(String[] args) {
    ArrayList<? super Person> arrayList1 = new ArrayList<Person>();
    arrayList1.add(new Person());
    arrayList1.add(new Student());
    arrayList1.add(new C());
    ArrayList<? super Person> arrayList2 = new ArrayList<>();
    arrayList2.add(new Person());
    arrayList2.add(new Student());
    Object o = arrayList1.get(0);
    System.out.println(o);
}

运行结果如下:
在这里插入图片描述
通过 Object 方法,就拿到了实例。

举报

相关推荐

0 条评论