Jade Dungeon

Java函数式编程:lambda与Monad

lambda表达式

语法:

(params) -> expression
(params) -> statement
(params) -> { statements } 

例:没有参数的方法:

list.forEach(n -> System.out.println(n)); 
// 等同于:
list.forEach(System.out::println);  // 使用方法引用 

如果你的方法接收两个参数,那么可以写成如下这样:

(int even, int odd) -> even + odd 

顺便提一句,通常都会把lambda表达式内部变量的名字起得短一些。 这样能使代码更简短,放在同一行。所以,在上述代码中, 变量名选用a、b或者x、y会比even、odd要好。

final 变量

Lambda 表达式中的变量必须是 final 或 effective final 变量,值不可变。 final 或 effectively final 变量一旦初始化就不能更改,因此不能再循环中或者内部类里初始化 。 final 与 Effective final 的区别:

final 变量:

final int x;
x = 3;

Effectively final 变量:

int x;
x = 4; // 永远不修改变量值, 实际等价于 final

变量前使用关键字为 final 变量, 不加关键字但永远不修改变量值为实际上的 final 变量。

单函数接口

单函数接口,简称SAM或是FunctionalInterface。 例如Runnable这样的只有一个方法的接口。

例如需要打印线程名称和当前时间,并将该任务提交到线程池中运行

方法 1:新建 class Task 实现 Runnable 接口

public class Task implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName() + "-->" + 
				System.currentTimeMillis() + "ms");
	}

}

executorService.submit(new Task());

方法 2:匿名内部类实现 Runnable 接口

executorService.submit(new Runnable() {

		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + 
					"-->" + System.currentTimeMillis() + "ms");
		}

});

方法 3:使用 Lambda 表达式

executorService.submit(()-> 
		System.out.println(Thread.currentThread().getName() + "-->" + 
			System.currentTimeMillis() + "ms"));

方法 4:使用方法引用

private void print(){
	System.out.println(Thread.currentThread().getName() + "-->" + 
			System.currentTimeMillis() + "ms");
}

executorService.submit(this::print);

通过上面不同的行为传递方式,能够比较直观的体会到随着函数式特性的引入, 行为传递少了很多样板代码,增加了一丝灵活;可见Lambda表达式是一种紧凑的、 传递行为的方式。

常用函数接口类型

Lambda的几种表现形式

无参无返回

() -> System.out.println("Hello World")

无参有返回

() -> 23

有参无返回

(String s) -> System.out.println(s)

有参有返回

(int i) -> i + 10

多参多行语句

(int a, int b) -> {
    int c = a + b;
    return c;
}

Java自带单函数接口类型:

接口 参数 返回值 说明
Function T R 函数型接口
Predicate T boolean 断言型接口
Supplier none T 生产型接口
Consumer T void 消费型接口
UnaryOperator T T 一元值操作符
BinaryOperator (T, T) T 二元操作符
Runnable none void Runnable
Callable none T Callable

函数型接口

  • Function<T,R>
  • 参数:T
  • 返回值:R

示例:

Function<Long, String> toStr = value -> String.valueOf(value);

转换字符串为Integer

public static Integer convert(String str, Function<String, Integer> function) 
{
    return function.apply(str);
}

public static void main(String[] args) {
    Integer value = convert("28", x -> Integer.parseInt(x));
}

断言型接口

  • Predicate<T>
  • 参数类型:T
  • 返回值:boolean

示例:

Predicate<String> isAdmin = name -> "admin".equals(name);

筛选出只有2个字的水果

public static List<String> filter(
		List<String> fruit, Predicate<String> predicate) 
{
    List<String> f = new ArrayList<>();
    for (String s : fruit) {
        if(predicate.test(s)) { f.add(s); }
    }
    return f;
}

public static void main(String[] args) {
    List<String> fruit = Arrays.asList( //
				"香蕉", "哈密瓜", "榴莲", "火龙果", "水蜜桃");
    List<String> newFruit = filter(fruit, (f) -> f.length() == 2);
    System.out.println(newFruit);
}

Predicate是jdk8中的新增接口,共有5个方法。

  • test(T t)
  • negate()
  • and(Predicate<? super T> p)
  • or(Predicate<? super T> p)
  • xor(Predicate<? super T> p)

该接口除了test方法是抽象方法, 其余都是default方法, 该接口可接受一个lambda表达式, 其实就是实现了test接口的一个匿名类

	public static void main(String[] args) {
		List<String> languages = Arrays.asList(
				"Java", "Scala", "C++", "Haskell", "Lisp");

		filter(languages, (str) -> ((String) str).startsWith("J"))
			.forEach(System.out::println);                           // `J` 开头
		filter(languages, (str) -> ((String) str).endsWith("a"))
			.forEach(System.out::println);                           // `a` 开头
		filter(languages, (str) -> ((String) str).length() > 4)
			.forEach(System.out::println);                           // 长度超过4
		filter(languages, (str) -> true)
			.forEach(System.out::println);                       // 所有记录者符合
		filter(languages, (str) -> false)
			.forEach(System.out::println);                       // 所有记录都不符合

		// 定义条件
		Predicate<String> startWithJ = (n) -> n.startsWith("J");  
		Predicate<String> startWithA = (n) -> n.startsWith("a");
		Predicate<String> fourLength = (n) -> n.length() == 4;
		
		languages.stream().filter(startWithJ.and(fourLength))
			.forEach(System.out::println);               // and 连接多个条件
		languages.stream().filter(startWithA.or(startWithJ).and(fourLength))
			.forEach(System.out::println);               // and 与 or 连接多个条件
	}

	public static <T> List<T> filter(List<T> names, Predicate<T> condition) {
		return names.stream().filter(x -> condition.test(x))
			.collect(Collectors.toList());
	}

供给型接口

  • Supplier<T>
  • 参数:none
  • 返回值:T

示例:

Supplier<Date> now = () -> new Date();
public static List<Integer> supply(Integer num, Supplier<Integer> supplier) 
{
       List<Integer> resultList = new ArrayList<Integer>();
       for(int x=0;x<num;x++)  
           resultList.add(supplier.get());
       return resultList ;
}

public static void main(String[] args) {
    List<Integer> list = supply(10,() -> (int)(Math.random()*100));
    list.forEach(System.out::println);
}

消费型接口

  • Consumer<T>
  • 参数:T
  • 返回值:void

示例:

Consumer<String> print = msg -> System.out.println(msg);
public static void donation(Integer money, Consumer<Integer> consumer) {
    consumer.accept(money);  
}

public static void main(String[] args) {
    donation(1000, money -> 
				System.out.println("好心的麦乐迪为Blade捐赠了"+money+"元"));
}

一元操作符

  • UnaryOperator<T>
  • 参数:T
  • 返回值:T

示例:

UnaryOperator<Boolean> negation = value -> !value.booleanValue();

二元操作符

  • BinaryOperator<T>
  • 参数:(T, T)
  • 返回值:T

示例:

BinaryOperator<Integer> intDouble = (i, j) -> i + j;

Runnable

  • Runnable
  • 参数:none
  • 返回值:void

示例:

Runnable helloWord = () -> System.out.println("Hello World");

Callable

  • Callable<T>
  • 参数:none
  • 返回值:T

示例:

Callable<Date> now1 = () -> new Date();

自定义函数接口类型

可以根据需求自定义函数接口,为了保证接口的有效性,可以在上面添加 @FunctionalInterface注解,该注解会强制 javac 检测一个接口是否符合函数式接口的规范, 例如:

@FunctionalInterface
interface CustomFunctionalInterface{
	void print(String msg);
}

CustomFunctionalInterface cfi = msg -> System.out.println(msg);

方法引用

构造函数引用:

ClassName::new               // 类名称::NEW
ClassName[]::new             // 有参数时,使用:类名称[]::NEW

