java迭代器及foreach详解

一.迭代器

我们知道,在集合框架中,Collection是根接口,而Collection继承了Iterable接口,所以所有实现collection接口的类都可以使用迭代器.
在这里插入图片描述
在集合框架中,ArrayList,LinkedList,HashSet,TreeSet都可以使用迭代器.我们看看Iterable接口定义了哪些方法.

public interface Iterable<T> {
    
    
	//返回实现java.util.Iterator迭代器对象
	public abstract Iterator<T> iterator();
}

iterator方法要求返回一个Iterator类型的对象,我们再来看看这个Iterator是什么.
跳转到Iterator中,我们发现它也是一个接口

public interface Iterator<E> {
    
    
	//判断是否有后继元素,若有则返回true
	boolean hasNext();
	//返回后继元素,如果没有后继元素,就抛出异常
	E next();
	//删除迭代器对象表示的集合当前元素
    void remove()}

Iterator 是为了实现对java容器(collection)进行遍历功能的1个接口.
我们定义iterator 是实现了Iterator 接口的1个对象.
首先iterator 实现了Iterator接口后, 相当于把1个Collection容器的所有对象, 做成1个线性表(List) . 而iterator本身于1个指针.
这个指针一开始是位于 第1个元素之前的.

boolean hasNext();
判断iterator 内是否存在下1个元素, 如果存在返回true, 否则返回false.
注意, 这时上面的那个指针位置不变.

Object next();
返回iterator 内下1个元素, 同时上面的指针向后移动一位.
如果不断地循环执行next()方法, 就可以遍历容器内所有的元素了.

void remove();
删除iterator 内指针的前1个元素, 前提是至少执行过1次next();
这个方法不建议使用, 建议使用容器本身的remove方法.

我们可以使用迭代器遍历一个集合,如下:

public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		Iterator it1=list.iterator();
		System.out.println("迭代器遍历(while)");
		while(it1.hasNext()) {
    
    
			System.out.println(it1.next());
		}
		System.out.println("迭代器遍历(for)");
		for(Iterator it2=list.iterator();it2.hasNext();) {
    
    
			System.out.println(it2.next());
		}
    }
}
//程序输出结果为
//			迭代器遍历(while)
//			Jack
//			Bill
//			迭代器遍历(for)
//			Jack
//			Bill

list的iterator()方法会new一个迭代器对象返回.上面的while和for其实都是一样的.next方法一个个向下遍历,直到hasNext()返回false时,循环结束.

二.foreach循环

一个类只要实现了Iterable接口,就可以使用迭代器遍历,也可以使用foreach语句.在这里插入图片描述
声明的循环变量的类型必须和数组的类型相同

import java.util.*;
public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		for(String elem:list) {
    
    
			System.out.println(elem);
		}
    }
}
//程序输出结果为
//				Jack
//				Bill

如果把elem改成char类型,编译器会报错:Type mismatch: cannot convert from element type String to char

foreach虽然能遍历数组或者集合,但是只能用来遍历,无法在遍历过程中对数组或者集合进行修改,而for循环可以在遍历的过程中对原数组或者集合进行修改

import java.util.*;
public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		for(String elem:list) {
    
    
			elem="233";
		}
		System.out.println(list.toString());
    }
}
//程序输出结果为
//			[Jack, Bill]

这里我们把每个循环变量赋值为233,但是结果却没有任何变化.

下面我们将深入讲解一下,为什么赋值没有用
foreach语句等同于使用迭代器进行遍历,如下图
在这里插入图片描述
两种形式可以相互转换
我们在foreach语句中,定义的循环变量是一个临时变量.
在上面的例子中

for(String elem:list) {
    
    
	elem="233";
}

我们一个个带入上面的图中,type=String,var=elem,coll=list,循环体为elem=”233”;
可以把foreach转换成迭代器形式.

for(Iterator<String> iter=list.iterator();iter.hasNext();){
    
    
	String elem=iter.next();
	循环体
}

循环体为elem=”233”;

for(Iterator<String> iter=list.iterator();iter.hasNext();){
    
    
	String elem=iter.next();
	elem="233";
}

由上面的可以知道,233赋值的是elem临时变量,并没有改变集合中的元素,所以foreach只能用来遍历,无法改变数组或者集合中的元素.

三.自己实现Iterable接口

public class SeqList<T>{
    
