目 录CONTENT

文章目录

Lambda表达式和Stream流

lionkliu
2022-08-31 / 0 评论 / 1 点赞 / 20 阅读 / 5,554 字

1. 函数式编程

函数式编程语言操纵代码片段就像操作数据一样容易。虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程

1.0 函数式接口

定义:接口中有且只有一个抽象方法,便称为函数式接口

@FunctionalInterface 
public interface MyFunctionalInterface { 
    void myMethod(); 
}
复制代码

@FunctionalInterface:用于一个接口的定义上,一旦使用该注解定义接口,则编译器将会强制检查该接口是否确实有且只有一个抽象方法,否则报错。

1.1 Lambda表达式

Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。

特征

  • 可选类型声明:可以不声明参数类型,编译器将统一识别参数值
  • 可选参数圆括号():一个参数可以不定义圆括号,但没有参数或多个参数需要定义圆括号
  • 可选大括号{}:若 Lambda 体只有一个语句,可以不写 {}
  • 可选返回关键字 return:若 Lambda 体只有一个表达式返回值,则可以省略 return 和 {}
(params) -> expression
或
(params) ->{ statements; }
//参数 -> { 方法体 }
//1. 参数。
//2. 接着` ->`,可视为“产出”。
//3. `->` 之后的内容都是方法体。

例:

// 1. 不需要参数,返回值为 5  
() -> 5  
//相当于 () -> return 5    
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

常见函数式接口

位于java.util.function包下

image-20220822143913777

1.2 方法引用

所谓方法引用,是指如果某个方法签名和接口恰好一致,就可以直接传入方法引用。

定义:"::"引用运算符,包含引用运算符的表达式称为方法引用

语法

  • 对象引用::实例方法名 System.out::println
  • 类名::静态方法名 Math::random
  • 类名::实例方法名 Product::getName
  • 类名::new Product::new

2. 流式编程

流是Java8引入的全新概念,它用来处理集合中的数据,暂且可以把它理解为一种高级集合。

众所周知,集合操作非常麻烦,若要对集合进行筛选、投影,需要写大量的代码,而流是以声明的形式操作集合,它就像SQL语句,我们只需告诉流需要对集合进行什么操作,它就会自动进行操作,并将执行结果交给你,无需我们自己手写代码。

因此,流的集合操作对我们来说是透明的,我们只需向流下达命令,它就会自动把我们想要的结果给我们。由于操作过程完全由Java处理,因此它可以根据当前硬件环境选择最优的方法处理,我们也无需编写复杂又容易出错的多线程代码了。

原文链接:https://blog.csdn.net/u010425776/article/details/52344425

Stream 流式编程的特点

  1. 只能遍历一次

    我们可以把流想象成一条流水线,流水线的源头是我们的数据源(一个集合),数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。

  2. 采用内部迭代方式

    若要对集合进行处理,则需我们手写处理代码,这就叫做外部迭代。而要对流进行处理,我们只需告诉流我们需要什么结果,处理过程由流自行完成,这就称为内部迭代。

  3. 支持函数式编程和链式操作

Stream流操作流程

  1. 准备一个数据源
  2. 执行中间操作(Intermediate Operations):中间操作会返回一个新的流,一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而是在终端操作开始的时候才真正开始执行。
  3. 执行终端操作(Terminal Operations):是指返回最终的结果。一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

2.1 创建Stream

要使用Stream,就必须先创建Stream对象,创建Stream对象有以下几种方式。

(1) 直接将几个值变成流对象 – Stream.of()

Stream<String> stream = Stream.of("chaimm","peter","john");

(2) 通过集合创建 – collection.stream()

List<Person> list = new ArrayList<Person>(); 
Stream<Person> stream = list.stream();

(3) 通过数组创建 – Arrays.stream(arr)

通过Arrays类提供的静态函数stream()获取数组的流对象:

String[] names = {"chaimm","peter","john"};
Stream<String> stream = Arrays.stream(names);

(4) 通过文件创建流

Java7中引入了处理文件I/O操作的NIO API(非阻塞I/o),便于使用Stream API。

java.nio.file.Files中的很多静态方法都会返回一个流。如, Files.lines()方法

try(Stream lines = Files.lines(Paths.get(“文件路径名”),Charset.defaultCharset())){
//可对lines做一些操作
}catch(IOException e){
    
}

(5) 基于Supplier

创建Stream还可以通过Stream.generate()方法,它需要传入一个Supplier对象:

Stream<String> s = Stream.generate(Supplier<String> sp);

