并发修改异常
需求:有一个集合,里面有三个元素。遍历集合,得到每一个元素,看有没有"world"这个元素,如果有,九田家一个“javee”的元素
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class LIstDemo {
public static void main(String[] args) {
// 创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//遍历集合得到每一个元素,看有没有"world"这个元素,如果有,我就添加一个"javaee"元素
Iterator<String> it = list.iterator();
while (it.hasNext()) {
String s = it.next();
if(s.equals("world")){
list.add("javaee");
}
}
//输出集合对象
System.out.println(list);
}
}
得到一个异常 java.util.ConcurrentModificationException 。这是个运行时异常,当不允许这种修改,可以通过检测到对象的并发修改方法来抛出此异常。 通过源码分析讲解并发修改异常。 ArraysList继承了一个类,然后实现了一个接口
public class ArrayList<E> extends AbstractList<E> implements List<E>
public interface List<E> {
Iterator<E> iterator();
boolean add(E e);
}
public class ArrayList<E> extends AbstractList<E> implements List<E>{
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
我们出问题最终是在next()方法中出的问题,为什么呢?在next()方法下首先调用了checkForComodification()方法。而在控制台看到的也是他出了问题。在这里面做了个判断,如果这两个值不相等,就会抛出并发修改异常。
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
modCount 这个是修改集合的次数,expectedModCount是预期修改集合的次数,如果他俩不相等就会抛出并发修改异常。一进来的时候,是将实际修改值赋值给预期修改值,所以他们应该是相等的
private class Itr implements Iterator<E> {
int expectedModCount = modCount;
当你做了某个操作,他俩值不一样,就抛出了并发修改异常。modCount来自ArrayList继承的父类里面
public abstract class AbstractList<E>{
protected int modCount = 0;
}
我们写的报错的程序中,写了一旦s.equals(“world”),就会list.add(“javaee”)。实际修改集合做了一个++,但是预期修改集合的次数没有做++。你下次调用next方法的时候肯定会走checkForComodification方法,判断到不一样,就会抛出并发修改异常。 遍历集合可以通过for循环遍历
for(int i =0 ;i<list.size();i++){
String s = list.get(i);
if(s.equals("world")){
list.add("javaee");
}
}
那么在上面方法list.get没有做判断吗?来看一下源码。在get方法里面,并没有做实际修改值和预期修改值的判断,他只是做了一个根据索引获取元素。
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
并发修改异常产生的原因:迭代器遍历过程中,通过集合对象修改了集合中元素的长度,造成了迭代器获取元素中判断预期修改值和实际修改值不一致。 解决方案是用for循环遍历,然后用集合对象做对应的操作
ListIterator
ListIterator被称为列表迭代器。通过List集合的listIterator()方法得到。 通过这个方法返回列表的迭代器。用于允许程序员沿任一方向遍历列表的列表和迭代器,在列表迭代期间修改列表,并获取列表中迭代器的当前位置 他有 hasNext() ,next() ,hasPrevious(),previous() 方法。还有add方法,把指定的元素插入列表,迭代器可以往集合中直接添加元素的。 next和hasNext方法是继承自iterator的。previous和hasPrevious是逆向遍历的。
import java.util.List;
import java.util.ArrayList;
import java.util.ListIterator;
public class ListIteratorDemo {
public static void main(String[] args) {
//创建集合对象
List<String> list = new ArrayList<String>();
//添加元素
list.add("hello");
list.add("world");
list.add("java");
//通过list集合的listIterator()方法得到
ListIterator<String> lit = list.listIterator();
while(lit.hasNext()){
String s = lit.next();
System.out.println(s);
}
}
}
//逆向遍历
while (lit.hasPrevious()){
String s = lit.previous();
System.out.println(s);
}
正向的时候很少使用列表迭代器,会直接使用iterator。这里通过列表迭代器的add添加元素。
ListIterator<String> lit = list.listIterator();
while(lit.hasNext()){
String s = lit.next();
if(s.equals("world")){
lit.add("javaee");
}
}
System.out.println(list);
public interface List<E> {
Iterator<E> iterator();
ListIterator<E> listIterator();
boolean add(E e);
}
public class AbstractList<E>{
protected int modCount = 0;
}
int modCount = 0;
public class ArrayList<E> extends AbstractList<E> implements List<E>{
public E get(int index) {
rangeCheck(index);
return elementData(index);
}
public boolean add(E e) {
modCount++;
add(e, elementData, size);
return true;
}
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
}
public ListIterator<E> listIterator() {
return new ListItr(0);
}
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
super();
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor - 1;
}
@SuppressWarnings("unchecked")
public E previous() {
checkForComodification();
int i = cursor - 1;
if (i < 0)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i;
return (E) elementData[lastRet = i];
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.set(lastRet, e);
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
ArrayList.this.add(i, e);
cursor = i + 1;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
}
ListITR继承自ITR,实现了ListIterator这个接口,所以说在调用listIterator这个方法得到的是ListIterator实现类的对象。 add方法重点看 expectedModCount = modCount这句,把实际修改值会赋值给预期修改值,列表迭代器add方法在添加了元素之后,调用next判断的时候,这两个值是一致的,所以不会出现并发修改异常,因为底层最终把实际修改值赋给预期修改值。
增强for循环
增强for循环是用来简化数组和和Collection集合的遍历 Collection集合继承了Iterable接口 Iterable接口,实现接口允许对虾是哪个成为foreach语句的目标。所以Collection体系的都可以成为foreach的目标。其内部原理是一个Iterator迭代器。 格式
for(元素数据类型 变量名 : 数组或者Collection集合) {
//在此处使用变量即可,该变量就是元素
}
int [] arr = { 1,2,3,4,5}
for(int i : arr){
System.out.println(i);
}
String[] strArray = {"hello","world","java"};
for (String s : strArray){
System.out.println(s);
}
List<String> list = new ArrayList<String>();
list.add("hello");
list.add("world");
list.add("java");
for(String s : list){
System.out.println(s);
}
如何验证他内部是一个Iterator迭代器呢?迭代器遍历集合,然后判断里面元素有没有world。如果有,通过结合调用add方法去往集合中添加元素,这时候就会抛出一个并发修改异常。如果异常,它内部就是一个Iterator迭代器。
for(String s : list){
if(s.equals("world")){
list.add("javaee");
}
}
案例:List集合存储学生对象用三种方式遍历
需求:创建一个存储学生对象集合,存储3个学生对象,使用程序实现在控制台遍历该集合。
定义学生类 创建List集合对象 创建学生对象 把学生添加到集合 遍历集合:①迭代器–集合特有的②普通for–带有索引的③增强for–最方便的
public class Student {
private String name;
private int age;
public Student(){};
public Student(String name,int age){
this.name = name;
this.age = age;
}
public void setName(String name){
this.name = name;
}
public String getName(){
return name;
}
public void setAge(int age){
this.age = age;
}
public int getAge(){
return age;
}
}
import java.util.ArrayList;
import java.util.List;
public class ListDemo {
public static void main(String[] args) {
//创建List对象
List<Student> list = new ArrayList<Student>();
//创建学生对象
Student s1 = new Student("林青霞",30);
Student s2 = new Student("张曼玉",35);
Student s3 = new Student("王祖贤",33);
//把学生添加到集合
list.add(s1);
list.add(s2);
list.add(s3);
}
}
Iterator<Student> it = list.iterator();
while(it.hasNext()){
Student s = it.next();
System.out.println(s.getName()+","+s.getAge());
}
for (int i = 0 ; i < list.size() ; i++){
Student s = list.get(i);
System.out.println(s.getName()+","+s.getAge());
}
for(Student s : list){
System.out.println(s.getName()+","+s.getAge());
}