01:前言
一直在用JDK8 ,却从未用过Stream,为了对数组或集合进行一些排序、过滤或数据处理,只会写for循环或者foreach,这就是我曾经的一个写照。

刚开始写写是打基础,但写的多了,各种乏味,非过来人不能感同身受。今天,我就要分享一篇如何解决上述问题的新方法 - Stream API。但学习Stream之前却不得不学一下Lambda表达式。说实话,网上介绍Lambda表达式的文章很多,大多晦涩难懂,今天我就想用自己的理解去说一下Lambda表达式是如何让我们的代码写的更少!

02:来自IDEA的提示
在IDE中,你是否遇到在写以下列代码时,被友情提示的情况:

  1. new Thread(new Runnable() {
  2. @Override
  3. public void run() {
  4. System.out.println("thread");
  5. }
  6. });

这时候,我们按一下快捷键,IDE自动帮我们把代码优化为酱个样子:

  1. new Thread(() -> System.out.println("thread"));

这就是Java8的新特性:lambda表达式。

03:Lambda入门

借用上面的示例,在调用new Thread的含参构造方法时,我们通过匿名内部类的方式实现了Runnable对象,但其实有用的代码只有System.out.println("thread")这一句,而我们却要为了这一句去写这么多行代码。正是这个问题,才有了Java8中的lambda表达式。那lambd表达式究竟是如何简化代码的呢?

先来看lambda表达式的语法:

  1. () -> {}

(): 括号就是接口方法的括号,接口方法如果有参数,也需要写参数。只有一个参数时,括号可以省略。

->: 分割左右部分的,没啥好说的。

{} : 要实现的方法体。只有一行代码时,可以不加括号,可以不写return。

看了上面的解释,也就不难理解IDE优化后的代码了。不过看到这里你也许意识到,如果接口中有多个方法时,按照上面的逻辑lambda表达式恐怕不行了。没错,lambda表达式只适用于函数型接口。说白了,函数型接口就是只有一个抽象方法的接口。这种类型的接口还有一个对应的注解:@FunctionalInterface。为了让我们在需要这种接口时不再自己去创建,Java8中内置了四大核心函数型接口。

消费型接口(有参无返回值)

  1. Consumer<T>
  2.  
  3. void accept(T t);

供给型接口(无参有返回值)

  1. Supplier<T>
  2.  
  3. T get();

函数型接口(有参有返回值)

  1. Function<T, R>
  2.  
  3. R apply(T t);

断言型接口(有参有布尔返回值)

  1. Predicate<T>
  2.  
  3. boolean test(T t);

看到这里如果遇到一般的lambda表达式,你应该可以从容面对了,但高级点的恐怕看到还是懵,不要急,其实也不难。Lambda表达式还有两种简化代码的手段,分别是方法引用构造引用

04:方法引用

方法引用是什么呢?如果我们要实现接口的方法与另一个方法A类似,(这里的类似是指参数类型与返回值部分相同),我们直接声明A方法即可。也就是,不再使用lambda表达式的标准形式,改用高级形式。无论是标准形式还是高级形式,都是lambda表达式的一种表现形式。

举例:

  1. Function function1 = (x) -> x;
  2. Function function2 = String::valueOf;

对比Function接口的抽象方法与String的value方法,可以看到它们是类似的。

  1. R apply(T t);
  2.  
  3. public static String valueOf(Object obj) {
  4. return (obj == null) ? "null" : obj.toString();
  5. }

方法引用的语法:

  1. 对象::实例方法
  2. 类::静态方法
  3. 类::实例方法

前两个很容易理解,相当于对象调用实例方法,类调用静态方法一样。只是第三个需要特殊说明。

当出现如下这种情况时:

  1. Compare<Boolean> c = (a, b) -> a.equals(b);

用lambda表达式实现Compare接口的抽象方法,并且方法体只有一行,且该行代码为参数1调用方法传入参数2。此时,就可以简化为下面这种形式:

  1. Compare<Boolean> c = String::equals;

