lambda学习笔记

243 阅读7分钟

lambda学习笔记

代码示例,请跳转此处链接,毕竟三分学,七分练。

匿名内部类

  • 将代码作为数据传递
  • 缺陷->冗长 不易读

lambda示例

/**
该Lambda 表达式不包含参数,使用空括号 () 表示没有参数。
该 Lambda 表达式 实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为 void。
*/
Runnable noArguments = () -> System.out.println("Hello World");


/*
Lambda 表达式包含且只包含一个参数,可省略参数的括号,这和例 2-2 中的 形式一样。
**/
ActionListener oneArgument = event -> System.out.println("button clicked");

/**
Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号 ({})将代码块括起来,
代码块和普通方法遵循的规则别无二致,可以用返 回或抛出异常来退出。只有一行代码的 Lambda 表达式也可使用大括号,用以明确 Lambda
表达式从何处开始、到哪里结束。
*/
Runnable multiStatement = () -> {           
    	  System.out.print("Hello");
          System.out.println(" World");
      };
/*
Lambda 表达式也可以表示包含多个参数的方法
这行代码并不是将两个数字相加,而是创建了一个函数,用来计算 两个数字相加的结果。
变量add的类型是BinaryOperator<Long>,它不是两个数字的和,而是将两个数字相加的那行代码。
**/
BinaryOperator<Long> add = (x, y) -> x + y; 
/*
到目前为止,所有 Lambda 表达式中的参数类型都是由编译器推断得出的。这当然不错,
但有时最好也可以显式声明参数类型,
此时就需要使用小括号将参数括起来,多个参数的 情况也是如此
**/
BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;

**

引用值,而不是变量

匿名内部类中需将使用的变量声明为final变量,Java 8虽然放松了这一限制,可以引用非final变量,但是该变量在既成事实上必须是final。虽然无需将变量声明为final,但在Lambda表达式中,也无法用作非终态变量。如 果坚持用作非终态变量,编译器就会报错。(可以理解为隐式final)

函数式接口

jdk中提供的函数式接口

image.png
判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是 Stream, 那么是惰性求值;如果返回值是另一个值或为空,那么就是及早求值

常用流的操作

  1. collect(toList()) collect(toList()) 方法由 Stream 里的值生成一个列表,是一个及早求值操作。

  2. map 如果有一个函数可以将一种类型的值转换成另外一种类型,map 操作就可以 使用该函数,将一个流中的值转换成一个新的流。

  3. filter

该模式的核心思想是保留 Stream中的一些元素,而过滤掉其他的

 String s1 = Stream.of("a", "b", "c").filter("c"::equals).findFirst().orElse("");
  1. flatMap flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream
  /**
     * {"hello", "world"} -> {"H","e","l", "o","W","r","d"}
     *
     * <p>流的平行化 flatMap和Map
     */
    String[] words = {"hello", "world"};
    List<String[]> collect =
        Arrays.stream(words).map(w -> w.split("")).distinct().collect(Collectors.toList());

    List<Stream<String>> collect1 =
        Arrays.stream(words)
            .map(w -> w.split(""))
            .map(Arrays::stream)
            .distinct()
            .collect(Collectors.toList());

    List<String> collect2 =
        Arrays.stream(words)
            .map(w -> w.split(""))
            .flatMap(Arrays::stream)
            .distinct()
            .collect(Collectors.toList());
  1. max和min

集合中求最大函数和最小函数

   	//求集合最大值
	Integer maxInt = Stream.of(1, 2, 3).max(Integer::compareTo).orElse(0);
	//求集合最小值
    Integer minInt = Stream.of(1, 2, 3).min(Integer::compareTo).orElse(0);
  1. reduce

reduce 操作可以实现从一组值中生成一个值。 count、min 和 max 方 法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作

//写法1
int count = Stream.of(1, 2, 3).reduce(0, (acc, element) -> acc + element);
//写法二
int count = Stream.of(1, 2, 3).reduce(0, Integer::sum);
  1. Optional

是为核心类库新设计的一个数据类型,用来替换 null 值。 1.使用 Optional 对象目的:首先,Optional 对象鼓励程序员适时检查 变量是否为空,以避免代码缺陷;其次,它将一个类的 API 中可能为空的值文档化,这比 阅读实现代码要简单很多。还有一点尤其重要的是可以避免过多的if else语句。

 // Optional.ofNullable - 允许传递为 null 参数 
