Java8的lambda表达式和Stream API

Wesley13
• 阅读 565

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

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

02来自IDEA的提示

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

new Thread(new Runnable() {
    @Override
    public void run() {
          System.out.println("thread");
    }
});

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

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

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

03Lambda入门

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

先来看lambda表达式的语法:

() -> {}

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

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

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

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

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

Consumer<T>

void accept(T t);
供给型接口(无参有返回值)

Supplier<T>

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

Function<T, R>

R apply(T t);
断言型接口(有参有布尔返回值)

Predicate<T>

boolean test(T t);

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

04方法引用

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

举例:

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

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

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

方法引用的语法:

对象::实例方法类::静态方法类::实例方法

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

当出现如下这种情况时:

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

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

Compare<Boolean> c = String::equals;

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

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

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

05构造引用

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

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

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

Supplier<String> supplier = String::new;

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

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

Function1 function = (x) -> new String(x);
也可以简化为:

Function1 function = String::new;
特殊点的数组类型:

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

可以简化为:

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

06Lambda总结

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

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

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

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

07Stream理解

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

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

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

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

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

08Stream生成

Collection系的 stream() 和 parallelStream()

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

通过Arrays.stram()

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

通过Stream.of()

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

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

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

通过Stream.generate()

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

09Stream中间操作

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

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

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

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

10Stream的终止操作

常用的终止API如下:

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

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

Integer reduce = list.stream()
        .map(s -> (s + 1))
        .reduce(0, (x, y) -> x + y);    //归约:0为第一个参数x的默认值,x是计算后的返回值,y为每一项的值。
System.out.println(reduce);

Optional<Integer> reduce1 = list.stream()
        .map(s -> (s + 1))
        .reduce((x, y) -> x + y);  // x是计算后的返回值,默认为第一项的值,y为其后每一项的值。
System.out.println(reduce);

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

//转集合
Set<Integer> collect = list.stream()
    .collect(Collectors.toSet());

List<Integer> collect2 = list.stream()
    .collect(Collectors.toList());

HashSet<Integer> collect1 = list.stream()
    .collect(Collectors.toCollection(HashSet::new));

//分组 {group=[444, 555, 666, 777, 555]}
Map<String, List<Integer>> collect3 = list.stream()
    .collect(Collectors.groupingBy((x) -> "group"));//将返回值相同的进行分组
System.out.println(collect3);

//多级分组 {group={777=[777], 666=[666], 555=[555, 555], 444=[444]}}
Map<String, Map<Integer, List<Integer>>> collect4 = list.stream()
    .collect(Collectors.groupingBy((x) -> "group", Collectors.groupingBy((x) -> x)));
System.out.println(collect4);

//分区 {false=[444], true=[555, 666, 777, 555]}
Map<Boolean, List<Integer>> collect5 = list.stream()
    .collect(Collectors.partitioningBy((x) -> x > 500));
System.out.println(collect5);

//汇总
DoubleSummaryStatistics collect6 = list.stream()
    .collect(Collectors.summarizingDouble((x) -> x));
System.out.println(collect6.getMax());
System.out.println(collect6.getCount());

//拼接 444,555,666,777,555
String collect7 = list.stream()
    .map(s -> s.toString())
    .collect(Collectors.joining(","));
System.out.println(collect7);

java8 新特性 Stream流 分组 排序 过滤 多条件去重

public class Streams {
    
    private static List<User> list = new ArrayList<User>();
    
    public static void main(String[] args) {
        list =  Arrays.asList(
                new User(1, "a", 10),
                new User(4, "d", 19),
                new User(5, "e", 13),
                new User(2, "b", 14),
                new User(3, "a", 10),
                new User(6, "f", 16)
                );
        
        long start = System.currentTimeMillis();
        
        order();
        
        println(String.format("耗时[%s]毫秒", (System.currentTimeMillis() - start)));
        
    }
    
    /**
     * 多条件去重
     * @param list
     */
    public static void order() {
        list.stream().collect(Collectors.collectingAndThen(
                Collectors.toCollection(() -> new TreeSet<>(
                        Comparator.comparing(o -> o.getAge() + ";" + o.getName()))), ArrayList::new)).forEach(u -> println(u));
    }
    
    public static void group() {
        Map<Integer, List<User>> collect = list.stream().collect(Collectors.groupingBy(User::getAge));
        System.out.println(collect);
    }
    