也就是“类::实例方法”的形式。

值得一提的是,当参数b不存在时,该方式依旧适用。例如:

  1. Function function1 = (x) -> x.toString();
  2. Function function1 = Object::toString;

05:构造引用

先来创建一个供给型接口对象:

  1. Supplier<String> supplier = () -> new String();

在这个lammbda表达式中只做了一件事,就是返回一个新的String对象,而这种形式可以更简化:

  1. Supplier<String> supplier = String::new;

提炼一下构造引用的语法:

  1. 类名::new

当通过含参构造方法创建对象,并且参数列表与抽象方法的参数列表一致,也就是下面的这种形式:

  1. Function1 function = (x) -> new String(x);

也可以简化为:

  1. Function1 function = String::new;

特殊点的数组类型:

  1. Function<Integer,String[]> function = (x) -> new String[x];

可以简化为:

  1. Function<Integer,String[]> function = String[]::new;

06:Lambda总结

上面并没有给出太多的lambda实例,只是侧重讲了如何去理解lambda表达式。到这里,不要懵。要记住lambda的本质:为函数型接口的匿名实现进行简化与更简化。

所谓的简化就是lambda的标准形式,所谓的更简化是在标准形式的基础上进行方法引用和构造引用。

方法引用是拿已有的方法去实现此刻的接口。

构造引用是对方法体只有一句new Object()的进一步简化。

07:Stream理解

如何理解Stream?在我看来,Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。简单来说,它的作用就是通过一系列操作将数据源(集合、数组)转化为想要的结果。

Stream有三点非常重要的特性:

  1. Stream 是不会存储元素的。

  2. Stream 不会改变原对象,相反,他们会返回一个持有结果的新Stream。

  3. Stream 操作是延迟执行的。意味着它们会等到需要结果的时候才执行。

08:Stream生成

Collection系的 stream() 和 parallelStream()

  1. List<String> list = new ArrayList<>();
  2. Stream<String> stream = list.stream();
  3. Stream<String> stringStream = list.parallelStream();

通过Arrays.stram()

  1. Stream<String> stream1 = Arrays.stream(new String[10]);

通过Stream.of()

  1. Stream<Integer> stream2 = Stream.of(1, 2, 3);

通过Stream.iterate()生成无限流

  1. Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
  2. iterate.limit(10).forEach(System.out::println);

通过Stream.generate()

  1. Stream<Double> generate = Stream.generate(() -> Math.random());generate.forEach(System.out::println);

09:Stream中间操作

多个中间操作连接而成为流水线,流水线不遇到终止操作是不触发任何处理的,所为又称为“惰性求值”。

  1. list.stream()
  2. .map(s -> s + 1) //映射
  3. .flatMap(s -> Stream.of(s)) //和map差不多,但返回类型为Stream,类似list.add()和list.addAll()的区别
  4. .filter(s -> s < 1000) //过滤
  5. .limit(5) //限制
  6. .skip(1) //跳过
  7. .distinct() //去重
  8. .sorted() //自然排序
  9. .sorted(Integer::compareTo) //自定义排序

关于map方法,参数为一个Function函数型接口的对象,也就是传入一个参数返回一个对象。这个参数就是集合中的每一项。类似Iterator遍历。其它的几个操作思想都差不多。

执行上面的方法没什么用,因为缺少终止操作。

10:Stream的终止操作

常用的终止API如下:

  1. list.stream().allMatch((x) -> x == 555); // 检查是否匹配所有元素
  2. list.stream().anyMatch(((x) -> x>600)); // 检查是否至少匹配一个元素
  3. list.stream().noneMatch((x) -> x>500); //检查是否没有匹配所有元素
  4. list.stream().findFirst(); // 返回第一个元素
  5. list.stream().findAny(); // 返回当前流中的任意一个元素
  6. list.stream().count(); // 返回流中元素的总个数
  7. list.stream().forEach(System.out::println); //内部迭代
  8. list.stream().max(Integer::compareTo); // 返回流中最大值
  9. Optional<Integer> min = list.stream().min(Integer::compareTo);//返回流中最小值
  10. System.out.println("min "+min.get());

