`
darrenzhu
  • 浏览: 807068 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

ConcurrentModificationException, clone, Collections.copy

    博客分类:
  • Java
阅读更多
注意,这里的参考文章基本来自Effective Java和jdk源码

1)ConcurrentModificationException
当你用for each遍历一个list时,如果你在循环主体代码中修改list中的元素,将会得到这个Exception,解决的办法是:
1)用listIterator, 它支持在遍历的过程中修改元素,
2)不用listIterator, new一个新的list,copy old list content to new list, and then modify the new created list, in this way we can avoid such expception.
注意Collection中只有iterator,没有listIterator,List接口中才有listIterator.

ListIterator<FilterCriteriaVO> listIterator=filterVOs.listIterator();
int size=filterVOs.size();
for (int i=0; i<size; i++) {
   FilterCriteriaVO filterExpression=listIterator.next();
  listIterator.remove();
  listIterator.add(leg1TypeExpression);
}

请注意,listIterator的remove方法是不需要参数的,因为它是针对最近一次由next或previous获取的对象而言的.
void java.util.ListIterator.remove()
Removes from the list the last element that was returned by next or previous (optional operation). This call can only be made once per call to next or previous. It can be made only if ListIterator.add has not been called after the last call to next or previous.

事实上,不仅只有remove方法这样,ListIterator里面的add,set方法也是基于next和previous返回的结果的。详情可以参考jdk源代码。


扩展知识:
java.util.ConcurrentModificationException
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.

For example, it is not generally permissible for one thread to modify a Collection while another thread is iterating over it. In general, the results of the iteration are undefined under these circumstances. Some Iterator implementations (including those of all the general purpose collection implementations provided by the JRE) may choose to throw this exception if this behavior is detected. Iterators that do this are known as fail-fast iterators, as they fail quickly and cleanly, rather that risking arbitrary, non-deterministic behavior at an undetermined time in the future.

Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.

Note that fail-fast behavior cannot be guaranteed as it is, generally speaking, impossible to make any hard guarantees in the presence of unsynchronized concurrent modification. Fail-fast operations throw ConcurrentModificationException on a best-effort basis. Therefore, it would be wrong to write a program that depended on this exception for its correctness: ConcurrentModificationException should be used only to detect bugs.

java.util.ListIterator<E>
An iterator for lists that allows the programmer to traverse the list in either direction, modify the list during iteration, and obtain the iterator's current position in the list. A ListIterator has no current element; its cursor position always lies between the element that would be returned by a call to previous() and the element that would be returned by a call to next(). An iterator for a list of length n has n+1 possible cursor positions, as illustrated by the carets (^) below:

Element(0)  ^ Element(1) ^  Element(2)   ... Element(n-1)^cursor positions:  ^         
Note that the remove and set(Object) methods are not defined in terms of the cursor position; they are defined to operate on the last element returned by a call to next or previous().
This interface is a member of the Java Collections Framework.

2)关于clone以及Cloneable接口
Cloneable里面没有包含任何方法,它只是决定了Object中受保护的clone方法实现的行为:如果一个类实现了Cloneable, Object的clone方法就返回该对象的逐域拷贝,否则就抛出CloneNotSupportedException。这是接口的一种极端非典型的用法,不值得效仿。通常情况下,实现接口是为了表明类可以为它的客户做些什么。然而对应Cloneable接口,它改变了超类中受保护的方法的行为。
Best Practise:
1)最好不要去实现自己的clone方法,即不要去implement Cloneable接口,要想拷贝,自己可以写一个copy方法,所有的内容完全由自己控制。
2)如果一定要实现Cloneable,那么首先通过super.clone()获取到最初始的拷贝对象,然后去覆盖一些域,因为对list而言,通过super.clone()方法获得的拷贝对象只是在栈中新建了一个list引用变量,但是这个引用变量还是指向原来list中的成员的,即并没有在堆中新建list的成员。

先看一下Jdk1.6 中HashMap的clone方法源码:
    /**
     * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and
     * values themselves are not cloned.
     *
     * @return a shallow copy of this map
     */
    public Object clone() {
        HashMap<K,V> result = null;
	try {
	    result = (HashMap<K,V>)super.clone();
	} catch (CloneNotSupportedException e) {
	    // assert false;
	}
        result.table = new Entry[table.length];
        result.entrySet = null;
        result.modCount = 0;
        result.size = 0;
        result.init();
        result.putAllForCreate(this);

        return result;
    }

java.lang.Cloneable
A class implements the Cloneable interface to indicate to the java.lang.Object.clone() method that it is legal for that method to make a field-for-field copy of instances of that class.

Invoking Object's clone method on an instance that does not implement the Cloneable interface results in the exception CloneNotSupportedException being thrown.

By convention, classes that implement this interface should override Object.clone (which is protected) with a public method. See java.lang.Object.clone() for details on overriding this method.

Note that this interface does not contain the clone method. Therefore, it is not possible to clone an object merely by virtue of the fact that it implements this interface. Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.

3)Collections.copy

List<Object> dest=new ArrayList<Object>(src.size());
//or List<Object> dest=new ArrayList<Object>();
Collections.copy(dest, src());


按如上方式用时,很容易得到"Source does not fit in dest" Exception,原因是copy方法里面会首先比较dest和src的size,而往往你新new的list的size是0,所以会报错。
为什么明明已经设置了长度为src.size,为什么还会出错!
后来打印出dest.size()才知道dest的长度为0,new ArrayList<Object>(src.size())表示的是这个List的容纳能力为src.size,并不是说dest中就有了src.size个元素。查看api才知 道,它的capacity(容纳能力大小)可以指定(最好指定)。而初始化时size的大小永远默认为0,只有在进行add和remove等相关操作 时,size的大小才变化。然而进行copy()时候,首先做的是将desc1的size和src1的size大小进行比较,只有当desc1的 size 大于或者等于src1的size时才进行拷贝,否则抛出IndexOutOfBoundsException异常。

可以通过下面的方法指定目标desc的大小
List<Object> values=new ArrayList<Object>(Arrays.asList(new Object[srcList.size()]));
Collections.copy(values, srcList());

执行第一句后,desc 的size的大小是为srcList的size,其实它是对一个空数组的浅拷贝,虽然是一个Object类型的空数组,但是list里面只存储引用变量,所以list的size已经变化了,只是每个引用变量还没有指向具体的对象而已。


Collections.copy源码
    /**
     * Copies all of the elements from one list into another.  After the
     * operation, the index of each copied element in the destination list
     * will be identical to its index in the source list.  The destination
     * list must be at least as long as the source list.  If it is longer, the
     * remaining elements in the destination list are unaffected. <p>
     *
     * This method runs in linear time.
     *
     * @param  dest The destination list.
     * @param  src The source list.
     * @throws IndexOutOfBoundsException if the destination list is too small
     *         to contain the entire source List.
     * @throws UnsupportedOperationException if the destination list's
     *         list-iterator does not support the <tt>set</tt> operation.
     */
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
	    ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }


4)Collections.copy() versus ArrayList(Collection<? extends E> c)

两者都是浅拷贝,只是对源list的元素进行拷贝,拷贝的只是引用。拷贝后两个list的元素(引用)不同,但是引用所指向的对象是一样的。即两个list的每个元素指向的还是同一内存。
分享到:
评论
2 楼 darrenzhu 2014-05-30  
Checkmate 写道
最后一条我倒是看到了不同的解释.
Both methods are shallow copy. So what is the difference between these two methods?

First, Collections.copy() won’t reallocate the capacity of dstList even if dstList does not have enough space to contain all srcList elements. Instead, it will throw an IndexOutOfBoundsException. One may question if there is any benefit of it. One reason is that it guarantees the method runs in linear time. Also it makes suitable when you would like to reuse arrays rather than allocate new memory in the constructor of ArrayList.

Collections.copy() can only accept List as both source and destination, while ArrayList accepts

我也觉得貌似都是浅克隆.


you are right, 都是浅拷贝.
1 楼 Checkmate 2013-09-26  
最后一条我倒是看到了不同的解释.
Both methods are shallow copy. So what is the difference between these two methods?

First, Collections.copy() won’t reallocate the capacity of dstList even if dstList does not have enough space to contain all srcList elements. Instead, it will throw an IndexOutOfBoundsException. One may question if there is any benefit of it. One reason is that it guarantees the method runs in linear time. Also it makes suitable when you would like to reuse arrays rather than allocate new memory in the constructor of ArrayList.

Collections.copy() can only accept List as both source and destination, while ArrayList accepts

我也觉得貌似都是浅克隆.

相关推荐

Global site tag (gtag.js) - Google Analytics