泛型..

news2024/10/5 15:33:04

1.泛型

所谓泛型 在类定义处是一种类型参数(我们平常所见到的参数指的就是方法中的参数 他接收有外界传递来的值 然后在方法中进行使用) 在类内部的话 则充当一种占位符 并且还提高了代码的复用率
何以见得提高了代码的复用率 其实就是通过对比使用了泛型技术和没有使用泛型技术之间的区别:
以下是没有使用泛型技术的案例

public class Student1 {
    // 第一个学生类他的分数接收的是一个浮点类型的数据
    double score;
    // 定义getter、setter方法
    void setScore(double score){
        this.score = score;
    }
    double getScore(){
        return score;
    }
}
public class Student2 {
    // 这个学生类接收的是字符串类型的分数
    String score;
    // 定义getter、setter方法
    void setScore(String score){
        this.score = score;
    }
    String getScore(){
        return score;
    }
}
public class Student3 {
    // 第三个学生类的分数就定义为整型
    int score;
    // 定义相关的getter、setter方法
    void setScore(int score){
        this.score = score;
    }
    int getScore(){
        return score;
    }
}

从以上代码中我们可以看出 定义了三种不同类型的分数 就要定义三份逻辑相似的代码 如果分数的类型规模一旦庞大起来 那么其实效率是十分低下的
现在引入了泛型技术以后 就不会出现以上这种局面 并且会提高同一份代码的复用率

public class Student<T>{
    // 泛型是一种类型参数 有外界的实参所决定 并且可以在类中使用
    T score;
    // 定义一个getter、setter方法
    void setScore(T score){
        this.score = score;
    }
    T getScore(){
        return score;
    }
}

我们可以在主函数中测试一下这个代码的正确性

public class Main {
    public static void main(String[] args) {
        // 我们来试用一下泛型好不好用吧
        Student<String> s1 = new Student<>();
        Student<Integer> s2 = new Student<>();
        Student<Double> s3 = new Student<>();
        s1.setScore("优秀");
        s2.setScore(100);
        s3.setScore(99.5);
        System.out.println(s1.getScore());
        System.out.println(s2.getScore());
        System.out.println(s3.getScore());
    }

}

从结果可以看出 这个泛型类型是写的很成功的 并且从中我们还能够得知 从java7开始 右边<>内就可以不用写出具体的泛型了

而且我们的泛型名称是有命名建议的:
T Type
E Element
K Key
N Number
V Value
S、U、V 当你想要表示连续类型中的第二个、第三个、第四个泛型的时候的命名建议

类名后面不仅可以支持一个泛型 也可以支持多个泛型

public class Student<N, S>{
    private N no;
    private S score;
    public Student(N no, S score){
        this.no = no;
        this.score = score;
    }
}
public class Main {
    public static void main(String[] args) {
        Student<Integer, Double> s = new Student<>(1, 100.0);
    }
}

何为泛型类型?其实就指的是哪些使用了泛型的类或者接口
比如一些诸如java.util.Comparator、java.util.Comparable之类的接口

2.泛型类型的继承

我们从前面的多态都知道 如果类或者接口之间存在父子关系的话 那么让父类引用指向子类对象是可以编译成功的(子类引用也可以指向父类对象 前提是要进行强制转换)
以下的案例中 从结果可以明显发现strBox和intBox不存在父子关系 objBox和strBox之间不存在父子关系
有些人可能会以泛型的继承关系来作为泛型类型的继承关系 那好 我们可以超这个方向继续往下想 如果结论果真是如此的话 那么第一对关系肯定不符合 因为String和Integer压根不存在父子关系 只有第二对中的String和Object之间存在父子关系 好 接着往下想 如果是父子关系的话 多态是可以编译通过 也就是说 strBox和objBox指向的是同一个对象 然后我通过objBox设置了所指对象的element值 他的类型为Object类型 然后通过strBox获取所指对象的element值 并且通过一个String变量进行获取 但是我们可以清楚的发现由于之前element已经被设置为Object类型 所以导致Object赋值给了String 其实如果没有强制转换的话 这个语句是不能通过编译的 但是实际上这个语句是编译通过的 所以说我们刚才的strBox和objBox之间是不存在父子关系的

public class Main {
    public static void main(String[] args) {
        Box<String> strBox = new Box<>();
        Box<Integer> intBox = new Box<>();
        Box<Object> objBox = new Box<>();
        strBox = intBox;// error
        objBox = strBox;// error
        // 如果上面代码是正确的话 那么思考一下下面的代码
        objBox.setElement(new Object());
        String str = strBox.getElement();
    }
}

