热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

在Java中使用List.subList(intfromIndex,inttoIndex)方法时需要注意的问题

关于集合类,《阿里巴巴Java开发手册》中有一个规定:这里我们简单分析下。subListsubList是List接口中定义的一个方法

关于集合类,《阿里巴巴Java开发手册》中有一个规定:

 

-w1379

这里我们简单分析下。

subList

subList是List接口中定义的一个方法,该方法主要用于返回一个集合中的一段、可以理解为截取一个集合中的部分元素,他的返回值也是一个List。

如以下代码:

public static void main(String[] args) {List names = new ArrayList() {{add("Hollis");add("hollischuang");add("H");}};List subList = names.subList(0, 1);System.out.println(subList);
}

以上代码输出结果为:

[Hollis]

如果我们改动下代码,将subList的返回值强转成ArrayList试一下:

public static void main(String[] args) {List names = new ArrayList() {{add("Hollis");add("hollischuang");add("H");}};ArrayList subList = names.subList(0, 1);System.out.println(subList);
}

以上代码将抛出异常:

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

不只是强转成ArrayList会报错,强转成LinkedList、Vector等List的实现类同样也都会报错。

那么,为什么会发生这样的报错呢?我们接下来深入分析一下。

底层原理

首先,我们看下subList方法给我们返回的List到底是个什么东西,这一点在JDK源码中注释是这样说的:

Returns a view of the portion of this list between the specifiedfromIndex, inclusive, and toIndex, exclusive.

也就是说subList 返回是一个视图,那么什么叫做视图呢?

我们看下subList的源码:

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

这个方法返回了一个SubList,这个类是ArrayList中的一个内部类。

SubList这个类中单独定义了set、get、size、add、remove等方法。

当我们调用subList方法的时候,会通过调用SubList的构造函数创建一个SubList,那么看下这个构造函数做了哪些事情:

SubList(AbstractList parent,int offset, int fromIndex, int toIndex) {this.parent = parent;this.parentOffset = fromIndex;this.offset = offset + fromIndex;this.size = toIndex - fromIndex;this.modCount = ArrayList.this.modCount;
}

可以看到,这个构造函数中把原来的List以及该List中的部分属性直接赋值给自己的一些属性了。

也就是说,SubList并没有重新创建一个List,而是直接引用了原有的List(返回了父类的视图),只是指定了一下他要使用的元素的范围而已(从fromIndex(包含),到toIndex(不包含))。

所以,为什么不能讲subList方法得到的集合直接转换成ArrayList呢?因为SubList只是ArrayList的内部类,他们之间并没有集成关系,故无法直接进行强制类型转换。

视图有什么问题

前面通过查看源码,我们知道,subList()方法并没有重新创建一个ArrayList,而是返回了一个ArrayList的内部类——SubList。

这个SubList是ArrayList的一个视图。

那么,这个视图又会带来什么问题呢?我们需要简单写几段代码看一下。

1、非结构性改变SubList

public static void main(String[] args) {List sourceList = new ArrayList() {{add("H");add("O");add("L");add("L");add("I");add("S");}};List subList = sourceList.subList(2, 5);System.out.println("sourceList : " + sourceList);System.out.println("sourceList.subList(2, 5) 得到List :");System.out.println("subList : " + subList);subList.set(1, "666");System.out.println("subList.set(3,666) 得到List :");System.out.println("subList : " + subList);System.out.println("sourceList : " + sourceList);}

得到结果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.set(3,666) 得到List :
subList : [L, 666, I]
sourceList : [H, O, L, 666, I, S]

当我们尝试通过set方法,改变subList中某个元素的值得时候,我们发现,原来的那个List中对应元素的值也发生了改变。

同理,如果我们使用同样的方法,对sourceList中的某个元素进行修改,那么subList中对应的值也会发生改变。读者可以自行尝试一下。

1、结构性改变SubList