基于Supplier创建的Stream会不断调用Supplier.get()方法来不断产生下一个元素,这种Stream保存的不是元素,而是算法,它可以用来表示无限序列。

例如,我们编写一个能不断生成自然数的Supplier,它的代码非常简单,每次调用get()方法,就生成下一个自然数:

public class Main {
    public static void main(String[] args) {
        Stream<Integer> natual = Stream.generate(new NatualSupplier());
        // 注意:无限序列必须先变成有限序列再打印:
        natual.limit(20).forEach(System.out::println);
    }
}

class NatualSupplier implements Supplier<Integer> {
    int n = 0;
    public Integer get() {
        n++;
        return n;
    }
}

2.2 常用中间操作

中间操作不会消耗流,只是将一个流转换成另外一个流,类似于流水线。

(1) 筛选filter()

filter函数接收一个Lambda表达式作为参数,返回值为boolean类型,在执行过程中,流将元素逐一输送给filter,并筛选出执行结果为true的元素。

        Collection<Integer> collection = new LinkedList();
        collection.add(1);
        collection.add(2);
        collection.add(3);
        collection.add(3);
        System.out.println("过滤大于2的整数:"+collection.stream()
                .filter(n -> n > 2)
                .collect(Collectors.toList()));
		//过滤大于2的整数:[3, 3]

(2) 去重distinct()

        System.out.println("去重:" + collection.stream()
                .distinct().
                collect(Collectors.toList()));
		//去重:[1, 2, 3]

(3) 截取limit()

截取流的前三个元素:

        System.out.println("截取流的前两个:" + collection.stream()
                .limit(2)
                .collect(Collectors.toList()));
		//截取流的前两个:[1, 2]

(4) 跳过skip()

跳过流的前3个元素:

        System.out.println("跳过流的前3个元素:" + 
                collection.stream()
                .skip(3)
                .collect(Collectors.toList()));
		//跳过流的前3个元素:[3]

(5) 排序sorted()

将流中的元素按照自然排序方式进行排序。

List<Integer> nums = Arrays.asList(1, 2, 5, 6, 8, 3);

System.out.println(nums.stream()
    .sorted()
    .collect(Collectors.toList())
);

System.out.println("降序:"+nums.stream()
    .sorted(Comparator.reverseOrder())
    .collect(Collectors.toList())
);

(6) map()

将流中的元素进行再次加工形成一个新流,流中的每一个元素映射为另外的元素。

flatMap()

扁平化映射,它具体的操作是将多个stream连接成一个stream,这个操作是针对类似多维数组的,比如集合里面包含集合,相当于降维作用。

peek()

对流中每个元素执行操作,并返回一个新的流,返回的流还是包含原来流中的元素。

  • map()
  • flatMap()
  • distinct()
  • sorted()
  • peek()
  • limit()
  • skip()

2.3 终端操作

终端操作会消耗流,以产生一个最终结果,终端操作完成后,流就被消耗了,不可再调用相关操作流的方法。

方法 说明
forEach(Consumer action) 遍历流中所有元素,对每个元素执行action
toArray() 将流中所有元素转换为一个数组
reduce() 该方法有三个重载的版本,都用于通过某种操作来合并流中的元素
min() 返回流中所有元素的最小值
max() 返回流中所有元素的最大值
count() 返回流中所有元素的数量
anyMatch(Predicate predicate) 判断流中是否至少包含一个元素符合 Predicate 条件。
allMatch(Predicate predicate) 判断流中是否每个元素都符合 Predicate 条件
noneMatch(Predicate predicate) 判断流中是否所有元素都不符合 Predicate 条件
findFirst() 返回流中的第一个元素
findAny() 返回流中的任意一个元素
  • forEach()
  • forEachOrdered()
  • toArray()
  • reduce()
  • collect()
  • min()
  • max()
  • count()
  • anyMatch()
  • allMatch()
  • noneMatch()
  • findFirst()
  • findAny()

3. Optional<T>

Optional<T>是 Java 8 新加的容器,只存放0个或1个元素,用于防止出现 NullpointException

boolean isPresent():判断容器是否有值

T get():获取容器中的元素,若容器为空则抛出 NoSuchElement 异常。

List<Integer> list = Arrays.asList(1, 2, 1, 3, -1, 2, 4);
Optional<Integer> first = list.stream().findFirst();
if (first.isPresent()) {
    System.out.println("返回第一个元素:" + first.get());
}
// 更优雅的方式
first.ifPresent(integer -> System.out.println("返回第一个元素:" + integer));

Optional<Integer> anyEle = list.stream().findAny();
System.out.println(anyEle.get());

参考:

1

评论区

// // // //