所以说下面这张图其实清楚的阐明了Number和Integer以及Box之间的关系
在这里插入图片描述

接着一个案例
在这里插入图片描述
在这里插入图片描述

我们都知道jdk中存在这这样如图所示的这样一种关系
由于上述各种接口或者类之间存在这父子关系 所以以下的代码其实都是可以编译通过的

public class Main {
    public static void main(String[] args) {
        Iterable<String> it = null;
        Collection<String> col = null;
        List<String> li = null;
        ArrayList<String> al = null;
        it = col;
        col = li;
        li = al;
    }
}

但是如果是以下代码的话 就不能够编译通过了
假设我们的List和ArrayList都是自定义的(就是非jdk源码 是允许的) 并且他们类或者接口的结构是一样的 那么如果按照刚才那个反例来看的话 那么list和al是父子关系 那么list和al所指对象是一致的(假设list的泛型为Object al的泛型是String) 并且我通过list设置所指对象的no 这个no就是Object类型的 然后通过al获取所指对象的no 并且通过String变量保留 而Object是不能够在没有强制转换的前提下完成这个赋值的 但是实际上这个赋值操作是允许的 所以说list和al其实不是父子关系

public class Main {
    public static void main(String[] args) {
        List<Object> list = null;
        ArrayList<String> al = null;
        list = al;// error
    }
}

我可以总结一下就是说
当泛型不一致的话 那么泛型类型就一定不存在父子关系
当泛型一致的话 那么泛型类型也不一定存在父子关系 只有当泛型类型是有继承或者实现关系的话 那么泛型类型才有父子关系

前面说到泛型可以支持连续多个的 当我们两个类或者接口中的第一个泛型是一致的 并且子类可以存在第二个泛型的时候 那么这时候子类和父类之间是存在父子关系的

public interface MyList<E, T> extends List<E> {
    void setNo(T no);
}
public class Main {
    public static void main(String[] args) {
        List<String> li = null;
        MyList<String, Integer> ml1 = null;
        MyList<String, Double> ml2 = null;
        MyList<String, String> ml3 = null;
        li = ml1;
        li = ml2;
        li = ml3;
    }
}

从结果显示 编译是通过的 说明其实上述的这种操作是完全被允许的

3.原始类型

何为原始类型 其实就是没有为泛型提供一个具体的类型
在这里插入图片描述
从上图中 我们想说的是:
当我们使用了原始类型的时候 那么编译器就会警告我们这是一个原始类型 发出rawtypes警告
当我们将非原始类型赋值给原始类型的时候 编译器是很正常的
但是当我们将原始类型赋值给非原始类型的时候 编译器会发出unchecked警告
我们对于以上这些由编译器发出的警告可以通过@SuppressWarnings这个注解进行消除

public class Box<T>{
    private T no;
    public T getNo(){
        return no;
    }
    public void setNo(T no){
        this.no = no;
    }
}
public class Main {
    public static void main(String[] args) {
        Box box = new Box();
        box.setNo(new Object());
        Object o = box.getNo();
    }
}

通过上面这段代码 我想说的是如果是原始类型的话 那么当我们进行getter和setter方法的时候 所涉及到的no都是Object类型的 那么我们可能会有一个疑惑 就是这不是和泛型为Object的泛型类型一样吗 那么这两个等价吗 其实他们两者是有着本质的区别的:
一个是原始类型 一个是非原始类型 只是原始类型的getter和setter方法中涉及到的no都是Object类型的

4.泛型方法

使用了泛型的方法 但是这边有一个误区是 在泛型类型中 如果方法的返回值或者参数为泛型的话 那么不是泛型方法 真正我们去判断一个类或者一个方法是否为泛型类型或者泛型方法的依据是:他们的声明位置处是否有< T>标志 泛型方法的标志一般都是写在返回值之前

现在有一个需求是:
我们想要将setter方法为学生对象赋值的操作封装在一个方法中

public class Student<N, S>{
    private N no;
    private S score;
    public N getNo(){
        return no;
    }
    public void setNo(N no){
        this.no = no;
    }
    public S getScore(){
        return score;
    }
    public void setScore(S score){
        this.score = score;
    }
}
public class Main {
    public static void main(String[] args) {
        Student<Integer, Integer> stu1 = new Student<>();
        int no1 = 1;
        int score1 = 90;
        set(stu1, no1, score1);
        Student<String, String> stu2 = new Student<>();
        String no2 = "2";
        String score2 = "100";
        set(stu2, no2, score2);// error
    }
    public static void set(Student<Integer, Integer> stu, Integer no, Integer score){
        stu.setNo(no);
        stu.setScore(score);
    }
    public static void set(Student<String, String> stu, String no, String score){
        stu.setNo(no);
        stu.setScore(score);
    }
}

