关于List中的坑(上)

以下为List 常见坑:

1.数组转换为List

通过Arrays.asList(T… a)将数组转换为List。如:

1.1不支持添加/删除元素

public class ArrayListTest {

    public static void main(String[] args) {
        String[] seasons = {"spring", "summer", "autumn"};
        List<String> list = Arrays.asList(seasons);
        list.add("winter");
        System.out.println(list);
    }
}

在这里插入图片描述
上述代码的运行结果:执行add()方法时,抛出了UnsupportedOperationException异常。

查看Arrays.asList(T… a)源代码:

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

此方法返回的也确实是ArrayList,但为什么不能执行add()方法呢?
原来这个ArrayList只是Arrays类中一个内部类,并不是java.util.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);
        }
    }

内部类ArrayList中的add()/remove()…都是来自父类AbstractList类的,且它并没有重写父类的方法。

看看AbstractList类的add()/remove()

public abstract class AbstractList<E> extends AbstractCollection<E> implements List<E> {
	public boolean add(E e) {
        add(size(), e);
        return true;
    }
	public void add(int index, E element) {
        throw new UnsupportedOperationException();
    }
    public E remove(int index) {
        throw new UnsupportedOperationException();
    }
}

由源码可知:调用add()/remove()方法都会抛出UnsupportedOperationException异常。

所以:由Arrays.asList(T… a)返回的内部类ArrayList并不支持添加/删除元素

1.2共享原始数组

1.2.1修改新集合,影响原始数组

现对上述例子作出修改:把新集合下标为0的“spring”替换为“winter”

public class ArrayListTest {

    public static void main(String[] args) {
        String[] seasons = {"spring", "summer", "autumn"};
        List<String> list = Arrays.asList(seasons);

        list.set(0, "winter");
        System.out.println(list);
        System.out.println(Arrays.toString(seasons));
    }
}

在这里插入图片描述
上述结果表明:当新集合修改时,原数组也会被修改

1.2.2修改原数组,影响新集合

把原数组下标为1的“summer”修改为“winter”

public static void main(String[] args) {
    String[] seasons = {"spring", "summer", "autumn"};
    List<String> list = Arrays.asList(seasons);
 
    seasons[1] = "winter";
    System.out.println(list);
    System.out.println(Arrays.toString(seasons));
}

在这里插入图片描述
修改原数组也会影响新集合

所以,原数组和新集合可以互相影响

查看Arrays类中的内部类ArrayList源码:
运行语句:

List<String> list = Arrays.asList(seasons);

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
requireNonNull()方法返回的是一个引用。

a = Objects.requireNonNull(array) ==> 相当于:

a = seasons  // 进行引用赋值

        由java的基础知识,我们知道:java中引用赋值时,如:a = b,则:a和b指向的是同一个地址。那么上述:a = seasons时,则:a和seasons指向的是同一个数组。即:数组[]a和数组seasons的内容是一样的。

由源码可以看出,内部类ArrayList是使用成员变量 数组[]a来进行存储值的。

新集合修改值时:

 list.set(0, "winter");

就会调用内部类中的set()方法:
在这里插入图片描述
        此set()方法就会对成员变量a所指向的数组的内容进行修改,而成员变量a和变量seasons指向的是同一个数组,所以,修改成员变量a所指向的数组的内容就会修改掉变量seasons所指向的数组的内容。即:调用set()方法会修改变量seasons所指向的数组的内容。

在实际开发中,最好不要这样使用,因为可能会发生意想不到的后果。可以这样使用:

public static void main(String[] args) {
   String[] seasons = {"spring", "summer", "autumn"};
    List list = new ArrayList(Arrays.asList(seasons));
    list.set(0, "winter");
    System.out.println(list);
    String s = Arrays.toString(seasons);
    System.out.println(s);
}

在这里插入图片描述
Arrays.asList(T… a)方法外套一层java.util.ArrayList,这样,真正可以完成集合的各种操作了。

2.JDK中其它类似

List中的subList()方法

ArrayList中subList 生成新集合也会与原始 List 互相影响
如:

public static void main(String[] args) {
    List<String> days = new ArrayList<>();
    days.add("Mon");
    days.add("Tue");
    days.add("Wed");

    List<String> subList = days.subList(0, 2);
    days.set(0, "Thu");
    subList.set(1, "Fri");

    System.out.println(days);
    System.out.println(subList);
}

把原集合下标为0,新集合下标为1的值分别修改为:“Thu”、“Fri”
在这里插入图片描述
查看ArrayList类源码:
调用ArrayList中的subList()方法

 List<String> subList = days.subList(0, 2);

在这里插入图片描述
在这里插入图片描述
可以看到,这个构造函数中把原来的List以及该List中的部分属性直接赋值给自己的一些属性了。
也就是说,SubList并没有重新创建一个List,而是直接引用了原有的List,只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))

SubList这个类中单独定义了set、get、size、add、remove等方法。所有这些读写动作看起来是在操作 SubList ,实际上底层动作却都发生在原始 List 中。比如:
set()方法:操作ArrayList类中[]elementData数组,此数组就是用来存储ArrayList中的元素
在这里插入图片描述
add()方法:调用ArrayList中的add()方法
在这里插入图片描述

List触发fail-fast机制

不了解fail-fast机制,可点击查看fail-fast机制
举个例子:

public static void main(String[] args) {
    List<String> days = new ArrayList<>();
    days.add("Mon");
    days.add("Tue");
    days.add("Wed");

    List<String> subList = days.subList(0, 2);
    days.set(0, "Thu");
    subList.set(1, "Fri");

    subList.add("Sat");
    days.add("Sun");

    System.out.println(days);
    System.out.println(subList);
}

在这里插入图片描述
上面程序,给修改原集合结构时,即添加新元素,就抛出了ConcurrentModificationException异常。

System.out.println(subList);

根据堆栈异常信息知:运行上面那条语句,就会抛出ConcurrentModificationException异常。

源码解析:
上面那条语句会调用PrintStream类中的println()方法
在这里插入图片描述
再调用String类中的valueOf()方法
在这里插入图片描述
再调用AbastractCollection类中的toString()方法
在这里插入图片描述

public abstract Iterator<E> iterator();

iterator()在AbastractCollection类中是个抽象方法,由子类实现。所以调用了ArrayList类中的iterator()
在这里插入图片描述
经过一层层调用,最终会调用checkForComodification()方法
在这里插入图片描述
由于原集合调用add()方法,所以ArrayList中的成员变量modCount要比内部类中的成员变量大1,不相等就会抛出ConcurrentModificationException异常。

【小结】:List的subList方法并没有创建一个新的List,而是使用了原List的视图,这个视图使用内部类SubList表示。所以,我们不能把subList方法返回的List强制转换成ArrayList等类,因为他们之间没有继承关系。

视图和原List的修改还需要注意几点,尤其是他们之间的相互影响:

  1. 对原集合和子集合做的非结构性修改(non-structural changes),都会影响到彼此。
  2. 对子集合做结构性修改,操作同样会反映到原集合上。
  3. 对原集合做结构性修改,会抛出异常ConcurrentModificationException。

转载

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值