上一篇教学讨论了一些基本 Java Lambda 的语法和 Functional Interface 的概念,大家可能已经蠢蠢欲动,建立了一些 Functional Interface 并加上 @FunctionalInterface Annotation,正准备应用到新的项目上。

但我得要提醒大家,其实在大多数情况之下 (90% 以上),您是无须建立一个专门的 Functional Interface,因为 Java 8 很贴心,已经 pre-define 了一堆 built-in 的 Functional Interface 给大家随意使用。

Java Lambda – java.util.function Package

Java Lambda 的设计原意是想大家用 Function (函数)的思维来设计和开发程序,而函数大致不外乎以下几种形式:

Function Name Description
Predicate Evaluate a single input argument against the condition and return a boolean value
Consumer Accept a single input argument, perform some processing and return no result
Supplier Accept no input and produce (return) a result
Function Accept a single input argument, perform some processing and return one result
BiConsumer Accept 2 input arguments, perform some processing and return no result
BiFunction Accept 2 input arguments, perform some processing and return one result
UnaryOperator Special version of Function, in which the input argument and return result are of the same type
BinaryOperator Special version of BiFunction, in which the input arguments and return result are of the same type

有见及此,Java 8 的 java.util.function 这个 package 就已经定义了以上的函数,并提供了主要两大种类的 Functional Interface。

  1. Primitive-typed:例如 IntPredicate、DoubleSupplier、LongConsumer 等。
  2. Generic:例如 Predicate<T>、Function<T, R>、BiConsumer<T, U>、UnaryOperator<T> 等。

Primitive-typed Functional Interface

举一个例子说明,如果我们有一个 integer list,想找出并列印所有大于 10 的整数,我们首先写一个 processIntList 的 method,如下:

public static void processIntList(List intList, 
        IntPredicate matcher, IntConsumer action) {
    if (intList != null) {
        for (int value : intList) {
            // SAM of IntPredicate is boolean test(int value)
            if (matcher.test(value)) {
            // SAM of IntConsumer is void accept(int value)
                action.accept(value);
            }
        }
    }
}

然后,我们就可以利用 Lambda Expression 去 invoke processIntList 逹成我们想要的效果,如下

// number -> number > 0 is the lambda for boolean IntPredicate.test(int value);
// number -> System.out.println(number) is the lambda for void IntConsumer.accept(int value);
processIntList(intList, number -> number > 10, number -> System.out.println(number));

如果,我们对程序的要求有所改变,例如现在我想把 intList 内的双数用 log4j logger 输出,我们可以完全不须要改变 processIntList 中的逻辑,只须调整我们的 Lambda Expression 就可以了,其改变如下

processIntList(intList, number -> number%2 == 0, number -> logger.info(number));

由上例可见,Lambda 给予我们编程的便利,我们可以把部份程序逻辑用 abstract function 来取代,给予 Method Caller 用 Lambda Expression 来提供实际执行的函数逻辑 ( function logic )。

Generics Functional Interface

如果我们要处理的 Object 并不是 Integer、Long、Double 这些 Primitive type,那怎么办呢?
不用担心, java.util.function package 内的 Generics Functional Interface 足够应付我们所有 Custom Java Type 的需求。
假设现在我们要把所有 IT architect 的人工加 10% ,我们首先设计我们的 adjustSalary method 如下

public static <T> void adjustSalary(List<T> staffList, Predicate<T> matcher, Consumer<T> action) {
    if (staffList != null) {
        for (T obj : staffList) {
            if (matcher.test(obj)) {
                action.accept(obj);
            }
        }
    }
}

您会发现 adjustSalary 的结构,除了换上了 Generics 外,基本上跟早前的 processIntList 没有大分别,如要加 IT Architect 的人工,就得要配上以下的代码:

adjustSalary(staffList, staff -> staff.getJobRole().equals("IT Architect"), 
    staff -> staff.setSalary(staff.getSalary() * 1.1));

Staff 这个 Java Type 会自动让 JVM Type inference 并附上 Java Lambda Expression中,但前提当然是 staffList 要是一个 List<Staff> 的实例 ( Instance ),让 JVM 有足够的资讯去推断 Lambda 中的 staff argument 其实是 Staff 的实例。

通过以上的例子,相信大家应该明白如何运用 java.util.function package 内的 built-in functional interface 。我建议大家在设计自己的 functional interface ,先看看 java.util.function 其实有没有现成可用的 interface 并加以利用。因为使用 java.util.function 中的 built-in interface 有以下的好处

  1. 您无须额外建立一个 Java interface
  2. java.util.function interface 一定无可能改变其 functional interface 的用途
  3. java.util.function interface 是 Java 8 的标准,其设计原意和用途大家已有共识 ( commonly-understood ),方便日后技术人员沟通

