【杭州研发中心-后端二团队】Java8新特性之Lambda表达式和Steam流

简介: 【杭州研发中心-后端二团队】Java8新特性之Lambda表达式和Steam流

Lambda 表达式

众所周知的 Lambda 表达式。它是推动 Java 8 发布的最重要新特性。是继泛型(Generics)和注解(Annotation)以来最大的变化。

使用 Lambda 表达式可以使代码变的更加简洁紧凑。让 java 也能支持简单的函数式编程

Lambda 表达式是一个匿名函数,java 8 允许把函数作为参数传递进方法中。

语法格式

(parameters) -> expression 或
(parameters) ->{ statements; }Copy to clipboardErrorCopied

Lambda 实战

我们用常用的实例来感受 Lambda 带来的便利

替代匿名内部类

过去给方法传动态参数的唯一方法是使用内部类。比如

1.Runnable 接口

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("The runable now is using!");
            }
}).start();
//用lambda
new Thread(() -> System.out.println("It's a lambda function!")).start();Copy to clipboardErrorCopied

2.Comparator 接口

List<Integer> strings = Arrays.asList(1, 2, 3);
Collections.sort(strings, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
    return o1 - o2;}
});
//Lambda
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - o2);
//分解开
Comparator<Integer> comperator = (Integer o1, Integer o2) -> o1 - o2;
Collections.sort(strings, comperator);Copy to clipboardErrorCopied

3.Listener 接口

JButton button = new JButton();
button.addItemListener(new ItemListener() {
@Override
public void itemStateChanged(ItemEvent e) {
   e.getItem();
}
});
//lambda
button.addItemListener(e -> e.getItem());Copy to clipboardErrorCopied

4.自定义接口

上面的 3 个例子是我们在开发过程中最常见的,从中也能体会到 Lambda 带来的便捷与清爽。它只保留实际用到的代码,把无用代码全部省略。那它对接口有没有要求呢?我们发现这些匿名内部类只重写了接口的一个方法,当然也只有一个方法须要重写。这就是我们上文提到的函数式接口,也就是说只要方法的参数是函数式接口都可以用 Lambda 表达式。

@FunctionalInterface
public interface Comparator<T>{}
@FunctionalInterface
public interface Runnable{}Copy to clipboardErrorCopied

我们自定义一个函数式接口

@FunctionalInterface
public interface LambdaInterface {
 void f();
}
//使用
public class LambdaClass {
    public static void forEg() {
        lambdaInterfaceDemo(()-> System.out.println("自定义函数式接口"));
    }
    //函数式接口参数
    static void lambdaInterfaceDemo(LambdaInterface i){
        System.out.println(i);
    }
}Copy to clipboardErrorCopied

集合迭代

void lamndaFor() {
        List<String> strings = Arrays.asList("1", "2", "3");
        //传统foreach
        for (String s : strings) {
            System.out.println(s);
        }
        //Lambda foreach
        strings.forEach((s) -> System.out.println(s));
        //or
        strings.forEach(System.out::println);
                 //map
        Map<Integer, String> map = new HashMap<>();
        map.forEach((k,v)->System.out.println(v));
}Copy to clipboardErrorCopied

方法的引用

Java 8 允许使用 :: 关键字来传递方法或者构造函数引用,无论如何,表达式返回的类型必须是 functional-interface。

public class LambdaClassSuper {
    LambdaInterface sf(){
        return null;
    }
}
public class LambdaClass extends LambdaClassSuper {
    public static LambdaInterface staticF() {
        return null;
    }
    public LambdaInterface f() {
        return null;
    }
    void show() {
        //1.调用静态函数,返回类型必须是functional-interface
        LambdaInterface t = LambdaClass::staticF;
        //2.实例方法调用
        LambdaClass lambdaClass = new LambdaClass();
        LambdaInterface lambdaInterface = lambdaClass::f;
        //3.超类上的方法调用
        LambdaInterface superf = super::sf;
        //4. 构造方法调用
        LambdaInterface tt = LambdaClassSuper::new;
    }
}Copy to clipboardErrorCopied

