天天看点

23种设计模式----迭代器模式----行为模式

文章目录

  • ​​1.迭代器模式目的​​
  • ​​2.迭代器模式实现​​
  • ​​2.1一些名词​​
  • ​​迭代器:进行遍历行为的类​​
  • ​​容器:存放元素的类​​
  • ​​2.3实现类定义​​
  • ​​2.4 实体类定义​​
  • ​​2.5 测试类定义​​
  • ​​2.6 测试结果​​
  • ​​3.迭代器模式扩展​​
  • ​​java中util包中的​​
  • ​​至于容器的接口​​
  • ​​3.2例子​​
  • ​​3.2.1学校(容器)​​
  • ​​3.2.2学生(实体)​​
  • ​​3.2.3学校的迭代器​​
  • ​​3.2.4测试代码​​
  • ​​3.3实现自己的集合​​
  • ​​4.常见的问题​​
  • ​​4.1在遍历的过程中删除​​
  • ​​4.2遍历的并发安全性​​

23种设计模式

#迭代器模式

1.迭代器模式目的

  迭代器模式主要实现行为的分离。

实现遍历行为的分离。

2.迭代器模式实现

2.1一些名词

迭代器接口:一个接口,定义hasNext和next方法。

容器接口:一个接口,定义iterator方法。

迭代器:实现了迭代器接口的类。

容器:实现了容器接口的类。

hasNext:判断next方法是否可用

next:返回当前对象,并指向下一个

iterator:返回当前容器的迭代器

remove:删除当前元素

迭代器:进行遍历行为的类

容器:存放元素的类

###2.2接口的定义

迭代器接口

public interface MyIterator {

  boolean hasNext();
  
  People next();
  
}      

容器接口

public interface Container {

  RoomEach iterator();
  
}      

2.3实现类定义

迭代器

public class RoomEach implements MyIterator{

  private Room room;
  
  private int index;
  
  public RoomEach(Room room) {
    this.room = room;
    index = 0;
  }
  
  @Override
  public boolean hasNext() {
    if(index < room.getSize())
      return true;
    else
      return false;
  }

  @Override
  public People next() {
    People people = room.getPeople(index);
    index++;
    return people;
  }

}      

容器

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Room implements Container{

  private List<People> roomPeople;
  
  public Room(int size){
    roomPeople = new ArrayList<People>();
    for(int i = 0 ;i < size;i++){
      People people = new People();
      people.setName(new SimpleDateFormat("hh:mm:ss").format(new Date()).toString());
      people.setAge(Integer.valueOf(System.currentTimeMillis()%1000+""));
      roomPeople.add(people);
    }
  }
  
  public int getSize(){
    return this.roomPeople.size();
  }
  
  public People getPeople(int i){
    return roomPeople.get(i);
  }
  
  @Override
  public RoomEach iterator() {
    return new RoomEach(this);
  }

}      

2.4 实体类定义

元素

import java.text.SimpleDateFormat;
import java.util.Date;

public class People {

  private String name;
  
  private int age;
  
  public People(){
    this.name = new SimpleDateFormat("hh:mm:ss").format(new Date()).toString();
    this.age = Integer.valueOf(System.currentTimeMillis()%1000 + "");
  }
  
  public void setName(String name){
    this.name = name;
  }
  
  public void setAge(int age){
    this.age = age;
  }
  
  public String getName(){
    return this.name;
  }
  
  public int getAge(){
    return this.age;
  }
  
}      

2.5 测试类定义

测试

public class Main {

  public static void main(String[] args) {
    
    Room room = new Room(10);
    
    RoomEach roomEach = room.iterator();
    while(roomEach.hasNext()){
      People people = roomEach.next();
      System.out.println(people.getName()+"|||||||"+people.getAge());
    }
    
  }

}      

2.6 测试结果

04:22:15|||||||980
04:22:15|||||||980
04:22:15|||||||980
04:22:15|||||||981
04:22:15|||||||981
04:22:15|||||||981
04:22:15|||||||981
04:22:15|||||||981
04:22:15|||||||981
04:22:15|||||||981      

