Stream流式编程基础

Created
Apr 2, 2023 07:15 AM
Tags
在 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 或者官方文档找到。
notion image
列举一些最常用的例子如下。
// 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 流式编程基础

notion image
主要关注:
  1. 流的创建,必须通过各种办法获得流
  1. 流的中间操作,可以串联多个中间操作
  1. 流的终止操作,必须有且只有一个终止操作
notion image

流的创建

主要有以下几种方法。
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