本文基于《Effective java》第58条在此基础之上退出了本人的了解。
for循环是平时写代码用的最多的,然而之前看《Effective java》,大佬在某些场景写并不举荐。联合着本人之前刷算法题的经验,受益匪浅。
一、for循环的毛病
在以往遍历元素的时候,咱们通常采纳以下的模式:
`public class Main {
public static void main(String[] args) {
//1、数组元素
int[] num = new int[] {1,2,3,4,5};
//数组的遍历
for(int i=0;i<num.length;i++)
System.out.println(num[i]);
//2、对象元素
ArrayList<Person> lists = new ArrayList<>();
lists.add(new Person("张三"));
lists.add(new Person("李四"));
lists.add(new Person("愚公要移山"));
//对象元素的遍历
for(Iterator<Person> it=lists.iterator();it.hasNext();) {
Person p = it.next();
System.out.println(p.getName());
}
}
}
`
这种写法看起来还不错,然而却并不完满。咱们来剖析一下,有什么毛病。而后给出解决方案。
问题1:迭代器或索引屡次呈现,容易造成应用谬误
从下面两种遍历的代码上来看,对于数组元素是通过索引i来遍历的,然而整个for循环呈现了四次i,对于对象元素是通过迭代器it来遍历的,然而整个for循环呈现了三次it。在for循环遍历元素的时候,就有屡次机会应用了谬误的变量。而且有时候这些谬误编译器无奈发现。对整个利用零碎造成无奈预知的谬误。
问题2:遍历对象元素时,须要留神容器类型
比方咱们这里应用的是list,当然还有可能是其余容器类型,这些类型在更改时比拟麻烦。
问题3:嵌套迭代抛出异样
这种状况比较复杂一些,先来搞个例子。比如说,咱们想要列举每种花,这些花有两种属性一种是色彩,一种是大小。
`public class Main {
//枚举色彩和尺寸
enum Color { RED, GREEN, BLUE, BLACK }
enum Size { ONE, TWO, THREE, FOUR, FIVE,
SIX, SEVEN, EIGHT,NINE, TEN}
//定义花
static class Flower{
public Flower(Color color, Size size) {}
}
public static void main(String[] args) {
Collection<Color> colors = Arrays.asList(Color.values());
Collection<Size> sizes = Arrays.asList(Size.values());
List<Flower> flowers = new ArrayList<>();
//for循环增加所有的花和尺寸
for (Iterator<Color> color = colors.iterator(); color.hasNext(); ) {
for (Iterator<Size> size = sizes.iterator(); size.hasNext(); ) {
flowers.add(new Flower(color.next(), size.next()));
}
}
}
}
`
看似人畜有害,当初咱们运行一波。
`Exception in thread "main" java.util.NoSuchElementException
at java.util.AbstractList$Itr.next(Unknown Source)
at com.f2.Main.main(Main.java:25)
`
是不是感觉有点奇怪,如同双重循环遍历没啥问题,然而呈现了异样,起因是内部的Color迭代器调用了屡次,第一层for循环被调用了,然而又在第二层for循环外部被调用了,所以color的next被调用完了。所以呈现了NoSuchElementException。然而有时候也不会呈现这种状况,场景是内部循环迭代器调用的次数刚好是外部调用的n倍。
问题4:嵌套迭代不抛异样,然而后果不正确
这种状况是内部循环迭代器调用的次数刚好是外部调用的n倍。咱们再来个例子:
`public class Main {
//枚举色彩
enum Color { RED, GREEN, BLUE, BLACK }
public static void main(String[] args) {
Collection<Color> colors = Arrays.asList(Color.values());
//两层for循环
for (Iterator<Color> c1 = colors.iterator(); c1.hasNext(); ) {
for (Iterator<Color> c2 = colors.iterator(); c2.hasNext(); ) {
System.out.println(c1.next()+" "+c2.next());
}
}
}
}
`
当初对色彩进行for循环遍历,一共两层for循环,因为一共有四种色彩,两层for循环应该是打印16个后果。当初运行一遍看看后果:
`RED RED
GREEN GREEN
BLUE BLUE
BLACK BLACK
`
没错,的确是打印了四条。起因和问题三是一样的。有一种形式能够很好地解决这种嵌套的问题。
嵌套迭代问题解决:
间接看代码。既然是内部的迭代器it在外部应用了,那我在外部和内部之间用一个变量缓存起来不久好了。
`public class Main {
//枚举色彩
enum Color { RED, GREEN, BLUE, BLACK }
public static void main(String[] args) {
Collection<Color> colors = Arrays.asList(Color.values());
//for循环
for (Iterator<Color> c1 = colors.iterator(); c1.hasNext(); ) {
//用一个变量缓存起来
Color c = c1.next();
for (Iterator<Color> c2 = colors.iterator(); c2.hasNext(); ) {
System.out.println(c+" "+c2.next());
}
}
}
}
`
当初再来运行,就能够很好地得出16种后果了。这种形式也比拟不错,然而却不能很好地解决问题1和问题2。因而,为了解决这一景象,大佬Joshua Bloch在书中提出,举荐应用for-each循环来代替for循环。
二、for-each循环
既然作者举荐应用for-each循环,咱们看看他有什么益处。是如何解决下面的问题的。
`public class Main {
//枚举色彩和尺寸
enum Color { RED, GREEN, BLUE, BLACK }
enum Size { ONE, TWO, THREE, FOUR, FIVE,
SIX, SEVEN, EIGHT,NINE, TEN}
//定义花
static class Flower{
public Flower(Color color, Size size) {}
}
public static void main(String[] args) {
Collection<Color> colors = Arrays.asList(Color.values());
Collection<Size> sizes = Arrays.asList(Size.values());
List<Flower> flowers = new ArrayList<>();
//for-each循环
for (Color color:colors) {
for (Size size:sizes ) {
flowers.add(new Flower(color, size));
}
}
}
}
`
看外面的for-each循环。下面的问题就全都解决了。好吧,可能你会感觉,就这?还有一个益处还没说,再往下看。
for-each 循环不仅容许遍历汇合和数组,还容许遍历实现 Iterable 接口的任何对象,该接口由单个办法组成。接 口定义如下:
`public interface Iterable<E> {
// Returns an iterator over the elements in this iterable
Iterator<E> iterator();
}
`
如果必须从头开始编写本人的 Iterator 实现,那么实现 Iterable 会有点辣手,然而如果你正在编写示意一组元素 的类型,那么你应该强烈思考让它实现 Iterable 接口,甚至能够抉择不让它实现 Collection 接口。这容许用户应用for-each 循环遍历类型,他们会永远感激不尽的 。
然而,有三种常见的状况是你不能别离应用 for-each 循环的:
(1)有损过滤(Destructive filtering):如果须要遍历汇合,并删除指定选元素,则须要应用显式迭代器,以便能够调用其 remove 办法。 通常能够应用在 Java 8 中增加的 Collection 类中的 removeIf 办法,来防止显式遍历。
(2)转换:如果须要遍历一个列表或数组并替换其元素的局部或全副值,那么须要列表迭代器或数组索引来替换元素的值。
(3)并行迭代:如果须要并行地遍历多个汇合,那么须要显式地管制迭代器或索引变量,以便所有迭代器或索引变量都能够同步进行 。
如果发现自己处于这些状况中的任何一种,请应用传统的 for 循环,并警觉本条目中提到的陷阱 。