JAVA 8 流(Stream)操作,lambda表达式与Optional类的使用与归纳

2,918 阅读11分钟

Java 8 中,得益于 lambda 带来的函数式编程,引入了一个全新的 Stream流 概念,用于解决集合已有的弊端。

关于“::”使用

  1. ::是用于给单方法的接口任命实现方法使用的
  2. 被用于任命的实现方法需要是静态方法或者构造函数
  3. 构造函数相当于返回值为该构造函数的类

示例

待分配的接口类:

@FunctionalInterface
public interface ITest<T> {

    T test(String s);

}

用于实现接口的类:

public class SomeThingTest {
    SomeThingTest(String s) {

        System.out.println("构造方法实现");
    }

    static String staticTest(String s) {
        System.out.println("静态方法实现");
        return "静态方法";
    }

}

使用:

public class MyMain {
    public static void main(String[] args) {

        ITest iTest = SomeThingTest::new;
        ITest iTest2 = SomeThingTest::staticTest;
        iTest.test("a");
        iTest2.test("a");
    }
}

关于lambda表达式

两个用法:

代替函数接口
  1. 代替函数式接口。函数式接口简单来说就是只包含一个抽象方法的接口,比如Java标准库中的java.lang.Runnable和java.util.Comparator都是典型的函数式接口。对于函数式接口,除了可以使用Java中标准的方法来创建实现对象之外,还可以使用lambda表达式来创建实现对象,这可以在很大程度上简化代码的实现。在使用lambda表达式时,只需要提供形式参数和方法体。由于函数式接口只有一个抽象方法,所以通过lambda表达式声明的方法体肯定是这个唯一的抽象方法的实现,而且形式参数的类型可以根据方法的类型声明进行自动推断(即形式参数可以省略类型)。

首先我们要知道实现类要实现单方法接口需要了解的属性:

1. 该方法需要传入的值

2. 该方法需要返回的值

这两点体现在lambda表达式的时候就是:

(方法传入的值) -> 方法返回的值

但是上面这是在接口方法有返回值时,如果接口方法返回值为void呢:

(方法传入的值) -> 方法内的一些操作

示例:
@FunctionalInterface
public interface ITest<T> {

    void test(String s);

}
ITest<String> stringITest = s -> System.out.println(s);
和集合配合使用
  1. 和集合配合使用。Java 8新增了两个对集合数据进行批量操作的包:java.util.function和java.util.stream。可以说,lambda表达式和stream是自Java语言添加泛型和注解以来最大的变化,lambda表达式很大程度上影响了我们在处理集合时的编码方式。下文会有所体现

关于<? extends T><? super T>

  1. <? extends T>代表匹配泛型T及其子类
  2. <? super T>代表匹配泛型T及其父类

Stream流操作

原集合 —> 流 —> 各种操作(过滤、分组、统计) —> 终端操作 Stream流的操作流程一般都是这样的,先将集合转为流,然后经过各种操作,比如过滤、筛选、分组、计算。最后的终端操作,就是转化成我们想要的数据,这个数据的形式一般还是集合,有时也会按照需求输出count计数。

特性

  1. 无存储:Stream是基于数据源的对象,它本身不存储数据元素,而是通过管道将数据源的元素传递给操作。
  2. 函数式编程:对Stream的任何修改都不会修改背后的数据源,比如对Stream执行filter操作并不会删除被过滤的元素,而是会产生一个不包含被过滤元素的新的Stream。
  3. 延迟执行:Stream的操作由零个或多个中间操作(intermediate operation)和一个结束操作(terminal operation)两部分组成。只有执行了结束操作,Stream定义的中间操作才会依次执行,这就是Stream的延迟特性。
  4. 可消费性:Stream只能被“消费”一次,一旦遍历过就会失效。就像容器的迭代器那样,想要再次遍历必须重新生成一个新的Stream。

创建流

stream() : 串行流

parallelStream(): 并行流

在创建Stream时,默认是创建串行流。但是可以使用parallelStream()来创建并行流或者parallel()将串行流转换成并行流。并行流也可以通过sequential()转换成串行流。