    //顺序表
	protected Object[] element;
	protected int n;
	public SeqList(int length) {
    
    
		//初始化数组
		this.element=new Object[length];
		this.n=0;
	}
	public SeqList() {
    
    
		this(64);
		//如果什么参数都不加,那么默认给你申请长度为64的数组
	}
	public SeqList(T[] values) {
    
    
		this(values.length); //初始化长度为values.length的数组
		for(int i=0;i<values.length;i++) {
    
    
			this.element[i]=values[i];
		}
		this.n=element.length;
	}
	public T get(int i) {
    
    
		//返回第i个元素
		if (i>=0 && i<this.n) {
    
    
			return (T)this.element[i];
		}
		return null;
	}
	public T remove(int i) {
    
    
		if(this.n>0 && i>=0 &&i <this.n) {
    
    
			T old =(T)this.element[i];
			//old保存要删除的元素
			for(int j=i;j<this.n-1;j++) {
    
    
				//后面的元素依次前移
				this.element[j]=this.element[j+1];
			}
			//把最后的元素置零
			this.element[this.n-1]=null;
			this.n--;
			return old;
		}
		return null;
	}
}

这里有一个顺序表类,n代表数组的长度,get取到第i个元素.remove删除第i个元素,我们想要让SeqList类可以使用foreach遍历.
想要让自己写的类,可以使用foreach遍历,必须实现iterable接口,我们在类名后面添加implements关键字:

public class SeqList<T> implements java.lang.Iterable<T>

还要实现接口中的方法(iterator):

public java.util.Iterator<T> iterator(){
    
    
		return new SeqIterator();
}

iterator方法返回一个迭代器对象,这里return的类的名字可以随便取,我们取名叫SeqIterator
返回的SeqIterator类的对象必须实现Iterator接口(不是Iterable)

