『On Java 8』读书笔记 (三) : 函数式编程与流式编程

218 阅读8分钟

函数式编程

OOobject oriented,面向对象)是抽象数据FPfunctional programming,函数式编程)是抽象行为

在《Java 8 实战》中,提到过一个概念——行为参数化,和这里的抽象行为有异曲同工之妙。

Lambda表达式

基本语法:

  1. 参数。
    • 一个参数,不需要括号 ();两个以上参数,使用括号 () 包裹参数。
    • 没有参数,使用括号 () 表示空参数列表。
  2. 接着 ->,可视为“产出”。
  3. -> 之后的内容都是方法体。
    • 单行不需要{ }包裹;多行需要{}包裹。
// 无参数
() -> System.out.println("hello")
// 单参数
(a) -> System.out.println(a + "hello")
// 多参数
(a,b) -> System.out.println(a + b + "hello")
// 多行方法体
(a,b) -> {
  System.out.println(a + b + "hello");
  return a + b;
}

方法引用

类名或对象名::方法名称

函数式接口

Java 8 引入了 java.util.function 包。包中的接口,是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为函数式方法。

包中创建了一组完整的目标接口,让我们一般情况下不需再定义自己的接口。

使用@FunctionalInterface注解,表明该接口是函数式接口,并强制接受检查,接口内只能存在一个抽象方法。

function基本命令规则:

  1. 如果只处理对象(非基本类型),名称为 FunctionConsumerPredicateSupplier等。参数类型通过泛型添加。

    这是基本类型,后面基本都是其变体。

  2. 如果接收参数是基本类型,则由名称的第一部分表示。如 LongConsumerDoubleFunctionIntPredicate 等,但返回基本类型的 Supplier 接口例外。

  3. 如果返回值为基本类型,则用 To 表示,如 ToLongFunction <T>IntToLongFunction

  4. 如果返回值类型与参数类型一致,则表明是一个运算符:单个参数使用 UnaryOperator,两个参数使用 BinaryOperator

  5. 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。

  6. 如果接收的两个参数类型不同,则名称中有一个 Bi

用基本类型的唯一原因是,可以避免传递参数和返回结果过程中的自动装箱和自动拆箱,进而提升性能。

高阶函数

消费或产生函数的函数。

// 仅仅相当于把Function函数,换了个名字
interface FuncSS extends Function<String, String> {}
 
public class ProduceFunction {
  // produce()被称为高阶函数
  static FuncSS produce() {
    return s -> s.toLowerCase(); // 返回一个函数
  }
  public static void main(String[] args) {
    FuncSS f = produce();
    System.out.println(f.apply("YELLING"));
  }
}

闭包

看完也没明白,闭包是要干什么……

函数组合

函数组合(Function Composition)意为“多个函数组合成新函数”。

一般使用andThen()compose()and()or()等等方法进行组合。

柯里化和部分求值

将一个多参数的函数,转换为一系列单参数函数。这个也不是很明白。

public class CurryingAndPartials {
   // 未柯里化:
   static String uncurried(String a, String b) {
      return a + b;
   }
   public static void main(String[] args) {
      // 柯里化的函数:
      Function<String, Function<String, String>> sum =
         a -> b -> a + b; // 第二个参数是另一个函数。
     // 输出 Hi Ho
      System.out.println(uncurried("Hi ", "Ho"));
 
      Function<String, String> hi = sum.apply("Hi "); // [2]
     //输出 Hi Ho
      System.out.println(hi.apply("Ho"));
 
      // 部分应用:
      Function<String, String> sumHi = sum.apply("Hup ");
     // Hup Ho
      System.out.println(sumHi.apply("Ho"));
     // Hup Hey
      System.out.println(sumHi.apply("Hey"));
   }
}

流式编程

集合优化了对象的存储,而流和对象的处理有关。

流创建

  1. Stream.of()

    Stream<Integer> stream = Stream.of(1, 2, 3, 4);
    
  2. 集合调用stream()

    List<String> list = new ArrayList<>();
    Stream<String> stream1 = list.stream();
    
  3. 随机数流

    Random中也新增了生成流的方法。

    Stream<Integer> stream2 = new Random(50).ints().boxed();
    
  4. 整型序列流

    IntStream stream3 = IntStream.range(1, 10);
    
  5. generate()

    @FunctionalInterface
    public interface Supplier<T> {
        T get();
    }
     
    public class Generator implements Supplier<String> {
        Random rand = new Random(47);
        char[] letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
        // 实现了函数式接口的方法
        public String get() {
            return "" + letters[rand.nextInt(letters.length)];
        }
     
        public static void main(String[] args) {
              // 传入一个提供者类型的接口
            String word = Stream.generate(new Generator())
                                .limit(30)
                                .collect(Collectors.joining());
            System.out.println(word);
        }
    }
    
  6. iterate()

    Stream.iterate() 产生的流的第一个参数是种子,然后将种子传递给第二个参数(第二个参数是一个方法)。运算结果,存储起来作为下次调用 iterate()时的第一个参数。

    // 斐波那契数列public class Fibonacci {    int x = 1;     Stream<Integer> numbers() {        return Stream.iterate(0, i -> {            int result = x + i;            x = i;            return result;        });    }}
    
  7. 流的建造者模式

    Stream.Builder<Integer> builder = Stream.builder();builder.add(1).add(2).add(3);Stream<Integer> stream4 = builder.build();
    
  8. Arrays

    String[] arrays = new String[] {"1","2","3"};Stream<String> stream5 = Stream.of(args);
    
  9. 正则表达式

    String str = "a.b.c,f";String s = Pattern.compile("[ .,?]+")  .splitAsStream(str)  .map(a -> a + " ")  .collect(Collectors.joining());System.out.println(s);
    