reduce (归约):将流中元素反复结合起来得到一个值

  1. Integer reduce = list.stream()
  2. .map(s -> (s + 1))
  3. .reduce(0, (x, y) -> x + y); //归约:0为第一个参数x的默认值,x是计算后的返回值,y为每一项的值。
  4. System.out.println(reduce);
  5.  
  6. Optional<Integer> reduce1 = list.stream()
  7. .map(s -> (s + 1))
  8. .reduce((x, y) -> x + y); // x是计算后的返回值,默认为第一项的值,y为其后每一项的值。
  9. System.out.println(reduce);

collect(收集):将流转换为其他形式。需要Collectors类的一些方法。

  1. //转集合
  2. Set<Integer> collect = list.stream()
  3. .collect(Collectors.toSet());
  4.  
  5. List<Integer> collect2 = list.stream()
  6. .collect(Collectors.toList());
  7.  
  8. HashSet<Integer> collect1 = list.stream()
  9. .collect(Collectors.toCollection(HashSet::new));
  10.  
  11. //分组 {group=[444, 555, 666, 777, 555]}
  12. Map<String, List<Integer>> collect3 = list.stream()
  13. .collect(Collectors.groupingBy((x) -> "group"));//将返回值相同的进行分组
  14. System.out.println(collect3);
  15.  
  16. //多级分组 {group={777=[777], 666=[666], 555=[555, 555], 444=[444]}}
  17. Map<String, Map<Integer, List<Integer>>> collect4 = list.stream()
  18. .collect(Collectors.groupingBy((x) -> "group", Collectors.groupingBy((x) -> x)));
  19. System.out.println(collect4);
  20.  
  21. //分区 {false=[444], true=[555, 666, 777, 555]}
  22. Map<Boolean, List<Integer>> collect5 = list.stream()
  23. .collect(Collectors.partitioningBy((x) -> x > 500));
  24. System.out.println(collect5);
  25.  
  26. //汇总
  27. DoubleSummaryStatistics collect6 = list.stream()
  28. .collect(Collectors.summarizingDouble((x) -> x));
  29. System.out.println(collect6.getMax());
  30. System.out.println(collect6.getCount());
  31.  
  32. //拼接 444,555,666,777,555
  33. String collect7 = list.stream()
  34. .map(s -> s.toString())
  35. .collect(Collectors.joining(","));
  36. System.out.println(collect7);
  37.  
  38. //最大值
  39. Optional<Integer> integer = list.stream()
  40. .collect(Collectors.maxBy(Integer::compare));
  41. System.out.println(integer.get());

至于Stream的其它用法推荐参考下源码与API文档。