Optional<Integer> a = Optional.ofNullable(value1);
        
// Optional.of - 如果传递的参数是 null,抛出异常 NullPointerException
Optional<Integer> b = Optional.of(value2);

//传统if else写法
Apple apple = new Apple("red", 2.0d, "red");
String name = "";
if (apple != null) {
    name = apple.getName();
}

// 改进写法 optional获取name
//1.
Optional<String> s = Optional.ofNullable(apple).map(Apple::getName);
//2.这里添加了判断
String uknow = Optional.ofNullable(apple).map(Apple::getName).orElse("Uknow");


// 传统if-else 当然这里可以合并这些判断逻辑
if (person != null) {
    if (person.getCar() != null) {
        if (person.getCar().getInsurance() != null) {
            insurance = person.getCar().getInsurance().getName();
        }
    }
}
// optional 优化写法
String unknown = Optional.ofNullable(person).map(Person::getCar)
    .map(Car::getInsurance)
    .map(Insurance::getName)
    .orElse("Unknown");

收集器和字符串

这里不会涉及过多细节,仅以几段代码作为示例。 收集器在于处理集合,工作中使用场景很多。当然收集器也是可以自定义的。(Jdk提供的收集器已经够用。)

 /**
   * partitioningBy收集器 用于数据分块 返回所有红色汽车
   *
   * @param cars
   * @return
   */
  public Map<Boolean, List<Car>> redCars(Stream<Car> cars) {
    return cars.collect(Collectors.partitioningBy(c -> c.isRed));
  }

  /**
   * groupingBy收集器 用于将数据分组
   *
   * @param cars
   * @return
   */
  public Map<String, List<Car>> groupByBrand(Stream<Car> cars) {
    return cars.collect(Collectors.groupingBy(o -> o.brand));
  }

  /**
   * 字符串
   *
   * @param cars
   * @return
   */
  public String test(List<Car> cars) {
    return cars.stream().map(o -> o.brand).collect(Collectors.joining(", ", "[", "]"));
  }
  1. 注意细节
  2. forEach 方法不能保证元素是 按顺序处理的。如果需要保证按顺序处理,应该使用forEachOrdered 方法。

并行与串行

概念不再解释了。这里要注意的是 parallelStream其实就是一个并行执行的流.它通过默认的ForkJoinPool,可能提高你的多线程任务的速度.

1.如何使用并行流

stream -> parallelStream stream 单管道 parallelStream 多管道

//如果运行该代码 你会发现并行流输出的结果是没有没顺序的。
//当然你可能认为这是数据源为Set这种无序导致的,如若你改用List,看输出结果也是无序的。
//因为无序 在操作临界资源无法保证数据的安全性
Set<String> collect1 = Stream.of("a", "b", "c", "d", "e").collect(Collectors.toSet());
// 串行
collect1.stream().sequential().forEach(System.out::print);
// 并行
collect1.parallelStream().forEach(System.out::print);
  1. 数组的并行化操作

parallelPrefix 任意给定一个函数,计算数组的和 parallelSetAll 使用 Lambda 表达式更新数组元素       parallelSort 并行化对数组元素排序

选择并行的因素

  • 数据大小输入数据的大小会影响并行化处理对性能的提升。将问题分解之后并行化处理,再将结 果合并会带来额外的开销。因此只有数据足够大、每个数据处理管道花费的时间足够多 时,并行化处理才有意义。

  • 源数据结构每个管道的操作都基于一些初始数据源,通常是集合。将不同的数据源分割相对容易, 这里的开销影响了在管道中并行处理数据时到底能带来多少性能上的提升。

  • 装箱处理基本类型比处理装箱类型要快。

  • 核的数量极端情况下,只有一个核,因此完全没必要并行化。显然,拥有的核越多,获得潜在性 能提升的幅度就越大。在实践中,核的数量不单指你的机器上有多少核,更是指运行时 你的机器能使用多少核。这也就是说同时运行的其他进程,或者线程关联性(强制线程 在某些核或 CPU 上运行)会影响性能。

  • 单元处理开销比如数据大小,这是一场并行执行花费时间和分解合并操作开销之间的战争。花在流中每个元素身上的时间越长,并行操作带来的性能提升越明显。