java8 函数式编程一

  1、函数接口
  
  2、Lambda 表达式
  
  3、默认方法
  
  4、其余
  
  回到顶部
  
  1、函数接口
  
  接口    参数    返回类型    描述
  
  Predicate<T>    T    boolean    用来比较操做
  
  Consumer<T>    T    void    没有返回值的函数
  
  Function<T, R>    T    R    有返回值的函数
  
  Supplier<T>    None    T    工厂方法-返回一个对象
  
  UnaryOperator<T>    T    T    入参和出参都是相同对象的函数
  
  BinaryOperator<T>    (T,T)    T    求两个对象的操做结果
  
  为何要先从函数接口提及呢?由于我以为这是 java8 函数式编程的入口呀!每一个函数接口都带有 @FunctionalInterface 注释,有且仅有一个未实现的方法,表示接收 Lambda 表达式,它们存在的意义在于将代码块做为数据打包起来。
  
  没有必要过度解读这几个函数接口,彻底能够把它们当作普通的接口,不过他们有且仅有一个抽象方法(由于要接收 Lambda 表达式啊)。
  
  @FunctionalInterface 该注释会强制 javac 检查一个接口是否符合函数接口的标准。 若是该注释添加给一个枚举类型、 类或另外一个注释, 或者接口包含不止一个抽象方法, javac 就会报错。
  
  回到顶部
  
  2、Lambda 表达式
  
  一、Lambda 表达式和匿名内部类
  
  先来复习一下匿名内部类的知识:
  
  若是是接口,至关于在内部返回了一个接口的实现类,而且实现方式是在类的内部进行的;
  
  若是是普通类,匿名类至关于继承了父类,是一个子类,并能够重写父类的方法。
  
  须要特别注意的是,匿名类没有名字,不能拥有一个构造器。若是想为匿名类初始化,让匿名类得到一个初始化值,或者说,想使用匿名内部类外部的一个对象,则编译器要求外部对象为final属性,不然在运行期间会报错。
  
  new Thread(new Runnable() {
  
  @Override
  
  public void run() {
  
  System.out.println(123);
  
  }
  
  }).start();
  
  new Thread(()-> System.out.println(123)).start();
  
  如上,和传入一个实现某接口的对象不一样, 咱们传入了一段代码块 —— 一个没有名字的函数。() 是参数列表, 和上面匿名内部类示例中的是同样的。 -> 将参数和 Lambda 表达式的主体分开, 而主体是以后操做会运行的一些代码。
  
  Lambda 表达式简化了匿名内部类的写法,省略了函数名和参数类型。即参数列表 () 中能够仅指定参数名而不指定参数类型。
  
  Java 是强类型语言,为何能够不指定参数类型呢?这得益于 javac 的类型推断机制,编译器可以根据上下文信息推断出参数的类型,固然也有推断失败的时候,这时就须要手动指明参数类型了。javac 的类型推断机制以下:
  
  对于类中有重载的方法,javac 在推断类型时,会挑出最具体的类型。
  
  若是只有一个可能的目标类型, 由相应函数接口里的参数类型推导得出;
  
  若是有多个可能的目标类型, 由最具体的类型推导得出;
  
  若是有多个可能的目标类型且最具体的类型不明确, 则需人为指定类型。
  
  二、Lambda 表达式和集合
  
  java8 在 java.util 包中引入了一个新的类 —— Stream.java。java8 以前咱们迭代集合,都只能依赖外部迭代器 Iterator 对集合进行串行化处理。而 Stream 支持对集合顺序和并行聚合操做,将更多的控制权交给集合类,是一种内部迭代方式。这有利于方便用户写出更简单的代码,明确要达到什么转化,而不是如何转化。
  
  Stream 的操做有两种,一种是描述 Stream ,如 filter、map 等最终不产生结果的行为称为"惰性求值";另一种像 foreach、collect 等是从 Stream 中产生结果的行为称为"及早求值"。
  
  接下来让咱们瞧瞧 Stream 如何结合 Lambda 表达式优雅的处理集合...
  
  foreach - 迭代集合
  
  list.forEach(e -> System.out.println(e));
  
  map.forEach((k, v) -> {
  
  System.out.println(k);
  
  System.out.println(v);
  
  });
  
  collect(toList()) - 由Stream里的值生成一个列表。
  
  List<String> list = Stream.of("java", "C++", "Python").collect(Collectors.toList());
  
  等价于:
  
  List<String> asList = Arrays.asList("java", "C++", "Python");
  
  filter - 遍历并检查过滤其中的元素。
  
  long count = list.stream().filter(x -> "java".equals(x)).count();
  
  map、mapToInt、mapToLong、mapToDouble - 将流中的值转换成一个新的值。
  
  List<String> mapList = list.stream().map(str -> str.toUpperCase()).collect(Collectors.toList());
  
  List<String> list = Stream.of("java", "javascript", "python").collect(Collectors.toList());
  
  IntSummaryStatistics intSummaryStatistics = list.stream().mapToInt(e -> e.length()).summaryStatistics();
  
  System.out.println("最大值:" + intSummaryStatistics.getMax());
  
  System.out.println("最小值:" + intSummaryStatistics.getMin());
  
  System.out.println("平均值:" + intSummaryStatistics.getAverage());
  
  System.out.println("总数:" + intSummaryStatistics.getSum());
  
  mapToInt、mapToLong、mapToDouble 和 map 操做相似,只是把函数接口的返回值改成 int、long、double 而已。
  
  flatMap - 将多个 Stream 链接成一个 Stream
  
  List<String> streamList = Stream.of(list, asList).flatMap(x -> x.stream()).collect(Collectors.toList());
  
  flatMap 方法的相关函数接口和 map 方法的同样, 都是 Function 接口, 只是方法的返回值限定为 Stream 类型罢了。
  
  Max-求最大值、Min-求最小值
  
  String maxStr = list.stream(www.michenggw.com).max(Comparator.comparing(e -> e.length())).get();
  
  String minStr = list.stream(www.yigouyule2.cn ).min(Comparator.comparing(e -> e.length())).get();
  
  reduce - 聚合操做,从一组元素中生成一个值,sum()、max()、min()、count() 等都是reduce操做,将他们单独设为函数只是由于经常使用。
  
  Integer sum1 = Stream.of(1, 2, 3).reduce(0, (acc, e) -> acc + e);
  
  上述执行求和操做,有两个参数: 传入 Stream 中初始值和 acc。 将两个参数相加,acc 是累加器,保存着当前的累加结果。
  
  待续...
  
  回到顶部
  
  3、默认方法
  
  java8 中新增了 Stream 操做,那么第三方类库中的自定义集合 MyList 要怎么作到兼容呢?总不能升级完 java8,第三方类库中的集合实现全都不能用了吧?
  
  为此,java8 在接口中引入了"默认方法"的概念!默认方法是指接口中定义的包含方法体的方法,方法名有 default 关键字作前缀。默认方法的出现是为了 java8 可以向后兼容。
  
  public interface Iterable<T> {
  
  /**
  
  * Performs the given action for each element of the {@code Iterable}
  
  * until all elements have been processed or the action throws an
  
  * exception.  Unless otherwise specified by the implementing class,
  
  * actions are performed in www.trgj888.com/  the order of iteration (if an iteration order
  
  * is specified).  Exceptions thrown by the action are relayed to the
  
  * caller.
  
  *
  
  * @implSpec
  
  * <p>The default implementation behaves as if:
  
  * <pre>{@code
  
  *     for (T t : this)
  
  *         action.accept(t);
  
  * }</pre>
  
  *
  
  * @param action The action to be performed for each element
  
  * @throws NullPointerException if the specified action is null
  
  * @since 1.8
  
  */
  
  default void forEach(Consumer<? super T> action) {
  
  Objects.requireNonNull(www.yongshiyule178.com  action);
  
  for (T t : this) {
  
  action.accept(t);
  
  }
  
  }
  
  }
  
  看 java8 中的这个 Iterable.java 中的默认方法 forEach(Consumer<? super T> action),表示“若是大家没有实现 forEach 方法,就使用个人吧”。
  
  默认方法除了添加了一个新的关键字 default,在继承规则上和普通方法也略有差异:
  
  类胜于接口。若是在继承链中有方法体或抽象的方法声明,那么就能够忽略接口中定义的方法。
  
  子类胜于父类。果一个接口继承了另外一个接口, 且两个接口都定义了一个默认方法,那么子类中定义的方法胜出。
  
  若是上面两条规则不适用, 子类要么须要实现该方法, 要么将该方法声明为抽象方法。
  
  回到顶部
  
  4、其余
  
  使用 Lambda 表达式,就是将复杂性抽象到类库的过程。
  
  面向对象编程是对数据进行抽象, 而函数式编程是对行为进行抽象。
  
  Java8 虽然在匿名内部类中能够引用非 final 变量, 可是该变量在既成事实上必须是final。即若是你试图给该变量屡次赋值, 而后在 Lambda 表达式中引用它, 编译器就会报错。
  
  Stream 是用函数式编程方式在集合类上进行复杂操做的工具。
  
  对于须要大量数值运算的算法来讲, 装箱和拆箱的计算开销, 以及装箱类型占用的额外内存, 会明显减缓程序的运行速度。为了减少这些性能开销, Stream 类的某些方法对基本类型和装箱类型作了区分。好比 IntStream、LongStream 等。
  
  Java8 对为 null 的字段也引进了本身的处理,既不用一直用 if 判断对象是否为 null,来看看?
  
  public static List<AssistantVO> getAssistant(Long tenantId) {
  
  // ofNullable 若是 value 为null,会构建一个空对象。
  
  Optional<List<AssistantVO>www.quwanyule157.com> assistantVO = Optional.ofNullable(ASSISTANT_MAP.get(tenantId));
  
  // orElse 若是 value 为null,选择默认对象。
  
  assistantVO.orElse(ASSISTANT_MAP.www.mingcheng178.comget(DEFAULT_TENANT));
  
  return assistantVO.get();  1、函数接口
  
  2、Lambda 表达式
  
  3、默认方法
  
  4、其余
  
  回到顶部
  
  1、函数接口
  
  接口    参数    返回类型    描述
  
  Predicate<T>    T    boolean    用来比较操做
  
  Consumer<T>    T    void    没有返回值的函数
  
  Function<T, R>    T    R    有返回值的函数
  
  Supplier<T>    None    T    工厂方法-返回一个对象
  
  UnaryOperator<T>    T    T    入参和出参都是相同对象的函数
  
  BinaryOperator<T>    (T,T)    T    求两个对象的操做结果
  
  为何要先从函数接口提及呢?由于我以为这是 java8 函数式编程的入口呀!每一个函数接口都带有 @FunctionalInterface 注释,有且仅有一个未实现的方法,表示接收 Lambda 表达式,它们存在的意义在于将代码块做为数据打包起来。
  
  没有必要过度解读这几个函数接口,彻底能够把它们当作普通的接口,不过他们有且仅有一个抽象方法(由于要接收 Lambda 表达式啊)。
  
  @FunctionalInterface 该注释会强制 javac 检查一个接口是否符合函数接口的标准。 若是该注释添加给一个枚举类型、 类或另外一个注释, 或者接口包含不止一个抽象方法, javac 就会报错。
  
  回到顶部
  
  2、Lambda 表达式
  
  一、Lambda 表达式和匿名内部类
  
  先来复习一下匿名内部类的知识:
  
  若是是接口,至关于在内部返回了一个接口的实现类,而且实现方式是在类的内部进行的;
  
  若是是普通类,匿名类至关于继承了父类,是一个子类,并能够重写父类的方法。
  
  须要特别注意的是,匿名类没有名字,不能拥有一个构造器。若是想为匿名类初始化,让匿名类得到一个初始化值,或者说,想使用匿名内部类外部的一个对象,则编译器要求外部对象为final属性,不然在运行期间会报错。
  
  new Thread(new Runnable() {
  
  @Override
  
  public void run() {
  
  System.out.println(123);
  
  }
  
  }).start();
  
  new Thread(()-> System.out.println(123)).start();
  
  如上,和传入一个实现某接口的对象不一样, 咱们传入了一段代码块 —— 一个没有名字的函数。() 是参数列表, 和上面匿名内部类示例中的是同样的。 -> 将参数和 Lambda 表达式的主体分开, 而主体是以后操做会运行的一些代码。
  
  Lambda 表达式简化了匿名内部类的写法,省略了函数名和参数类型。即参数列表 () 中能够仅指定参数名而不指定参数类型。
  
  Java 是强类型语言,为何能够不指定参数类型呢?这得益于 javac 的类型推断机制,编译器可以根据上下文信息推断出参数的类型,固然也有推断失败的时候,这时就须要手动指明参数类型了。javac 的类型推断机制以下:
  
  对于类中有重载的方法,javac 在推断类型时,会挑出最具体的类型。
  
  若是只有一个可能的目标类型, 由相应函数接口里的参数类型推导得出;
  
  若是有多个可能的目标类型, 由最具体的类型推导得出;
  
  若是有多个可能的目标类型且最具体的类型不明确, 则需人为指定类型。
  
  二、Lambda 表达式和集合
  
  java8 在 java.util 包中引入了一个新的类 —— Stream.java。java8 以前咱们迭代集合,都只能依赖外部迭代器 Iterator 对集合进行串行化处理。而 Stream 支持对集合顺序和并行聚合操做,将更多的控制权交给集合类,是一种内部迭代方式。这有利于方便用户写出更简单的代码,明确要达到什么转化,而不是如何转化。
  
  Stream 的操做有两种,一种是描述 Stream ,如 filter、map 等最终不产生结果的行为称为"惰性求值";另一种像 foreach、collect 等是从 Stream 中产生结果的行为称为"及早求值"。
  
  接下来让咱们瞧瞧 Stream 如何结合 Lambda 表达式优雅的处理集合...
  
  foreach - 迭代集合
  
  list.forEach(e -> System.out.println(e));
  
  map.forEach((k, v) -> {
  
  System.out.println(k);
  
  System.out.println(v);
  
  });
  
  collect(toList()) - 由Stream里的值生成一个列表。
  
  List<String> list = Stream.of("java", "C++", "Python").collect(Collectors.toList());
  
  等价于:
  
  List<String> asList = Arrays.asList("java", "C++", "Python");
  
  filter - 遍历并检查过滤其中的元素。
  
  long count = list.stream().filter(x -> "java".equals(x)).count();
  
  map、mapToInt、mapToLong、mapToDouble - 将流中的值转换成一个新的值。
  
  List<String> mapList = list.stream().map(str -> str.toUpperCase()).collect(Collectors.toList());
  
  List<String> list = Stream.of("java", "javascript", "python").collect(Collectors.toList());
  
  IntSummaryStatistics intSummaryStatistics = list.stream().mapToInt(e -> e.length()).summaryStatistics();
  
  System.out.println("最大值:" + intSummaryStatistics.getMax());
  
  System.out.println("最小值:" + intSummaryStatistics.getMin());
  
  System.out.println("平均值:" + intSummaryStatistics.getAverage());
  
  System.out.println("总数:" + intSummaryStatistics.getSum());
  
  mapToInt、mapToLong、mapToDouble 和 map 操做相似,只是把函数接口的返回值改成 int、long、double 而已。
  
  flatMap - 将多个 Stream 链接成一个 Stream
  
  List<String> streamList = Stream.of(list, asList).flatMap(x -> x.stream()).collect(Collectors.toList());
  
  flatMap 方法的相关函数接口和 map 方法的同样, 都是 Function 接口, 只是方法的返回值限定为 Stream 类型罢了。
  
  Max-求最大值、Min-求最小值
  
  String maxStr = list.stream(www.michenggw.com).max(Comparator.comparing(e -> e.length())).get();
  
  String minStr = list.stream(www.yigouyule2.cn ).min(Comparator.comparing(e -> e.length())).get();
  
  reduce - 聚合操做,从一组元素中生成一个值,sum()、max()、min()、count() 等都是reduce操做,将他们单独设为函数只是由于经常使用。
  
  Integer sum1 = Stream.of(1, 2, 3).reduce(0, (acc, e) -> acc + e);
  
  上述执行求和操做,有两个参数: 传入 Stream 中初始值和 acc。 将两个参数相加,acc 是累加器,保存着当前的累加结果。
  
  待续...
  
  回到顶部
  
  3、默认方法
  
  java8 中新增了 Stream 操做,那么第三方类库中的自定义集合 MyList 要怎么作到兼容呢?总不能升级完 java8,第三方类库中的集合实现全都不能用了吧?
  
  为此,java8 在接口中引入了"默认方法"的概念!默认方法是指接口中定义的包含方法体的方法,方法名有 default 关键字作前缀。默认方法的出现是为了 java8 可以向后兼容。
  
  public interface Iterable<T> {
  
  /**
  
  * Performs the given action for each element of the {@code Iterable}
  
  * until all elements have been processed or the action throws an
  
  * exception.  Unless otherwise specified by the implementing class,
  
  * actions are performed in www.trgj888.com/  the order of iteration (if an iteration order
  
  * is specified).  Exceptions thrown by the action are relayed to the
  
  * caller.
  
  *
  
  * @implSpec
  
  * <p>The default implementation behaves as if:
  
  * <pre>{@code
  
  *     for (T t : this)
  
  *         action.accept(t);
  
  * }</pre>
  
  *
  
  * @param action The action to be performed for each element
  
  * @throws NullPointerException if the specified action is null
  
  * @since 1.8
  
  */
  
  default void forEach(Consumer<? super T> action) {
  
  Objects.requireNonNull(www.yongshiyule178.com  action);
  
  for (T t : this) {
  
  action.accept(t);
  
  }
  
  }
  
  }
  
  看 java8 中的这个 Iterable.java 中的默认方法 forEach(Consumer<? super T> action),表示“若是大家没有实现 forEach 方法,就使用个人吧”。
  
  默认方法除了添加了一个新的关键字 default,在继承规则上和普通方法也略有差异:
  
  类胜于接口。若是在继承链中有方法体或抽象的方法声明,那么就能够忽略接口中定义的方法。
  
  子类胜于父类。果一个接口继承了另外一个接口, 且两个接口都定义了一个默认方法,那么子类中定义的方法胜出。
  
  若是上面两条规则不适用, 子类要么须要实现该方法, 要么将该方法声明为抽象方法。
  
  回到顶部
  
  4、其余
  
  使用 Lambda 表达式,就是将复杂性抽象到类库的过程。
  
  面向对象编程是对数据进行抽象, 而函数式编程是对行为进行抽象。
  
  Java8 虽然在匿名内部类中能够引用非 final 变量, 可是该变量在既成事实上必须是final。即若是你试图给该变量屡次赋值, 而后在 Lambda 表达式中引用它, 编译器就会报错。
  
  Stream 是用函数式编程方式在集合类上进行复杂操做的工具。
  
  对于须要大量数值运算的算法来讲, 装箱和拆箱的计算开销, 以及装箱类型占用的额外内存, 会明显减缓程序的运行速度。为了减少这些性能开销, Stream 类的某些方法对基本类型和装箱类型作了区分。好比 IntStream、LongStream 等。
  
  Java8 对为 null 的字段也引进了本身的处理,既不用一直用 if 判断对象是否为 null,来看看?
  
  public static List<AssistantVO> getAssistant(Long tenantId) {
  
  // ofNullable 若是 value 为null,会构建一个空对象。
  
  Optional<List<AssistantVO>www.quwanyule157.com> assistantVO = Optional.ofNullable(ASSISTANT_MAP.get(tenantId));
  
  // orElse 若是 value 为null,选择默认对象。
  
  assistantVO.orElse(ASSISTANT_MAP.www.mingcheng178.comget(DEFAULT_TENANT));
  
  return assistantVO.get();javascript

相关文章
相关标签/搜索