泛型的通配符

86 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天

一、泛型的上界

class Myclass<类型参数 extends 类型上界> {

}

类型的上界决定了泛型的范围。

class MyArray<T extends Number> {
    
}
public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();
        MyArray<Boolean> myArray1 = new MyArray<Boolean>();
    }

在这里插入图片描述 我们发现指定了泛型的上界为数值类Number时,传入Boolean类型就会报错。 如果没有指定类型的边界,可以认可 T extends Object,当指定了某个类型为上界,那么只接受某类型本身和子类型作为E的类型实参

上界为接口

我们要实现一个类去找数组的一个的最大值 在这里插入图片描述 这里报错了,因为我们这里是Object类型,如果是类型实参是对象的话不就能这样比较了,所以我们将上界定为Comparable接口。

class MyArray<T extends Comparable<T>> {
   
    public T findMax(T[] arr) {
        T max = arr[0];
        for (int i = 1; i < arr.length - 1; i++) {
            if (arr[i].compareTo(max) > 0) {
                max = arr[i];
            }
        }
        return max;
    }
}

我们来求一个Integer类型的最大值。

public static void main(String[] args) {
        MyArray<Integer> myArray = new MyArray<>();
        Integer[] arr = {1,4,6,3,2};
        System.out.println(myArray.findMax(arr));
    }

在这里插入图片描述 我们来求一个Person类的最大值。

在这里插入图片描述 我们发现使用Person作为类型实参,未法创建对象,因为我们将泛型上界设置为了实现了Comparable的类类型。

class Person implements Comparable<Person>{
    public int age;

    public Person(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(Person o) {
        return this.age - o.age;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                '}';
    }
}
public static void main(String[] args) {
        MyArray<Person> myArray = new MyArray<>();
        Person[] per = {new Person(15),new Person(18),new Person(20)};
        System.out.println(myArray.findMax(per));
    }

在这里插入图片描述

二、泛型方法

1 静态方法

刚刚我们实现的求某个类型数组的最大值必须得创建一个对象,那我们可以不创建对象直接调用吗? 在这里插入图片描述 我们直接给方法加上static时,系统报错了,因为static方法是在类加载指定,擦除机制是在编译时检查和强制转换,在类加载时,并不知道是什么类型,所以不能直接加static。 静态方法语法:

public static<泛型形参> T findMax(T[] arr)
class MyArray {
    public static<T extends Comparable<T>>  T findMax(T[] arr) {
        T max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i].compareTo(max) > 0) {
                max = arr[i];
            }
        }
        return max;
    }
}
public static void main(String[] args) {
        Integer[] arr = {1,5,4,7,9,2};
        Integer max = MyArray.findMax(arr);
        System.out.println(max);
    }

在这里插入图片描述 这样我们就可以不依赖对象求出最大值了,那类型实参在那里传? 在这里插入图片描述 一般在引用点的后面,一般可以省略。

2 普通方法

静态方法语法:

public <泛型形参> T findMax(T[] arr)
class MyArray {
    public <T extends Comparable<T>>  T findMax(T[] arr) {
        T max = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i].compareTo(max) > 0) {
                max = arr[i];
            }
        }
        return max;
    }
}
public static void main(String[] args) {
        Integer[] arr = {1,5,4,7,9,2};
        MyArray myArray = new MyArray();
        System.out.println(myArray.findMax(arr));
    }

在这里插入图片描述

三、通配符

什么是通配符:? 用于在泛型的使用

1 通配符的引入

class Data<T> {
    public T data;

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }
}
public static void main(String[] args) {
        Data<Integer> data = new Data<>();
        data.setData(18);
        print(data);
    }
    public static void print(Data<Integer> data) {
        System.out.println(data.getData());
    }

在这里插入图片描述 如果我们想输入类型实参为String类型呢?重写一个方法吗?支持重载吗? 在这里插入图片描述 我们当写入两种不同泛型类型的方法时,系统报错了,因为在运行阶段是没有泛型这个概念的,相当于他们的类型都是Data类型。 我们来验证一下,打印两种不同泛型实参类型的对象:

public static void main(String[] args) {
        Data<Integer> data = new Data<>();
        Data<String> data1 = new Data<>();
        System.out.println(data);
        System.out.println(data1);
    }

在这里插入图片描述 我们发现打印出来对象的类型是一样的。

2 通配符的使用

我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符"?"来处理

public static void main(String[] args) {
        Data<Integer> data = new Data<>();
        data.setData(10);
        print(data);
        Data<String> data1 = new Data<>();
        data1.setData("woyaojindachang");
        print(data1);
    }
    //此处?代表可以接受任意类型,但由于类型不确定,所以无法修改
    public static void print(Data<?> data) {
        System.out.println(data.getData());
    }

在这里插入图片描述

3 通配符的上界

? extends 类:设置泛型上限

class Data<T> {
    public T data;
    
    public T getData() {
        return this.data;
    }
    
    public void setData(T data) {
        this.data = data;
    }
}
public class Test {
    public static void main(String[] args) {
        Data<Integer> data = new Data<>();
        data.setData(10);
        print(data);
        Data<String> data1 = new Data<>();
        data1.setData("woyaojindachang");
        print(data1);//String不属于Number的子类
    }
    public static void print(Data<? extends Number> data) {
        System.out.println(data.getData());
    }
}

在这里插入图片描述 这里我们设置通配符的上界为Number,所以无法传入String。

在这里插入图片描述 因为此时通配符里可以接受任意类型,由于类型不确定,所以无法修改。

public static void print(Data<? extends Number> data) {
        Number number = data.getData();
    }

因为data存储的是Number和它的子类,所以可以进行读取数据。

4 通配符的下界

? super 类:设置泛型下限

public static void fun(Data<? super Integer> data) {
        data.setData(10);
    }

可以传入的实参的类型是Integer或者Integer的父类类型,此处可以修改,因为添加的是Integer或者它的父类。 在这里插入图片描述 但不能接收,因为无法确定是那个父类。

四、拆箱和装箱

手动装拆箱:

public static void main(String[] args) {
        //装箱
        Integer a = Integer.valueOf(10);
        Integer b = new Integer(10);
        //拆箱
        int c = a.intValue();
    }

每次手动装拆箱太过于麻烦,系统提供自动装拆箱。

public static void main(String[] args) {
        //自动装箱
        Integer a = 10;
        Integer b = (Integer)10;
        //自动拆箱
        int j = a;
        int k = (int)a;
    }

在这里插入图片描述 我们打开字节码看一下,发现系统自动在编译阶段调用了Integer.valueof和Integer.intValue方法

public static void main(String[] args) {
        Integer a = 127;
        Integer b = 127;
        Integer c = 200;
        Integer d = 200;
        System.out.println(a == b);
        System.out.println(c == d);
    }

经典面试题

上面这段代码会输出什么呢? 在这里插入图片描述 在这里插入图片描述 我们可以发现当数值在一定的范围内时,返回的是固定引用,只有超出范围才会new 新对象。 在这里插入图片描述 在这里插入图片描述 我们可以得出 i 是介于-128 - 127之间的。 在这里插入图片描述 cache数组存储着这些引用,并且final不可修改。 Byte、Short、Integer、Long这四种包装类型默认创建了[-128,127]的相应类型缓存数据,Character创建了数值在[0,127]范围的缓存数据,Boolean直接返回true或者false。