静态方法引用:

ClassName::staticMethod      // 类名称::静态方法

成员方法的引用:

objectName::instanceMethod   // 实例名称::实例方法

实例的成员方法引用, 等同于Lambda表达式把第一个参数作为方法调用者, 后面参数作为方法参数进行使用 :

ClassName::instanceMethod    // 类名称::实例方法

超类实体方法引用:

SuperClassName::mehtodName

构造器引用

需要有无参的构造器,例如:BigDecimal::new等同于x -> new BigDecimal(x)

另一个例子:

class Persion {

	public Persion() {}

}

Person construntorRef(Supplier<Person> sup){
	Person p = sup.get();
	return p;
}

public void testConstructorRef(){
	Person p = construntorRef(Person::new);
	System.out.println(p);
}

静态方法引用

只要静态方法的参数列表和需要的参数一致就可以, System.out::println等同于s -> System.out.println(s);

另一个例子:

private static void print(String s) {
	System.out.println(s);
}

public void testStaticRef(){
	Arrays.asList("aa","bb","cc").forEach(TestMethodReference::print);
}

成员方法引用

@Test
public void testMemberMethodRef(){
	Arrays.asList("aa","bb","cc").forEach(System.out::println);
}

实例的成员方法引用

最后一种为类名称调用实例方法,等同于Lambda表达式把第一个参数作为方法调用者, 后面参数作为方法参数进行使用,例如:

Object::equals
// 等同于
(a, b) -> a.equals(b)

另一个例子:

@Test
public void testClassMemberMethodRef(){
	String[] strs={"zzaa", "xxbb", "yycc"};
	Arrays.sort(strs, String::compareToIgnoreCase);//OK
	System.out.println(Arrays.asList(strs));
	File[] files = new File("C:").listFiles(File::isHidden); // OK
}

进一步观察实现细节:


public interface TestInterface {
	//随便什么名字,lambda并不关心,因为只有一个接口,并且根据参数来匹配
	public void apply(TestBean1 bean1,TestBean2 bean2);
}

public class TestBean1 {
	public void expect1(TestBean1 bean1) { }
	public void expect2(TestBean2 bean2) { }
	public void test1(TestInterface i)   { }
}

public class TestBean2 {
	public void expect1(TestBean1 bean1) { }
	public void expect2(TestBean2 bean2) { }
	public void test1(TestInterface i)   { }
}

TestBean1 bean1=new TestBean1();
bean1.test1(TestBean1::expect1); //1 - error
bean1.test1(TestBean1::expect2); //2 编译通过
bean1.test1(TestBean2::expect1); //3 - error
bean1.test1(TestBean2::expect2); //4 - error

TestBean2 bean2=new TestBean2(); 
bean2.test1(TestBean1::expect1); //5 - error
bean2.test1(TestBean1::expect2); //6  编译通过
bean2.test1(TestBean2::expect1); //7 - error
bean2.test1(TestBean2::expect2); //8 - error

TestInterface::apply()的参数类型是(TestBean1,TestBean2)

  1. 我们先看1行,我们传入的::前导的类是TestBean1, 而expect1方法匹配的是TestBean1类型的入参bean1, 也就是说省略了TestBean2类型的参数bean2,FI中的最后一个参数。 即便我们使用类TestBean1去new一个对象,也找不到TestBean2,因此这个错误。
  2. 我们先看②行,我们传入的::前导的类是TestBean1, 而expect2方法匹配的是TestBean2类型的入参bean2, 也就是说省略了TestBean1类型的参数bean1, 那么lambda就可以使用::前导的TestBean1构建一个对象,作为第一个参数, 从而匹配FI的接口方法。ok。
  3. 我们先看③行,我们传入的::前导的类是TestBean2, 而expect1方法匹配的是TestBean1类型的入参bean1, 也就是说省略了TestBean2类型的参数bean2,FI的最后一个参数。 按照第二步的分析,我们用::前导的类TestBean2去new一个对象, 应该可以凑足两个参数。实际测试会发现这不灵。这就证明了只能省略第一个参数, 而且,用::前导的类也必须是第一个参数的类型。
  4. 同第一步类似,第④行代码,找不到TestBean1的参数,有错误可以理解。
  5. 至于⑤~⑧,只是替换了外层的test1的主体,没有任何区别。这证明了, lambda的匹配与外层是什么鬼没有任何关系,它只关心外层需要的FI的参数列表。

请不要看下一步,在这里停下来冷静的思考一下, 如果我们把TestInterface中FI方法的参数位置换一下,即: public void anyStringAsName(TestBean2 cat,TestBean1 dog);, 结果应该是哪两行正确呢?认真思考一下,实在想不明白跑一下测试用例, 也许对理解更有帮助。

如果想明白了用这个思路验证一下:参照参数列表(TestBean2,TestBean1), 可以确定只可以省略第一个参数即TestBean2,那么::前导类必须是TestBean2, 用于自动创建对象;而未省略的参数是TestBean1,那么方法名为expect1, 结果为xxx(TestBean2::expect1),即③和⑦

类型推断

类型推断,是 Java7 就引入的目标类型推断的扩展,在 Java8 中对其进行了改善, 程序员可以省略 Lambda 表达式中的所有参数类型,Javac 会根据 Lambda 表达式式上下文信息自动推断出参数的正确类型。

大多数情况下 javac 能够准确的完成类型推断,但由于 Lambda 表达式与函数名无关, 只与方法签名相关,因此会出现类型对推断失效的情况, 这时可以使用手工类型转换帮助 javac 进行正确的判断

// Supplier<String>, Callable<String> 具有相同的方法签名
private void print(Supplier<String> stringSupplier) {
	System.out.println("Hello " + stringSupplier.get());
}

private void print(Callable<String> stringCallable){
	try {
		System.out.println("Hello " + stringCallable.call());
	} catch (Exception e) {
		e.printStackTrace();
	}
}

// print(()->"World");    // Error, 因为两个print同时满足需求

print((Supplier<String>) ()->"World"); // 使用类型转换,为编译器提供更多信息
print((Callable<String>) ()-> "world");

接口中方法的默认实现

在Java8种引入新的机制,支持在接口中声明方法同时提供实现,可以在定义函数时:

  1. 在接口内声明静态方法
  2. 指定一个默认方法。

例,在JDK8中List接口有default关键字修饰的默认方法:

default void sort(Comparator<? super E> c) {
    Object[] a = this.toArray();
    Arrays.sort(a, (Comparator) c);
    ListIterator<E> i = this.listIterator();
    for (Object e : a) {
        i.next();
        i.set((E) e);
    }
}

有了这个方法我们可以直接调用sort方法进行排序:

List<Integer> list = Arrays.asList(2, 7, 3, 1, 8, 6, 4);
list.sort(Comparator.naturalOrder());
System.out.println(list);

使用Java8操作集合的时候可以直接foreach的原因也是在Iterable接口中也 新增了一个默认方法forEach

default void forEach(Consumer<? super T> action) {
	Objects.requireNonNull(action);
	for (T t : this) {
		action.accept(t);
	}
}

// Collection中的stream实现
default Stream<E> stream() {
	return StreamSupport.stream(spliterator(), false);
}

和类不同,接口中没有成员变量, 因此默认方法只能通过调用子类的方法来修改子类本身,避免了对子类的实现做出各种假设。

默认方法与子类

添加默认方法特性后,方法的重写规则也发生了变化,具体的场景如下:

没有重写

没有重写是最简单的情况,子类调用该方法的时候,自然继承了默认方法。

interface Parent{
	default void welcome(){
		System.out.println("Parent");
	}
}

// 调用Parent中的welcome, 输入"Parent"
class ParentNotImpl implements Parent {
	/* ... */
}

子接口重写

子接口对父接口中的默认方法进行了重新,其子类方法被调用时, 执行子接口中的默认方法:

interface Parent{
	default void welcome(){
		System.out.println("Parent");
	}
}

interface ChildInterface extends Parent{
	@Override
		default void welcome(){
			System.out.println("ChildInterface");
		}
}

// 执行ChildInterface中的welcome, 输入 "ChildInterface"
class ChildImpl implements ChildInterface{
	/* ... */
}

类重写

一旦类中重写了默认方法,优先选择类中定义的方法,如果存在多级类继承, 遵循类继承逻辑:

interface Parent{
	default void welcome(){
		System.out.println("Parent");
	}
}


interface ChildInterface extends Parent{
	@Override
		default void welcome(){
			System.out.println("ChildInterface");
		}
}

//执行子类中的welcome方法,输出"ChildImpl"
class ChildImpl1 implements ChildInterface{
	@Override
		public void welcome(){
			System.out.println("ChildImpl");
		}
}

多重继承

接口允许多重继承,因此有可能会碰到两个接口包含签名相同的默认方法的情况, 此时 javac 并不明确应该继承哪个接口中的方法,因此会导致编译出错, 这时需要在类中实现该方法,如果想调用特定父接口中的默认方法, 可以使用ParentInterface.super.method()的方式来指明具体的接口。

interface Parent1 {
	default void print(){
		System.out.println("parent1");
	}
}

interface Parent2{
	default void print(){
		System.out.println("parent2");
	}
}

class Child implements Parent1, Parent2{
	@Override
		public void print() {
			System.out.println("self");
			Parent1.super.print();
			Parent2.super.print();
		}
}

现在的接口提供了某种形式上的多继承功能,然而多重继承存在很多诟病。 很多人认为多重继承的问题在于对象状态的继承,而不是代码块的继承, 默认方法避免了状态的继承,也因此避免了 C++ 中多重继承最大的缺点。

接口和抽象类之间还是有明显的区别。接口允许多重继承,却没有成员变量;抽象类可以继承成员变量,却不能多重继承。 从某种角度出发,Java 通过接口默认方法实现了代码多重继承,通过类实现了状态单一继承。

三定律

如果对默认方法的工作原理,特别是在多重继承下的行为没有把握, 可以通过下面三条简单定律帮助大家:

  1. 类胜于方法。 如果在继承链中有方法体或抽象的方法声明,那么就可以忽略接口中定义的方法。
  2. 子类胜于父类。 如果一个接口继承另一个接口,且两个接口都定义了一个默认方法,那么子接口中定义的方法胜出。
  3. 没有规则三。 如果上面两条规则不适用,子类要么实现该方法,要么将该方法声明为抽象方法。

接口静态方法

人们在编程过程中积累了这样一条经验,创建一个包含很多静态方法的一个类。 很多时候类是一个放置工具方法的好地方,比如 Java7 引入的Objects类, 就包含很多工具方法,这些方法不是属于具体的某个类。

如果一个方法有充分的语义原因和某个概念相关, 那么就应该讲该方法和相关的类或接口放在一起,而不是放到另一个工具类中, 这非常有助于更好的组织代码。

在接口中定义静态方法,只需使用static关键字进行描述即可, 例如Stream接口中的of方法。

/**
 * Returns a sequential {@code Stream} containing a single element.
 *
 * @param t the single element
 * @param <T> the type of stream elements
 * @return a singleton sequential stream
 */
public static<T> Stream<T> of(T t) {
	return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false);
}

例子:函数式接口定义通用Builder

通过函数式接口,不直接调用构造函数与getter-setter方法, 创建自己的通过型builder链式地调用构造函数与getter-setter方法。

效果如下:

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public class User {
	private String id;
	private String name;
	private String email;
	private LocalDate birthday;
	private List<String> keywords;
	private Map<String, LocalDate> taskMap;

	public void addKeyword(String keyword) {
		this.keywords = Optional.ofNullable(this.keywords).orElse(new ArrayList<>());
		this.keywords.add(keyword);
	}

	public void addTask(String task, LocalDate closingDate) {
		this.taskMap = Optional.ofNullable(this.taskMap).orElse(new HashMap<>());
		this.taskMap.put(task, closingDate);
	}

	public static void main(String[] args) {
		User user = Builder.of(User::new) // 通过链式方法创建Builder
				.with(User::setId, "007") //
				.with(User::setName, "Joe") //
				.with(User::setEmail, "joe@mycomp.com") //
				.with(User::setBirthday, LocalDate.of(1984, 8, 9)) //
				.with(User::addKeyword, "new user") //
				.with(User::addKeyword, "VIP") //
				.with(User::addTask, "key1", LocalDate.of(2021, 8, 19)) //
				.with(User::addTask, "key2", LocalDate.of(2021, 8, 20)) //
				.with(User::addTask, "key3", LocalDate.of(2021, 8, 21)) //
				.build(); // 构造出实例
		System.out.println(user.toString());
	}

	public User() {
		super();
	}

	/*  ... 省略 getter / setter ... */

}

Builder的实现代码如下:

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;

public class Builder<T> {
	private final Supplier<T> instantiator;
	private List<Consumer<T>> modifiers = new ArrayList<>();

	private Builder(Supplier<T> instantiator) { // 私有构造方法
		this.instantiator = instantiator;
	}

	public static <T> Builder<T> of(Supplier<T> instantiator) { // 工厂方法
		return new Builder<>(instantiator);
	}

	public T build() {         // builder生产最终属性值都相同的实例
		T value = instantiator.get();
		modifiers.forEach(modifier -> modifier.accept(value));
		modifiers.clear();
		return value;
	}

	@FunctionalInterface
	public interface Consumer1<T, P1> { // 1个参数的Consumer
		void accept(T t, P1 p1);
	}

	@FunctionalInterface
	public interface Consumer2<T, P1, P2> { // 2个参数的Consumer
		void accept(T t, P1 p1, P2 p2);
	}

	@FunctionalInterface
	public interface Consumer3<T, P1, P2, P3> { // 3个参数的Consumer
		void accept(T t, P1 p1, P2 p2, P3 p3);
	}
	// ... 定义更之个参数的Consumer接口,比如多到255个参数 ...

	public <P1> Builder<T> with(Consumer1<T, P1> consumer, P1 p1) {
		Consumer<T> c = instance -> consumer.accept(instance, p1);
		modifiers.add(c);
		return this;
	}

	public <P1, P2> Builder<T> with(Consumer2<T, P1, P2> consumer, P1 p1, P2 p2) {
		Consumer<T> c = instance -> consumer.accept(instance, p1, p2);
		modifiers.add(c);
		return this;
	}

	public <P1, P2, P3> Builder<T> with(Consumer3<T, P1, P2, P3> consumer, P1 p1, P2 p2, P3 p3) {
		Consumer<T> c = instance -> consumer.accept(instance, p1, p2, p3);
		modifiers.add(c);
		return this;
	}
	// ... 定义更之个参数的with(),比如多到255个参数 ... 

}

为了这里只实现了匹配3个参数的情况,实际应用中参数可能有更多, 那就多写到255个……

Optional

创建:

  • 静态方法:Optional.empty()空实例
  • 静态方法:Optional.of(obj)不能为空,抛NullPointerException
  • 静态方法:Optional.ofNullable(obj)可以为空

取内容:

  • optObj.get()取内容
  • optObj.orElse(defValue)取内容,为空时返回默认值
  • optObj.orElseGet(supplier())取内容,只有为空时才执行supplier函数生产实例
  • optObj.orElseThrow(supplier())取内容,只有为空时才执行supplier函数生产异常

@Test
public void givenPresentValue_whenCompare_thenOk() {
    User user = new User("john@gmail.com", "1234");
    logger.info("Using orElse");
    User result = Optional.ofNullable(user).orElse(createNewUser());
    logger.info("Using orElseGet");
    User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}