中间操作

跟踪调试

peek() 操作的目的是帮助调试,接收一个Consumer函数式接口。

排序

sorted()

Stream<Integer> stream2 = new Random(50).ints(10,1000).boxed();stream2  .skip(10)  .limit(10)  .sorted(Comparator.comparing(Integer::intValue))  .map(a -> a + " ")  .forEach(System.out::print);// 18 105 132 147 192 405 480 602 760 892

移除

移除元素有两种方法,去重和过滤。

  • distinct():消除流中的重复元素。

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 2, 3);List<Integer> collect = stream.distinct().peek(e -> System.out.println(e)).collect(Collectors.toList());// 1 2 3 4
    
  • filter(Predicate)predicate()true ,保留元素。

    Stream<Integer> stream = Stream.of(1, 2, 3, 4, 2, 3);List<Integer> collect1 = stream.filter(a -> a.equals(2)).peek(e -> System.out.println(e)).collect(Collectors.toList());//2 2
    

函数

  • map(Function):将函数操作应用在输入流的元素中,并将返回值传递到输出流中。
  • mapToInt(ToIntFunction):操作同上,但结果是 IntStream
  • mapToLong(ToLongFunction):操作同上,但结果是 LongStream
  • mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 2, 3);String collect = stream.map(a -> {    String aStr = a + "";    aStr += "^_^";    return aStr;}).collect(Collectors.joining(" "));System.out.println(collect);// 1^_^ 2^_^ 3^_^ 4^_^ 2^_^ 3^_^

组合流

把多个流,合并成一个流,也叫扁平化流。

flatMap() 做了两件事:将产生流的函数应用在每个元素上(与 map() 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。

flatMap(Function):当 Function 产生流时使用。

另外还有三个方法,跟map()的另外三个类似。

// 这是获取流的流Stream.of(1,2,3,4)        .map(a -> Stream.of(a+"a",a+"b",a+"c"))        .forEach(System.out::println);//java.util.stream.ReferencePipeline$Head@6a41eaa2//java.util.stream.ReferencePipeline$Head@7cd62f43//java.util.stream.ReferencePipeline$Head@6d4b1c02//java.util.stream.ReferencePipeline$Head@6093dd95 Stream.of(1,2,3,4)        .flatMap(a -> Stream.of(a+"a",a+"b",a+"c"))        .forEach(System.out::println); //1a 1b 1c 2a 2b 2c 3a 3b 3c 4a 4b 4c

Optional 类

一些流操作会返回Optional 对象,这是为了避免空指针异常。

  • findFirst() 返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.empty
  • findAny() 返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.empty
  • max()min() 返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty

Optional 便利函数

  • ifPresent(Consumer):当值存在时调用 Consumer,否则什么也不做。
  • orElse(otherObject):如果值存在则直接返回,否则生成 otherObject
  • orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个替代对象。
  • orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数生成一个异常。

Optional 创建方法

  • empty():生成一个空 Optional
  • of(value):将一个非空值包装到 Optional 里。
  • ofNullable(value):针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。

Optional 对象操作

  • filter(Predicate):对 Optional 中的内容应用Predicate 并将结果返回。如果内容不满足 Predicate ,返回空 Optional 。如果 Optional 已经为空,则直接返回空Optional
  • map(Function):如果 Optional 不为空,则内容执行方法,并返回结果。否则直接返回 Optional.empty
  • flatMap(Function):同 map(),不进行Optional封装。

Optional 流

// 创建 Optional 流Stream stream = Stream.of(1,null,2,3).map(signal -> Optional.ofNullable(signal)); // 使用 Optional 流stream  .limit(10)  .filter(Optional::isPresent)  .map(Optional::get)  .forEach(System.out::println);

终端操作

执行终端操作后,流操作结束。

数组

  • toArray():将流转换成适当类型的数组。
  • toArray(generator):在特殊情况下,生成自定义类型的数组。

循环

  • forEach(Consumer)常见如 System.out::println 作为 Consumer 函数。
  • forEachOrdered(Consumer): 保证 forEach 按照原始流顺序操作。

parallel():可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作。

集合

  • collect(Collector):使用 Collector 收集流元素到结果集合中。
  • collect(Supplier, BiConsumer, BiConsumer):同上,第一个参数 Supplier 创建了一个新的结果集合,第二个参数 BiConsumer 将下一个元素收集到结果集合中,第三个参数 BiConsumer 用于将两个结果集合合并起来。

组合

  • reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional
  • reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果。

匹配

  • allMatch(Predicate) :在第一个 false 时,则停止执行计算。
  • anyMatch(Predicate):在第一个 true 是停止执行计算。
  • noneMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。

查找

  • findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty
  • findAny(:返回含有任意流元素的 Optional,如果流为空返回 Optional.empty

信息

  • count():流中的元素个数。
  • max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。
  • min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。

数字流信息

  • average() :求取流元素平均值。
  • max()min():数值流操作无需 Comparator
  • sum():对所有流元素进行求和。