访问变量

int i = 0;
Collections.sort(strings, (Integer o1, Integer o2) -> o1 - i);
//i =3;Copy to clipboardErrorCopied

lambda 表达式可以引用外边变量,但是该变量默认拥有 final 属性,不能被修改,如果修改,编译时就报错。

Stream

java 新增了 java.util.stream 包,它和之前的流大同小异。之前接触最多的是资源流,比如java.io.FileInputStream,通过流把文件从一个地方输入到另一个地方,它只是内容搬运工,对文件内容不做任何CRUD

Stream依然不存储数据,不同的是它可以检索(Retrieve)和逻辑处理集合数据、包括筛选、排序、统计、计数等。可以想象成是 Sql 语句。

它的源数据可以是 CollectionArray 等。由于它的方法参数都是函数式接口类型,所以一般和 Lambda 配合使用。

流类型

  1. stream 串行流
  2. parallelStream 并行流,可多线程执行

常用方法

接下来我们看java.util.stream.Stream常用方法

/**
* 返回一个串行流
*/
default Stream<E> stream()
/**
* 返回一个并行流
*/
default Stream<E> parallelStream()
/**
* 返回T的流
*/
public static<T> Stream<T> of(T t)
/**
* 返回其元素是指定值的顺序流。
*/
public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
}
/**
* 过滤,返回由与给定predicate匹配的该流的元素组成的流
*/
Stream<T> filter(Predicate<? super T> predicate);
/**
* 此流的所有元素是否与提供的predicate匹配。
*/
boolean allMatch(Predicate<? super T> predicate)
/**
* 此流任意元素是否有与提供的predicate匹配。
*/
boolean anyMatch(Predicate<? super T> predicate);
/**
* 返回一个 Stream的构建器。
*/
public static<T> Builder<T> builder();
/**
* 使用 Collector对此流的元素进行归纳
*/
<R, A> R collect(Collector<? super T, A, R> collector);
/**
 * 返回此流中的元素数。
*/
long count();
/**
* 返回由该流的不同元素(根据 Object.equals(Object) )组成的流。
*/
Stream<T> distinct();
/**
 * 遍历
*/
void forEach(Consumer<? super T> action);
/**
* 用于获取指定数量的流,截短长度不能超过 maxSize 。
*/
Stream<T> limit(long maxSize);
/**
* 用于映射每个元素到对应的结果
*/
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
/**
* 根据提供的 Comparator进行排序。
*/
Stream<T> sorted(Comparator<? super T> comparator);
/**
* 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。
*/
Stream<T> skip(long n);
/**
* 返回一个包含此流的元素的数组。
*/
Object[] toArray();
/**
* 使用提供的 generator函数返回一个包含此流的元素的数组,以分配返回的数组,以及分区执行或调整大小可能需要的任何其他数组。
*/
<A> A[] toArray(IntFunction<A[]> generator);
/**
* 合并流
*/
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)Copy to clipboardErrorCopied

实战

本文列出 Stream 具有代表性的方法之使用,更多的使用方法还是要看 Api。

