开篇
放假前从学校图书馆中借来一本书,Oracle官方的《精通Lambda表达式:Java多核编程》。
假期已过大半才想起来还没翻上几页,在此先推荐给大家。
此书内容简洁干练,如果你对Java语法有基础的认识看起来就会不费劲,唯一的缺点就是代码部分的内容以及排版有些错误,不过瑕不掩瑜,无伤大雅。
这个系列就是我对书中每一小节的一个提炼、总结以及实践,每一个知识点我都会附上自己写的代码,这些代码用来验证所学的知识。
才疏学浅,如果有理解错误之处请指正,欢迎交流讨论。
遍历一个集合
最传统的方法大概是用Iterator,当然我比较Low,习惯用i<arr.size()
这类的循环。(现在我用for/in,本质上还是Iterator...)
这一类方法叫做外部迭代,意为显式地进行迭代操作,即集合中的元素访问是由一个处于集合外部的东西来控制的,在这里控制着循环的东西就是迭代器。
书中举的例子是pointList,我在这里把它换成一个电话簿。
public class ContactList extends ArrayList<String>{}
里面存储着String类型的联系人。
for (Iterator<String> contactListIterator = contactList.iterator(); contactListIterator.hasNext(); ) {
System.out.println(contactListIterator.next());
}
现在我们将这种遍历方式换成内部迭代。
顾名思义,这种方式的遍历将在集合内部进行,我们不会显式地去控制这个循环。
无需关心遍历元素的顺序,我们只需要定义对其中每一个元素进行什么样的操作。注意在这种设定下可能无法直接获取到当前元素的下标。
Java5中引入Collection.forEach(...)
,对集合中每一个元素应用其行为参数,这个方法是从其父接口Iterable中继承。
现在我们可以去重写forEach方法。
@Override
public void forEach() {
for(String s : this) {
System.out.println(s + " is your contact.");
}
}
这下我们对电话簿调用forEach方法的时候,遍历操作就会在类的内部完成了。
当然这看起来非常傻并且很不灵活。如果我们想把行为作为参数传给forEach呢?
所幸Java8提供了这种可能,这种行为参数叫做Consumer。
public interface Consumer<T> {
void accept(T t);
}
一个明显的函数式接口,行为定义在accept方法中。
在定义好了我们自己的Consumer之后,现在这样写:
@Override
public void forEach(Consumer<String> c) {
for(String s : this) {
c.accept(s);
}
}
傻的程度减轻了一些,当然还是不够机智。
在这种情况下一个匿名内部类就能搞定问题(Android里面监听器写到手抽...)
contactList.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s + " is your contact.");
}
});
这下足够简洁,当然聪明的编译器应该能够推断出我们传入forEach方法的只能是一个Consumer并且需要调用的就是Consumer的唯一方法accept,这段代码还有简化的余地。
所以现在需要登场的就是Lambda表达式了。
既然我们的电话簿ContactList本质上是一个ArrayList<String>
,那么编译器也一定能推断出Consumer的类型参数标识为String。
所以这下连参数类型都省了。
contactList.forEach(s -> System.out.println(s + " is your contact, again!"));
->前的s为参数名(即String s
中的s),->后为方法体,也可以用一个大括号括起来,因为我这里只写了一句所以就没用。
这就是内部迭代和Lambda相结合的终极奥义了。
现在附上测试代码:
import java.util.ArrayList;
import java.util.Iterator;
import java.util.function.Consumer;
public class ContactList extends ArrayList<String> {
@Override
public void forEach(Consumer<? super String> action) {
super.forEach(action);
}
public static void main(String[] args) {
ContactList contactList = new ContactList();
contactList.add("Foo");
contactList.add("Bar");
contactList.add("Nico");
for (Iterator<String> contactListIterator = contactList.iterator(); contactListIterator.hasNext(); ) {
System.out.println(contactListIterator.next());
}
System.out.println("\n--- Consumer is coming! ---\n");
contactList.forEach(new ContactAction());
System.out.println("\n--- Lambda is coming! ---\n");
contactList.forEach(s -> System.out.println(s + " is your contact, again!"));
}
static class ContactAction implements Consumer<String> {
@Override
public void accept(String s) {
System.out.println(s + " is your contact.");
}
}
}
以及运行结果:
Foo
Bar
Nico
--- Consumer is coming! ---
Foo is your contact.
Bar is your contact.
Nico is your contact.
--- Lambda is coming! ---
Foo is your contact, again!
Bar is your contact, again!
Nico is your contact, again!
此外我们再进入forEach方法一看究竟:
@Override
public void forEach(Consumer<? super E> action) {
Objects.requireNonNull(action);
final int expectedModCount = modCount;
@SuppressWarnings("unchecked")
final E[] elementData = (E[]) this.elementData;
final int size = this.size;
for (int i=0; modCount == expectedModCount && i < size; i++) {
action.accept(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
可以发现其内部依旧是使用了一个for循环遍历本身,只不过对并发做了一些处理而已。
可见外部迭代与内部迭代并没有本质上的区别,两者存在形式上的不同。
内部迭代的更多优势与特性随着本书的深入将会逐渐显现。