@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
    User result = Optional.ofNullable(user)
      .orElseThrow( () -> new IllegalArgumentException());
}

高阶函数

  • optObj.ifPresent(function(..))不为空时执行函数
  • optObj.map(...)不为空时执行函数
  • optObj.flatMap(...)不为空时执行函数
  • optObj.filter(...)不为空时执行函数

@Test
public void whenMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    String email = Optional.ofNullable(user)
      .map(u -> u.getEmail()).orElse("default@gmail.com");

    assertEquals(email, user.getEmail());
}

@Test
public void whenFlatMap_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    user.setPosition("Developer");
    String position = Optional.ofNullable(user)
      .flatMap(u -> u.getPosition()).orElse("default");

    assertEquals(position, user.getPosition().get());
}

@Test
public void whenFilter_thenOk() {
    User user = new User("anna@gmail.com", "1234");
    Optional<User> result = Optional.ofNullable(user)
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"));

    assertTrue(result.isPresent());
}

结合lambda函数:

@Test
public void whenChaining_thenOk() {
    User user = new User("anna@gmail.com", "1234");

    String result = Optional.ofNullable(user)
      .flatMap(u -> u.getAddress())
      .flatMap(a -> a.getCountry())
      .map(c -> c.getIsocode())
      .orElse("default");

    assertEquals(result, "default");
}

// 上面的代码可以通过方法引用进一步缩减:

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

java9 新方法:

  • optObj.or(supplier)为空时Supplier 参数产生的另一个 Optional 对象。
  • optObj.isPresentOrElse(consumer, runnable)不为空时执行consumer,为空时执行runnable
  • optObj.stream()转为流
@Test
public void whenEmptyOptional_thenGetValueFromOr() {
    User result = Optional.ofNullable(user)
      .or( () -> Optional.of(new User("default","1234"))).get();

    assertEquals(result.getEmail(), "default");
}

Optional.ofNullable(user).ifPresentOrElse(
	u -> logger.info("User is:" + u.getEmail()),
	() -> logger.info("User not found"));


@Test
public void whenGetStream_thenOk() {
    User user = new User("john@gmail.com", "1234");
    List<String> emails = Optional.ofNullable(user)
      .stream() // filter()、map() 和 collect() 接口,以获取 List。 
      .filter(u -> u.getEmail() != null && u.getEmail().contains("@"))
      .map( u -> u.getEmail())
      .collect(Collectors.toList());

    assertTrue(emails.size() == 1);
    assertEquals(emails.get(0), user.getEmail());
}

柯里化

import java.util.function.BiFunction;
import java.util.function.Function;

public class Curryable {

	public static <A, B, C> 
		Function<A, Function<B, C>> curry(BiFunction<A, B, C> func) //
	{
		return (A a) -> (B b) -> func.apply(a, b);
	}
	
	public static <A, B, C> 
		BiFunction<A, B, C> uncurry(Function<A, Function<B, C>> func) //
	{
		return (A a, B b) -> func.apply(a).apply(b);
	}

}

测试:


private BiFunction<Integer, Integer, Integer> addFun1 = //
		(Integer a, Integer b) -> a + b;

private Function<Integer, Function<Integer, Integer>> addFun2 = //
		(Integer a) -> (Integer b) -> a + b;

@Test
public void testCurrying() {
	int r1 = Curryable.curry(addFun1).apply(3).apply(2);
	assertEquals(5, r1);

	int r2 = Curryable.uncurry(addFun2).apply(3, 2);
	assertEquals(5, r2);
}

延迟示值

Examples

Let’s suppose we have the following expensive computation:

static boolean compute(String str) {
    System.out.println("executing...");
    // expensive computation here
    return str.contains("a");
}

Eager evaluation

Consider the following function which takes two booleans and returns “match” if both are true, otherwise returns “incompatible!”.

static String eagerMatch(boolean b1, boolean b2) {
    return b1 && b2 ? "match" : "incompatible!";
}

public static void main(String [] args) {
    System.out.print(eagerMatch(compute("bb"), compute("aa")));
} 

Running this program produces the following output:

executing...
executing...
incompatible! 

Lazy evaluation

Let’s implement a lazy version using the Supplier functional interface.

From the Java specification, interface Supplier

  • Represents a supplier of results.
  • There is no requirement that a new or distinct result be returned each time the supplier is invoked.
  • This is a functional interface

whose functional method is get()

Basically it represents a function that takes no arguments and returns a value. In this case we use a supplier of boolean to create a lazy match equivalent to eager match:

static String lazyMatch(Supplier<boolean> a, Supplier<boolean> b) {
    return a.get() && b.get() ? "match" : "incompatible!";
}

Because Supplier is a functional interface, it can be used as the assignment of a lambda expression:

public static void main(String [] args) {
    System.out.println(lazyMatch(() -> compute("bb"), () -> compute("aa")));
} 

The output of running this program for no match:

executing...
incompatible!

Two important things to notice in running this example:

  • compute is executed when the functional method get is invoked.
  • && operator exhibits “short-circuiting”, which means that the second operand is evaluated only if needed.

The combination of lazy argument and operand evaluation allows this program to avoid the expensive execution in compute("aa").

Conclusion

We have seen how simple it is to convert an eagerly evaluated method into a lazy evaluated one. Although the lazy call is a bit more involved compared to the eager call, the performance gains payoff the cosmetic drawback. Having said that, do not use lazy strategy everywhere, but do use it for cases where there are clear signs of performance improvements, i.e. :

  • Avoiding needless computation.
  • Logging:util.logging.Logger
  • Generating an infinite data structure that will only be used until some unknown limit.

The next blog will show how to move from an imperative style to a declarative style exploiting the lazy nature of streams.

尾递归

For clarity though, tail call optimization is through compile time magic and not any nice JVM feature.

Maybe some day...

The JVM does not support TCO, and this is even more of a problem because the stack size is the same for all threads. This means that increasing the stack size to allow for deeper recursion is a waste of memory.

However, it is easy to implement TCO in Java. The trick is to build a (linked) list of computational steps, until the terminal condition is found. then, we just have to execute the steps in reverse order. Here is how it may be implemented:

First, we define a class for representing the computational steps:

import java.util.function.Supplier;

public abstract class CallStack<T> {

	/**
	 * eval all recursive call chain
	 * 
	 * @return
	 */
	public abstract T eval();

	/**
	 * factory for create stack frame of stack top (recursive end)
	 * 
	 * @param <T>        result type
	 * @param eagerValue immediately value
	 * @return stack end frame
	 */
	public static <T> StackEnd<T> of(T eagerValue) {
		return new StackEnd<>(eagerValue);
	}

	/**
	 * factory for create middle stack frame
	 * 
	 * @param <T>       result type
	 * @param lazyValue supplier for lazy-eval value
	 * @return middle stack frame
	 */
	public static <T> StackFrame<T> of(Supplier<CallStack<T>> lazyValue) {
		return new StackFrame<>(lazyValue);
	}

	/**
	 * eval this stack frame in all recursive call chain
	 */
	protected abstract CallStack<T> stepForward();

	/**
	 * check is recursive end
	 * 
	 * @return isEnd
	 */
	protected abstract boolean isEnd();

	/**
	 * recursive end stack frame
	 * 
	 * @author qwshan
	 *
	 * @param <T> result type
	 */
	public static class StackEnd<T> extends CallStack<T> {

		private final T value;

		private StackEnd(T value) {
			this.value = value;
		}

		@Override
		public T eval() {
			return value;
		}

		@Override
		public boolean isEnd() {
			return true;
		}

		@Override
		public CallStack<T> stepForward() {
			throw new IllegalStateException("Return has no resume");
		}
	}

	public static class StackFrame<T> extends CallStack<T> {

		private final Supplier<CallStack<T>> lazyValue;