3.迭代器模式扩展

###3.1迭代器模式的接口可以使用jdk的接口

java中util包中的

public interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}      

一般remove方法很少被用到,主要还是前两个方法。

所以呢,要实现迭代器需要实现迭代器的接口

至于容器的接口

java.lang包

import java.util.Iterator;
public interface Iterable<T> {
    Iterator<T> iterator();
}      

所以,如果你只需要迭代器最普通的实现,那么只需要在对应的类上实现对应的接口就行。

3.2例子

3.2.1学校(容器)
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class School implements Iterable<Student>{

  private List<Student> students;
  
  public School(int n) {
    students = new ArrayList<Student>();
    for(int i = 0;i < n;i++){
    Student student = new Student();
    student.setName(i+"");
    student.setAge(i);
    student.setSubject(i+"");
    students.add(student);
    }
  }
  
  public int getLength() {
    return students.size();
  }
  
  public Student getStudent(int x){
    return students.get(x);
  }
  
  public void removeStudent(int x){
    students.remove(x);
  }
  
  @Override
  public Iterator<Student> iterator() {
    return new SchoolQuery(this);
  }

}      
3.2.2学生(实体)
public class Student {

  private String name;
  
  private int age;
  
  private String subject;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public int getAge() {
    return age;
  }

  public void setAge(int age) {
    this.age = age;
  }

  public String getSubject() {
    return subject;
  }

  public void setSubject(String subject) {
    this.subject = subject;
  }
}      
3.2.3学校的迭代器
import java.util.Iterator;

public class SchoolQuery implements Iterator<Student>{

  private School school;
  
  private int index;
  
  public SchoolQuery(School school) {
    this.school = school;
    index = 0;
  }
  
  @Override
  public boolean hasNext() {
    if(index < school.getLength())
      return true;
    else
      return false;
  }

  @Override
  public Student next() {
    Student student = school.getStudent(index);
    index++;
    return student;
  }

  @Override
  public void remove() {
    school.removeStudent(index - 1);
  }

}      
3.2.4测试代码
School school = new School(50);
    SchoolQuery schoolQuery = (SchoolQuery) school.iterator();
    while (schoolQuery.hasNext()) {
      Student student = schoolQuery.next();
      System.out.println("name:"
          + student.getName()
          + "\tage:"
          + student.getAge()
          + "\tsubject:"
          + student.getSubject()
          + "\ttime:"
          + new SimpleDateFormat("ss,sss").format(System
              .currentTimeMillis()));
    }      

3.3实现自己的集合

实现自己需要用到的数据存储集合:

一般常用的库帮助我们写了非常多的方法,常用的List,set,Map…等等,有好多都有实现了迭代器,所以我们能够使用这些集合的非常方便的遍历数据。

4.常见的问题

4.1在遍历的过程中删除

普通情况下,我们可能不使用迭代器进行遍历,就像这样:

//普通for循环
for(int i = 0;i < length;i++){
//......
}
//增强for循环
for(Class c:array){
//.....
}
//Class 元素类
//c元素变量
//array数组变量      

就for循环来说,最常用可能就这两种,其他的也有,但是比较少用,普通for在基本类型数组中较多,增强for循环在复杂类型数组及存储集合中较多。

在遍历的过程中删除有一个比较麻烦的事情:

使用普通for循环,因为是基本数组,删除其中一个元素,意味着需要把后面的元素依次前移(有序)或者用最后一个元素替换删除元素(无序)然后删除最后一个元素,但是不管怎么删除,因为基本类型数组,可能在一开始就限定了大小,所以,删除需要复制到新数组,需要实现复制方法。

使用增强for循环,是复杂类型数组,一般jdk实现了对这些集合的基本操作(增删取。。。。)。所以删除比较方便,可能调用个方法就行。

但是在增强for循环中直接删除会发生异常:

试验代码:

for(Student student:school.getList()){
      if(student.getAge()%10==4){
        school.getList().remove(student);
      }
    }      

在上面的例子中修改:

在School.java中添加一个方法:

public List<Student> getList(){
    return students;
  }      

运行结果:

Exception in thread "main" java.util.ConcurrentModificationException
  at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
  at java.util.AbstractList$Itr.next(AbstractList.java:343)
  at com.startime.iterator.Main.main(Main.java:33)      

查看是AbstractList.java:372行抛出的异常:

代码如下:

final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
    }      

其中涉及到两个参数:modCount和expectedModCount

这两个参数的定义如下:

/**
     * The number of times this list has been <i>structurally modified</i>.
     * Structural modifications are those that change the size of the
     * list, or otherwise perturb it in such a fashion that iterations in
     * progress may yield incorrect results.
     *
     * <p>This field is used by the iterator and list iterator implementation
     * returned by the {@code iterator} and {@code listIterator} methods.
     * If the value of this field changes unexpectedly, the iterator (or list
     * iterator) will throw a {@code ConcurrentModificationException} in
     * response to the {@code next}, {@code remove}, {@code previous},
     * {@code set} or {@code add} operations.  This provides
     * <i>fail-fast</i> behavior, rather than non-deterministic behavior in
     * the face of concurrent modification during iteration.
     *
     * <p><b>Use of this field by subclasses is optional.</b> If a subclass
     * wishes to provide fail-fast iterators (and list iterators), then it
     * merely has to increment this field in its {@code add(int, E)} and
     * {@code remove(int)} methods (and any other methods that it overrides
     * that result in structural modifications to the list).  A single call to
     * {@code add(int, E)} or {@code remove(int)} must add no more than
     * one to this field, or the iterators (and list iterators) will throw
     * bogus {@code ConcurrentModificationExceptions}.  If an implementation
     * does not wish to provide fail-fast iterators, this field may be
     * ignored.
     */
    protected transient int modCount = 0;      

看注释猜到这个modCount应该是记录修改次数的变量。

这是另一个参数:

/**
   * The modCount value that the iterator believes that the backing
   * List should have.  If this expectation is violated, the iterator
   * has detected concurrent modification.
   */
  int expectedModCount = modCount;      

差不多就是并发修改标识。

总之就是这两个值相同才是正确的,不相同表示发生错误。

这是ArrayList的remove方法:

public boolean remove(Object o) {
    if (o == null) {
            for (int index = 0; index < size; index++)
        if (elementData[index] == null) {
            fastRemove(index);
            return true;
        }
    } else {
        for (int index = 0; index < size; index++)
        if (o.equals(elementData[index])) {
            fastRemove(index);
            return true;
        }
        }
    return false;
    }      

发现在remove方法中并没有对上述两个值的改变,但是有一个方法:

private void fastRemove(int index) {
        modCount++;
        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // Let gc do its work
    }      

这个方法在remove方法中调用。

在增加的方法中:

public boolean add(E e) {
    ensureCapacity(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
    }      

add方法中调用了这个方法:

public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
            if (newCapacity < minCapacity)
        newCapacity = minCapacity;
            // minCapacity is usually close to size, so this is a win:
            elementData = Arrays.copyOf(elementData, newCapacity);
    }
    }      

对于一个数组来说,最可能改变modCount的地方就是这两个方法。

举个例子,对于50个元素的arrayList的modCount的值是怎么变化的。

首先新建一个arrayList,modCount=0(初值是0)

然后添加一个元素,modCount加1

当50个元素增加完,modCount=50,expectedmodCount=50

然后删除一个元素时,

modCount加1,modCount=51

然后对删除元素后面的元素进行向前拷贝:

拷贝的方法:

System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);      

这个方法的原型:

public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);      

看到原型方法就很清楚这个方法的参数:

源数据,源数据开始下标

目标数据,目标数据开始下标

长度。