set(stu2, no2, score2)立马编译失败 这是因为我们set方法中的泛型位置处写死了 不能够接收任意的类型 所以我们应该将set方法设置为泛型方法

public class Main {
    public static void main(String[] args) {
        Student<Integer, Integer> stu1 = new Student<>();
        int no1 = 1;
        int score1 = 90;
        set(stu1, no1, score1);
        Student<String, String> stu2 = new Student<>();
        String no2 = "2";
        String score2 = "100";
        set(stu2, no2, score2);
        System.out.println(stu1.getNo());
        System.out.println(stu2.getNo());
    }
    public static <N, S> void set(Student<N, S> stu, N no, S score){
        stu.setNo(no);
        stu.setScore(score);
    }
}

从结果来看 这种做法的确完成了我们的需求
但是其实我们想使用泛型类型一样 我们在使用泛型方法的时候 也可以写明这个方法所需的泛型

public class Main {
    public static void main(String[] args) {
        Student<Integer, Integer> stu1 = new Student<>();
        int no1 = 1;
        int score1 = 90;
        Main.<Integer, Integer>set(stu1, no1, score1);
        Student<String, String> stu2 = new Student<>();
        String no2 = "2";
        String score2 = "100";
        <String, String>set(stu2, no2, score2);// error
        System.out.println(stu1.getNo());
        System.out.println(stu2.getNo());
    }
    public static <N, S> void set(Student<N, S> stu, N no, S score){
        stu.setNo(no);
        stu.setScore(score);
    }
}

但是<String, String>set(……);这种写法肯定是编译失败的
就算你不写完整版本的泛型方法 那么编译器也会自动推断出泛型方法调用时的泛型类型

以下是一个案例:
在这里插入图片描述
这个案例中 我们在声明addBox方法的时候 没有写死泛型 所以我们可以不用写出针对不同类型组合的多份类似代码 而是通过引入泛型技术做到代码复用率的提高

1.类型推断

前面我们在调用类型方法的时候 有简化版本和完整版本 我们知道 简化版本编译器会根据你传递的参数类型来获悉你的泛型具体是什么 但是如果一个类型方法不需要传递参数的话 那么编译器应该依据什么推断出泛型方法的泛型呢 其实除了根据参数得知泛型 也可以根据所调用的方法的返回值来获悉
在这里插入图片描述

2.构造方法

除了普通方法可以使用泛型技术以外 构造方法其实也可以使用这项技术
在这里插入图片描述
可以看到 其实我也没有写明传递给泛型的具体类型是什么 编译器会自动根据我传递的参数类型进行推断 得到具体的泛型

3.细节

在泛型类型中 静态方法是不能够使用来自泛型类型的泛型的 原因在于:
静态方法可以不通过实例调用 但是泛型是需要通过创建实例来进行具体赋值的 泛型没有具体的值 那么你就无法在类中去使用这个泛型 所以泛型其实和实例是相关联的 而静态方法可以不和实例关联

但是你非要在静态方法中使用泛型的话 那么你可以将这个方法升级为泛型方法 那么无论你在调用的时候 写的是完整版本还是简化版本 编译器都可以知道你泛型的具体类型 你就可以安心在静态方法中去使用这个泛型了

5.限制类型参数

我们可以利用extends对类型参数进行设限 比如< T extends A>
extends可以后接接口名或者类名 代表T必须是A类型或者继承、实现A
在这里插入图片描述
以上的案例中 我们可以传递Integer给类型参数 但是不能够传递String 而且当我们传递Integer后 可以调用父类的实例方法intValue

而且不仅仅可以添加一个限制 还可以添加多个限制
比如<T extends A & B & C>

但是多个限制存在一些细节:
1.和普通的继承一样 他仅支持单继承 也就是只能够继承一个类
2.和普通的接口一样 他支持多实现 也就是他能够实现多个接口
3.和普通的继承必须写在实现之前一样 他也遵循这个规定
4.不能够写成<T extends A | B | C>这种形式的 因为如果同时满足了A和B的话 可能会产生歧义(因为A | B | C限制了T只能满足其中一种 那么如果同时满足了两种的话 那么到底是满足哪一种呢 这是存在歧义的)

现在有这么一个需求:
就是想要获取一个任意类型的数组中的最大值 但是如果想要获取最大值的话 那么这个类型必须具备可比较性 也就是有这自定义的比较逻辑 所以说这个类型必须实现Comparable接口