    /**
     * filter过滤
     * @param list
     */
    public static void filterAge() {
        list.stream().filter(u -> u.getAge() == 10).forEach(u -> println(u));
    }
    
    /**
     * sorted排序
     */
    public static void stord() {
        list.stream().sorted(Comparator.comparing(u-> u.getAge())).forEach(u -> println(u));
        
    }
    /**
     * limit方法限制最多返回多少元素
     */
    public static void limit() {
        list.stream().limit(2).forEach(u -> println(u));
    }
    /**
     * 不要前多n个元素,n大于满足条件的元素个数就返回空的流
     */
    public static void skip() {
        list.stream().skip(2).forEach(u -> println(u));
    }
    // 最大值 最小值
    public static void statistics() {
        Optional<User> min = list.stream().min(Comparator.comparing(User::getUserId));
        println(min);
        Optional<User> max = list.stream().max(Comparator.comparing(User::getUserId));
        println(max);
    }
    
    // 统计
    public static void summarizingInt(){
        IntSummaryStatistics statistics = list.stream().collect(Collectors.summarizingInt(User::getAge));
        double average = statistics.getAverage();
        long count = statistics.getCount();
        int max = statistics.getMax();
        int min = statistics.getMin();
        long sum = statistics.getSum();
        println(average);
        println(count);
        println(min);
        println(sum);
        println(max);
        
    }
    /**
     * 转set
     */
    public static void toSet() {
        Set<User> collect = list.stream().collect(Collectors.toSet());
        Iterator<User> iterator = collect.iterator();
        while(iterator.hasNext()) {
            System.out.println(iterator.next().getUserId());
        }
    }

    /**
     * 转map
     */
    public static void toMap() {
        Map<Integer, User> collect = list.stream().collect(Collectors.toMap(User::getUserId, u -> u));
        for (Integer in : collect.keySet()) {
          User u = collect.get(in);//得到每个key多对用value的值
          println(u);
        }
    }
    /**
     *map
     */
    public static void map() {
        list.stream().map(User::getUserId).forEach(userId -> println(userId));
        list.stream().mapToInt(User::getAge).forEach(userId -> println(userId));
        list.stream().mapToDouble(User::getUserId).forEach(userId -> println(userId));
        list.stream().mapToLong(User::getUserId).forEach(userId -> println(userId));
    }
    
    /**
     * 查找与匹配
     * allMatch方法与anyMatch差不多,表示所有的元素都满足才返回true。noneMatch方法表示没有元素满足
     */
    public static void anyMatch() {
        boolean anyMatch = list.stream().anyMatch(u -> u.getAge() == 100);
        boolean allMatch = list.stream().allMatch(u -> u.getUserId() == 10);
        boolean noneMatch = list.stream().noneMatch(u -> u.getUserId() == 10);
        println(anyMatch);
        println(allMatch);
        println(noneMatch);
    }
    
    /**
     * reduce操作
     */
    public static void reduce() {
        Optional<Integer> sum = list.stream().map(User::getAge).reduce(Integer::sum);
        Optional<Integer> max = list.stream().map(User::getAge).reduce(Integer::max);
        Optional<Integer> min = list.stream().map(User::getAge).reduce(Integer::min);
        println(sum);
        println(max);
        println(min);
    }
    /**
     * 公共输出
     * @param c
     */
    public static void println(Object c) {
        System.out.println(c.toString());
    }
    

    static class User {
        private Integer userId;
        
        private String name;
        
        private Integer age;
        
        User(Integer userId, String name, Integer age) {
            super();
            this.userId = userId;
            this.name = name;
            this.age = age;
        }
        
        public Integer getUserId() {
            return userId;
        }

        public void setUserId(Integer userId) {
            this.userId = userId;
        }

        public String getName() {
            return this.name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }

        @Override
        public String toString() {
            return "[userId=" + userId + ", name=" + name + ", age=" + age + "]";
        }
        
    }
点赞
收藏
评论区
推荐文章
blmius blmius
2年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Jacquelyn38 Jacquelyn38
2年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
2年前
Java 8新特性之Stream 概念
Java8中有两大最为重要的改变。第一个是Lambda表达式;另外一个则是StreamAPI(java.util.stream.\)。Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用StreamAPI对集合数据进行操作,就类似于使用SQL执行
Easter79 Easter79
2年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
2年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
2年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
2年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
2年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
2个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这