【笔记】Java函数式编程

Lambda表达式

Lambda表达式由三个部分组成:第一部分为一个括号内用逗号分隔的参数列表,参数即函数式接口里面方法的参数;第二部分为一个箭头符号:->;第三部分为方法体,可以是表达式和代码块。

可选的类型声明:你不用去声明参数的类型。编译器可以从参数的值来推断它是什么类型。
可选的参数周围的括号:你可以不用在括号内声明单个参数。但是对于很多参数的情况,括号是必需的。
可选的大括号:如果表达式体里面只有一个语句,那么你不必用大括号括起来。
可选的返回关键字:如果表达式体只有单个表达式用于值的返回,那么编译器会自动完成这一步。若要指示表达式来返回某个值,则需要使用大括号。

为了让现有的功能和 Lambda 表达式友好兼容,于是就有了函数接口这个概念。函数接口是一种只有一个方法的接口,函数接口可以隐式地转换成 Lambda 表达式。
我们能够使用 Lambda 实例化它们,Lambda 表达式让你能够将函数作为方法参数,或者将代码作为数据对待。

Lambda 表达式让匿名类不再需要,这为 Java 增添了简洁但实用的函数式编程能力。

代码示例:

public class NewFeaturesTester {
    public static void main(String[] args) {
        NewFeaturesTester tester = new NewFeaturesTester();
        // 有类型声明
        MathOperation addition = (int a, int b) -> a + b;
        // 没有类型声明
        MathOperation subtraction = (a, b) -> a - b;
        // 带有大括号,返回值
        MathOperation multiplication = (int a, int b) -> { return a*b; };
        // 没有大括号和return
        MathOperation division = (int a, int b) -> a / b;

        System.out.println("10 + 5 = " + tester.operate(10, 5, addition));
        System.out.println("10 - 5 = " + tester.operate(10, 5, subtraction));
        System.out.println("10 x 5 = " + tester.operate(10, 5, multiplication));
        System.out.println("10 / 5 = " + tester.operate(10, 5, division));

        // 没有括号
        GreetingService greetingService1 = message -> System.out.println("Hello " + message);
        // 有括号
        GreetingService greetingService2 = (message) -> System.out.println("Hello " + message);

        greetingService1.sayMessage("World");
        greetingService1.sayMessage("China");
    }

    interface MathOperation {
        int operation(int a, int b);
    }

    interface GreetingService {
        void sayMessage(String message);
    }

    private int operate(int a, int b, MathOperation mathOperation) {
        return mathOperation.operation(a, b);
    }
}

作用域

public class NewFeaturesTester{
     final static String salutation = "Hello "; // 正确,不可再次赋值
    // static String salutation = "Hello "; // 正确,可再次赋值
    // String salutation = "Hello "; // 非静态,报错
    // final String salutation = "Hello "; // 非静态,报错

    public static void main(String args[]){
        // final String salutation = "Hello "; // 正确,不可再次赋值
        // String salutation = "Hello "; // 正确,隐性为 final,不可再次赋值

        // salution = "welcome to "
        GreetingService greetService1 = message ->
        System.out.println(salutation + message);
        greetService1.sayMessage("World");
    }

    interface GreetingService{
       void sayMessage(String message);
    }
}

可访问 static 修饰的成员变量,如果是 final static 修饰,不可再次赋值,只有 static 修饰可再次赋值。
可访问表达式外层的 final 局部变量(不用声明为 final,隐性具有 final 语义),不可再次赋值。

方法引用

方法也是一种对象,可以通过名字来引用。不过方法引用的唯一用途是支持 Lambda 的简写,使用方法名称来表示 Lambda。
方法引用可以通过方法的名字来引用其本身。方法引用是通过双冒号 :: 来描述的。

可以引用以下类型:

构造器引用。语法是 Class::new,或者更一般的 Class< T >::new,要求构造器方法是没有参数。
静态方法引用。语法是 Class::static_method,要求接受一个 Class 类型的参数。
特定类的任意对象方法引用。它的语法是 Class::method,要求方法是没有参数的。
特定对象的方法引用,它的语法是 instance::method。要求方法接受一个参数,与 3 不同的地方在于,3 是在列表元素上分别调用方法,而 4 是在某个对象上调用方法,将列表元素作为参数传入。