public class Student implements Comparable<Student>{
    private int age;
    public Student(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "[" + age + "]";
    }

    @Override
    public int compareTo(Student o) {
        // 但是如果参数为空的话 那么一定说明调用者比较大
        if(o == null)return 1;
        return age - o.age;// 如果返回值如此的话 那么说明年龄大的Student较大
    }
}
public class Main {
    public static void main(String[] args) {
        Integer[] is = {11, 22, 33};
        System.out.println(getMax(is));// 33
        Double[] ds = {11.1, 22.2, 33.3};
        System.out.println(getMax(ds));// 33.3
        Student[] ss = {new Student(11), new Student(22), new Student(33)};
        System.out.println(getMax(ss));// 第三个Student类
    }
    // 要获取各种类型的数组中的最大值的话 那么需要定义泛型方法才行
    static <T extends Comparable<T>> T getMax(T[] array){
        // 如果数组为空或者数组长度为0的话 那么就不执行任何操作
        if(array == null || array.length == 0)return null;
        // 设置最大值的初始值为数组首元素
        T max = array[0];
        // 然后和剩余的元素挨个比较 如果元素有较大值的话 那么就更新max值
        for(int i = 1; i < array.length; ++i){
            // 如果当前的元素为空的话 那么就继续比较下一个元素
            if(array[i] == null)continue;
            // 如果当前元素大于max值的话 那么就更新max值
            if(array[i].compareTo(max) > 0)max = array[i];
        }
        return max;
    }
}

而且需要注意的一点是:你不能够将max值作为调用者 因为如果首元素是空的话 那么最后最大值也只能够是空 即使你有更大的值的话

但是如果你的元素没有提供比较逻辑的话 那么Arrays.sort是无法对你的数组进行比较的

public class Student{
    private int age;
    public Student(int age){
        this.age = age;
    }

    @Override
    public String toString() {
        return "[" + age + "]";
    }
}
public class Main {
    public static void main(String[] args) {
        Student[] ss = {new Student(11), new Student(22), new Student(33)};
        Arrays.sort(ss);
        System.out.println(Arrays.toString(ss));
    }
}

结果会抛出ClassCastException异常 因为你的元素没有具备可比较性 换句话说就是你的元素类型没有实现Comparable接口 继而没有实现其中的compareTo方法

但是由于我的Arrays.sort方法可以接收另外一个参数 换做比较器 通过比较器其实我们也可以在元素自身没有具备可比较性的基础上是元素具备可比较性 就是设置Comparator接口中的compare方法

public class Student{
    private int age;
    public Student(int age){
        this.age = age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
    @Override
    public String toString() {
        return "[" + age + "]";
    }
}
public class Main {
    public static void main(String[] args) {
        Student[] ss = {new Student(11), new Student(22), new Student(33)};
        Arrays.sort(ss, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.getAge() - o2.getAge();
            }
        });
        System.out.println(Arrays.toString(ss));
    }
}

从打印结果来看 其实他也可以完成对我们的Student数组的排序操作 而且排序结果为升序排序

并且我们如果同时定义了两套排序规则 分别来源于自身具备的比较逻辑和比较器中的比较逻辑 那么后者会覆盖前者 称为我们比较过程中的比较逻辑

public class Student implements Comparable<Student>{
    private int age;
    public Student(int age){
        this.age = age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
    @Override
    public String toString() {
        return "[" + age + "]";
    }

    @Override
    public int compareTo(Student o) {
        if(o == null)return 1;
        return age - o.age;
    }
}
public class Main {
    public static void main(String[] args) {
        Student[] ss = {new Student(11), new Student(22), new Student(33)};
        Arrays.sort(ss, new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o2.getAge() - o1.getAge();
            }
        });
        System.out.println(Arrays.toString(ss));
    }
}

从打印结果来看 可知比较器的比较逻辑确实覆盖了自身具备的比较逻辑

并且如果采用的是比较器的方式的话 那么可以继续简化 原因在于Comparator接口是一个函数式接口 所以我们可以将匿名类简化成Lambda表达式

public class Student implements Comparable<Student>{
    private int age;
    public Student(int age){
        this.age = age;
    }
    public void setAge(int age){
        this.age = age;
    }
    public int getAge(){
        return age;
    }
    @Override
    public String toString() {
        return "[" + age + "]";
    }

    @Override
    public int compareTo(Student o) {
        if(o == null)return 1;
        return age - o.age;
    }
}
public class Main {
    public static void main(String[] args) {
        Student[] ss = {new Student(11), new Student(22), new Student(33)};
        Arrays.sort(ss, (o1, o2) -> o2.getAge() - o1.getAge());
        System.out.println(Arrays.toString(ss));
    }
}

