Java 函数式编程「一」

打印 上一主题 下一主题

主题 902|帖子 902|积分 2716

由 JS 转 Java,写惯了 React,习惯了函数式,因此转 Java 时也是先学函数式。
语法糖「Syntactic Sugar」

起初,Java 的函数式看起来是匿名类的一个语法糖。
  1. Stream.of(1, 2, 3).filter(new Predicate<Integer>() {
  2.   @Override
  3.   public boolean test(Integer integer) {
  4.     return integer % 2 == 0;
  5.   }
  6. }).collect(Collectors.toList());
复制代码
  1. Stream.of(1, 2, 3).filter(x -> x % 2 == 0).collect(Collectors.toList());
复制代码
后来,看到也能传方法引用时,我陷入了怀疑。
  1. // 类名::static 方法
  2. Stream.of(4, 2, 3).sorted(Integer::compareTo);
复制代码
  1. // 对象::对象方法
  2. Stream.of(1, 2, 3).forEach(System.out::println);
复制代码
  1. // 类名::对象方法
  2. Stream.of(4, 2, 3).sorted(Integer::compareTo);
复制代码
不过再想想,还是可以理解成语法糖:
  1. Stream.of(1, 2, 3).forEach(System.out::println);
  2. Stream.of(1, 2, 3).forEach(new Consumer<Integer>() {
  3.   @Override
  4.   public void accept(Integer integer) {
  5.     System.out.println(integer);
  6.   }
  7. });
复制代码
闭包「Closure」

Java 闭包也是假的,和匿名类的限制一样,要求闭包访问的外部作用域变量是 final 的。实现应该都是:基础类型值传递,引用类型引用传递。
以下 Java 代码编译会报错:
  1. int index = 0;
  2. Stream.of("a", "b", "c")
  3.   .forEach(x -> System.out.println((index++) + ":" + x));
复制代码
Error: Variable used in lambda expression should be final or effectively final
JS 闭包毫无问题:
  1. let index = 0;
  2. ["a", "b", "c"].forEach(x => console.log(index++ + ":" + x));
复制代码
尽管如此,函数式习惯还是可以带到 Java 了。
第一个要带过来的是:少量的数据结构搭配大量的操作。
在 OOP 的世界里,开发者被鼓励针对具体的问题建立专门的数据结构,并以方法的形式,将专门的操作关联在数据结构上。函数式编程语言选择了另一种重用思路。它们用很少的一组关键数据结构( 如 list、 set、 map)来搭配专为这些数据结构深度优化过的操作。我们在这些关键数据结构和操作组成的一套运转机构上面,按需要“ 插入” 另外的数据结构和高阶函数来调整机器,以适应具体的问题。
Neal Ford. 函数式编程思维
在 Java 中,关键数据结构就是指 Stream。
Stream 操作三板斧:map、filter、reduce

Java 和 JS 有些不一样。
不同点一:index 的取法

JS map、filter、reduce 都能拿到 index:
  1. ["a", "b", "c"]
  2.   .map((s, index) => `${index}: ${s}`)
  3.   .forEach(s => console.log(s));
复制代码
Java 想要 index 的信息,如果只是单线程,可以考虑利用引用类型保存迭代的索引:
  1. AtomicInteger i = new AtomicInteger(0);
  2. Stream.of("a", "b", "c")
  3.   .map(x -> i.getAndIncrement() + ":" + x)
  4.   .forEach(System.out::println);
复制代码
如果想支持多线程,可以先通过 zip 让流中的数据带上 index:
  1. public static <T, R> List<Pair<T, R>> zip(List<T> s1, List<R> s2) {
  2.   return IntStream.range(0, Math.min(s1.size(), s2.size()))
  3.     .mapToObj(index -> new Pair<T, R>(s1.get(index), s2.get(index)))
  4.     .collect(Collectors.toList());
  5. }
  6. public static void main(String[] args) {
  7.   List<String> list = Arrays.asList("a", "b", "c");
  8.   zip(IntStream.range(0, list.size()).boxed().collect(Collectors.toList()), list)
  9.     .parallelStream()
  10.     .map(p -> p.getKey() + ":" + p.getValue())
  11.     .forEach(System.out::println);
  12. }
复制代码
不同点二:reduce 的用法

Java 分为了 reduction 和 mutable reduction,在 JS 里是不区分的。
A mutable reduction operation accumulates input elements into a mutable result container, such as a Collection or StringBuilder, as it processes the elements in the stream.


  • JS reduction:
    1. [1, 2, 3].reduce((acc, cur) => acc + cur, 0);
    复制代码
  • Java reduction:
    1. Stream.of(1, 2, 3).reduce(0, Integer::sum);
    复制代码
  • JS mutable reduction:
    1. [1, 2, 2].reduce((acc, cur) => {
    2.   acc.add(cur);
    3.   return acc;
    4. }, new Set());
    复制代码
  • Java mutable reduction:
    1. Stream.of(1, 2, 2).collect(
    2.   () -> new HashSet<>(),
    3.   (set, el) -> set.add(el),
    4.   (s1, s2) -> s1.addAll(s2)
    5. );
    复制代码
    也可以简写为:
    1. Stream.of(1, 2, 2).collect(
    2.   HashSet::new,
    3.   HashSet::add,
    4.   HashSet::addAll
    5. );
    复制代码
    参数比 JS 多了个 HashSet::addAll 是为了并行处理。
后续继续总结高阶函数、柯里化、函子在 Java 中的应用。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

篮之新喜

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表