【笔记】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 方法的默认实现。