但是如果我现在的需求改动了一下 变成了数组元素是Student类 但是他比较逻辑中比较的对象是泛型 那么应该怎么重置Student类
现在的年龄变成了泛型的话 为了使得所有类型都有比较逻辑 具有可比较性 不止是那些内置的Integer、Double有比较逻辑 我们需要重置一下比较逻辑 也就是让泛型实现Comparable 并且要让我们的比较逻辑更为健壮一点 就是增加判空操作

public class Student<T extends Comparable<T>> implements Comparable<Student<T>>{
    private T age;
    public Student(T age){
        this.age = age;
    }
    public void setAge(T age){
        this.age = age;
    }
    public T getAge(){
        return age;
    }
    @Override
    public String toString() {
        return "[" + age + "]";
    }

    @Override
    public int compareTo(Student<T> o) {
        if(o == null)return 1;
        if(age != null)return age.compareTo(o.age);
        return o.age == null ? 0 : -1;
    }
}
public class Main {
    public static void main(String[] args) {
        Student[] ss = {new Student<>(11), new Student<>(22), new Student<>(33)};
        System.out.println(Arrays.toString(ss));
    }
}

6.通配符

在泛型中 ?表示通配符
通配符用作变量类型、返回值类型的类型参数
而且不能用作泛型方法调用、泛型类型实例化、泛型类型定义时的类型参数、泛型方法声明时的类型参数

public class Main {
    public static void main(String[] args) {
        Main.<?>test();// error
    }
    public static <T> void test(){
        
    }
}
public class Person<T> {
}
public class Main {
    public static void main(String[] args) {
        Person<?> p = new Person<?>();// 左边允许 右边error
    }
}
public class Person<?> {// error
    
}

1.通配符–上界

上界 顾名思义就是:保证<? extends xxx>中?是xxx或者xxx的子类型
我们可以通过extends设置参数的上界

现在我有一个这样的需求:
我想要设置一个参数 能够接收不同的泛型类型

public class Main {
    public static void main(String[] args) {
        Box<Integer> box1 = new Box<>();
        Box<String> box2 = new Box<>();
        Box<Object> box3 = new Box<>();
        showBox(box1);// error
        showBox(box2);// error
        showBox(box3);// ok
    }
    public static void showBox(Box<Object> box){
        
    }
}

可以看到 上述解决方案肯定是不可取的 因为不能因为泛型存在父子关系 就简单得出泛型类型存在父子关系 这其实是错的

public class Main {
    public static void main(String[] args) {
        Box<Integer> box1 = new Box<>();
        Box<String> box2 = new Box<>();
        Box<Object> box3 = new Box<>();
        showBox(box1);// ok
        showBox(box2);// ok
        showBox(box3);// ok
    }
    public static <T> void showBox(Box<T> box){

    }
}

将静态方法升级为泛型方法 这就可以完成上述需求了

public class Main {
    public static void main(String[] args) {
        Box<Integer> box1 = new Box<>();
        Box<String> box2 = new Box<>();
        Box<Object> box3 = new Box<>();
        showBox(box1);// ok
        showBox(box2);// ok
        showBox(box3);// ok
    }
    public static void showBox(Box<?> box){

    }
}

同样的 采用通配符的方式也能够完成接收不同泛型类型的需求

通配符和一般的泛型是有区别的:

public class Main {
    public static void main(String[] args) {
        Box<Integer> box1 = new Box<>();
        Box<String> box2 = new Box<>();
        Box<Object> box3 = new Box<>();
        Box<? extends Number> box4 = new Box<>();
        Box<Number> box5 = new Box<>();
        box4 = box1;// ok
        box5 = box1;// error
    }
}

从中我们可以知道使用通配符可以定义一些模糊的类型 但是泛型的话 必须是具体化的东西才行 正是通配符的这个特性 使得上述语句成立

现在还有一个需求:
就是利用通配符完成求和操作 并且参与求和的元素必须都是数值才行 可以是浮点数 也可以是整数(只要你的类型是Number的子类型或者同等类型的话 就可以保证这是数值)

public class Main {
    public static void main(String[] args) {
        List<Integer> is = Arrays.asList(11, 22, 33);
        System.out.println(sum(is));// 66.0
        List<Double> ds = Arrays.asList(11.1, 22.2, 33.3);
        System.out.println(sum(ds));// 66.6
    }
    public static double sum(List<? extends Number> list){
        double sum = 0.0;
        for(Number num: list){
            sum += num.doubleValue();
        }
        return sum;
    }
}