private class SeqIterator implements java.util.Iterator<T>{
    
    
		int index=-1,succ=0;
		//index代表当前元素,succ代表后继元素
		public boolean hasNext() {
    
    
`		//判断是否有后继元素
			return this.succ<SeqList.this.n;
		}
		public T next() {
    
    
		//返回后继元素
			T value=SeqList.this.get(this.succ);
			if(value!=null) {
    
    
				this.index=this.succ++;
				return value;
			}
			throw new java.util.NoSuchElementException();
		}
		public void remove() {
    
    
		//删除第 index个元素
			if(this.index>=0&&this.index<SeqList.this.n) {
    
    
				SeqList.this.remove(this.index);
				if(this.succ>0) {
    
    
					this.succ--;
				}
				this.index=-1;
				//设置不能连续删除
			}else {
    
    
				throw new java.util.NoSuchElementException();
			}
		}
	}

SeqList的全部代码为:

public class SeqList<T> implements java.lang.Iterable<T>{
    
    
	public java.util.Iterator<T> iterator(){
    
    
		return new SeqIterator();
	}
	private class SeqIterator implements java.util.Iterator<T>{
    
    
		int index=-1,succ=0;
		public boolean hasNext() {
    
    
			return this.succ<SeqList.this.n;
		}
		public T next() {
    
    
			T value=SeqList.this.get(this.succ);
			if(value!=null) {
    
    
				this.index=this.succ++;
				return value;
			}
			throw new java.util.NoSuchElementException();
		}
		public void remove() {
    
    
			if(this.index>=0&&this.index<SeqList.this.n) {
    
    
				SeqList.this.remove(this.index);
				if(this.succ>0) {
    
    
					this.succ--;
				}
				this.index=-1;
			}else {
    
    
				throw new java.util.NoSuchElementException();
			}
		}
	}
	//顺序表
	protected Object[] element;
	protected int n;
	public SeqList(int length) {
    
    
		//初始化数组
		this.element=new Object[length];
		this.n=0;
	}
	public SeqList() {
    
    
		this(64);
		//如果什么参数都不加,那么默认给你申请长度为64的数组
	}
	public SeqList(T[] values) {
    
    
		this(values.length); //初始化长度为values.length的数组
		for(int i=0;i<values.length;i++) {
    
    
			this.element[i]=values[i];
		}
		this.n=element.length;
	}
	public T get(int i) {
    
    
		//返回第i个元素
		if (i>=0 && i<this.n) {
    
    
			return (T)this.element[i];
		}
		return null;
	}
	public T remove(int i) {
    
    
		if(this.n>0 && i>=0 &&i <this.n) {
    
    
			T old =(T)this.element[i];
			//old保存要删除的元素
			for(int j=i;j<this.n-1;j++) {
    
    
				//后面的元素依次前移
				this.element[j]=this.element[j+1];
			}
			//把最后的元素置零
			this.element[this.n-1]=null;
			this.n--;
			return old;
		}
		return null;
	}
}

其中,SeqIterator内部类实现Iterator迭代器接口,为迭代器对象提供hasNext(),next()和remove()方法。SeqIterator类声明succ变量记住迭代过程中的后继元素,每次调用next()方法,获得第succ个元素,再succ++,直到最后一个元素
remove()方法在每次遍历过程中,只能使用一次.这也是为什么我们在remove方法中要写this.index=-1;。
remove方法不建议使用, 建议使用容器本身的remove方法
测试类:

public class 数组顺序表测试 {
    
    
	public static void main(String[] args) {
    
    
		// TODO Auto-generated method stub
		String values[]= {
    
    "a","b","c","d","e"};
		SeqList<String> lista;
		lista=new SeqList<String>(values);
		for(String elem: lista) {
    
    
			System.out.println(elem);
		}
	}
}
//程序输出结果:
//			a
//			b
//			c
//			d
//			e

foreach的运行其实就可以看成使用迭代器的for循环。
在这里插入图片描述
我们在自己类中实现迭代器接口时,如果hasNext(),next()函数写错了, 那么当我们使用foreach循环时,程序其实就是按照右边的循环执行的.

四.foreach+Lambda表达式

炫技技巧
不多废话,直接看例子应该就能明白
ArrayList_lambda:

List<String> items = new ArrayList<>();
items.add("A");
items.add("B");
items.add("C");
items.add("D");
items.add("E");
 
//lambda
//Output : A,B,C,D,E
items.forEach(item->System.out.println(item));
 
 
//Output : C
items.forEach(item->{
    
    
    if("C".equals(item)){
    
    
        System.out.println(item);
    }
});

hashmap_lambda:

Map<String, Integer> items = new HashMap<>();
items.put("A", 10);
items.put("B", 20);
items.put("C", 30);
items.put("D", 40);
items.put("E", 50);
items.put("F", 60);
 
 
items.forEach((k,v)->System.out.println("Item : " + k + " Count : " + v));
 
 
items.forEach((k,v)->{
    
    
    System.out.println("Item : " + k + " Count : " + v);
    if("E".equals(k)){
    
    
        System.out.println("Hello E");
    }
});

可以看出在foreach括号中左边是参数列表,中间是->,右边就是想要实现的函数体

五.总结

总结一下,我们拿到一个集合,可以有四种方法遍历它
1.普通的for循环

public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		for(int i=0;i<list.size();i++) {
    
    
			System.out.print(list.get(i)+"   ");
		}
    }
}
//程序输出结果为
//			Jack   Bill

2.迭代器实现

import java.util.*;
public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		Iterator it1=list.iterator();
		System.out.println("迭代器实现(while)");
		while(it1.hasNext()) {
    
    
			System.out.print(it1.next()+"   ");
		}
		System.out.println();
		System.out.println("迭代器实现(for)");
		for(Iterator it2=list.iterator();it2.hasNext();) {
    
    
			System.out.print(it2.next()+"   ");
		}
	}
} 
//程序输出结果为
//			迭代器实现(while)
//			Jack   Bill   
//			迭代器实现(for)
//			Jack   Bill   

3.foreach循环

import java.util.*;
public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		for(String elem:list) {
    
    
			System.out.printf(elem+"   ");
		}
	}
}
//程序输出结果为
//			Jack   Bill

4.foreach+Lambda

import java.util.*;
public class Iterator1{
    
    
	public static void main(String[] args) {
    
    
		List<String> list=new LinkedList<String>();
		list.add("Jack");
		list.add("Bill");
		list.forEach(elem->{
    
    System.out.println(elem);});
	}
}
//程序输出结果为
//			Jack
//			Bill

==============================================================================
参考博客:
java-foreach实现原理
java 自定义类如何实现foreach循环
How does the Java ‘for each’ loop work?
Java Iterator 接口简介和简单用法

猜你喜欢

转载自blog.csdn.net/cookie_plus/article/details/107358453