疯狂java


您现在的位置: 疯狂软件 >> 新闻资讯 >> 正文

初探Lambda表达式


 

初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代
 
开篇
 
放假前从学校图书馆中借来一本书,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(" --- Consumer is coming! --- ");
 
        contactList.forEach(new ContactAction());
        System.out.println(" --- Lambda is coming! --- ");
        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循环遍历本身,只不过对并发做了一些处理而已。
可见外部迭代与内部迭代并没有本质上的区别,两者存在形式上的不同。
内部迭代的更多优势与特性随着本书的深入将会逐渐显现。