arraylist al_让大姨妈来少点

arraylist al_让大姨妈来少点这是一个小白程序员问我的问题。 他:异常信息是 java.lang.UnsupportedOperationException,是调用 add 方法时抛出的。 恩,我大概明白了,这可能是 ArrayList的又一个坑,和 subList应该有异曲同工之妙。 Arrays.asL…

我是风筝,公众号「古时的风筝」,一个不只有技术的技术公众号,一个在程序圈混迹多年,主业 Java,另外 Python、React 也玩儿的 6 的斜杠开发者。 Spring Cloud 系列文章已经完成,可以到 我的github 上查看系列完整内容。也可以在公众号内回复「pdf」获取我精心制作的 pdf 版完整教程。

请看下面的代码,谁能看出它有什么问题吗?

String a = "古时的";
String b  = "风筝";
List<String> stringList = Arrays.asList(a,b);
stringList.add("!!!");

这是一个小白程序员问我的问题。

他说:成哥,帮我看看这代码有什么问题吗,为什么报错呢,啥操作都没有啊?

我:看上去确实没什么问题,但是我确实没用过 Arrays.asList这个方法,报什么错误?

他:异常信息是 java.lang.UnsupportedOperationException,是调用 add 方法时抛出的。

恩,我大概明白了,这可能是 ArrayList的又一个坑,和 subList应该有异曲同工之妙。

Arrays.asList

Arrays.asList 方法接收一个变长泛型,最后返回 List,好像是个很好用的方法啊,有了它,我们总是说的 ArrayList 初始化方式是不是就能更优雅了,既不用{{这种双括号方式,也不用先 new ArrayList,然后再调用 add方法一个个往里加了。但是,为啥没有提到这种方式呢?

虽然问题很简单,但还是有必要看一下原因的。于是,写了上面这 4 行代码做个测试,运行起来确实抛了异常,异常如下:

arraylist al_让大姨妈来少点

直接看源码吧,定位到 Arrays.asList 方法看一看。

public static <T> List<T> asList(T... a) {
    return new ArrayList<>(a);
}

咦,是 new 了一个 ArrayList出来呀,怎么会不支持 add操作呢,不仔细看还真容易被唬住,此ArrayList非彼ArrayList,这是一个内部类,但是类名也叫 ArrayList,你说坑不坑。

private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable {
  
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return a.clone();
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }

        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
}

里面定义了 setget等基本的方法,但是没有重写add方法,这个类也是继承了 AbstractList,但是 add方法并没有具体的实现,而是抛了异常出来,具体的逻辑需要子类自己去实现的。

public void add(int index, E element) {
    throw new UnsupportedOperationException();
}

所以说,Arrays.asList方法创建出来的 ArrayList 和真正我们平时用的 ArrayList只是继承自同一抽象类的两个不同子类,而 Arrays.asList创建的 ArrayList 只能做一些简单的视图使用,不能做过多操作,所以 ArrayList的几种初始化方式里没有 Arrays.asList这一说。

subList 方法

上面提到了那个问题和 subList的坑有异曲同工之妙,都是由于返回的对象并不是真正的 ArrayList类型,而是和 ArrayList集成同一父类的不同子类而已。

坑之一

所以会产生第一个坑,就是把当把 subList返回的对象转换成 ArrayList 的时候

List<String> stringList = new ArrayList<>();
stringList.add("我");
stringList.add("是");
stringList.add("风筝");
List<String> subList = (ArrayList) stringList.subList(0, 2);

会抛出下面的异常:

java.lang.ClassCastException: java.util.ArrayList$SubList cannot be cast to java.util.ArrayList

原因很明了,因为这俩根本不是一个对象,也不存在继承关系,如果真说有什么关系,顶多算是兄弟关系,因为都继承了 AbstractList 嘛 。

坑之二

当你在 subList 中操作的时候,其实就是在操作原始的 ArrayList,不明所以的同学以为这是一个副本列表,然后在 subList 上一顿操作猛如虎,最后回头一看原始 ArrayList已然成了二百五。

例如下面这段代码,在 subList 上新增了一个元素,然后又删除了开头的一个元素,结果回头一看原始的 ArrayList,发现它的结果也发生了变化。

List<String> stringList = new ArrayList<>();
stringList.add("我");
stringList.add("是");
stringList.add("风筝");
List<String> subList = stringList.subList(0, 3);
subList.add("!!!");
subList.remove(0);
System.out.println("------------------");
System.out.println("修改后的 subList");
System.out.println("------------------");
for (String s : subList) {
    System.out.println(s);
}
System.out.println("------------------");
System.out.println("原始 ArrayList");
System.out.println("------------------");
for (String a : stringList) {
    System.out.println(a);
}

以上代码的输出结果:

------------------
修改后的 subList ------------------
是
风筝
!!! ------------------
原始 ArrayList ------------------
是
风筝
!!!

为什么会发生这样的情况呢,因为 subList的实现就是这样子啊,捂脸。我们可以看一下 subList 这个方法的源码。

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}

