zoukankan      html  css  js  c++  java
  • Java集合与泛型中的几个陷阱,你掉进了几个?

    下面我总结了集合、泛型、数组转集合等一些常见的陷进,认真看完,相信你绝对有所收获。

    1、List ,List<?> 与 List<Object> 有区别吗?

    说实话,我敢保证很多人是不知道 List, List<?> 与 List<Object> 之间的区别的。

    1、我们先来看看 List 与 List<Object>

    很多可能觉得 List<Object>的用法与 List 是一样的,例如很多人认为

    List<Object> list;

    List list;

    这两种定义方法是一模一样的,然而他们是不一样的。看下面一段代码

        List<Integer> t1 = new ArrayList<>();
        // 编译通过
        List t2 = t1;
        //编译失败
        List<Object> t3 = t1;
    

    t1 可以赋给 t2, 但是 t1 不能赋给 t3,会抛出如下异常

    从这里可以看出

    List list;

    List<Object> list;

    是有区别的,List 变量可以接受任何泛型的变量,而 List 则不可以。

    2、我们在看看 Lis<?> 有什么需要注意的地方:

    看下面一段代码:

        List<Object> t1 = new ArrayList<>();
        List<?> t2 = t1;
        // 编译通过
        t2.remove(0);
        t2.clear();
        // 编译不通过
        t2.add(new Object());
    

    List<?> 是一个泛型,在没有赋值之前,是可以接受任何集合的赋值的,我想这点大家都知道,但是请注意,赋值之后就不能往里面添加元素了,提示如下错误:

    所以 List<?> 一般用来作为参数来接受外部的集合,或者返回一个不知道具体元素的集合。

    List 与 List<?>, List<Object> 的细微区别知道了吧?

    2、<? extends T> 与 <? super T>你真的懂吗?

    我们知道泛型 List<T> 只能放置一种类型,如果你采用 List<Object> 来放置多种类型,然后再进行类型强制转换的话,那会失去了泛型的初衷。

    为了能够放置多种类型,于是有了 <? extend T> 与 <? super T>,下面先说一些你可能原本就知道的知识:

    1、对于 <? extends T> a,a 这个变量可以接受 T 及其 T 子类的集合,上界为 T,并且从 a 取出来的类型都会被强制转换为 T。重点看下面一个例子:

    注意:我们先约定 Cat(猫) 继承自 Animal(动物),RedCat(黑猫) 继承自 Cat

        List<Animal> animals = new ArrayList<>();
        List<Cat> cats = new ArrayList<>();
        List<RedCat> redCats = new ArrayList<>();
        // 可以通过编译
        List<? extends  Cat> extendsCat = redCats;
        // 不能通过编译,因为只能接受 Cat 及其子类的集合
        extendsCat = animals;
            
        // 重点注意:下面三行都不能通过编译
        extendsCat.add(new Animal());
        extendsCat.add(new Cat());
        extendsCat.add(new RedCat());
        // 重点注意:可以通过编译
        extendsCat.add(null);
    

    注意,<? extends T>最需要注意的是,就是不能向里面添加除null之外的其他所有元素,这个和 List<?> 有点类似。

    2、现在说说 <? super T>,它和 <? extends T> 有点相反。对于 <? super T> a,a 这个变量可以接受 T 及其 T 父类的集合,下界为 T,并且从 a 取出来的类型都会被强制转换为 Object。重点看下面一个例子:

        List<Animal> animals = new ArrayList<>();
        List<Cat> cats = new ArrayList<>();
        List<RedCat> redCats = new ArrayList<>();
        // 可以通过编译
        List<? super  Cat> superCat = animals;
        // 不能通过编译,因为只能接受 Cat 及其父类的集合
        superCat = redCats;
    
        // 重点注意:不能通过编译,只能添加 Cat 及其 Cat 的子类
        superCat.add(new Animal());
        // 重点注意,可以通过编译
        superCat.add(new Cat());
        superCat.add(new RedCat());
        superCat.add(null);
    
    

    注意,<? super T>最需要注意的是,在虽然可以接受 T 及其父类的赋值,但是只能向里面添加 T 及其 T 的子类

    总结

    1、List<? extends T> a ,可以把 a 及其 a 的子类赋给 a,从 a 里取的元素都会被强制转换为 T 类型,不过需要注意的是,不能向 a 添加任何除 null 外是元素

    2、List<? super T> a ,可以把 a 及其 a 的父类赋给 a,从 a 里取的元素都会被强制转换为 Object 类型,不过需要注意的是,可以向 a 添加元素,但添加的只能是 T 及其子类元素

    3、泛型与重载

    我们先来看一道题,你觉得下面这道题能够编译通过吗?

     public class GernerTypes {
        public static void  method(List<Integer> list) {
            System.out.println("List<Integer> list");
        }
        public static void method(List<String> list) {
            System.out.println("List<String> list");
        }
    }
    

    答是编译不通过

    两个方法的参数不同,为什么会重载不通过呢?

    实际上在 Java 的泛型中,泛型只存在于源码中,在编译后的字节码中,泛型已经被替换为原生类型了,并且在相应的地方插入了强制转换的代码。为了方便理解,可以看下面的一段代码例子:

     // 源码
         public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            System.out.println(list.get(0));
        }
    

    编译之后泛型就不存在了,并且在相应的地方插入了强制转换的代码,编译之后,我们反编译的代码如下:

         // 反编译之后的代码
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add(1);
            System.out.println((Integer)list.get(0));
        }
    

    这种 编译之后泛型就不存在了,并且在相应的地方插入了强制转换代码的机制我们也称之为擦除

    所以上面的两个方法,看似参数不一样,但是经过编译擦出之后,他们的参数就是一样的了,所以编译不通过。

    4、数组与集合相互转换时需要注意的点

    1、数组转集合

    大家先看一个例子吧,

        public static void main(String[] args) {
            String[] arr = {"one", "two", "three"};
            // 数组转换成集合
            List<String> list = Arrays.asList(arr);
            // 向集合添加元素:编译正常,但运行时抛出了异常
            list.add("four");
        }
    

    向集合添加元素抛出了如下异常:

    问题来了,向集合添加元素为啥会抛出异常呢??

    我们先来看一下 Arrays.asList(arr) 方法究竟返回了什么?

    源码如下:

    返回的明明是 ArrayList 啊,为啥就不能添加元素呢??

    实际上,此 ArrayList 非彼 ArrayList,这个返回的 ArrayList 实际上是 Arrays 的一个内部类。该内部类也是十分简单,和真实的那个 ArrayList 没得比,部分源码如下:

    而且这个假的 ArrayList 是直接 引用原数组的,不然你看它的构造器(第二条画线)

    也就是说,ArrayList 内部是直接引用 arr 数组,你对 arr 数组进行改变,也会同时改变到 list 集合。

    下面的代码证明这一点

        public static void main(String[] args) {
            String[] arr = {"one", "two", "three"};
            // 数组转换成集合
            List<String> list = Arrays.asList(arr);
            // 修改 arr
            arr[0] = "0";
            //打印看看
            System.out.println(list.get(0));
        }
    

    打印结果是 “0”。

    所以,我们向 list 添加元素肯定失败,因为 arr 数组的长度了 3 ,本来就有 3 个元素了,你在向里面添加第四个元素,肯定是不行的。

    所以,在把数组转换为集合的过程中,需要特别注意。

    建议大家这样转换比较安全

    List<String> list = new ArrayList<>(Arrays.asList(arr));
    

    2、集合转数组

    集合转换为数组相对比较不苛刻,我就不拉很多源码来进行分析了,我只简单说下几个需要注意的地方。例如对于下面这个转换:

        // 集合大小为 size
        List<String> list = new ArrayList<>();
        // 长度为 n 的数组
        String[] arr = new String[n];
        // 进行转换
        list.toArray(arr);
    

    1、如果数组长度比集合小:由于 arr 的长度不够,所以集合里的元素不会赋给 arr,而且自己再重新创建一个新数组反回去。

    2、如果数组长度不小于集合:此时 arr 的长度够了,所以集合里的元素直接复制给 arr 数组,不会重新创建一个新的元素。

    一览源码:

    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // 重新创建一个数组来返回去
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        // 长度够的话直接复制给 a
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
        }
    

    以上这些陷进相信有不少人是不知道了,我把它总结整理了出来,希望大家看完能够有所收获。

    下面我总结了集合、泛型、数组转集合等一些常见的陷进,认真看完,相信你绝对有所收获。

    1、List ,List<?> 与 List<Object> 有区别吗?

    说实话,我敢保证很多人是不知道 List, List<?> 与 List<Object> 之间的区别的。

    1、我们先来看看 List 与 List<Object>

    很多可能觉得 List<Object>的用法与 List 是一样的,例如很多人认为

    List<Object> list;

    List list;

    这两种定义方法是一模一样的,然而他们是不一样的。看下面一段代码

        List<Integer> t1 = new ArrayList<>();
        // 编译通过
        List t2 = t1;
        //编译失败
        List<Object> t3 = t1;
    

    t1 可以赋给 t2, 但是 t1 不能赋给 t3,会抛出如下异常

    从这里可以看出

    List list;

    List<Object> list;

    是有区别的,List 变量可以接受任何泛型的变量,而 List 则不可以。

    2、我们在看看 Lis<?> 有什么需要注意的地方:

    看下面一段代码:

        List<Object> t1 = new ArrayList<>();
        List<?> t2 = t1;
        // 编译通过
        t2.remove(0);
        t2.clear();
        // 编译不通过
        t2.add(new Object());
    

    List<?> 是一个泛型,在没有赋值之前,是可以接受任何集合的赋值的,我想这点大家都知道,但是请注意,赋值之后就不能往里面添加元素了,提示如下错误:

    所以 List<?> 一般用来作为参数来接受外部的集合,或者返回一个不知道具体元素的集合。

    List 与 List<?>, List<Object> 的细微区别知道了吧?

    2、<? extends T> 与 <? super T>你真的懂吗?

    我们知道泛型 List<T> 只能放置一种类型,如果你采用 List<Object> 来放置多种类型,然后再进行类型强制转换的话,那会失去了泛型的初衷。

    为了能够放置多种类型,于是有了 <? extend T> 与 <? super T>,下面先说一些你可能原本就知道的知识:

    1、对于 <? extends T> a,a 这个变量可以接受 T 及其 T 子类的集合,上界为 T,并且从 a 取出来的类型都会被强制转换为 T。重点看下面一个例子:

    注意:我们先约定 Cat(猫) 继承自 Animal(动物),RedCat(黑猫) 继承自 Cat

        List<Animal> animals = new ArrayList<>();
        List<Cat> cats = new ArrayList<>();
        List<RedCat> redCats = new ArrayList<>();
        // 可以通过编译
        List<? extends  Cat> extendsCat = redCats;
        // 不能通过编译,因为只能接受 Cat 及其子类的集合
        extendsCat = animals;
            
        // 重点注意:下面三行都不能通过编译
        extendsCat.add(new Animal());
        extendsCat.add(new Cat());
        extendsCat.add(new RedCat());
        // 重点注意:可以通过编译
        extendsCat.add(null);
    

    注意,<? extends T>最需要注意的是,就是不能向里面添加除null之外的其他所有元素,这个和 List<?> 有点类似。

    2、现在说说 <? super T>,它和 <? extends T> 有点相反。对于 <? super T> a,a 这个变量可以接受 T 及其 T 父类的集合,下界为 T,并且从 a 取出来的类型都会被强制转换为 Object。重点看下面一个例子:

        List<Animal> animals = new ArrayList<>();
        List<Cat> cats = new ArrayList<>();
        List<RedCat> redCats = new ArrayList<>();
        // 可以通过编译
        List<? super  Cat> superCat = animals;
        // 不能通过编译,因为只能接受 Cat 及其父类的集合
        superCat = redCats;
    
        // 重点注意:不能通过编译,只能添加 Cat 及其 Cat 的子类
        superCat.add(new Animal());
        // 重点注意,可以通过编译
        superCat.add(new Cat());
        superCat.add(new RedCat());
        superCat.add(null);
    
    

    注意,<? super T>最需要注意的是,在虽然可以接受 T 及其父类的赋值,但是只能向里面添加 T 及其 T 的子类

    总结

    1、List<? extends T> a ,可以把 a 及其 a 的子类赋给 a,从 a 里取的元素都会被强制转换为 T 类型,不过需要注意的是,不能向 a 添加任何除 null 外是元素

    2、List<? super T> a ,可以把 a 及其 a 的父类赋给 a,从 a 里取的元素都会被强制转换为 Object 类型,不过需要注意的是,可以向 a 添加元素,但添加的只能是 T 及其子类元素

    3、泛型与重载

    我们先来看一道题,你觉得下面这道题能够编译通过吗?

     public class GernerTypes {
        public static void  method(List<Integer> list) {
            System.out.println("List<Integer> list");
        }
        public static void method(List<String> list) {
            System.out.println("List<String> list");
        }
    }
    

    答是编译不通过

    两个方法的参数不同,为什么会重载不通过呢?

    实际上在 Java 的泛型中,泛型只存在于源码中,在编译后的字节码中,泛型已经被替换为原生类型了,并且在相应的地方插入了强制转换的代码。为了方便理解,可以看下面的一段代码例子:

     // 源码
         public static void main(String[] args) {
            List<Integer> list = new ArrayList<>();
            list.add(1);
            System.out.println(list.get(0));
        }
    

    编译之后泛型就不存在了,并且在相应的地方插入了强制转换的代码,编译之后,我们反编译的代码如下:

         // 反编译之后的代码
        public static void main(String[] args) {
            List list = new ArrayList();
            list.add(1);
            System.out.println((Integer)list.get(0));
        }
    

    这种 编译之后泛型就不存在了,并且在相应的地方插入了强制转换代码的机制我们也称之为擦除

    所以上面的两个方法,看似参数不一样,但是经过编译擦出之后,他们的参数就是一样的了,所以编译不通过。

    4、数组与集合相互转换时需要注意的点

    1、数组转集合

    大家先看一个例子吧,

        public static void main(String[] args) {
            String[] arr = {"one", "two", "three"};
            // 数组转换成集合
            List<String> list = Arrays.asList(arr);
            // 向集合添加元素:编译正常,但运行时抛出了异常
            list.add("four");
        }
    

    向集合添加元素抛出了如下异常:

    问题来了,向集合添加元素为啥会抛出异常呢??

    我们先来看一下 Arrays.asList(arr) 方法究竟返回了什么?

    源码如下:

    返回的明明是 ArrayList 啊,为啥就不能添加元素呢??

    实际上,此 ArrayList 非彼 ArrayList,这个返回的 ArrayList 实际上是 Arrays 的一个内部类。该内部类也是十分简单,和真实的那个 ArrayList 没得比,部分源码如下:

    而且这个假的 ArrayList 是直接 引用原数组的,不然你看它的构造器(第二条画线)

    也就是说,ArrayList 内部是直接引用 arr 数组,你对 arr 数组进行改变,也会同时改变到 list 集合。

    下面的代码证明这一点

        public static void main(String[] args) {
            String[] arr = {"one", "two", "three"};
            // 数组转换成集合
            List<String> list = Arrays.asList(arr);
            // 修改 arr
            arr[0] = "0";
            //打印看看
            System.out.println(list.get(0));
        }
    

    打印结果是 “0”。

    所以,我们向 list 添加元素肯定失败,因为 arr 数组的长度了 3 ,本来就有 3 个元素了,你在向里面添加第四个元素,肯定是不行的。

    所以,在把数组转换为集合的过程中,需要特别注意。

    建议大家这样转换比较安全

    List<String> list = new ArrayList<>(Arrays.asList(arr));
    

    2、集合转数组

    集合转换为数组相对比较不苛刻,我就不拉很多源码来进行分析了,我只简单说下几个需要注意的地方。例如对于下面这个转换:

        // 集合大小为 size
        List<String> list = new ArrayList<>();
        // 长度为 n 的数组
        String[] arr = new String[n];
        // 进行转换
        list.toArray(arr);
    

    1、如果数组长度比集合小:由于 arr 的长度不够,所以集合里的元素不会赋给 arr,而且自己再重新创建一个新数组反回去。

    2、如果数组长度不小于集合:此时 arr 的长度够了,所以集合里的元素直接复制给 arr 数组,不会重新创建一个新的元素。

    一览源码:

    public <T> T[] toArray(T[] a) {
        if (a.length < size)
            // 重新创建一个数组来返回去
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        // 长度够的话直接复制给 a
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
        }
    

    以上这些陷进相信有不少人是不知道了,我把它总结整理了出来,如果大家看完觉得有收获,不妨点个底部小卡片 + 点赞鼓励我一下?

  • 相关阅读:
    左右下划线,中间文字
    sql语言动词
    SQL语言的四个组成部分
    MySQL执行一条查询语句的内部执行过程
    07 | 行锁功过:怎么减少行锁对性能的影响?
    06 | 全局锁和表锁 :给表加个字段怎么有这么多阻碍?
    05 | 深入浅出索引(下)
    04 | 深入浅出索引(上)
    03 | 事务隔离:为什么你改了我还看不见?
    02 | 日志系统:一条SQL更新语句是如何执行的?
  • 原文地址:https://www.cnblogs.com/kubidemanong/p/10457328.html
  • Copyright © 2011-2022 走看看