初探Lambda表达式-Java多核编程【0】从外部迭代到内部迭代

来源:互联网 发布:java获取resource路径 编辑:程序博客网 时间:2024/05/17 05:13

开篇

放假前从学校图书馆中借来一本书,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方法。

@Overridepublic 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之后,现在这样写:

@Overridepublic 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.");        }    }}

以及运行结果:

FooBarNico--- 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方法一看究竟:

@Overridepublic 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循环遍历本身,只不过对并发做了一些处理而已。
可见外部迭代与内部迭代并没有本质上的区别,两者存在形式上的不同。
内部迭代的更多优势与特性随着本书的深入将会逐渐显现。


0 0