下表是一个简单的总结各种 built-in functional interface 及其 Single Abstract Method (SAM) 的 method signature

Function Name Single Abstract Method Signature
Predicate<T> boolean test(T value)
Consumer<T> void accept(T value)
Supplier<T> T get()
Function<T, R> R apply(T value)
BiConsumer<T, U> void accept(T value1, U value2)
BiFunction<T, U, R> R apply(T value1, U value2)
UnaryOperator<T> T apply(T value)
BinaryOperator<T> T apply(T value1, T value 2)

Java Collection 改进

如果阁下已经是 Java Lambda 的高手,可能早已察觉我给予的例子并不是十分可取,原因是 processIntList 和 adjustSalary 根本不必要的,完全可以用 Java 8 中 Collection API 取代。

例如,processIntList 可以这样完全用以下的 Collections API 取代:

intList.stream().filter(number -> number > 10).forEach(number -> System.out.println(number));

而 adjustSalary 则可以这样完全被取代:

staffList.stream().filter(staff -> staff.getJobRole().equals("IT Architect"))
    .forEach(staff -> staff.setSalary(staff.getSalary() * 1.1));

无错,但为了让大家慢慢明白 Java Lambda ,我只好循序渐进地解释,更何况上述涉及 Java Stream,又是另一大课题,唯有留待日后教学章节再行讨论。

不过,除了 Stream 外,Java Collection API 为了 Java Lambda 亦加入了一些改进,例如

Newly added API Description
void forEach(Consumer<? super T> action) Equivalent to
for (T t : this)
action.accept(t);
boolean removeIf(Predicate<? super E> filter) Each element in the collection is evaluated against the given predicate filter. It the filter predicate is matched, the element is removed from the collection
Stream<E> stream() Return a sequential Stream with this collection as its source. To be discussed in latter tutorial
default Stream<E> parallelStream() Return a possibly parallel Stream with this collection as its source.

Gang Of Four – Template Method Design Pattern

最后,我想用我的个人见解作为这章的终结。
其实 Java Lambda 的设计并不是甚么新的 Object-oriented software 的设计意念,其实早于 1995 年由 Gang Of Four 所著的 Design Patterns: Elements of Resuable Object-Oriented Software 中,已经有类似 Design Pattern。

Gang Of Four 的 Design Pattern 著作,多年来可以说是 Object-oriented software 的圣经,不少 Java Framework 如 Spring Framework、EJB 等都留下不少它的影子,而我觉得最能代表 Java Lambda 的,就是书中所提及的一种 behavioral pattern – Template Method

In software engineering, the template method pattern is a behavioral design pattern that defines the program skeleton of an algorithm in a method, called template method, which defers some steps to subclasses. It lets one redefine certain steps of an algorithm without changing the algorithm’s structure.

用上 adjustSalary 的例子,如果我们用 Template Method Design Pattern 来开发我们的程序,大可以先定义一个 Abstract Class 包括其主要的加薪逻辑,如下:

public abstract class AbstractSalaryAdjustment<T> {
    
    public final void adjustSalary(List<T> staffList) {
        if (staffList != null) {
            for (T obj : staffList) {
                if (eliglibleForAdjustment(obj)) {
                    adjust(obj);
                }
            }
        }   
    }
        
    protected abstract boolean eliglibleForAdjustment(T obj);
    
    protected abstract void adjust(T obj);
}

然后,我们就可以 extend 这个 Abstract Class 来提供加薪条件和幅度,如下:

public class ITArchitectSalaryAdjustment extends AbstractSalaryAdjustment<Staff> {

    @Override
    protected boolean eliglibleForAdjustment(Staff obj) {
        return obj.getJobRole().equals("IT Architect");
    }

    @Override
    protected void adjust(Staff obj) {
        obj.setSalary(obj.getSalary() * 1.1);
    }
}

这样,我们就可以通过 ITArchitectSalaryAdjustment.adjustSalary(List<Staff> staffList) 来逹到相同的效果,由此可见,Java Lambda Expression 可以是说是让我们更方便去实作 Template Method Design Pattern。

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

Java Lambda 教学 Part 2
标签:                

2 thoughts on “Java Lambda 教学 Part 2

  • 22 6 月, 2018 在 2:46 下午
    永久连结

    Nice tutorial!
    Would like to know what company you are working for and what age are you.

    回复

发布留言

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