		private StackFrame(Supplier<CallStack<T>> lazyValue) {
			this.lazyValue = lazyValue;
		}

		@Override
		public T eval() {
			CallStack<T> step = this;
			while (!step.isEnd()) {
				step = step.stepForward();
			}
			return step.eval();
		}

		@Override
		public boolean isEnd() {
			return false;
		}

		@Override
		public CallStack<T> stepForward() {
			return lazyValue.get();
		}
	}
}

The TailCall abstract class has two realisations: the Suspend class represent in intermediate step (when the current processing is "suspended" in order to make the next recursive call), and the Return class representing the terminal computation.

Suppose we want to create a method computing the sum of all positive integers below a given value. We could define it as:

public int sum(int arg) {
	return sum(arg, 0);
}

public int sum(int arg, int acc) {
	return arg == 0 ? acc : sum(arg - 1, acc + arg);
}

The sum method calls a recursive helper method. This will work for about 3000 to 5000 steps (depending upon the system since the default stack size is system dependent). For higher values, it will overflow the stack.

We can use the TailCall class to make this method stack safe:

private CallStack<Integer> sumTCO(int arg, int acc) {
	return arg == 0 ? //
			CallStack.of(acc) : // recursive end
			CallStack.of(() -> sumTCO(arg - 1, acc + arg)); // in recursive
}

Now you can call the sum method with any argument value without blowing the stack.

函数式编程概念:Functor与Monad

https://medium.com/swlh/write-a-monad-in-java-seriously-50a9047c9839

https://zhuanlan.zhihu.com/p/422926894

这篇文章最初是《Reactive Programming with RxJava》一书中的附录。

最近随着函数式(或函数式风格)编程的兴起,一些流行语言如Scala和Clojure 的通用表达形式使得Monads成为了广为讨论的话题,这里有些流行的说法:

Monad不就是个自函子范畴上的幺半群么?这有什么难理解的?!
A monad is just a monoid in the category of endofunctors. what’s the problem? —— Phillip Wadler
Monad附带的诅咒就是一旦你领悟了它的真谛,当你明白: 「噢~原来是这么一回事」的时候。你就没有办法把它解释给其他人听了。
The curse of the monad is that once you get the epiphany, once you understand – “oh that’s what it is” – you lose the ability to explain it to anybody. ——Douglas Crockford

Monads在很多类库(Java类库尤其是JDK8以及之后的版本)中都有广泛应用。 这个概念高就高在一旦你领悟到了它, 瞬间所有不相干的用于实现完全不同目的的抽象和类库都变得熟悉起来。

例如你不必去学习Java 8中CompletableFuture的工作机理,一旦你知道它是一个Monad, 你就能精确地知道它是如何工作的并且能从它的语法中读出些什么。

如果这时你接触了RxJava,也许它听起来有些不同但因为Observable是Monad,

Functor

在解释什么是Monad之前,让我们研究一个称为functor的简单结构。 Functors(函子)是一种数据结构,能够封装一些值。

从语法角度来讲函子就是存放值的容器,里面的值永远取不出来, 只开放一个映射(map)操作对窗口里的结果进行转换。 转换出来的返回结果也是被functor封装起来的:

  • 一个封闭了字符串"233"的functor,通过函数Integer.parseInpt的映射, 会返回一个封闭了整数233的functor。
  • 一个封闭了整数233的functor,通过函数x -> x + 1的映射。 会返回一个封闭了整数234的functor。

因此如果要把functor抽象为一个接口,那么应该是这个样子:

import java.util.function.Function;

public interface Functor<T> {

	public <R> Functor<R> map(Function<? super T, ? extends R> mapper);

}

引入函子这样一个概念肯定不是为了给自己找不自在。 事实上函子能帮助抽象出Collection,Promise,Optional等很多容器类型的共通操作, 让它们能够流畅地重用同一套API。

为了正确地实现函子,还要严格仔细地符合以下特征:

  • Functor<T>始终是一个不可变的容器, 因此map绝不会改变它要执行的原始对象(这里指的是map不会改变Functor, 只会将里面的类型为T的值拿出来作为函数f的输入), 相反map会将计算得到的结果R包装在一个新的函子中并返回。
  • 另外当应用恒等函数(如map(x-> x))时函子不应该执行任何动作。 这种模式下应该返回相同的函子或者同一个实例。

让我介绍几个Functors,以使你更流畅地使用此API。

Identity

import java.util.function.Function;

public class FIdentity<T> implements Functor<T> {

	private final T value;

	public FIdentity(T value) { this.value = value; }

	@Override
	public <R> FIdentity<R> map(Function<? super T, ? extends R> mapper) {
		return new FIdentity<R>(mapper.apply(value));
	}

}

需要额外的类型参数T来指定容器Identity中元素的类型。 上面的代码是你见到的最简单的函子,仅仅持有一个值。 而你能够对这个值做的就只有在map()方法内部进行转换, 而且没有其他方法可以获得这个值。这已经超越了纯函子的范畴了。

唯一使用函子的方法就是对其应用一系列类型安全的转换:

FIdentity<String>  idString = new FIdentity<>("abc");
FIdentity<Integer> idInt    = idString.map(String::length);

或者你可以一条线地将几个函数组合起来:

FIdentity<byte[]> idBytes = new FIdentity<>(customer) //
		.map(Customer::getAddress)                        //
		.map(Address::getStreet)                          //
		.map((String s) -> s.substring(0, 3))             //
		.map(String::toLowerCase)                         //
		.map(String::getBytes);

这操作类似于:

byte[] bytes = customer //
		.getAddress()       //
		.getStreet()        //
		.substring(0, 3)    //
		.toLowerCase()      //
		.getBytes();

Optional

你可能会对这些冗余的命令行感到反感: 它不仅没有提供额外的附加值,而且还不能将里面的内容提取出来。

事实证明,这样写的好处有利于你利用这种函子抽象来建立很多其他的概念。 例如java 8中的java.util.Optional就是一个拥有map()方法的函子。

现在让我们从头实现它,有两种方法可以构建Optional:

  • 静态方法FOptional.<T>of(T a)创建非空实例。
  • 静态方法FOptional.<T>empty()创建null的实例。
public abstract class Option<T> implements Functor<T> {

	abstract public <R> Option<R> map(Function<? super T, ? extends R> mapper);

	@SuppressWarnings("unchecked")
	public static <T> None<T> empty() {
		return (None<T>) None.NONE;
	}

	public static <T> Option<T> of(T valueOrNull) {
		if (null == valueOrNull) {
			return empty();
		} else {
			return new Some<T>(valueOrNull);
		}
	}

}

内容非空的子类Some会对内容进行map操作:

public final class Some<T> extends Option<T> {

	private final T value;

	protected Some(T value) {
		this.value = value;
	}

	@Override
	public <R> Option<R> map(Function<? super T, ? extends R> mapper) {
		if (isEmpty()) {
			return empty();
		} else {
			return of(mapper.apply(value));
		}
	}

}

空内容的子类Nonemap()方法直接返回一个空的实例, 不会调用参数函数mapper

public class None<T> extends Option<T> {
	static final None<?> NONE = new None<>();

	protected None() {
	}

	@SuppressWarnings("unchecked")
	@Override
	public <R> None<R> map(Function<? super T, ? extends R> mapper) {
		return (None<R>) NONE;
	}

}

一个Optional<T>函子可以包含一个值,而这个值可以为空。 这是一种类型安全的方式表示null

多个元素的集合

这就意味着函子不必非要封装实际类型为T的值,而且它也可以包裹任意数量的值, 如List函子:

import java.util.ArrayList;
import java.util.function.Function;

import com.google.common.collect.ImmutableList;

public class FList<T> implements Functor<T> {
	private final ImmutableList<T> list;

