泛型
概念
在《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 方法,就拿到了实例。