十分钟学会Java8的lambda表达式和Stream API的更多相关文章

  1. 十分钟学会Java8:lambda表达式和Stream API

    Java8 的新特性:Lambda表达式.强大的 Stream API.全新时间日期 API.ConcurrentHashMap.MetaSpace.总得来说,Java8 的新特性使 Java 的运行 ...

  2. java8中Lambda表达式和Stream API

    一.Lambda表达式 1.语法格式 Lambda是匿名函数,可以传递代码.使用“->”操作符,改操作符将lambda分成两部分: 左侧:指定了 Lambda 表达式需要的所有参数 右侧:指定了 ...

  3. Java8的lambda表达式和Stream API

    一直在用JDK8 ,却从未用过Stream,为了对数组或集合进行一些排序.过滤或数据处理,只会写for循环或者foreach,这就是我曾经的一个写照. 刚开始写写是打基础,但写的多了,各种乏味,非过来 ...

  4. 30分钟入门Java8之lambda表达式

    前言 Google在今年发布Android N开发者预览版,一并宣布开始支持Java 8.我们终于能在Android开发中使用到Java8的一些语言特性了.目前支持: 默认方法 lambda表达式 多 ...

  5. java8新特性-lambda表达式和stream API的简单使用

    一.为什么使用lambda Lambda 是一个 匿名函数,我们可以把 Lambda表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递).可以写出更简洁.更灵活的代码.作为一种更紧凑的代码风 ...

  6. JDK1.8新特性(一) ----Lambda表达式、Stream API、函数式接口、方法引用

    jdk1.8新特性知识点: Lambda表达式 Stream API 函数式接口 方法引用和构造器调用 接口中的默认方法和静态方法 新时间日期API default   Lambda表达式     L ...

  7. Java8中Lambda表达式的10个例子

    Java8中Lambda表达式的10个例子 例1 用Lambda表达式实现Runnable接口 //Before Java 8: new Thread(new Runnable() { @Overri ...

  8. JDK1.8中的Lambda表达式和Stream

    1.lambda表达式 Java8最值得学习的特性就是Lambda表达式和Stream API,如果有python或者javascript的语言基础,对理解Lambda表达式有很大帮助,因为Java正 ...

  9. PHP学习过程_Symfony_(3)_整理_十分钟学会Symfony

    这篇文章主要介绍了Symfony学习十分钟入门教程,详细介绍了Symfony的安装配置,项目初始化,建立Bundle,设计实体,添加约束,增删改查等基本操作技巧,需要的朋友可以参考下 (此文章已被多人 ...

随机推荐

  1. java之Hibernate框架实现数据库操作

    之前我们用一个java类连接MySQL数据库实现了数据库的增删改查操作---------MySQL篇: 但是数据库种类之多,除了MySQL,还有Access.Oracle.DB2等等,而且每种数据库语 ...

  2. Python 内置的一些高效率函数用法

    1.  filter(function,sequence) 将sequence中的每个元素,依次传进function函数(可以自定义,返回的结果是True或者False)筛选,返回符合条件的元素,重组 ...

  3. arcEngine开发之查询的相关接口

    属性查询 IQueryDef 首先这个接口不能直接创建,可以由 IFeatureWorkspace 接口的CreateQueryDef创建. 这个接口有两个属性必须设置(帮助文档是这样说明的,但是实际 ...

  4. 使用FluentScheduler和IIS预加载在asp.net中实现定时任务管理

    FluentScheduler介绍 github地址:https://github.com/fluentscheduler/FluentScheduler FluentScheduler是一个简单的任 ...

  5. Python中’__main__’模块的作用

    Python不同于C/C++,程序执行并不需要主程序,如main(),而是文件自上而下的执行.但很多Python程序中都有 if __name__ == '__main__': statements ...

  6. SSM-SpringMVC-02:SpringMVC最简单的小案例

     ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 咱们这个案例做什么? 就是用处理器代替Servlet处理请求 开发步骤: 1.引入jar包 <!--单 ...

  7. php-msf 源码解读【转】

    php-msf: https://github.com/pinguo/php-msf 百度脑图 - php-msf 源码解读: http://naotu.baidu.com/file/cc7b5a49 ...

  8. Linux时间子系统之二:表示时间的单位和结构

    人们习惯用于表示时间的方法是:年.月.日.时.分.秒.毫秒.星期等等,但是在内核中,为了软件逻辑和代码的方便性,它使用了一些不同的时间表示方法,并为这些表示方法定义了相应的变量和数据结构,本节的内容就 ...

  9. OAuth 2 开发人员指南(Spring security oauth2)

    https://github.com/spring-projects/spring-security-oauth/blob/master/docs/oauth2.md 入门 这是支持OAuth2.0的 ...

  10. AJAX from S3 CORS fails on preflight OPTIONS with 403

    解决办法: 将 <!-- Sample policy --> <CORSConfiguration> <CORSRule> <AllowedOrigin> ...