Java中List的subList()方法及使用注意事项

x33g5p2x  于2022-06-27 转载在 Java  
字(4.4k)|赞(0)|评价(0)|浏览(830)
List<Object> list = new Arraylist<>();

List<Object> subList = list.subList(0, 5);

其中subList(0, 5)取得的是下标为0到4的元素,不包含下标为5的元素.

java.util.List中的subList方法返回列表中指定的 fromIndex(包括 )和 toIndex(不包括)之间的部分视图。

List<E> subList(int fromIndex, int toIndex);

fromIndex - subList 的低端(包括)
toIndex - subList 的高端(不包括)

此方法省去了显式范围操作(此操作通常针对数组存在)。通过传递 subList 视图而非整个列表,期望列表的任何操作可用作范围操作。例如,下面的语句从列表中移除了元素的范围:

list.subList(from, to).clear();

下面通过代码来理解subList方法的使用

List<String> parentList = new ArrayList<String>();
        
  for(int i = 0; i < 5; i++){
      parentList.add(String.valueOf(i));
  }
  
  List<String> subList = parentList.subList(1, 3);
  for(String s : subList){
      System.out.println(s);//output: 1, 2
  }
  subList.clear();
  
  System.out.println(parentList.size());//output: 3
  List<String> subList = parentList.subList(1, 3);

在subList集合中的元素{1,2}中的第0的位置设置为new 1,即把1设置成了new 1
  subList.set(0, "new 1"); 
  for(String s : parentList){
     System.out.println(s);//output: 0, new 1, 2, 3, 4
  }
  	
  subList.add(String.valueOf(2.5));
  for(String s : parentList){
	  System.out.println(s);//output:0, 1, 2, 2.5, 3, 4
  }

 //如果你在调用了sublist返回了子list之后,如果修改了原list的大小,那么之前产生的子list将会失效,变得不可使用。报 java.util.ConcurrentModificationException
 parentList.add("undefine");
 for(String s : subList){
     System.out.println(s);
 }
 subList.get(0);

注意事项

ArrayList的subList结果不可强转成ArrayList,否则会抛出ClassCastException 异常,即 java.util.RandomAccessSubList cannot be cast to java.util.ArrayList.

说明:subList 返回的是 ArrayList 的内部类 SubList,并不是 ArrayList ,而是 ArrayList 的一个视图,对于 SubList 子列表的所有操作最终会反映到原列表上。

在 subList 场景中,高度注意对原集合元素个数的修改,会导致子列表的遍历、增加、 删除均会产生 ConcurrentModificationException 异常。

List<Book> books = new ArrayList<>();
        Book book1 = new Book(1,"语文");
        books.add(book1);
        Book book2 = new Book(2,"数学");
        books.add(book2);
        Book book3 = new Book(1,"英语");
        books.add(book3);
        Book book4 = new Book(1,"网络安全");
        books.add(book4);
        Book book5 = new Book(1,"C语言程序设计");
        books.add(book5);
        System.out.println("原来的list:"+books.toString());
        List<Book> subList = books.subList(0, 1);
        for(int i=0;i<subList.size();i++){
            subList.get(i).setBookName(subList.get(i).getBookName()+"-updated");
        }
        System.out.println("修改后list:"+books.toString());

removeRange(int, int);这个方法AbstractList并没有暴露出来,我们应该如何得到一个截短的list?

1 如何得到一个list某个范围的子集sublist

首先想到sublist(int, int)方法
注意此方法参数左闭右开。
测试如下

1.1 修改sublist会影响原来的list

LinkedList<String> ll = new LinkedList<>();
ll.add("a");
ll.add("b");
ll.add("c");
List<String> l2 =  ll.subList(1, 2);//[)
l2.add("new");
System.out.println(ll);
System.out.println(l2);

运行结果:

[a, b, new, c]
[b, new]

可见sublist是快照,sulist插入会影响原list

1.2 修改原list,则sublist的所有操作会报错

LinkedList<String> ll = new LinkedList<>();
ll.add("a");
ll.add("b");
ll.add("c");
List<String> l2 =  ll.subList(1, 2);//[)
ll.add("d");
System.out.println(ll);
System.out.println(l2);

Exception in thread “main” java.util.ConcurrentModificationException
at java.util.SubList.checkForComodification(AbstractList.java:769)

可见如果更改了原来的list,sublist的任何操作都会报错,包括get() size(),listIterator()等所有调用checkForComodification()的地方。

2 如何正确的截断一个List?

subList()返回的是List!是Sublist,而不是原来的类型。

2.1在java.util.AbstractList.subList(int fromIndex, int toIndex)的定义,返回的是java.util.SubList.SubLis:

public List<E> subList(int fromIndex, int toIndex) {
        return (this instanceof RandomAccess ?
                new RandomAccessSubList<>(this, fromIndex, toIndex) :
                new SubList<>(this, fromIndex, toIndex));
    }
    声明:
    class SubList<E> extends AbstractList<E>{}

2.2 LinkedList并没有覆盖这个方法.ArryList自己覆盖了这个方法,返回的是java.util.ArrayList.SubList:

public List<E> subList(int fromIndex, int toIndex) {
    subListRangeCheck(fromIndex, toIndex, size);
    return new SubList(this, 0, fromIndex, toIndex);
}
声明:
private class SubList extends AbstractList<E> implements RandomAccess {}

看来ArryList处处体现出RandomAccess接口的特性——支持随机访问。

2.3 我们看一下java.util.AbstractList.clear()方法,这正是我们需要的,Sublist的clear就是这个方法

public void clear() {
    removeRange(0, size());
}

2.3.1 ArrayList的覆盖

public void clear() {
    modCount++;
 
    // clear to let GC do its work
    for (int i = 0; i < size; i++)
        elementData[i] = null;
 
    size = 0;
}

2.3.2 LinkedList的覆盖

public void clear() {
    for (Node<E> x = first; x != null; ) {
        Node<E> next = x.next;
        x.item = null;
        x.next = null;
        x.prev = null;
        x = next;
    }
    first = last = null;
    size = 0;
    modCount++;
}

3 根据1和2

截短一个List的正确姿势:

list.subList(from, to).clear();

总之:
subList是返回一个镜像而不是新示例 用了 得保证原来的list不能更改。
之前的抛异常是因为更改了原来的list而要使用sublist的时候必然报异常。
clear的这个跟这个问题说的是如何获得一个list的某一段顺便释放其他节点。
这个操作后原来的list会截取出来 类型不变。
而subList实际上返回的是java.util.Sublist或者java.util.ArrayList.Sublist。

相关文章