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

简介: 十分钟学会Java8:lambda表达式和Stream API

Java8 的新特性:Lambda表达式、强大的 Stream API、全新时间日期 API、ConcurrentHashMap、MetaSpace。总得来说,Java8 的新特性使 Java 的运行速度更快、代码更少、便于并行、最大化减少空指针异常。


本篇博客将以笔者的一些心得帮助大家快速理解lambda表达式和Stream API.


image.png


1.引言


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


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


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

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


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


2.lambda表达式


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


先来看lambda表达式的语法:


() -> {}


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


  1. -> : 分割左右部分的,没啥好讲的。


  1. {} : 要实现的方法体。只有一行代码时,可以不加括号,可以不写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表达式还有两种简化代码的手段,它们是方法引用、构造引用。


方法引用是什么呢?如果我们要实现接口的方法与另一个方法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;


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


构造引用


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

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


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


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;

3.lambda总结


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


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


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


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


image.png



在我看来,学习lambda与学习Stream的联系就是因为在许多博客、文档中对Stream API的讲解大量使用lambda表达式,导致不学lambda表达式看不懂Stream API。


1.如何理解Stream


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


2.Stream特点


  1. Stream 是不会存储元素的。
  2. Stream 不会改变原对象,相反,他们会返回一个持有结果的新Stream。
  3. Stream 操作是延迟执行的。意味着它们会等到需要结果的时候才执行。


3.生成Stream的方式


//Collection系的 stream() 和 parallelStream();
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> stringStream = list.parallelStream();
//通过Arrays
Stream<String> stream1 = Arrays.stream(new String[10]);
//通过Stream
Stream<Integer> stream2 = Stream.of(1, 2, 3);
//无限流
//迭代
Stream<Integer> iterate = Stream.iterate(0, (x) -> x + 2);
iterate.limit(10).forEach(System.out::println);
//生成
Stream<Double> generate = Stream.generate(() -> Math.random());
generate.forEach(System.out::println);

4.Stream的中间操作


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


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遍历。其它的几个操作思想都差不多。


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


5.Stream的终止操作


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);
        //最大值
        Optional<Integer> integer = list.stream()
                .collect(Collectors.maxBy(Integer::compare));
        System.out.println(integer.get());


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

目录
相关文章
|
1月前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
1月前
|
安全 Java API
Java中的Lambda表达式:简化代码的现代魔法
在Java 8的发布中,Lambda表达式的引入无疑是一场编程范式的革命。它不仅让代码变得更加简洁,还使得函数式编程在Java中成为可能。本文将深入探讨Lambda表达式如何改变我们编写和维护Java代码的方式,以及它是如何提升我们编码效率的。
|
2月前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
33 0
|
Java Unix 数据库管理
java定时框架:表达式设置
Quartz中时间表达式的设置-----corn表达式 (注:这是让我看比较明白的一个博文,但是抱歉,没有找到原作者,如有侵犯,请告知)   时间格式: ,   分别对应: 秒>分>小时>日>月>周>年,  举例: 1.
879 0
|
8天前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
46 17
|
18天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
4天前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
20天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
20天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
21天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
43 3