filter 过滤(T-> boolean)

filter里面->箭头后面跟着的是一个boolean值,可以写任何的过滤条件,就相当于sql中where后面的东西,换句话说,能用sql实现的功能这里都可以实现

个人测试理解,仅供参考

到了这个方法的使用的时候我出现了疑惑,如果只是单纯去理解他的用法那并不难,但是他具体实现的顺序是如何的呢,进入filter的源码可以看见:

Stream<T> filter(Predicate<? super T> predicate);
public interface Predicate<T> {
    boolean test(T t);
    }

明显看出lambda表达式实现的是Predicate里的test方法,返回值是boolean类型,进入的参数为T,但是因为<? super T>,意思是存入集合中的都是T的父类,所以这里进入的参数T必须为T或者T的子类,然而在实现后,filter方法并没有去直接调用test方法:(为了方便理解,我没有使用lambda表达式)

        List<String> names = new ArrayList<>();
        names.add("张三丰");
        names.add("张大宝");
        names.add("张三");
        names.add("德玛杰");
        names.add("乔峰");
        names.add("李大宝");

       names.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                System.out.println(s);
                return s.startsWith("张");
            }
        });
        System.out.println("结束");

结果:

在调用了collect方法之后才去执行了test的方法

        List<String> names = new ArrayList<>();
        names.add("张三丰");
        names.add("张大宝");
        names.add("张三");
        names.add("德玛杰");
        names.add("乔峰");
        names.add("李大宝");

        List<String> z = names.stream().filter(new Predicate<String>() {
            @Override
            public boolean test(String s) {
                System.out.println(s);
                return s.startsWith("张");
            }
        }).collect(Collectors.toList());
        System.out.println(z.toString());
        System.out.println("结束");

结果:

由此可知filter方法调用实现了之后,并没有去直接执行Predicate中的test方法,而是等待stream中其他可以调用对象的方法,整体去调用,还有一个疑问,就是集合的各个对象是在什么时候注入到test方法中的呢(这段源码本人实在看不懂o(╥﹏╥)o),我猜测应是在调用collect方法的时候一起注入使用的

distinct 去重

和sql中的distinct关键字很相似。

//distinct 去重
List<User> distinctList = filterList.stream().distinct()
        .collect(toList());

sorted 排序

如果流中的元素的类实现了 Comparable 接口,即有自己的排序规则,那么可以直接调用 sorted() 方法对元素进行排序,无参方法要求流中的元素必须实现Comparable接口,不然会报java.lang.ClassCastException异常,有参方法需要调用 sorted((T, T) -> int) 实现 Comparator 接口

//sorted()
List<User> sortedList = distinctList.stream().sorted(Comparator.comparingInt(User::getAge))
        .collect(toList());

limit(long n) 返回元素

返回前n个元素

//limit 返回前n个元素
List<User> limitList = sortedList.stream().limit(1)
        .collect(toList());

skip(long n) 跳过元素

与limit恰恰相反,skip的意思是跳过,也就是去除前n个元素。

map(T -> R) 类型转换

map是将T类型的数据转为R类型的数据:

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

比如我们有一个学生集合,我们需要从中提取学生的年龄以分析学生的年龄分布曲线。放在 Java 8 之前 我们要通过新建一个集合然后通过遍历学生集合来消费元素中的年龄属性。现在我们通过很简单的流式操作就完成了这个需求。

 // 伪代码
 List<Integer> ages=studentList.stream().map(Student::getAge).collect(Collectors.toList());

flatMap(T -> Stream) 类型转流

将类转换为流类型:

<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

提取所有班级下的所有学生的年龄以分析学生的年龄分布曲线。 如果使用map获取学生流:

 List<List<Student>> studentGroup= gradeList.stream().map(Grade::getStudents).collect(Collectors.toList());

发现List<List>嵌套其中,还需要一个一个拿出来转化为流,使用flatMap:

 // flatMap 提取 List<Students>  map 提取年龄
 List<Integer> ages = grades.stream().flatMap(grade -> grade.getStudents().stream()).map(Student::getAge).collect(Collectors.toList());

