Java Lambda Expression

Java 8 已于今年3月发布,其中一个很重大的改进就是 Java Lambda Expression,让 Java 平台正式支援 functional programming paradigm,甚至有评论认为,这是 Java 继 2005 年推出 Annotation 之后,另一个最革命性的改进。

无论您是 Java Platform 的老鸟或新手,都建议学懂 Lambda Expression,因它将会彻底改变将来 Java 编程的手法。( 老鸟如果看不明新手的 code ,将来又如何做 code review 呢?)

Lambda Expression 例子

Map<String, Double> map = getOrders().collect(Order::getLineItems).aggregateBy(LineItem::getName, () -> 0.0, (result, lineitem) -> result + lineItem.getValue());

以上是一个 Java Lambda Expression 的例子,其中红色的部份可以说是一些 syntactic sugar,现在看不懂不紧要,我会慢慢解释给大家的。

Java 8 之前的 Sorting

从前如果我们有一个 ArrayList<Person> ,而 Person Class 有firstName 和 lastName 两个 attribute,我们想用 lastName  依据英文字母排序起来,我们大概会这样写

Collections.sort(personList, new Comparator<Person>() {
	@Override
	public int compare(Person p1, Person p2) {
		return p1.getLastName().compareTo(p2.getLastName());
	}
});

Java 8 的 Sorting

现在您用 Lambda Expression 这样写

Collections.sort(persionList, 
     (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()) );

除了 Collections.sort(),Lambda Expression 还可以利用到以下的例子:

// java.util.concurrent.Executor.execute(java.lang.Runnable)
someExecutor.execute( () -> doSomethingAsRunnable() );

// java.awt.Button.addActionListener(java.awt.event.ActionListener);
someButton.addActionListener( e -> onButtonClick() );

大家能否看出以上的例子有甚么共通点呢?
Java 8 中并不是所以 method invocation 都可以利用到 Lambda Expression。简单的一个原则是所牵涉的 method argument 必须为一个 Functional Interface 或叫做 Single Abstract Method (SAM),那么原本的 method argument 就可以用 Lambda Expression 来取代。

Functional Interface

Functional Interface 的定义就是一个只有一个 public abstract method 的 Java Interface。

以上的例子中的 Comparator、Runnable、ActionListener,它们全都属于 Functional Interface ,因为它们各自只有一个 public method,例如:

  • int Comparator<T>.compare(T o1, T o2);
  • void Runnable.run();
  • void ActionListener.actionPerformed(ActionEvent e);

当我们发现 method 中的 argument 是一个 Functional Interface 的时候,我们就可以利用 Lambda Expression 的 basic form 去取代原本繁复的 anonymous class 语法:

//old way
new SomeFunctionalInterface() {
    @Override
        public someReturnType someMethod(args) { body }
    }

//new way
(args) -> { body }

正如以下的例子

// Old way
Arrays.sort(personArray, new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        int retValue = p1.getLastName().compareTo(p2.getLastName());
        if (retValue == 0) {
            retValue = p1.getFirstName().compareTo(p2.getFirstName());
        }
        return retValue; 
    }
});

// New way
Arrays.sort(personArray, (p1, p2) -> {
              int retValue = p1.getLastName().compareTo(p2.getLastName());
              if (retValue == 0) {
                  retValue = p1.getFirstName().compareTo(p2.getFirstName());
              }
              return retValue;
});

其他 Lambda Expression 语法

如果 body 只有一行 statement ,大家还可以省略 method body 中的 { },如下

(args) -> one-statement-body

例如:

// New way -- return keyword will be inferred from the method signature
Arrays.sort(personArray, (p1, p2) -> p1.getLastName().compareTo(p2.getLastName()));

如果只有一个 argument,大家就可以省略当中的 ( ),如下

one-arg -> { body }
or
one-arg -> one-statement-body

例如:

// java.awt.Button.addActionListener(java.awt.event.ActionListener);
someButton.addActionListener( event -> setBackground(Color.BLUE) );

拆解当中的把戏

由于 Lambda Expression 只可以应用于 Functional Interface ( 即只有一个 public method ),Java compiler 就可以从中推断 ( type inferencing ) 得到需要多少个 method arguments 、 argument types 及 return type,从而进行编译。

其实 Lamdba 只是一个 Java Object,是 VM 临时创建出来的 anonymous class,大家可以用以下的代码来验証。

// Can pre-construct the comparator using Lambda Expression
Comparator<Person> comparator = (p1, p2) -> p1.getLastName().compareTo(p2.getLastName());
Collections.sort(personList, comparator);
// Print the class name
System.out.println(comparator.getClass().getCanonicalName());

看到这里,大家或许会说 Lambda 也不是甚么惊人的创举,只是一个 syntactic sugar、shorthand 的方法来写 java code,没有我吹得那么伟大。
其实它还有更加强大的用途,例如可以让 Compiler 和 JVM 更加有效地做 optimization 和利用多核心 CPU 去执行程序,而且它可以推动 functional programming paradigm ,实作一些 map-reduce 的 algorithm,为应付 Big-Data作出重大的贡献,不过篇幅有限,留待日后的章节再跟大家分享。

现在,我还是先讨论多一些有关 Lambda 的基础知识吧。

Method References

上文的例子都是即时创建一条 Lambda Expression 去满足 method invocation 的需求。其实,如果我们有一个 Java static method ,而刚巧它的 method signature 跟 Functional Interface 中的 Single Abstract Method (SAM) 完全一样的话,我们是可以直接用那个 Java static method 去取代 Lambda Expression 去 invoke method。这样做的话, Java 8 叫做 Method References,其语法例子如下:

public class Example {
    // Define an artitary static method matching the signature of Comparator<Person>.compare();
    public static int returnSomeIntegerOnly(Person p1, Person p2) {
        return p1.getLastName().compareTo(p2.getLastName());
    }

    public static void main(String[] args) {
        List<Person> personList = new ArrayList<Person>();
        Collections.sort(personList, Example::returnSomeIntegerOnly);
    }
}

其语法是

  class-name::static-method-name

试想想,有了 Method Reference 这招杀着,很多旧或 3rd party 的 Java Code 都可以很方便 reuse 。

@FunctionalInterface Annotation

Java 8 还新加入的 @FunctionalInterface 这个 annotation,它可以让我们标记一个 Functional Interface 的设计原委 ( Design Intent ),其用途如下

@FunctionalInterface
public interface MyFuncInterface {
    public void onlyOneMethod(String a1, String a2);
}

一个有 @FunctionalInterface 标示的 interface  ,就可以声明给其他人 (同事) 知道,这个 Interface 是打算用来做 Functional Interface ,就请大家千万不要胡乱增加其他的 public abstract method 给它,否则就会 !@#$%^&……

如果真的有人多手加多一个 public abstract method,会怎样呢?
其实现代的 IDE 如 Eclipse 、NetBean 和 RAD ( 新版支持 Java 8 未推出,不过我估正常都会做到的 ) 等都会标示错误,而 Compile 时亦会报告错误。

但您是无须要一定要把@FunctionalInterface 加在每一个 Functional Interface 上,它的作用跟 @Override 类似,加与不加不会直接影响 Lambda Expression 的操作,加了只是一个 best practice ,让其他人清楚了解这个 interface 的独特意义。

下篇再续,如有问题,请留言给我,谢谢!

Java Lambda 教学 Part 1
标签:                

发布留言

发布留言必须填写的电子邮件地址不会公开。 必填栏位标示为 *