在 Java 里函数式编程已经是很常见的操作,这篇文章就是简单的入门记录。
认识 Lambda 表示式
用代码来对比一下:
// 常规方式创建线程 new Thread(new Runnable() { @Override public void run() { System.out.println("create thread in normal way"); } }).start(); // Lambda 方式创建线程 new Thread(()-> System.out.println("create thread by lambda")).start();
函数式编程接口
函数式编程接口是指只有一个抽象方法的接口,可以使用
@FunctionalInterface
注解来确保接口只有一个抽象方法。使用函数式编程接口,可以将函数作为参数传递,函数即参数。上文中的 Runnable 就是一个函数式编程接口,可以看到它的定义如下。
@FunctionalInterface public interface Runnable { public abstract void run(); }
我们也可以自己声明这样的接口,如下:
@FunctionalInterface interface IDemo { void doSomething(); }
有了这样的函数式接口后,以后方法实现的部分都可以用 lambda 的方式来快速声明,从而提高编程效率。比如这样。
public class Demo{ void foo(IDemo demo) { System.out.println("using demo from function foo"); demo.doSomething(); } public static void main(String[] args) { var funcDemo = new Demo(); funcDemo.foo(() -> System.out.println("demo can be this..")); funcDemo.foo(() -> System.out.println("demo can be that..")); } }
其实,除非我们对函数式接口的名字有特别的癖好,否则是没必要声明的,因为 JDK 中已经自带了几乎所有场景下需要的函数式接口。
内置的函数式接口
在 `package java.util.function` 下面,Java几乎帮我们实现了所有的函数式接口,详细清单可以在 IDE 或者官方文档找到。
列举一些最常用的例子如下。
// Supplier 不需要参数,只返回结果 Supplier<String> supplier = () -> "this is a supplier demo"; System.out.println(supplier.get()); // Consumer 只接收一个参数,不返回结果 Consumer<String> consumer = i -> System.out.println("this is a consumer demo for " + i); consumer.accept("hello world"); // Function 接收一个参数,返回一个结果 Function<Integer, Integer> function = i -> i * i; System.out.println("function demo: " + function.apply(9)); // BiFunction 接收 2 个参数,返回一个结果 BiFunction<String, Integer, String> biFunc = (name, age) -> String.format("%s's age is %s.", name, age); System.out.println(biFunc.apply("toby", 19)); // Predicate 接收一个参数,返回一个 bool 值 Predicate<Integer> isOdd = i -> i % 2 == 0; System.out.println(isOdd.test(3));
Stream 流式编程基础
主要关注:
- 流的创建,必须通过各种办法获得流
- 流的中间操作,可以串联多个中间操作
- 流的终止操作,必须有且只有一个终止操作
流的创建
主要有以下几种方法。
String[] arr = {"apple", "box", "cat", "dog"}; // 通过 Collection 创建 Arrays.stream(arr).forEach(System.out::println); // 通过 Arrays 创建 Arrays.asList(arr).stream().forEach(System.out::println); // 通过 Stream 类中的 of 静态方法创建 Stream.of(arr).forEach(System.out::println); // 通过迭代创建 Stream.iterate(1, i-> i+1).limit(10).forEach(System.out::println); // 通过生成创建 Stream.generate(()->new Random().nextInt(100)).limit(5).forEach(System.out::println);
流的中间操作
流的中间操作包括以下几种方法:
filter(Predicate p)
:过滤流中的元素
map(Function f)
:将流中的每个元素映射为另一个元素
flatMap(Function f)
:将流中的每个元素映射为一个流,然后将这些流合并为一个流
distinct()
:去重,去掉流中重复的元素
sorted()
:排序,将流中的元素按照自然顺序排序
peek(Consumer c)
:对流中的每个元素执行操作,返回一个新的流
流的终止操作
主要有以下几种方法。
forEach(Consumer c)
:对流中的每个元素执行操作
count()
:返回流中元素的个数
collect(Collector c)
:将流中的元素收集到一个集合中
reduce(BinaryOperator op)
:将流中的元素依次归约为一个结果
min(Comparator c)
:返回流中的最小元素
max(Comparator c)
:返回流中的最大元素
在终止操作中,还有一些断路操作,例如:
findFirst()
:返回流中的第一个元素
findAny()
:返回流中的任意一个元素
anyMatch(Predicate p)
:判断流中是否有任意一个元素匹配给定的条件
allMatch(Predicate p)
:判断流中的所有元素是否都匹配给定的条件
noneMatch(Predicate p)
:判断流中是否没有任何一个元素匹配给定的条件
流式编程示例
我们需要将一个数组中第一个带有 o 字母的单词找到,并按字母表倒序,然后将 o 替换成 0 后输出,代码如下。
String[] arr = {"apple", "box", "cat", "dog"}; Stream.of(arr).filter(i-> i.contains("o")) .limit(1) .flatMap(i-> Stream.of(i.split(""))) .sorted(Comparator.reverseOrder()) .map(i-> i.replace('o', '0')) .forEach(System.out::print); // output: x0b