allMatch(T->boolean) 判断是否全部符合,返回boolean

检测是否全部满足参数行为,假如这些用户是网吧上网的用户名单,那就需要检查是不是每个人都年满18周岁了。

boolean isAdult = list.stream().allMatch(user -> user.getAge() >= 18);

anyMatch(T->boolean) 判断是否有符合的对象,返回boolean

检测是否有任意元素满足给定的条件,比如,想知道同学名单里是否有女生。

//anyMatch(T -> boolean) 是否有任意一个元素满足给定的条件
boolean isGirl = list.stream().anyMatch(user -> user.getSex() == 1);

noneMatch(T -> boolean) 判断是否全部不符合

比如检测是否没有来自巴黎的用户。

boolean isLSJ = list.stream().noneMatch(user -> user.getAddress().contains("巴黎"));

findFirst( ):找到第一个元素

Optional<User> fristUser  = list.stream().findFirst();

findAny():找到任意一个元素

Optional<User> anyUser  = list.stream().findAny();

这里我们发现findAny返回的也总是第一个元素,那么为什么还要进行区分呢?因为在并行流 parallelStream() 中找到的确实是任意一个元素。

max和min 最大值和最小值

Property property = properties.stream()
            .max(Comparator.comparingInt(p -> p.priceLevel))
            .get();

查找Stream中的最大或最小元素,首先要考虑的是用什么作为排序的指标。以查找价格最低的店铺为例,排序的指标就是店铺的价格等级。为了让Stream对象按照价格等级进行排序,需要传给它一个Comparator对象。Java8提供了一个新的静态方法comparingInt,使用它可以方便地实现一个比较器。放在以前,我们需要比较两个对象的某项属性的值,现在只需要提供一个存取方法就够了。

collect 收集结果

获取距离我最近的2个店铺:

List<Property> properties = properties.stream()
            .sorted(Comparator.comparingInt(x -> x.distance))
            .limit(2)
            .collect(Collectors.toList());

获取每个店铺的价格等级:

Map<String, Integer> map = properties.stream()
        .collect(Collectors.toMap(Property::getName, Property::getPriceLevel));

所有价格等级的店铺列表

Map<Integer, List<Property>> priceMap = properties.stream()
                .collect(Collectors.groupingBy(Property::getPriceLevel));

peek 查看某一步的元素

peek方法可以不调整元素顺序和数量的情况下消费每一个元素,然后产生新的流,如果想看每个元素在多次流操作中间的流转情况,就可以使用这个方法实现

Stream.of("one", "two", "three", "four")
     .filter(e -> e.length() > 3)
     .peek(e -> System.out.println("Filtered value: " + e))
     .map(String::toUpperCase)
     .peek(e -> System.out.println("Mapped value: " + e))
     .collect(Collectors.toList());
输出:
Filtered value: three
Mapped value: THREE
Filtered value: four
Mapped value: FOUR

forEach 遍历

forEach方法的作用跟普通的for循环类似,不过这个可以支持多线程遍历,但是不保证遍历的顺序

        List<String> names = new ArrayList<>();

        Stream.of("one", "two", "three", "four")
            .filter(e -> e.length() > 2)
            .map(String::toUpperCase)
            .forEach(s -> names.add(s));
        System.out.println(names.toString());

forEachOrdered 顺序遍历

forEachOrdered方法可以保证顺序遍历,比如这个流是从外部传进来的,然后在这之前调用过parallel方法开启了多线程执行,就可以使用这个方法保证单线程顺序遍历

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
//顺序遍历输出元素
stringStream.forEachOrdered(System.out::println);
//多线程遍历输出元素,下面这行跟上面的执行结果是一样的
//stringStream.parallel().forEachOrdered(System.out::println);

toArray 流中元素转换为数组

toArray有一个无参和一个有参的方法,无参方法用于把流中的元素转换成Object数组

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
Object[] objArray = stringStream.toArray();

有参方法toArray(IntFunction<A[]> generator)支持把流中的元素转换成指定类型的元素数组