2.通配符–下界

下界 顾名思义就是<? super xxx>中?必须是xxx或者xxx的父类型
我们可以通过super设置参数的下界

public class Main {
    public static void main(String[] args) {
        List<Integer> l1 = null;
        List<Number> l2 = null;
        List<? super Integer> l3 = null;
        List<? super Number> l4 = null;
        testLower(l1);// ok
        testLower(l2);// ok
        testLower(l3);// ok
        testLower(l4);// ok
    }
    public static void testLower(List<? super Integer> list){
        
    }
}

我们现在有一个需求:
向数组中添加元素

public class Main {
    public static void main(String[] args) {
        List<Integer> is = new ArrayList<>();
        addNumber(is);
        System.out.println(is);
        List<Number> ns = new ArrayList<>();
        addNumber(ns);
        System.out.println(ns);
    }
    public static void addNumber(List<? super Integer> list){
        for(int i = 0; i < 10; ++i){
            list.add(i);
        }
    }
}

从结果可以知道 符合预期

3.通配符–无限制

?什么类型都可以传递

public class Main {
    public static void main(String[] args) {
        Box<Integer> box1 = null;
        Box<String> box2 = null;
        Box<Double> box3 = null;
        Box<Object> box4 = null;
        Box<? extends Object> box5 = null;
        Box<? super Object> box6 = null;
        test(box1);// ok
        test(box2);// ok
        test(box3);// ok
        test(box4);// ok
        test(box5);// ok
        test(box6);// ok
    }
    public static void test(Box<?> box){
        
    }
}

4.通配符–继承

这个图的记忆方式:就是小范围继承自大范围
在这里插入图片描述

5.通配符–注意

现在有一个需求:
就是打印任意类型的List中的所有元素

public class Main {
    public static void main(String[] args) {
        List<Integer> is = Arrays.asList(11, 22, 33);
        printList(is);// ok
        List<Double> ds = Arrays.asList(11.1, 22.2, 33.3);
        printList(ds);// ok
    }
    public static void printList(List<?> list){
        for(Object obj: list){
            System.out.print(obj + " ");
        }
        System.out.println();
    }
}

结果显示 可以满足需求

编译器有一个特点就是 他在编译的过程中必须要知道类型参数的确切值 否则就会报错
所以他在解析List< E>.set(int index, E element)的时候 由于E位置处为通配符? 无法确定E的真实类型 所以报错了
但是代码提示处这个E他提示为Object类型 这其实是因为他不知道这个具体的类型是什么 但是事实是不管是什么类型 都是Object类型 只是具体就是不知道属于什么类型

public class Main {
    public static void main(String[] args) {
        
    }
    void foo(List<?> list){
        Object obj = list.get(0);// ok
        list.set(0, obj);// error
        list.set(0, list.get(0));// error
    }
}

但是你实在是想要foo方法中的内容全部都编译通过的话 有一种做法就是定义一个结构类似的泛型方法 然后让foo调用他即可

public class Main {
    public static void main(String[] args) {

    }
    void foo(List<?> list){
        fooHelper(list);
    }
    <T> void fooHelper(List<T> list){
        T obj = list.get(0);
        list.set(0, obj);
        list.set(0, list.get(0));
    }
}

还有一个细节就是平常的类型参数是不能使用super关键字 只能使用extends 只有通配符才可以同时使用super以及extends两个关键字

7.泛型的使用限制

1.泛型不能够传递基本类型 只能够使用引用类型

public class Main {
    public static void main(String[] args) {
        Map<Character, Integer> map = new HashMap<>();// ok
        Map<char, int> map2 = new HashMap<>();// error
    }
}

2.泛型不能够实例化

public class Student<E> {
    public void add(){
        E element = new E();// error
    }
}

但是如果非要实例化 也不是不行 可以通过Class.newInstance间接做到实例化需求

public class Student<E> {
    public void add(Class<E> cls) throws Exception {
        E e = cls.newInstance();// ok
    }
}

我们从源码中可以看出newInstance的返回值是Class的泛型E 但是具体的原理我是不知情的???
3.不能够定义类型为类型参数的静态变量(也不能用于静态方法上 原因是一致的)
原因在于:静态变量可以不和实例挂钩 但是类型参数必须和实例挂钩

public class Student<E> {
    private static E age;// error
}

4.不能创建带有泛型的数组

public class Main {
    public static void main(String[] args) {
        Student<Integer>[] ss = new Student<>[4];// error
        Student<Integer>[] ss2 = new Student[4];// ok
    }
}