@Test
public void test() {
  List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
    //返回符合条件的stream
    Stream<String> stringStream = strings.stream().filter(s -> "abc".equals(s));
    //计算流符合条件的流的数量
    long count = stringStream.count();
    //forEach遍历->打印元素
    strings.stream().forEach(System.out::println);
    //limit 获取到1个元素的stream
    Stream<String> limit = strings.stream().limit(1);
    //toArray 比如我们想看这个limitStream里面是什么,比如转换成String[],比如循环
    String[] array = limit.toArray(String[]::new);
    //map 对每个元素进行操作返回新流
    Stream<String> map = strings.stream().map(s -> s + "22");
    //sorted 排序并打印
    strings.stream().sorted().forEach(System.out::println);
    //Collectors collect 把abc放入容器中
    List<String> collect = strings.stream().filter(string -> "abc".equals(string)).collect(Collectors.toList());
    //把list转为string,各元素用,号隔开
    String mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(","));
    //对数组的统计,比如用
    List<Integer> number = Arrays.asList(1, 2, 5, 4);
    IntSummaryStatistics statistics = number.stream().mapToInt((x) -> x).summaryStatistics();
    System.out.println("列表中最大的数 : "+statistics.getMax());
    System.out.println("列表中最小的数 : "+statistics.getMin());
    System.out.println("平均数 : "+statistics.getAverage());
    System.out.println("所有数之和 : "+statistics.getSum());
    //concat 合并流
    List<String> strings2 = Arrays.asList("xyz", "jqx");
    Stream.concat(strings2.stream(),strings.stream()).count();
    //注意 一个Stream只能操作一次,不能断开,否则会报错。
    Stream stream = strings.stream();
    //第一次使用
    stream.limit(2);
    //第二次使用
    stream.forEach(System.out::println);
    //报错 java.lang.IllegalStateException: stream has already been operated upon or closed
    //但是可以这样, 连续使用
    stream.limit(2).forEach(System.out::println);
}Copy to clipboardErrorCopied

延迟执行

在执行返回 Stream 的方法时,并不立刻执行,而是等返回一个非 Stream 的方法后才执行。因为拿到 Stream 并不能直接用,而是需要处理成一个常规类型。这里的 Stream 可以想象成是二进制流(2 个完全不一样的东东),拿到也看不懂。

我们下面分解一下 filter 方法。

@Test
public void laziness(){
  List<String> strings = Arrays.asList("abc", "def", "gkh", "abc");
  Stream<Integer> stream = strings.stream().filter(new Predicate() {
      @Override
      public boolean test(Object o) {
        System.out.println("Predicate.test 执行");
        return true;
        }
      });
   System.out.println("count 执行");
   stream.count();
}
/*-------执行结果--------*/
count 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行
Predicate.test 执行Copy to clipboardErrorCopied

按执行顺序应该是先打印 4 次「Predicate.test 执行」,再打印「count 执行」。实际结果恰恰相反。说明 filter 中的方法并没有立刻执行,而是等调用count()方法后才执行。

上面都是串行 Stream 的实例。并行 parallelStream 在使用方法上和串行一样。主要区别是 parallelStream 可多线程执行,是基于 ForkJoin 框架实现的,有时间大家可以了解一下 ForkJoin 框架和 ForkJoinPool。这里可以简单的理解它是通过线程池来实现的,这样就会涉及到线程安全,线程消耗等问题。下面我们通过代码来体验一下并行流的多线程执行。

@Test
public void parallelStreamTest(){
   List<Integer> numbers = Arrays.asList(1, 2, 5, 4);
   numbers.parallelStream() .forEach(num->System.out.println(Thread.currentThread().getName()+">>"+num));
}
//执行结果
main>>5
ForkJoinPool.commonPool-worker-2>>4
ForkJoinPool.commonPool-worker-11>>1
ForkJoinPool.commonPool-worker-9>>2Copy to clipboardErrorCopied

从结果中我们看到,for-each 用到的是多线程。

小结

从源码和实例中我们可以总结出一些 stream 的特点

  1. 通过简单的链式编程,使得它可以方便地对遍历处理后的数据进行再处理。
  2. 方法参数都是函数式接口类型
  3. 一个 Stream 只能操作一次,操作完就关闭了,继续使用这个 stream 会报错。
  4. Stream 不保存数据,不改变数据源