Stream<String> stringStream = Stream.of("-2", "-1", "0", "1", "2", "3");
String[] strArray = stringStream.toArray(String[]::new);

count 统计流内元素个数

count方法用于统计流内元素的总个数

Stream<Integer> numStream = Stream.of(-2, -1, 0, 1, 2, 3);
//count=6
long count = numStream.count();

Optional类

总结:

  1. Optional类的出现是为了用函数表达式解决程序中常见的NullPointerException(空指针异常)异常问题
  2. 当你很确定一个对象不可能为null的时候,应该使用of()方法,否则,尽可能使用ofNullable()方法
  3. 尽量使用它的map函数表达式来简化if-else或者判空操作

基本

java.util.Optional <T> 类是一个封装了Optional值的容器对象,Optional值可以为null,如果值存在,调用isPresent()方法返回true,调用get()方法可以获取值。

创建Optional对象

Optional类提供类三个方法用于实例化一个Optional对象,它们分别为empty()of()ofNullable(),这三个方法都是静态方法,可以直接调用。 其构造函数为私有化,无法用new实例化

 private Optional() {
        this.value = null;
    }

empty() 创建一个没有值的Optional对象:

 Optional<String> optionalITest=Optional.empty();

如果对变量调用isPresent()方法会返回false,调用get()方法抛出NullPointerException异常。

of() 方法使用一个非空的值创建Optional对象:

String str = "Hello World";
Optional<String> notNullOpt = Optional.of(str);

如果传入为空值,直接抛出NullPointerException异常

ofNullable()方法接收一个可以为null的值:

Optional<String> nullableOpt = Optional.ofNullable(str);

如果str的值为null,得到的nullableOpt是一个没有值的Optional对象。

map(T -> R) 获取Optional内对象的值集

Optional<User> userOpt = Optional.ofNullable(user);
Optional<String> roleIdOpt = userOpt.map(User::getRoleId);

orElse(T other) 获取值,有就返回,没有就给一个同类型默认值

Optional<String> optionalITest=Optional.ofNullable(null);
String s = optionalITest.orElse("");

orElseGet(Supplier<? extends T>):与orElse()方法作用类似,区别在于生成默认值的方式不同。

该方法接受一个Supplier<? extends T>函数式接口参数,用于生成默认值;

Optional<List<String>> optionalITest=Optional.ofNullable(null);
optionalITest.orElseGet(()->new ArrayList<>());

orElseThrow(Supplier<? extends X> exceptionSupplier) 自定义抛出异常的取值方法。

当值为空时也会抛出异常,但是可以自己定义异常

        Optional<List<String>> optionalITest=Optional.ofNullable(null);
        optionalITest.orElseThrow(() -> new IllegalArgumentException("自己设定的异常"));

ifPresent(Consumer<? super T>)方法 接收Consumer<? super T>一般用于打印到后台

Optional<String> strOpt = Optional.of("Hello World");
strOpt.ifPresent(System.out::println);

filter(Predicate<? super T> predicate) 条件过滤

与Stream的filter(Predicate<? super T> predicate)用法相同

区别为stream返回一个过滤后的stream对象,optional返回一个过滤后的optional对象

Optional<String> optionalITest=Optional.ofNullable("add");
        Optional<String> a = optionalITest.filter(s -> s.startsWith("a"));
        a.ifPresent(System.out::println);

orElse()方法的使用

简化:

原本:

return str != null ? str : "Hello World"

改进后:

return strOpt.orElse("Hello World")

简化if-else

简化:

原本:

User user = ...
if (user != null) {
    String userName = user.getUserName();
    if (userName != null) {
        return userName.toUpperCase();
    } else {
        return null;
    }
} else {
    return null;
}

改进后:

User user = ...
Optional<User> userOpt = Optional.ofNullable(user);

return userOpt.map(User::getUserName)
            .map(String::toUpperCase)
            .orElse(null);

参考文章:
juejin.cn/post/684490…
juejin.cn/post/684490…
juejin.cn/post/684490…
juejin.cn/post/684490…
juejin.cn/post/684490…
juejin.cn/post/684668…