5.下面的重载是不允许的

public class Main {
    public static void main(String[] args) {
        Student<Integer>[] ss = new Student<>[4];// 右边error 但是左边ok
    }
    public static void test(Student<? extends String> box){
        
    }
    public static void test(Student<? extends Integer> box){

    }
}
public class Main {
    public static void main(String[] args) {
        Student<Integer>[] ss = new Student<>[4];// 右边error 但是左边ok
    }
    public static void test(Student<String> box){

    }
    public static void test(Student<? extends Integer> box){

    }
}
public class Main {
    public static void main(String[] args) {
        Student<Integer>[] ss = new Student<>[4];// 右边error 但是左边ok
    }
    public static void test(Student<String> box){

    }
    public static void test(Student<Integer> box){

    }
}

6.不能够自定义带反省的异常

public class MyException<T> extends Exception{//error
    
}

7.catch的异常类型不能使用类型参数

public class MyException<T>{
    void add(){
        try{
            System.out.println("add");
        }catch(T t){// error
            System.out.println("哈哈");
        }
    }
    <E> void remove(){
        try{
            System.out.println("remove");
        }catch(E e) {// error
            System.out.println("呵呵");
        }
    }
}

8.抛出的异常可以是类型参数

public class MyException<T extends Exception>{
    public void test() throws T{// ok
        
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1412454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS2019下各种报错合集(持续更新)

VS2019下的各种报错处理(长期更新)&#xff0c;欢迎大家在评论区补充错误代码/描述 解决方案&#xff01;&#xff01;&#xff01; 1、printf 代码运行到printf函数打印不出来内容&#xff0c;打断点之后&#xff0c;f10走过去&#xff0c;程序直接运行起来了&#xff0c;而…

Linux下串口編程遇到的接收数据错误及原因(

近日在调试串口的时候发现&#xff0c;另一设备向我ARM板的串口发送0x0d&#xff0c;我接收之后变成了0x0a&#xff0c;这是问题一&#xff1b;另外当对方向我发送一串数据&#xff0c;如果其中有0x11&#xff0c;那么我总是漏收此数&#xff0c;这是问题二。 由于问题莫名其妙…

深度视觉目标跟踪进展综述-论文笔记

中科大学报上的一篇综述&#xff0c;总结得很详细&#xff0c;整理了相关笔记。 1 引言 目标跟踪旨在基于初始帧中指定的感兴趣目标( 一般用矩形框表示) &#xff0c;在后续帧中对该目标进行持续的定位。 基于深度学习的跟踪算法&#xff0c;采用的框架包括相关滤波器、分类…

【SVA断言_2023.01.24】

在RTL设计中&#xff0c;仿真时查看异常情况&#xff0c;异常出现时&#xff0c;断言会报警&#xff0c;断言占整个设计的比例应不少于30% assertion作用&#xff1a; 检查特定条件或事件序列的出现情况提供功能覆盖 断言失败的严重程度&#xff1a;$fatal&#xff08;中止仿…

Oracle RAC 集群的安装(保姆级教程)

文章目录 一、安装前的规划1、系统规划2、网络规划3、存储规划 二、主机配置1、Linux主机安装&#xff08;rac01&rac02&#xff09;2、配置yum源并安装依赖包&#xff08;rac01&rac02&#xff09;3、网络配置&#xff08;rac01&rac02&#xff09;4、存储配置&#…

FitSM与ITIL及ITSM的对比

FitSM 是一种轻量级 IT 服务管理&#xff08;ITSM&#xff09;标准&#xff0c;其所有材料都是免费提供的&#xff0c;旨在促进 IT 服务提供中的服务管理&#xff0c;包括联合场景。但是&#xff0c;FitSM 适合您的组织吗&#xff1f;了解 FitSM 的适用性涉及两个方面。首先需要…

阿里云PAI-DSW部署ChatGLM3过程记录

1、申请免费的阿里云 PAI-DSW 资源&#xff0c;这个比较基础不做介绍了 2、从第二步开始&#xff0c;我们介绍部署ChatGLM3过程&#xff1a;PAI-DSW 资源界面如下图&#xff1a;点击开启新终端 3、默认目录下面建一个test目录&#xff0c;如下图&#xff1a; 4、执行命令cd te…

TPCC-MySQL

简介 TPC-C是专门针对联机交易处理系统&#xff08;OLTP系统&#xff09;的规范&#xff0c;一般情况下我们也把这类系统称为业务处理系统。 Tpcc-mysql是percona基于TPC-C(下面简写成TPCC)衍生出来的产品&#xff0c;专用于MySQL基准测试。其源码放在launchpad上&#xff0c…

2023年NOC大赛(学而思赛道)创意编程Python初中组决赛真题

2023年NOC大赛&#xff08;学而思赛道&#xff09;创意编程Python初中组决赛真题 题目总数&#xff1a;7 总分数&#xff1a;100 编程题 第 1 题 问答题 二进制回文 编程实现: 输入一个正整数&#xff0c;判断它的二进制形式是否是回文数&#xff0c;如果是输出True…

Web开发5:第三方扩展与部署

在Web开发中&#xff0c;第三方扩展和部署是提高开发效率和功能扩展的重要手段。第三方扩展可以帮助我们快速集成常用功能和工具&#xff0c;而部署则是将我们的应用程序发布到生产环境中。本文将介绍第三方扩展的重要性、如何选择和使用常见的第三方扩展&#xff0c;并讨论应用…

code server安装使用教程

1. 安装 1.1. 下载code-server安装包 类似这种文件&#xff1a;code-server-3.10.2-linux-amd64.tar.gz 解压&#xff1a;tar -xvf code-server-3.10.2-linux-amd64.tar.gz 1.2 &#xff08;可选&#xff09;建立软连接 ln -s path/to/code-server-3.10.2-linux-amd64/bin…

力扣15、三数之和(中等)

1 题目描述 图1 题目描述 2 题目解读 在整数数组nums中&#xff0c;找出三元组&#xff0c;它们的和为0&#xff0c;要求返回所有和为0且不重复的三元组。这是两数之和的扩展题目&#xff0c;可以将三数之和问题。 3 解法一&#xff1a;排序 双指针 将整数数组排序之后&#…

阿里云快速搭建《幻兽帕鲁》服务器自建指南

如何自建幻兽帕鲁服务器&#xff1f;基于阿里云服务器搭建幻兽帕鲁palworld服务器教程来了&#xff0c;一看就懂系列。本文是利用OOS中幻兽帕鲁扩展程序来一键部署幻兽帕鲁服务器&#xff0c;阿里云百科aliyunbaike.com分享官方基于阿里云服务器快速创建幻兽帕鲁服务器教程&…

使用javadoc生成maven项目的文档

概述&#xff1a;Maven 提供了 javadoc 插件来执行这个任务。 废话不多说&#xff0c;让我们开始操作吧&#xff01;&#xff01;&#xff01; 第一步&#xff1a;引入插件 在 pom.xml 中配置 javadoc 插件&#xff1a; 在 Maven 项目的 pom.xml 文件中&#xff0c;你需要添加…

AutoPSA的计算结果

1.中煤集团某用户问:请问&#xff0c;我导出来的.psa文件&#xff0c;在我同事另一台电脑上计算应力 怎么跟我电脑上的数据受力还有应力完全不一样呢? 原来&#xff0c;用户同事用的版本是9.3.5&#xff0c;用户用的版本是10.3.用户把.psa文件发给我们测试后&#xff0c;9.3…

Oracle篇—分区索引的重建和管理(第三篇,总共五篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

LeetCode:1706. 球会落何处(Java 模拟)

目录 1706. 球会落何处 题目描述&#xff1a; 实现代码与解析&#xff1a; 原理思路&#xff1a; 1706. 球会落何处 题目描述&#xff1a; 用一个大小为 m x n 的二维网格 grid 表示一个箱子。你有 n 颗球。箱子的顶部和底部都是开着的。 箱子中的每个单元格都有一个对角线…

数据中台的护城河,基于Flink实时构建数据仓

hello宝子们...我们是艾斯视觉擅长ui设计和前端开发10年经验&#xff01;希望我的分享能帮助到您&#xff01;如需帮助可以评论关注私信我们一起探讨&#xff01;致敬感谢感恩&#xff01; 数据中台的护城河&#xff1a;基于Flink实时构建数据仓 在数字化时代&#xff0c;数据…

leetcode hot100 全排列

在本题中&#xff0c;是要求我们求一个不重复数组的全排列&#xff0c;那么全排列&#xff0c;一定是长度和数组长度一致的&#xff0c;并且&#xff0c;排列问题是有顺序的&#xff0c;即1&#xff0c;2&#xff0c;3和1&#xff0c;3&#xff0c;2是两个不同的排列。 那么&a…

(数据结构练习题)合并两个有序数组

&#x1f308;前言&#xff1a;在刷题过程中发现超精简的代码。 力扣链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 &#x1f4ab;正文 首先这是题目内容&#xff0c;大家看到这个题时肯定会有很多不同的做法比如遍历链表将两个链表…