[Java] 迭代器的备注
目标
-
- Iterator の使いどころの整理
-
- 類似の ListIterator についても簡単に
- ついでに古いコレクションフレームワークの Enumeration も少しだけ
关于 java.util.Iterator
使用forEach遍历集合类等元素时使用。
虽然Java5.0引入了增强for循环,因此很少需要明确使用Iterator。但是在Java8中增加了流相关特性,这将进一步减少使用Iterator的情况。
我认为有的情况下可能需要显式地使用迭代器。
方法
很容易。
Iterator#hasNext() : 次のデータがある場合に真
Iterator#next() : 次のデータを取得
Iterator#remove() : 読み込んだデータを削除 (オプション操作)
Iterator#forEachRemaining() : 各要素を順番に処理する(Java8で追加)
由于`remove()`是一个可选操作,因此根据使用的`Iterator`的不同,可能无法进行删除操作(例如,`Collections.unmodifiableList()`返回的是不可变列表,因此从中获取的`Iterator`无法删除元素)。
另外一提,从Java8开始,`remove()`已成为默认方法。
默认实现只是抛出`UnsupportedOperationException`异常而已。
另外,Java8新增了forEachRemaining()方法,它也是默认方法。
删除要素
我认为必须明确处理迭代器的情况几乎都是在想要删除元素的情况下。
如果要从集合中删除不需要的元素或已处理过的元素,我认为可以按照以下方式进行。
List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));
Iterator<Integer> iterator = list.iterator();
while (iterator.hasNext()) {
int num = iterator.next();
if (num == 0) {
// 0 は削除
iterator.remove();
}
}
System.out.println(list); // -> [3, 1, 4, 1, 5, 9, 2, 6]
remove() 函数用于删除前一个 next() 函数返回的元素。
因此,在没有调用 next() 函数或已经调用 remove() 函数的情况下,不可以调用 remove() 函数(会抛出异常)。
关于java.util.ListIterator
在Iterator之外,还有一个比较不常见的ListIterator。
顾名思义,它是专为列表而扩展的迭代器。
ListIterator 和 Iterator 的不同之处在于它可以向反方向移动,并且可以添加和替换元素。此外,Iterator 只能从头部获取元素,而 ListIterator 可以从列表的任意位置获取元素。
方法
ListIterator#hasNext() : 次のデータがある場合に真
ListIterator#next() : 次のデータを取得
ListIterator#hasPrevious() : 前のデータがある場合に真
ListIterator#previous() : 前のデータを取得
ListIterator#remove() : 読み込んだデータを削除 (オプション操作)
ListIterator#forEachRemaining() : 各要素を順番に処理する(Java8で追加)
ListIterator#add() : next() で返ってくる要素の手前にデータを追加 (オプション操作)
ListIterator#set() : 読み込んだデータを置き換え (オプション操作)
ListIterator#nextIndex() : next() で返ってくる要素のインデックス(末尾の場合には、リストのサイズ)
ListIterator#previousIndex() : previous() で返ってくる要素のインデックス(先頭の場合には、-1)
获取ListIterator
在中文中,通常通过List#listIterator()方法获取ListIterator。
如果没有参数,则获取从头开始的 ListIterator。
如果有参数,将从指定位置获取ListIterator。
假设我们有一个名为target的列表。
-
- 引数なしと同じ位置は target.listIterator(0)
先頭1つを飛ばす場合は target.listIterator(1)
一番最後からの場合は target.listIterator(target.size())
如果要添加要素的话
如果想要添加列表的元素,可以使用 add() 方法。
例如,下面是将已有元素分割为多个的情况示例。
List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
int num = iterator.next();
if (num == 0) {
// 0 は削除
iterator.remove();
} else if (num % 2 == 0) {
// 2 で割り切れる場合には、分割
iterator.remove();
int sub = num / 2;
iterator.add(sub); // ★
iterator.add(sub); // ★
}
}
System.out.println(list); // -> [3, 1, 2, 2, 1, 5, 9, 1, 1, 3, 3]
通过add() 方法添加的元素会在previous() 方向上添加。
也就是说,会变成以下这样。
-
- 次に next() を呼び出しても取得できない
- 次に previous() を呼び出すと取得できる
之前先删除了元素再添加,但是简单地进行添加会变成以下这样。
List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
int num = iterator.next();
if (num == 0) {
// 0 は削除
iterator.remove();
} else if (num % 2 == 0) {
// 2 で割り切れる場合には、分割
iterator.remove();
int sub = num / 2;
iterator.add(sub);
iterator.add(sub);
} else if (num == 1) {
// 1 の後には 999 を追加
iterator.add(999); // ★
}
}
System.out.println(list); // -> [3, 1, 999, 2, 2, 1, 999, 5, 9, 1, 1, 3, 3]
如果想要替换要素
如果您想替换列表的元素,您可以使用set()函数。
List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
int num = iterator.next();
if (num == 0) {
// 0 は削除
iterator.remove();
} else if (num % 2 == 0) {
// 2 で割り切れる場合には、分割
iterator.remove();
int sub = num / 2;
iterator.add(sub);
iterator.add(sub);
} else if (num == 1) {
// 1 の後には 999 を追加
iterator.add(999);
} else if (num == 5) {
// 5 は 25 に置換
iterator.set(25); // ★
}
}
System.out.println(list); // -> [3, 1, 999, 2, 2, 1, 999, 25, 9, 1, 1, 3, 3]
set() 方法用于替换上一次 next() 或 previous() 返回的元素。因此,在以下情况下会引发异常。
next() あるいは previous() を呼び出す前
add() あるいは remove() を呼び出した後
顺便提一下,remove() 方法也会删除上次调用 next() 或 previous() 返回的元素。
如果想要朝相反的方向前进唯一的选择。
迭代器只能顺序前进,但是列表迭代器可以逆向前进。
以下的例子展示了当将元素分割成多个部分后,想要对分割后的元素进一步处理的场景。
List<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));
ListIterator<Integer> iterator = list.listIterator();
while (iterator.hasNext()) {
int num = iterator.next();
if (num == 0) {
// 0 は削除
iterator.remove();
} else if (num % 2 == 0) {
// 2 で割り切れる場合には、分割
// さらに分割したものも処理する
iterator.remove();
int sub = num / 2;
iterator.add(sub);
iterator.add(sub);
iterator.previous(); // ★
iterator.previous(); // ★
} else if (num == 1) {
// 1 の後には 999 を追加
iterator.add(999);
} else if (num == 5) {
// 5 は 25 に置換
iterator.set(25);
}
}
System.out.println(list); // -> [3, 1, 999, 1, 999, 1, 999, 1, 999, 1, 999, 1, 999, 25, 9, 1, 999, 1, 999, 3, 3]
有时候,可能希望从列表的末尾开始处理。
这种情况下可以使用 hasPrevious() 和 previous() 替代 hasNext() 和 next() 来实现。
需要注意的是,由于add()是向previous()方向添加的,所以在接下来的previous()调用之后会返回结果。
关于java.util.Enumeration
由于这是一个旧的集合库,基本上不会使用它……虽然我想说的是如此,但是由于旧的库中有一些直接返回 Enumeration 的方法,所以偶尔还是需要使用它。
由于Java9新增了名为asIterator()的默认方法,因此迭代器化也变得更加简单。
enumeration.asIterator().forEachRemaining(i -> foo(i));
在使用Collections#list()方法时,我们可以将其放入另一个List中,这样在使用扩展for循环进行处理时非常方便。不过如果数据量较大时,可能会感到有些浪费,让人有些担心。
for (var i : Collections.list(enumeration)) {
foo(i);
}
相反地,如果想将现有的 Collection 转化为 Enumeration,可以使用 Collections#enumeration() 方法。
@Override
public Enumeration<String> getHeaderNames() {
return Collections.enumeration(map.keySet());
}
其他
空的迭代器等
我觉得不太常用,但自Java7开始也提供了获取空Iterator等方法。
-
- Collections#emptyIterator()
-
- Collections#emptyListIterator()
- Collections#emptyEnumeration()
如果直接返回Iterator的情况
在一些库中,除了Iterable#iterator()方法之外,还有直接返回Iterator的方法。
或者某些方法名为iterator(),却没有实现Iterable,或者方法名不同等情况可能存在。
在这种情况下(如果是Java8及以上版本),可以使用lambda表达式或方法引用来将其转换为可迭代对象。
LinkedList<Integer> list = new LinkedList<>(Arrays.asList(0, 3, 1, 4, 1, 5, 9, 2, 6));
// 逆順に処理する
for (int num : (Iterable<Integer>) list::descendingIterator) {
System.out.println(num);
}
很遗憾,由于不能直接编写lambda表达式或方法引用,所以需要进行强制类型转换。
把迭代器转换为流
参考:将Iterator转换为Stream
基本类型的迭代器 (Ji Ben Lei Xing De Die Dai Qi)
Java8新增了一个名为java.util.PrimitiveIterator的迭代器接口,专门用于原始类型。实际上,我认为会实现诸如PrimitiveIterator.OfInt等子接口。
如果使用通常的迭代器,它会自动装箱,但使用添加的方法可以保持原始数据类型。
如果要创建原始数据类型的迭代器,可能可以实现这个接口。