	@Override
	public <R> FList<R> map(Function<? super T, ? extends R> mapper) {
		ArrayList<R> result = new ArrayList<R>(list.size());
		for (T t : list) {
			result.add(mapper.apply(t));
		}
		return new FList<>(result);
	}

	private FList(Iterable<T> value) {
		this.list = ImmutableList.copyOf(value);
	}

	public static <T> FList<T> of(Iterable<T> value) {
		return new FList<>(value);
	}

	public static <T> FList<T> empty() {
		return new FList<>(ImmutableList.of());
	}

}

这个API和之前相似的地方在于你会得到一个将T -> R的函子,但具体的表现形式不同。 现在我们可以将变换应用在FList中的每个元素上,显式地转换整个list。

因此,如果你有客户列表,并且想要他们的街道列表,则非常简单:

import static java.util.Arrays.asList;

FList<Customer> customers = FList.of(//
		Arrays.asList(customer1, customer2));

FList<String> streets = customers //
		.map(Customer::getAddress)    //
		.map(Address::getStreet);

上面的代码就不能简单的用customers.getAddress().street()来代替了, 你不能在一个客户集合上调用getAddress(), 你必须在每个单独的客户上调用getAddress()然后将其放回一个集合中。

顺便说一句,Groovy发现这种模式是如此普遍,以至于实际上它有一个语法糖: customer*.getAddress()*.street()。该运算符称为「展开点」,实际上是一种map伪装。

也许你想知道为什么我要在list内部手动迭代map而不是使用Java 8中的Steam,如: list.stream().map(f).collect(toList())

看到这里你会想到什么,其实java.util.stream.Stream<T>就是一个函子, 同时也是一个monad。

现在,你应该看到Functors的第一个好处:它们抽象了内部表示形式, 对于各种不同的数据结构可以忽略内部细节,提供一致且易于使用的API。

异步流程Promise

作为最后一个示例,让我介绍Promise函子,它和Java中的Future很类似。 Promise「承诺」有一天将提供一个值。它尚未出现,可能是因为产生了一些后台计算, 或者我们正在等待外部事件。但是它将在将来的某个时间出现。 完成Promise<T>的机制不用多说,我们先来看它的使用方法:

FPromise<Customer> customer = // ...

FPromise<byte []> r1 = customer  //
		.map(Customer::getAddress)   //
		.map(Address::getStreet)     //
		.map((String s) -> s.substring(0, 3)) //
		.map(String::toLowerCase)    //
		.map(String::getBytes);
assertNotNull(r1);

看起来很熟悉?这就是我想说的!Functors的实现不是我们要讨论的。 但这里的用法非常接近从Java 8实现的CompletableFuture和RxJava中的Observable了。

回到函子上,Promise <Customer>在此时还没有包含Customer, 它承诺在将来会拥有这类值。但我们仍然可以在这类函子上进行映射, 就像我们使用FOptional和FList一样,语法是一样的, 而且其实现的功能就如函子所表示的那样。

调用customer.map(Customer::getAddress)会产生Promise<Address>, 这意味着map()方法是非阻塞的。customer.map()不会等待底层的customer Promise去完成, 相反它会返回另外一种不同类型的Promise。当上游的Promise完成时, 下游的Promise就会应用一个函数到map()中然后再将结果往下游传。 此时函子已经允许我们以一种非阻塞的形式连接异步计算,不过你不必了解这个过程, 你只用知道怎么使用Promise这个函子的语法规则就行。

Functors还有许多其他很好的例子,例如以组合方式表示值或错误。 但是现在是时候看看Monads了。

从Functor到Monad

函子只有在转换函数返回值没有包装时才支持链式调用:

FOptional<Integer> r1 = FOptional.of("42")   //
	.map(s -> Integer.parseInt(s)) // 转换函数的类型是:`String -> Int`
	.map(i -> i + 1)               // 转换函数的类型是:`Int -> Int`
	.map(i -> i + 2)               // 转换函数的类型是:`Int -> Int`
	.map(i -> i + 3);

如果map()函数的参数mapper返回类型是不是Integer而是Optional<Integer>, 那么map()的返回类型就是再次装过的FOptional<FOptional<Integer>>

如果转换函数的值也被包装在一个函子内部,那么就没有办法继续链式调用:

FOptional<Integer> r1 = FOptional.of("42")   //
	.map(s -> {
			try {
				final int i = Integer.parseInt(s);
				return FOptional.of(i);
			} catch (NumberFormatException e) {
				return FOptional.empty();
			}
		}) // 转换函数的类型是:`String -> Foptional<Int>`
	.map(i -> i + 1);         // 编译错误:无法继续转换FOptional<FOptional<Int>>

为了处理这种需求,一种方法是引入一种特殊的无参数join()方法, 以「展开」的方式函子:

FOptional<Integer> num3 = num2.join()

这种方式非常有效而且非常普遍,所以它有个特定的名字叫flatMap()。 因为flatMap()map()非常相似,所以把具有flatMap()功能的接口抽象为Monad:

import java.util.function.Function;

public interface Monad<T> extends Functor<T> {

	<R> Monad<? extends R> map(Function<? super T, ? extends R> mapper);

	<R, M extends Monad<? extends R>> M flatMap(Function<? super T, M> mapper);
	
}

我们可以简单地总结为flatMap就是一语法糖,更方便组合。而且flatMap方法 (通常称为Haskell bind或>>=从Haskell 调用)非常特别, 因为它允许在纯净的函数式风格编程中集成复杂的转换。

public abstract class Option<T> implements Monad<T> {

	abstract public <R> Option<R> map(Function<? super T, ? extends R> mapper);

	abstract public <R, M extends Monad<? extends R>> M flatMap(Function<? super T, M> mapper);

}

public final class Some<T> extends Option<T> {

	private final T value;

	@Override
	public <R> Option<R> map(Function<? super T, ? extends R> mapper) {
		return of(mapper.apply(value));
	}

	@Override
	public <R, M extends Monad<? extends R>> M flatMap(Function<? super T, M> mapper) {
		return mapper.apply(value);
	}

}

public class None<T> extends Option<T> {

	static final None<?> NONE = new None<>();

	@Override @SuppressWarnings("unchecked")
	public <R> None<R> map(Function<? super T, ? extends R> mapper) {
		return (None<R>) NONE;
	}

	@Override @SuppressWarnings("unchecked")
	public <R, M extends Monad<? extends R>> M flatMap(Function<? super T, M> mapper) {
		return (M) NONE;
	}
}

这里由于Java类型推导的限制,flatMap的类型范围过大:

abstract public <R, M extends Monad<? extends R>> M flatMap(Function<? super T, M> mapper);

正确的类型应该是更加严格地限制为只能是Option,而不是所有的Monad类型:

abstract public <R> Option<? extends R> flatMap(Function<? super T, Option<? extends R>> mapper);

如果Optional是一个monad实例,那么以下的代码就会通过编译:

MOptional<String>  num    = FOptional.of("42");
MOptional<Integer> answer = num.flatMap(this::tryParse);

Monads不需要实现map,它可以通过flatMap()很容易地实现。 事实上flatMap才是那个重要的运算函数,它会使整个变换过程焕然一新。

然和函子一样,语法的符合并不足以使某些类成为Monad, flatMap()的调用者还必须遵从Monad的规则, 必须要与flatMap()有很直观的关联性和同一性。 对于任何拥有值x和函数f的Monad,同一性要求m(x).flatMap(f)要等同于f(x)。 我们不会对monad的理论作进一步的深入(范畴论的内容对大多数人来说太过高深, B格高到直冲天际),相反的我们会将注意力放在它的实践上。

当monad内部结构变得不再简单时,它的性能就会变得很出众, 例如Promise Monad会在未来的某个时刻持有一个值。 你能依据类型系统从下面的代码中猜出Promise是怎样工作的吗? 首先所有的方法都会在后台花少许时间返回一个Promise:

import java.time.DayOfWeek;

MPromise<Customer> loadCustomer(int id) {
	//...
}

MPromise<Basket> readBasket(Customer customer) {
	//...
}

MPromise<BigDecimal> calculateDiscount(Basket basket, DayOfWeek dow) {
	//...
}

现在我们可以将这些函数组合起来,就像是它们在阻塞地使用monad的运算函数一样:

MPromise<BigDecimal> discount = loadCustomer(42)
	.flatMap(this::readBasket)
	.flatMap(b -> calculateDiscount(b, DayOfWeek.FRIDAY));

这变得很有趣。flatMap()必须返回Monads类型的结果,因此所有中间对象均为Promises。 这不仅仅是保持类型一致,因为之前的程序突然变得完全异步了! loadCustomer()返回一个Promise,因此它不会阻塞。readBasket()接受Promise具有 (将要拥有)的任何东西,并应用一个函数到这个值上然后返回另一个Promise等等, 像这样一直持续下去。

基本上我们建立了一个异步的计算管道,一个阶段的完成会自动地触发下一个阶段。

探索flatMap()

使用两个Monad并且将它们封装的值结合起来是很常见的, 然而函子和Monad都不允许直接访问它们的内部,因为这会使函数式编程变得不纯净, 相反我们必须小心地进行变换而不用转义Monad。

想象一下你有两个Monad,而且你想将它们结合起来:

import java.time.LocalDate;
import java.time.Month;

Monad<Month> month = //...
Monad<Integer> dayOfMonth = //...

Monad<LocalDate> date = month.flatMap(
		(Month m) -> dayOfMonth.map(
			(int d) -> LocalDate.of(2016, m, d)));

请花一些时间研究一下上面的伪代码, 我并没有使用Monad实现如Promise或者List来强调这个核心概念。

我们有两个独立的Monad,其中一种是内容类型是Month, 另一种内容类型是Integer。为了利用它们建立LocalDate, 我们必须使用内嵌的变换来访问这两个Monad的内部。

要把类型弄明白,尤其是要搞清楚为什么我会把flatmap()放在前面而把map()放在后面, 思考一下如果有第三个Monad类型为Monad<Year>,那么你会如何构建代码?

这种应用两个参数(在我们的例子中是md)的函数的模式非常普遍。 在Haskell中有一个特别有用的函数叫liftM2,是通过map与flatmap实现的, 专门用来进行这样的变换。用Java伪代码写出来是这样的:

Monad<R> liftM2(Monad<T1> t1, Monad<T2> t2, BiFunction<T1, T2, R> fun) {
	return t1.flatMap((T1 tv1) -> t2.map(
				(T2 tv2) -> fun.apply(tv1, tv2)));
}

你不必为每个Monad都实现此方法,用flatMap()已经足够了, 而且它对所有Monad都起作用。当你考虑如何使用各种Monad时,liftM2非常有用。 例如,listM2(list1, list2, function)将以笛卡尔积的形式把list1list2 中所有的元素传入函数function

另一方面,对于Optional类型来说,仅当两个Optional均为非空时, 才会作为参数传入函数function

更好的是对于Promise Monad来说,仅当这两个Promise都完成了, 函数function才会被异步地执行。

这就意味这我们为两个异步阶段创造了一个简单的同步机制(就像fork-join算法中的join()一样)。

过滤器Filter

flatMap()基础上,我们可以很容易地建立的另一个有用的运算函数filter(Predicate<T>)。 它接受Monad中的所有元素,如果某个元素不符合指定的谓词(不满足predicate的条件) 则将其丢弃。

在某种程度上上filter和map很相似,但与1对1的映射不同的是, filter有1对0和1对1两种情况。filter对每一个Monad都有着相同的语法, 但又随着Monad的不同其实现的功能又不一样。

下面的代码就会过滤掉list中不符合条件的元素:

FList<Customer> vips = customers.filter(c -> c.totalOrders > 1_000);

filter对Optional同样适用,在这样的情况下如果Optional里面的内容没有满足某些条件, 那么我们可以将非空的Optional转化为空的Optional。

从「Monads的列表」到「列表的Monads」

源自flatMap()的另一个有用的运算符是sequence()

你只需查看类型签名即可轻松猜测其作用:

Monad<Iterable<T>> sequence(Iterable<Monad<T>> monads)

我们经常会有一些相同类型的Monad,并且我们想要将这些Monad转化为一个包含list的Monad。 对你来说这听起来可能有些抽象,但确实非常有用。

想象一下你想要并发地通过ID从数据库中加载一些customer, 所以你会对不同的ID多次调用loadCustomer(id)方法,每一次调用都会返回Promise, 所以你就会得到一个Promises的列表,但你想要的是customers的列表, 因此可以使用sequence()(在RxJava中依使用的情况不同, sequence()叫做concat()或者merge())运算函数:

FList<Promise<Customer>> custPromises = FList
	.of(1, 2, 3)
	.map(database::loadCustomer);

Promise<FList<Customer>> customers = custPromises.sequence();

customers.map((FList<Customer> c) -> ...);

在得到了包含客户ID的FList之后,我们对它进行映射 (你能看出FList作为一个函子是怎样起到帮助作用的吗?), 对每一个客户的ID调用database.loadCustomer(id)方法, 这样就会产生一个非常不方便的关于Promises的列表。这时sequence()拯救了这一切, 而且这个函数并不仅仅是语法糖,因为上面的代码完全是非阻塞的。在不同的计算环境下, sequence()对于不同的Monad仍然是有意义的。 例如,它能够将FList<FOptional>转化为FOptional<FList>。 而且你也能自己实现在flatmap()的基础上实现sequence()(就像map()一样)。

这里提到flatMap()和Monad的有用性仅仅只是冰山一角。 尽管Monad的分类定义的的非常模糊, 但它在面向对象的编程语言如Java中却是非常有用的抽象。 能够将一些函数组合起来并返回Monad是非常有益的, 这样会使得一些一些毫不相干的类都遵从Monad式的行为。

而且一旦你将数据封装进Monad中,那么你想明确地将它拿出来将会非常困难。 这种取值操作并不是Monad行为的一部分,它会产生非惯用的代码。

例如对Promise调用Promise.get()在理论上会返回T,但此时这个方法是阻塞的。 然而对于Promise,所有基于flatmap()的运算函数都是非阻塞的。

另一个例子是FOptional.get(),这个方法可能会失败,因为FOptional可能为空。 甚至是FList.get(idx)这种访问列表中特定元素的方法看起来都怪怪的, 因为你都能用map()函数取代for循环了。

我希望你能明白为什么最近一段时间Monad如此受欢迎了, 甚至在Java这种面向对象语言中Monad也是非常有用的抽象。

函数式编程的设计模式

Lambda 表达式大大简化了 Java 中行为传递的问题,对于很多行为式设计模式而言, 减少了不少构建成本。

命令模式

命令者是一个对象,其封装了调用另一个方法的实现细节, 命令者模式使用该对象可以编写根据运行时条件,顺序调用方法的一般性代码。

大多数命令模式中的命令对象,其实是一种行为的封装, 甚至是对其他对象内部行为的一种适配,这种情况下,Lambda 表达式并有了用武之地。

interface Command {
	void act();
}

interface Editor {
	void open();
	void write(String data);
	void save();
}

class CommandRunner {

	private List<Command> commands = new ArrayList<>();

	public void run(Command command) {
		command.act();
		this.commands.add(command);
	}

	public void redo() {
		this.commands.forEach(Command::act);
	}

}

class OpenCommand implements Command {

	private final Editor editor;

	OpenCommand(Editor editor) {
		this.editor = editor;
	}

	@Override public void act() {
		this.editor.open();
	}

}

class WriteCommand implements Command{