代码示例:

import java.util.List;
import java.util.ArrayList;

public class NewFeaturesTester{

    public static void main(String args[]){
        List<String> names = new ArrayList<>();

        names.add("Peter");
        names.add("Linda");
        names.add("Smith");
        names.add("Zack");
        names.add("Bob");

        //     通过 System.out::println 引用了输出的方法
        names.forEach(System.out::println);
    }
}

函数式接口

函数式接口通过一个单一的功能来表现。例如,带有单个 compareTo 方法的比较接口,被用于比较的场合。Java 8 开始定义了大量的函数式接口来广泛地用于 Lambda 表达式。

Java 8 引入的一个核心概念是函数式接口(Functional Interfaces)。通过在接口里面添加一个抽象方法,这些方法可以直接从接口中运行。
如果一个接口定义唯一一个抽象方法,那么这个接口就成为函数式接口。
同时,引入了一个新的注解:@FunctionalInterface。可以把他它放在一个接口前,表示这个接口是一个函数式接口。
这个注解是非必须的,只要接口只包含一个方法的接口,虚拟机会自动判断,不过最好在接口上使用注解 @FunctionalInterface 进行声明。
在接口中添加了 @FunctionalInterface 的接口,只允许有一个抽象方法,否则编译器也会报错。

函数式接口列表:

接口 描述
BitConsumer<T,U> 该接口代表了接收两个输入参数 T、U,并且没有返回的操作
BiFunction<T,U,R> 该接口代表提供接收两个参数 T、U,并且产生一个结果 R 的方法
BinaryOperator 代表了基于两个相同类型的操作数,产生仍然是相同类型结果的操作
BiPredicate<T,U> 代表了对两个参数的断言操作(基于 Boolean 值的方法)
BooleanSupplier 代表了一个给出 Boolean 值结果的方法
Consumer 代表了接受单一输入参数并且没有返回值的操作
DoubleBinaryOperator 代表了基于两个 Double 类型操作数的操作,并且返回一个 Double 类型的返回值
DoubleConsumer 代表了一个接受单个 Double 类型的参数并且没有返回的操作
DoubleFunction 代表了一个接受 Double 类型参数并且返回结果的方法
DoublePredicate 代表了对一个 Double 类型的参数的断言操作
DoubleSupplier 代表了一个给出 Double 类型值的方法
DoubleToIntFunction 代表了接受单个 Double 类型参数但返回 Int 类型结果的方法
DoubleToLongFunction 代表了接受单个 Double 类型参数但返回 Long 类型结果的方法
DoubleUnaryOperator 代表了基于单个 Double 类型操作数且产生 Double 类型结果的操作
Function<T,R> 代表了接受一个参数并且产生一个结果的方法
IntBinaryOperator 代表了对两个 Int 类型操作数的操作,并且产生一个 Int 类型的结果
IntConsumer 代表了接受单个 Int 类型参数的操作,没有返回结果
IntFunction 代表了接受 Int 类型参数并且给出返回值的方法
IntPredicate 代表了对单个 Int 类型参数的断言操作

加有 @FunctionalInterface 注解的方法均是此类接口,位于 java.util.Funtion 包中。

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class NewFeaturesTester{
   public static void main(String args[]){
      List<Integer> list = Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
      System.out.println("All of the numbers:");
      eval(list, n->true);
      System.out.println("Even numbers:");
      eval(list, n-> n%2 == 0 );
      System.out.println("Numbers that greater than  5:");
      eval(list, n -> n > 5 );
   }

   public static void eval(List<Integer> list, Predicate<Integer> predicate){
      for(Integer n: list){
         if(predicate.test(n)){
            System.out.println(n);
         }
      }
   }
}

默认方法作为一种向后兼容能力而出现,旧的接口也能用到 Lambda 表达式中。
Java 8 引入默认方式使得 List 和 Collection 接口能够拥有 forEach 方法的默认实现。