看到它内部是 new 了一个 SubList 类,这个类就是上面提到的 ArrayList的子类,看到第一个参数 this了吗,this就是当前的 ArrayList 原始列表,之后的增删改其实都是在 this上操作,最终也就是在原始列表上进行的操作,所以你的一举一动最后都会诚实的反应到原始列表上,之后你再想用原始列表,对不起,已经找不到了。

坑之三

如果你使用 subList 方法获取了一个子列表,这之后又在原始列表上进行了新增或删除的操作,这是,你之前获取到的 subList 就已经废掉了,不能用了,不能用的意思就是你在 subList 上进行遍历、增加、删除操作都会抛出异常,没错,连遍历都不行了。

例如下面这段代码

List<String> stringList = new ArrayList<>();
stringList.add("我");
stringList.add("是");
stringList.add("风筝");

List<String> subList = stringList.subList(0, 3);
// 原始列表元素个数改变
stringList.add("!!!");

// 遍历 subList
for (String s : subList) {
    System.out.println(s);
}

// get 元素
subList.get(0);

// remove 元素
subList.remove(0);

//增加元素
subList.add("hello");

遍历、get、remove、add 都会抛出以下异常

arraylist al_让大姨妈来少点

其实与二坑的原因相同,subList 其实操作的是原始列表,当你在 subList 上进行操作时,会执行 checkForComodification方法,此方法会检查原始列表的个数是否和最初的相同,如果不相同,直接抛出 ConcurrentModificationException异常。

private void checkForComodification() {
    if (ArrayList.this.modCount != this.modCount)
       throw new ConcurrentModificationException();
}

最后

没有在项目中踩过 JDK 坑的程序员,不足以谈人生。所以,各位同学在使用一些看似简单、优雅的方法时,一定要清楚它的特性和原理,不然就离坑不远了。


壮士且慢,先给点个赞吧,总是被白嫖,身体吃不消!

我是风筝,公众号「古时的风筝」。一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!你可选择现在就关注我,或者看看历史文章再关注也不迟。

arraylist al_让大姨妈来少点

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
转载请注明出处: https://daima100.com/13700.html

(0)

相关推荐

  • 字节跳动陈志锋主要管_字节跳动难进吗

    字节跳动陈志锋主要管_字节跳动难进吗导读: 作为一种基础的数据结构,图数据的应用场景无处不在,如社交、风控、搜广推、生物信息学中的蛋白质分析等。如何高效地对海量的图数据进行存储、查询、计算及分析,是当前业界热门的方向。本文将介绍字节跳动

    2023-05-22
    163
  • 如何检查 Python 版本

    如何检查 Python 版本Python是一个广泛使用的编程语言,但是在不同的项目中使用不同的Python版本可能会导致代码执行出现问题。因此,检查当前Python版本非常重要,以便在项目中使用正确的Python版本。

    2024-08-20
    26
  • 为啥redis16个数据库_数据库外键怎么设置

    为啥redis16个数据库_数据库外键怎么设置注:本文原作者并没有解释‘为什么’,只是说是配置文件默认16. 所以我猜redis作者只是在告诉使用者,这个数请随意改。 导读:在实际项目中Redis常被应用于做缓存,分布式锁、消息队列等。但是在搭…

    2023-03-09
    158
  • Python新手必学的第一条命令——print()

    Python新手必学的第一条命令——print()在python中,print()函数用来输出内容到控制台或文件中,是Python的核心命令之一。下面是一些常见的print()函数的用法:

    2024-03-04
    87
  • MySQL必知存储引擎「建议收藏」

    MySQL必知存储引擎「建议收藏」Mysql存储引擎 1.MyISAM MySQL 5.0 之前的默认数据库引擎,最为常用。拥有较高的插入,查询速度,但不支持事务. 2.InnoDB事务型数据库的首选引擎,支持ACID事务,支持行级锁

    2022-12-30
    166
  • Mac安装maven的方法_mac安装

    Mac安装maven的方法_mac安装下载https://maven.apache.org/download.cgi下载apache-maven-3.6.3-bin.zip安装解压到某个目录,比如:/usr/local/apache-ma

    2023-08-18
    118
  • 10年感触:架构是什么?——消灭架构![通俗易懂]

    10年感触:架构是什么?——消灭架构![通俗易懂]架构是一个约定,一个规则,一个大家都懂得遵守的共识。那这是什么样的约定、什么样的规则、什么样的共识呢? 我以包为例,我经常出差,双肩背包里装了不少东西。笔记本电脑、电源、2个上网卡、鼠标、USB线、一盒大的名片、一盒小的名片、口香糖、Mini-DisplayPort转VGA接口…

    2023-08-05
    130
  • 使用Python tqdm让你的代码进度条更丰富有趣

    使用Python tqdm让你的代码进度条更丰富有趣在编写长时间运行的代码时,我们常常会使用进度条来实时显示任务的进度。Python提供了许多实现进度条的库,其中tqdm是一个高度可定制的库,它可以让你的代码进度条更加丰富有趣。

    2024-03-02
    84

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注