相关文章
|
2天前
|
安全 Java API
Java 18 概述:新特性一览
Java 18 作为 Java 平台的最新版本,引入了多项令人振奋的新特性和改进,包括模式匹配、记录类型、流库改进、外部函数与内存 API 以及并发处理增强。这些新功能不仅提升了开发者的生产力,还显著增强了 Java 的性能和安全性。本文将详细介绍 Java 18 的主要新特性,并通过代码示例帮助读者更好地理解和应用这些功能。
|
5天前
|
存储 Java
探索Java中的Lambda表达式
【9月更文挑战第6天】Lambda表达式是Java 8引入的一个强大特性,它允许我们将函数作为参数传递或作为返回值。在这篇文章中,我们将深入探讨Lambda表达式的概念、语法和用法,以及如何在实际项目中应用它们来简化代码。通过学习本文,你将能够更好地理解Lambda表达式的作用,并掌握如何在Java中使用它们。
|
3天前
|
并行计算 Java 开发者
探索Java中的Lambda表达式:简化代码,提升效率
Lambda表达式在Java 8中引入,旨在简化集合操作和并行计算。本文将通过浅显易懂的语言,带你了解Lambda表达式的基本概念、语法结构,并通过实例展示如何在Java项目中应用Lambda表达式来优化代码,提高开发效率。我们将一起探讨这一现代编程工具如何改变我们的Java编码方式,并思考它对程序设计哲学的影响。
|
9天前
|
安全 前端开发 Java
浅析JVM invokedynamic指令与Java Lambda语法的深度融合
在Java的演进历程中,Lambda表达式无疑是Java 8引入的一项革命性特性,它极大地简化了函数式编程在Java中的应用,使得代码更加简洁、易于阅读和维护。而这一切的背后,JVM的invokedynamic指令功不可没。本文将深入探讨invokedynamic指令的工作原理及其与Java Lambda语法的紧密联系,带您领略这一技术背后的奥秘。
9 1
|
12天前
|
Java 编译器 开发者
Java中的Lambda表达式与函数式接口
【8月更文挑战第31天】本文将深入探讨Java 8中引入的Lambda表达式和函数式接口,它们如何改变我们编写代码的方式。通过简化集合操作、事件处理等示例,我们将看到这些特性如何提升代码可读性、减少冗余,并提高开发效率。准备好一起探索这个让Java编程更加简洁强大的新世界吧!
|
12天前
|
Java
Java中的Lambda表达式:简化代码,提升效率
【8月更文挑战第31天】Lambda表达式在Java 8中引入,旨在使代码更加简洁和易读。本文将探讨Lambda表达式的基本概念、使用场景及如何通过Lambda表达式优化Java代码。我们将通过实际示例来展示Lambda表达式的用法和优势,帮助读者更好地理解和应用这一特性。
|
11天前
|
Java 开发者
探索Java中的Lambda表达式:简化你的代码
【8月更文挑战第31天】 在Java 8的发布中,Lambda表达式无疑是最令人兴奋的新特性之一。它不仅为Java开发者提供了一种更加简洁、灵活的编程方式,而且还极大地提高了代码的可读性和开发效率。本文将通过实际代码示例,展示如何利用Lambda表达式优化和重构Java代码,让你的编程之旅更加轻松愉快。
|
11天前
|
Java 开发者
探索Java中的Lambda表达式:简化代码的现代方法
【8月更文挑战第31天】Lambda表达式在Java 8中首次亮相,为Java开发者提供了一种更简洁、灵活的编程方式。它不仅减少了代码量,还提升了代码的可读性和可维护性。本文将通过实际示例,展示Lambda表达式如何简化集合操作和事件处理,同时探讨其对函数式编程范式的支持。
|
11天前
|
Java
探索Java中的Lambda表达式
【8月更文挑战第31天】在Java的世界中,Lambda表达式是一股清新之风,它简化了代码的编写方式,让函数式编程成为可能。本文将带你领略Lambda表达式的魅力,通过实例演示其在Java中的应用,让你的代码更加简洁、高效。
|
12天前
|
Java
深入理解Java中的Lambda表达式
【8月更文挑战第31天】本文将带你领略Java中Lambda表达式的风采,从它的定义、用法到背后的原理,我们一步步揭开它的神秘面纱。通过实例演示,你将学会如何在实际编程中运用Lambda表达式,提高代码的简洁性和可读性。准备好,让我们开始这段探索之旅!