public static void main(String[] args) {List sourceList = new ArrayList() {{add("H");add("O");add("L");add("L");add("I");add("S");}};List subList = sourceList.subList(2, 5);System.out.println("sourceList : " + sourceList);System.out.println("sourceList.subList(2, 5) 得到List :");System.out.println("subList : " + subList);subList.add("666");System.out.println("subList.add(666) 得到List :");System.out.println("subList : " + subList);System.out.println("sourceList : " + sourceList);}

得到结果:

sourceList : [H, O, L, L, I, S]
sourceList.subList(2, 5) 得到List :
subList : [L, L, I]
subList.add(666) 得到List :
subList : [L, L, I, 666]
sourceList : [H, O, L, L, I, 666, S]

我们尝试对subList的结构进行改变,即向其追加元素,那么得到的结果是sourceList的结构也同样发生了改变。

1、结构性改变原List

public static void main(String[] args) {List sourceList = new ArrayList() {{add("H");add("O");add("L");add("L");add("I");add("S");}};List subList = sourceList.subList(2, 5);System.out.println("sourceList : " + sourceList);System.out.println("sourceList.subList(2, 5) 得到List :");System.out.println("subList : " + subList);sourceList.add("666");System.out.println("sourceList.add(666) 得到List :");System.out.println("sourceList : " + sourceList);System.out.println("subList : " + subList);}

得到结果:

Exception in thread "main" java.util.ConcurrentModificationExceptionat java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1239)at java.util.ArrayList$SubList.listIterator(ArrayList.java:1099)at java.util.AbstractList.listIterator(AbstractList.java:299)at java.util.ArrayList$SubList.iterator(ArrayList.java:1095)at java.util.AbstractCollection.toString(AbstractCollection.java:454)at java.lang.String.valueOf(String.java:2994)at java.lang.StringBuilder.append(StringBuilder.java:131)at com.hollis.SubListTest.main(SubListTest.java:28)

我们尝试对sourceList的结构进行改变,即向其追加元素,结果发现抛出了ConcurrentModificationException。关于这个异常,我们在《一不小心就踩坑的fail-fast是个什么鬼?》中分析过,这里原理相同,就不再赘述了。

小结

我们简单总结一下,List的subList方法并没有创建一个新的List,而是使用了原List的视图,这个视图使用内部类SubList表示。

所以,我们不能把subList方法返回的List强制转换成ArrayList等类,因为他们之间没有继承关系。

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

1、对父(sourceList)子(subList)List做的非结构性修改(non-structural changes),都会影响到彼此。

2、对子List做结构性修改,操作同样会反映到父List上。

3、对父List做结构性修改,会抛出异常ConcurrentModificationException。

所以,阿里巴巴Java开发手册中有另外一条规定:

 

如何创建新的List

如果需要对subList作出修改,又不想动原list。那么可以创建subList的一个拷贝:

subList = Lists.newArrayList(subList);
list.stream().skip(strart).limit(end).collect(Collectors.toList());

 


推荐阅读
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • IhaveconfiguredanactionforaremotenotificationwhenitarrivestomyiOsapp.Iwanttwodiff ... [详细]
  • 本文详细介绍了PHP中与URL处理相关的三个函数:http_build_query、parse_str和查询字符串的解析。通过示例和语法说明,讲解了这些函数的使用方法和作用,帮助读者更好地理解和应用。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • 先看官方文档TheJavaTutorialshavebeenwrittenforJDK8.Examplesandpracticesdescribedinthispagedontta ... [详细]
  • JDK源码学习之HashTable(附带面试题)的学习笔记
    本文介绍了JDK源码学习之HashTable(附带面试题)的学习笔记,包括HashTable的定义、数据类型、与HashMap的关系和区别。文章提供了干货,并附带了其他相关主题的学习笔记。 ... [详细]
  • 海马s5近光灯能否直接更换为H7?
    本文主要介绍了海马s5车型的近光灯是否可以直接更换为H7灯泡,并提供了完整的教程下载地址。此外,还详细讲解了DSP功能函数中的数据拷贝、数据填充和浮点数转换为定点数的相关内容。 ... [详细]
author-avatar
台州路桥古筝音乐家教
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有