一情景
在我平时开发的过程中,会设计到很多的集合问题,当然也会涉及很多对这个集合做操作,比如遍历集合,修改元素,移除元素或者增加一个元素,这样做的话就会有两个问题,一我们能直接访问集合的内部结构导致集合数据的安全隐患,二就是我们可能按照一定的顺序来遍历集合,比如从前到后,也可以从后到前,我们不可能直接去修改这个集合本身的遍历方式。所以我们只能用一个辅助的对象来帮我做这些操作。这个辅助的对象不仅能遍历这一个集合也可以遍历另外一个集合,也可以用不同的方式遍历集合,如果用这个辅助对象帮我完成这些操作,用户也不用直接去访问数据结构,导致数据安全问题。根据依赖置换原则我们就把这个辅助的对象抽象化。这种方式就是我学习的迭代器模式,我这里理解的遍历不是for或者while循环。
二迭代器模式
一个对象不需要考虑是什么但需要遍历的时候,我们可以考虑使用迭代器模式。什么意思呢?举个例子假设我们定义两个集合一个存放动物猫,一个存放飞机,我们需要对这两个集合进行迭代,此时就需要定义一个迭代器,不用管它们是什么东西(不用管是猫还是飞机,这两个东西不属于同一种类)都能使用这个迭代器。
迭代器模式 提供一种方法顺序访问一个聚合对象中的各个元素,这个模式叫做迭代器模式。
具体实现,第一步需要定义一个抽象的迭代器,用来管理具体的迭代器实现,具体有三个行为,一个是获取下一个元素,一个是获取第一个元素,一个是判断是否还有下一个元素。这里我是简单的抽象一个迭代器,可能还有很多其他行为,比如移除等。
public interface Itor {
public Object next();//获取下一个元素
public Object first();//获取第一个元素
public boolean hasNext();//判断是否还有下一个元素
}
然后应该是具体的实现迭代器的类,这里我先不放,一会再说原因,先放具体需要别被迭代的聚合对象
聚合对象的抽象类
public abstract class Agg {
public abstract Itor creatItor();//创建一个迭代器
}
具体的聚合对象,
public class ConAgg extends Agg {
private Object[] list;
public ConAgg(Object[] list){
this.list=list;
}
public Itor creatItor() {//具体的创建迭代器 1处
return new ConItor();
}
具体实现迭代器的类 放在内部类里面,节省资源。这里使用的是从前往后的顺序访问目标聚合类
public class ConItor implements Itor{// 2处public int count=0;
public ConItor(){
}
public Object next() {//这里使用从前往后的顺序访问,也可以调整自己想要的方式
Object ret=null;
count++;
if(count<list.length){
ret=list[count];
}
return ret;
}
public Object first() {
return list[0];
}
public boolean hasNext() {
return count<list.length-1;
}
}
}
客户端代码
String[] list={"s","a","c","d","a2","wq"};
ConAgg l= new ConAgg(list);
Itor it= l.creatItor();
while(it.hasNext()){//看到这里是不是有一种很熟悉的感觉,对的我们平常使用的Iterator就是迭代器模式
System.out.println(it.next());
}
}
看2处代码 ,我解释一下,刚刚为啥说先不编写迭代器的实现方式的代码,我是准备把这个类放在聚合类的内部类里面了这样有一个好处,如果我把这个类分离出去,在聚合对象1处new 的时候,我就需要把对象ConAgg 中的list集合传到迭代器对象里才行(比如return new ConItor(list);),放在内部类里面就能实现主类和内部类实现资源共享,这样也节省了资源。
其实这种方式在我们java中不能说大部反正就我了解的很多都用这种方式,只是他们的内部类实现方式可能不同,比如Arrlist,看一下arrlist实现迭代器的接口
private class Itr
implements Iterator<E>
{
int cursor;
int lastRet = -1;
int expectedModCount = modCount;
private Itr() {}
public boolean hasNext()
{
return cursor != size;
}
public E next()
{
checkForComodification();
int i = cursor;
if (i >= size) {
throw new NoSuchElementException();
}
Object[] arrayOfObject = elementData;
if (i >= arrayOfObject.length) {
throw new ConcurrentModificationException();
}
cursor = (i + 1);
return (E)arrayOfObject[(lastRet = i)];
}
public void remove()
{
if (lastRet < 0) {
throw new IllegalStateException();
}
checkForComodification();
try
{
remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
}
catch (IndexOutOfBoundsException localIndexOutOfBoundsException)
{
throw new ConcurrentModificationException();
}
}
public void forEachRemaining(Consumer<? super E> paramConsumer)
{
Objects.requireNonNull(paramConsumer);
int i = size;
int j = cursor;
if (j >= i) {
return;
}
Object[] arrayOfObject = elementData;
if (j >= arrayOfObject.length) {
throw new ConcurrentModificationException();
}
while ((j != i) && (modCount == expectedModCount)) {
paramConsumer.accept(arrayOfObject[(j++)]);
}
cursor = j;
lastRet = (j - 1);
checkForComodification();
}
final void checkForComodification()
{
if (modCount != expectedModCount) {//当
throw new ConcurrentModificationException();
}
}
}
看完源码我又想到了一个学习点。
既然说到这里了就顺便学习一下快速失败,和安全失败,这段代码设计到一个异常ConcurrentModificationException,在这段代码里有两个属性值一个是expectedModCount一个是modCount,在这个迭代器Itr中 初始化的时候这两个值是相等的,int expectedModCount = modCount;
在遍历的过程的过程中,会检测整两个相等不相等,
if (modCount != expectedModCount) {//当
throw new ConcurrentModificationException();
}
如下不不相等就会抛出异常,是什么时候会导致modCount改变呢?就是在遍历的过程中,如果对象本身去改变的话会导致modCount改变,如果使用迭代器来改变的话,就不会改变。这句话什么意思,再看上面的代码,上面的代码是一个具体迭代器实现类,也就是Arrlist里面itr这个类,里面有add和remove 方法,但这些方法中都设置expectedModCount = modCount,所以不会改变,什么叫做使用对象本身更改呢,再看看arrlist对象本身的方法。
private void ensureExplicitCapacity(int paramInt)
{
modCount += 1;//每次改变modCount都会加一
if (paramInt - elementData.length > 0) {
grow(paramInt);
}
}
这个是ArrayList 本身的add方法,add会调用addensureExplicitCapacity方法,每次改变modCount都会加一,所以在遍历的时候回抛出异常。这里主要针对的是遍历这种方式,不是使用for或者while的方式。
java.util下面的集合都是快速失败的
了解了快速失败,安全绳失败就很好理解,就是如果一个集合不收修改次数影响就是安全失败,java.util.concurrent下的集合都是安全失败的