所以,在n个元素中删除第i个

参数为:

data,i+1,data,i,n-i-1(这里多减一是因为从0开始)

可以看到,在remove方法中删除元素,导致modCount值+1,且对元素进行重新拷贝。

但是

expectedmodCount的值没有发生改变。

然后我们取出下一个元素的时候,调用了获取元素的方法:

public E next() {
            checkForComodification();
      try {
    E next = get(cursor);
    lastRet = cursor++;
    return next;
      } catch (IndexOutOfBoundsException e) {
    checkForComodification();
    throw new NoSuchElementException();
      }
  }      

这个应该是增强for循环的一种实现方式,有可能是重载了冒号这个运算符,类似Java中字符串可以用加号连接一样。(我是通过加断点的方式得出调用这个方法获取下一个元素)

其中有一个检测的方法:

final void checkForComodification() {
        if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
    }
    }      

当modCount和expectedmodCount值不同,抛出异常。

那么使用迭代器呢?

测试方案:去掉学生age%10==4的学生

在测试方法中进行修改,修改为以下:

import java.text.SimpleDateFormat;

public class Main {

  public static void main(String[] args) {

    Room room = new Room(10);

    RoomEach roomEach = room.iterator();
    while (roomEach.hasNext()) {
      People people = roomEach.next();
      System.out.println(people.getName() + "|||||||" + people.getAge());
    }

    School school = new School(50);
    SchoolQuery schoolQuery = (SchoolQuery) school.iterator();
    while (schoolQuery.hasNext()) {
      Student student = schoolQuery.next();
      System.out.println("name:"
          + student.getName()
          + "\tage:"
          + student.getAge()
          + "\tsubject:"
          + student.getSubject()
          + "\ttime:"
          + new SimpleDateFormat("ss,sss").format(System
              .currentTimeMillis()));
      if(student.getAge()%10 == 4){
        schoolQuery.remove();
      }
    }

    schoolQuery = (SchoolQuery) school.iterator();
    while (schoolQuery.hasNext()) {
      Student student = schoolQuery.next();
      System.out.println("name:"
          + student.getName()
          + "\tage:"
          + student.getAge()
          + "\tsubject:"
          + student.getSubject()
          + "\ttime:"
          + new SimpleDateFormat("ss,sss").format(System
              .currentTimeMillis()));
    }

    
//    for(Student student:school.getList()){
//      if(student.getAge()%10==4){
//        school.getList().remove(student);
//      }
//    }
    
    
  }

}
      

执行结果:

03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||827
03:15:19|||||||828
03:15:19|||||||828
name:0  age:0 subject:0 time:19,019
name:1  age:1 subject:1 time:19,019
name:2  age:2 subject:2 time:19,019
name:3  age:3 subject:3 time:19,019
name:4  age:4 subject:4 time:19,019
name:6  age:6 subject:6 time:19,019
name:7  age:7 subject:7 time:19,019
name:8  age:8 subject:8 time:19,019
name:9  age:9 subject:9 time:19,019
name:10 age:10  subject:10  time:19,019
name:11 age:11  subject:11  time:19,019
name:12 age:12  subject:12  time:19,019
name:13 age:13  subject:13  time:19,019
name:14 age:14  subject:14  time:19,019
name:16 age:16  subject:16  time:19,019
name:17 age:17  subject:17  time:19,019
name:18 age:18  subject:18  time:19,019
name:19 age:19  subject:19  time:19,019
name:20 age:20  subject:20  time:19,019
name:21 age:21  subject:21  time:19,019
name:22 age:22  subject:22  time:19,019
name:23 age:23  subject:23  time:19,019
name:24 age:24  subject:24  time:19,019
name:26 age:26  subject:26  time:19,019
name:27 age:27  subject:27  time:19,019
name:28 age:28  subject:28  time:19,019
name:29 age:29  subject:29  time:19,019
name:30 age:30  subject:30  time:19,019
name:31 age:31  subject:31  time:19,019
name:32 age:32  subject:32  time:19,019
name:33 age:33  subject:33  time:19,019
name:34 age:34  subject:34  time:19,019
name:36 age:36  subject:36  time:19,019
name:37 age:37  subject:37  time:19,019
name:38 age:38  subject:38  time:19,019
name:39 age:39  subject:39  time:19,019
name:40 age:40  subject:40  time:19,019
name:41 age:41  subject:41  time:19,019
name:42 age:42  subject:42  time:19,019
name:43 age:43  subject:43  time:19,019
name:44 age:44  subject:44  time:19,019
name:46 age:46  subject:46  time:19,019
name:47 age:47  subject:47  time:19,019
name:48 age:48  subject:48  time:19,019
name:49 age:49  subject:49  time:19,019
name:0  age:0 subject:0 time:19,019
name:1  age:1 subject:1 time:19,019
name:2  age:2 subject:2 time:19,019
name:3  age:3 subject:3 time:19,019
name:5  age:5 subject:5 time:19,019
name:6  age:6 subject:6 time:19,019
name:7  age:7 subject:7 time:19,019
name:8  age:8 subject:8 time:19,019
name:9  age:9 subject:9 time:19,019
name:10 age:10  subject:10  time:19,019
name:11 age:11  subject:11  time:19,019
name:12 age:12  subject:12  time:19,019
name:13 age:13  subject:13  time:19,019
name:15 age:15  subject:15  time:19,019
name:16 age:16  subject:16  time:19,019
name:17 age:17  subject:17  time:19,019
name:18 age:18  subject:18  time:19,019
name:19 age:19  subject:19  time:19,019
name:20 age:20  subject:20  time:19,019
name:21 age:21  subject:21  time:19,019
name:22 age:22  subject:22  time:19,019
name:23 age:23  subject:23  time:19,019
name:25 age:25  subject:25  time:19,019
name:26 age:26  subject:26  time:19,019
name:27 age:27  subject:27  time:19,019
name:28 age:28  subject:28  time:19,019
name:29 age:29  subject:29  time:19,019
name:30 age:30  subject:30  time:19,019
name:31 age:31  subject:31  time:19,019
name:32 age:32  subject:32  time:19,019
name:33 age:33  subject:33  time:19,019
name:35 age:35  subject:35  time:19,019
name:36 age:36  subject:36  time:19,019
name:37 age:37  subject:37  time:19,019
name:38 age:38  subject:38  time:19,019
name:39 age:39  subject:39  time:19,019
name:40 age:40  subject:40  time:19,019
name:41 age:41  subject:41  time:19,019
name:42 age:42  subject:42  time:19,019
name:43 age:43  subject:43  time:19,019
name:45 age:45  subject:45  time:19,019
name:46 age:46  subject:46  time:19,019
name:47 age:47  subject:47  time:19,019
name:48 age:48  subject:48  time:19,019
name:49 age:49  subject:49  time:19,019
      

那为什么使用迭代器就是安全的?

首先在调试的时候有一个文件AbstractList$Itr这个文件:

public void remove() {
      if (lastRet == -1)
    throw new IllegalStateException();
            checkForComodification();

      try {
    AbstractList.this.remove(lastRet);
    if (lastRet < cursor)
        cursor--;
    lastRet = -1;
    expectedModCount = modCount;
      } catch (IndexOutOfBoundsException e) {
    throw new ConcurrentModificationException();
      }
  }      

使用迭代器删除,我看其他讲这个的文章说的大概意思就是迭代器创建了一个副本(这个可以理解,我们实现迭代器的时候有一个参数:private School school;)所以呢,在副本中进行操作是因为我们在迭代器中实现了方法。而且使用迭代器的删除使用的是这个remove方法,在方法中对expectedmodCount值进行更新,所以不会报错。(还有网上解释说有其他的变量,我暂时还没有搞懂,所以这只是我的一个猜测)

4.2遍历的并发安全性

继续阅读