	private final Editor editor;
	private final String data;
	
	WriteCommand(Editor editor, String data) {
		this.editor = editor;
		this.data = data;
	}

	@Override public void act() {
		editor.write(this.data);
	}

}

class SaveCommand implements Command{

	private final Editor editor;

	SaveCommand(Editor editor) {
		this.editor = editor;
	}

	@Override public void act() {
		this.editor.save();
	}

}

public void useCommand(){
	CommandRunner commandRunner = new CommandRunner();
	Editor editor = new EditorImpl();
	String data1 = "data1";
	String data2 = "data2";
	commandRunner.run(new OpenCommand(editor));
	commandRunner.run(new WriteCommand(editor, data1));
	commandRunner.run(new WriteCommand(editor, data2));
	commandRunner.run(new SaveCommand(editor));
}

public void useLambda(){
	CommandRunner commandRunner = new CommandRunner();
	Editor editor = new EditorImpl();
	String data1 = "data1";
	String data2 = "data2";
	commandRunner.run(() -> editor.open());
	commandRunner.run(() -> editor.write(data1));
	commandRunner.run(() -> editor.write(data2));
	commandRunner.run(() -> editor.save());
}

class EditorImpl implements Editor{

	@Override public void open() { }

	@Override public void write(String data) { }

	@Override public void save() { }

}

策略模式

抽象一个接口,对应多种不同的具体实现。例:

  • 压缩策略接口,有两种具体的实现:gzip与zip
  • 调用压缩与解压逻辑时,只要以压缩策略接口为参数。 接收不同的具体实现可以对应不同的压缩与解压算法。
interface CompressionStrategy{

	OutputStream compress(OutputStream outputStream) throws IOException;
	
}

class GzipBasedCompressionStrategy implements CompressionStrategy{

	@Override
	public OutputStream compress(OutputStream outputStream) 
		throws IOException 
	{
		return new GZIPOutputStream(outputStream);
	}
}

class ZipBasedCompressionStrategy implements CompressionStrategy{

	@Override
	public OutputStream compress(OutputStream outputStream) 
		throws IOException 
	{
		return new ZipOutputStream(outputStream);
	}
}

class Compressor{

	private final CompressionStrategy compressionStrategy;

	Compressor(CompressionStrategy compressionStrategy) {
		this.compressionStrategy = compressionStrategy;
	}

	public void compress(Path inFile, File outFile) 
		throws IOException 
	{
		try (OutputStream outputStream = new FileOutputStream(outFile)){
			Files.copy(inFile, this.compressionStrategy.compress(outputStream));
		}
	}

}

Compressor gzipCompressor = new Compressor(new GzipBasedCompressionStrategy());
gzipCompressor.compress(in, out);

Compressor ziCompressor = new Compressor(new ZipBasedCompressionStrategy());
ziCompressor.compress(in, out);

Compressor gzipCompressor = new Compressor(GZIPOutputStream::new);
gzipCompressor.compress(in, out);

Compressor ziCompressor = new Compressor(ZipOutputStream::new);
ziCompressor.compress(in, out);

观察者模式

观察者模式中,被观察者持有观察者的一个列表,当被观察者的状态发送变化时, 会通知观察者。

对于一个观察者来说,往往是对一个行为的封装。

interface NameObserver{
	void onNameChange(String oName, String nName);
}

@Data
class User {

	private final List<NameObserver> nameObservers = new ArrayList<>();
	
	@Setter(AccessLevel.PRIVATE)
	private String name;

	public void updateName(String nName) {
		String oName = getName();
		setName(nName);
		nameObservers.forEach(nameObserver -> 
				nameObserver.onNameChange(oName, nName));
	}

	public void addObserver(NameObserver nameObserver){
		this.nameObservers.add(nameObserver);
	}

}

class LoggerNameObserver implements NameObserver{

	@Override
	public void onNameChange(String oName, String nName) {
		System.out.println(
				String.format("old Name is %s, new Name is %s", oName, nName));
	}

}

class NameChangeNoticeObserver implements NameObserver{

	@Override
	public void onNameChange(String oName, String nName) {
		notic.send(
				String.format("old Name is %s, new Name is %s", oName, nName));
	}
	
}

User user = new User();
user.addObserver(new LoggerNameObserver());
user.addObserver(new NameChangeNoticeObserver());
user.updateName("张三");

User user = new User();
user.addObserver((oName, nName) ->
		System.out.println(
			String.format("old Name is %s, new Name is %s", oName, nName)));

user.addObserver((oName, nName) ->
		notic.send(
			String.format("old Name is %s, new Name is %s", oName, nName)));

user.updateName("张三");

模板方法模式

模板方法将整体算法设计成一个抽象类,他有一系列的抽象方法, 代表方法中可被定制的步骤,同时这个类中包含一些通用代码, 算法的每一个变种都由具体的类实现,他们重新抽象方法,提供相应的实现。

模板方法,实际是行为的一种整合,内部大量用到行为的传递。 先看一个标准的模板方法:

interface UserChecker{

	void check(User user);

}

abstract class AbstractUserChecker implements UserChecker {
	@Override
	public final void check(User user){
		checkName(user);
		checkAge(user);
	}

	abstract void checkName(User user);

	abstract void checkAge(User user);

}

class SimpleUserChecker extends AbstractUserChecker {

	@Override
	void checkName(User user) {
		Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName()));
	}

	@Override
	void checkAge(User user) {
		Preconditions.checkArgument(user.getAge() != null);
		Preconditions.checkArgument(user.getAge().intValue() > 0);
		Preconditions.checkArgument(user.getAge().intValue() < 150);
	}

}

UserChecker userChecker = new SimpleUserChecker();
userChecker.check(new User());

class LambdaBaseUserChecker implements UserChecker {

	private final List<Consumer<User>> userCheckers = Lists.newArrayList();
	
	public LambdaBaseUserChecker(List<Consumer<User>>userCheckers) {
		this.userCheckers.addAll(userCheckers);
	}

	@Override
	public void check(User user) {
		this.userCheckers.forEach(userConsumer -> userConsumer.accept(user));
	}

}

UserChecker userChecker = new LambdaBaseUserChecker(Arrays.asList(
	user -> Preconditions.checkArgument(StringUtils.isNotEmpty(user.getName())),
	user -> Preconditions.checkArgument(user.getAge() != null),
	user -> Preconditions.checkArgument(user.getAge().intValue() > 0),
	user -> Preconditions.checkArgument(user.getAge().intValue() < 150)
));

userChecker.check(new User());

@Data
class User{
	private String name;
	private Integer age;
}

在看一个 Spring JdbcTemplate,如果使用 Lambda 进行简化:

public JdbcTemplate jdbcTemplate;

public User getUserById(Integer id){

	return jdbcTemplate.query(
			"select id, name, age from tb_user where id = ?", 
			new PreparedStatementSetter() {
				@Override
				public void setValues(PreparedStatement preparedStatement) 
					throws SQLException 
				{
					preparedStatement.setInt(1, id);
				}
			}, new ResultSetExtractor<User>() {
				@Override
				public User extractData(ResultSet resultSet) 
					throws SQLException, DataAccessException 
				{
					User user = new User();
					user.setId(resultSet.getInt("id"));
					user.setName(resultSet.getString("name"));
					user.setAge(resultSet.getInt("age"));
					return user;
				}});

}

public User getUserByIdLambda(Integer id){
	return jdbcTemplate.query(
			"select id, name, age from tb_user where id = ?",
			preparedStatement -> preparedStatement.setInt(1, id),
			resultSet -> {
				User user = new User();
				user.setId(resultSet.getInt("id"));
				user.setName(resultSet.getString("name"));
				user.setAge(resultSet.getInt("age"));
				return user;
			});
}

@Data
class User {
	private Integer id;
	private String name;
	private Integer age;
}