有人能解释为什么subList()不是subSet()方法,而是抛出一个subSet,而subSet不是。这两个方法都创建了一个支持集合,因此可能subList()方法设计人员依赖不可修改的原始列表创建了这个方法,但是如果所有的支持集合都具有相同的行为(比如subSet()),那么不会更好吗?
//代码
public class ConcurrentModificationException {
public static void main(String[] args) {
String[] array = {"Java","Python","Pearl","Ada","Javascript","Go","Clojure"};
subListEx(array);
subSetEx(array);
}
private static void subListEx(String[] array) {
List<String> l = new ArrayList<String>(Arrays.asList(array));
List<String> l2 = l.subList(2, 4);
System.out.println(l.getClass().getName());
// l.add("Ruby"); // ConcurrentModificationException
// l.remove(2); // ConcurrentModificationException
l2.remove("Ada"); // OK
for (String s:l) { System.out.print(s+", "); }
System.out.println();
for (String s:l2) { System.out.print(s+", "); }
}
private static void subSetEx(String[] array) {
SortedSet<String> s1 = new TreeSet<String>(Arrays.asList(array));
SortedSet<String> s2 = s1.subSet("Java", "Python");
s1.remove("Ada");
for (String s:s1) { System.out.print(s+", "); }
System.out.println();
for (String s:s2) { System.out.print(s+", "); }
}}提前感谢!
发布于 2013-08-21 21:07:57
现在已经很清楚的是,这种行为是按照文献记录的。但我认为你的主要问题是为什么ArrayList和TreeSet的行为不同。好吧,这与数据如何在两个集合内部存储有关。
ArrayList内部使用一个数组来存储数据,随着ArrayList大小的动态增加,数据的大小会进行调整。现在,当您创建给定列表的subList时,具有指定索引的原始列表与subList关联。因此,在原始列表中执行的任何结构更改(对原始数组的索引)都将使作为子列表的一部分存储的索引变得毫无意义。这就是为什么在ArrayList#subList方法中不允许进行任何结构更改的原因。subList方法在inner类中返回一个名为SubList的inner类的实例,如下所示:
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> 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;
}如您所见,SubList包含对原始列表的引用。而parentOffset只不过是您正在创建的subList的起始索引。现在,修改原始list可能会更改原始列表中的fromIndex值,而不是SubList中的值。在这种情况下,parentOffset类中的SubList和原始列表中的fromIndex将指向不同的数组元素。还可能在某个时候,原始数组变得足够短,使存储在SubList中的索引无效,并使其成为OutOfRange。这当然是不可取的,并且返回的subList的语义被认为是未定义的,对于原始列表的这种结构更改。
另一方面,TreeSet在内部将其数据存储在TreeMap中。现在,由于在Map中没有这样的指数概念,所以不存在指数分解的问题。Map不过是键值对的映射.创建SubSet涉及到创建一个由原始Map支持的SubMap。修改原始Set只需要相应的键值映射无效,从而将更改传播到为subSet创建的subMap。
发布于 2013-08-21 20:52:26
List.subList(int, int)的合同涵盖了这一点。这是相关的部分,重点是我。
返回的列表由此列表支持,因此返回列表中的非结构更改反映在此列表中,反之亦然。 ..。 如果支持列表(即该列表)是结构修改的,则此方法返回的列表的语义变得不明确,而不是通过返回的列表。(结构修改是那些改变列表大小的修改,或者以其他方式干扰它,以致正在进行的迭代可能产生不正确的结果。)
在您的示例中,您正在对支持列表进行结构更改,因此结果未定义。
发布于 2013-08-21 20:53:54
文档非常清楚:aList.subList()返回列表的视图。返回的对象是由原始列表支持的,或者您应该假设。
关于Set的文档也非常清楚:
这个类的迭代器方法返回的迭代器是快速失败的:如果在迭代器创建后的任何时候对集合进行修改,那么除了通过迭代器自己的remove方法之外,以任何方式,迭代器将抛出一个ConcurrentModificationException。
为什么用List来制造问题比用Set容易呢?因为在Set中,删除元素的效果非常明显:元素不再存在于集合中,无论是对于set还是任何子集。插入操作也是如此。但是,当您从List中删除一个元素时,它是否意味着子列表相应地调整它们的索引(可能还包括大小),或者只是通过从右边或左边添加元素来保持它们的大小?在第二种情况下,如果支持名单变得太短怎么办?行为太复杂,无法定义。而且对于实现来说也太有限了。
例如:一个列表是a,b,c,d,子列表是list.subList(1,3) (b,c)。a已从列表中删除。清单中的效果是明确的。但是子列表是保持b,c,还是成为c,d?
例如:一个列表是a,b,c,d,子列表是list.subList(1,3) (b,c)。b已从列表中删除。同样,列表中的效果也是显而易见的。但是子列表是否变成C、a,c或c,d?
https://stackoverflow.com/questions/18367173
复制相似问题