Stream流

笔记出自自学视频:05.Lambda练习一_哔哩哔哩_bilibili

Java8的Stream使用的是函数式编程模式,他可以用来对集合或数组进行链状流式的操作。

常用操作

创建流

  • 单列集合:集合对象.stream()
1
2
List<Integer> list  = new ArrayList<>();
list.stream();
  • 数组:Arraysstream(数组)或者用Stream.of来创建
1
2
3
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);

双列集合:转换成单列集合后再创建

1
2
Map<String,Integer> map = new HashMap<>();
Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();

中间操作

filter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) {
Computer c1 = new Computer("1",12,12);
Computer c2 = new Computer("2",12,12);
Computer c3 = new Computer("ko",12,12);
Computer c4 = new Computer("ko",21,12);
Computer c5 = new Computer("ok",12,12);
List<Computer> list = new ArrayList<>();
list.add(c1);
list.add(c2);
list.add(c3);
list.add(c4);
list.add(c5);
System.out.println("去重前");
for (Computer com : list) {
System.out.println(com);
}
System.out.println("去重后");
list.stream()
.distinct()
.filter(computer -> computer.getLevel()>=12)
.forEach(computer -> System.out.println(computer));

}

map

这里的map不是单纯的map结构而是映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static void main(String[] args) {
Computer c1 = new Computer("1",12,12);
Computer c2 = new Computer("2",12,12);
Computer c3 = new Computer("ko",12,12);
Computer c4 = new Computer("ko",21,12);
Computer c5 = new Computer("ok",12,12);
List<Computer> list = new ArrayList<>();
list.add(c1);
list.add(c2);
list.add(c3);
list.add(c4);
list.add(c5);
/* System.out.println("去重前");
for (Computer com : list) {
System.out.println(com);
}
System.out.println("去重后");
list.stream()
.distinct()
.filter(computer -> computer.getLevel()>=12)
.forEach(computer -> System.out.println(computer));*/
list.stream()
.map(computer -> computer.getName().toString())
.forEach(name-> System.out.println(name));
}
1
2
3
4
5
6
7
8
9
System.out.println("将电脑类转为手机");
list.stream()
.map(computer -> {
Phone p = new Phone();
p.setName(computer.getName());
p.setPrice(computer.getPrice());
p.setLevel(computer.getLevel());
return p;
}).forEach(phone -> phone.show());

distinct

自定义类需要重写equals方法。

1
2
3
list.stream()
.distinct()
.forEach(computer -> System.out.println(computer));

sorted

对流中的元素进行排序。

1
2
3
list.stream()
.sorted((o1, o2) -> o1.getPrice() - o2.getPrice())
.forEach(computer -> System.out.println(computer));

limit

设置流的最大长度,超出部分丢弃

1
2
list.stream().limit(3)
.forEach(computer -> System.out.println(computer));

skip

跳过流的前n个元素,返回剩下的元素

1
2
list.stream().skip(5)
.forEach(computer -> System.out.println(computer));

flatMap

map只能把一个对象装换成另一个对象来作为流的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素。

image-20221011171019529

终结操作

forEach

对流中的元素进行遍历操作。

count可以用来获取当前流中元素的个数。

max&min

获取当前流的最值。

1
2
3
4
5
6
Optional<Computer> max = list.stream().max(new Comparator<Computer>() {
@Override
public int compare(Computer o1, Computer o2) {
return o1.getPrice() - o2.getPrice();
}
});

collect

把当前流转换成一个集合。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
List<Computer> collect = list.stream()
.sorted(new Comparator<Computer>() {
@Override
public int compare(Computer o1, Computer o2) {
return o1.getPrice() - o2.getPrice();
}
})
.collect(Collectors.toList());
Set<Computer> getSet = list.stream()
.sorted(new Comparator<Computer>() {
@Override
public int compare(Computer o1, Computer o2) {
return o1.getPrice() - o2.getPrice();
}
})
.collect(Collectors.toSet());

查找匹配

anyMatch

用来判断是否有任意符合匹配条件的元素,结果为boolean类型

1
2
3
4
5
6
7
boolean flag = list.stream()
.anyMatch(new Predicate<Computer>() {
@Override
public boolean test(Computer computer) {
return computer.getName().equals("华硕");
}
});

allMatch

用来判断是否都符合匹配条件,结果为boolean类型

1
2
3
4
5
6
7
boolean lowPrice = list.stream()
.allMatch(new Predicate<Computer>() {
@Override
public boolean test(Computer computer) {
return computer.getPrice() < 2000;
}
});

nonoMatch

判断是否元素都不符合匹配条件。

1
2
3
4
5
6
7
boolean allNone = list.stream()
.noneMatch(new Predicate<Computer>() {
@Override
public boolean test(Computer computer) {
return computer.getName().equals("联想");
}
});

获取流中元素

findAny

获取流中任意的一个元素。

1
2
Optional<Computer> any = list.stream()
.findAny();

findFirst

获取流中的第一个元素

1
2
3
4
5
6
7
8
9
 Optional<Computer> first = list.stream()
.findFirst();
//如果first中存在需要的值,就执行accept中的函数体
first.ifPresent(new Consumer<Computer>() {
@Override
public void accept(Computer computer) {
System.out.println(computer);
}
});

reduce

对流中的数据按照指定的计算方法计算出一个结果。(缩减操作)

reduce的作用是把stream中元素组合起来,我们可以给定一个初始值。

1
2
3
4
5
6
7
//内部计算方式
T result = identity; //将初始化值赋给result
for (T element : this stream)
result = accumulator.apply(result,element)
//accumulator是自定义的方法
return result;

1
2
3
4
5
6
7
8
9
10
11
Integer reduce1 = list.stream()
.map(computer -> computer.getPrice())
.reduce(200, (result, element) -> result + element);
Integer reduce2 = list.stream()
.map(computer -> computer.getPrice())
.reduce(200, new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});

image-20221011210319502

总结


image-20221011210457319

Optiona

可以精简判断对象不为空时的操作

创建对象

一般使用Optional的静态方法ofNullable把数据封装成一个Optional对象。无论传入的参数是不是null都不会出现问题。

1
2
Author authoe = getAuthor()
Optional<Author> authorOptional = Optional.ofNUllable(author);

Mybatis从5.3版本已经支持Optional。可以直接把到dao方法的返回值类型定义成Optional类型,Mybatis会自己把数据封装成Optional对象返回。

也可以使用Optional的静态方法of把数据封装成Optional对象。但需要注意传入的参数不能为空。

1
2
Author author = new Author();
Optional<Author> authorOptional = Opthinal.of(author);

image-20221011221520730

安全消费值

获取到一个Optional对象之后可以使用ifPresent方法消费其中的值。

该方法会判断期内封装的数据是否为空,不为空时才会执行具体的消费代码。

1
2
3
4
5
6
7
8
9
Phone phone = new Phone();
Optional<Phone> phone1 = Optional.ofNullable(phone);
phone1.ifPresent(new Consumer<Phone>() {
@Override
public void accept(Phone phone) {
phone.show();
System.out.println(phone.getName());
}
});

安全获取值

可以使用get方法获取其中的值,但不推荐。当Optional内部的数据为空的时候会出现异常。推荐使用以下方法

  1. orElseGet

    获取数据,当数据为空时设置默认值

    1
    2
    3
    4
    5
    6
    7
    Phone phone2 = phone1.orElseGet(new Supplier<Phone>() {
    @Override
    public Phone get() {
    return new Phone();
    }
    });
    /** 当封装的类中有数据时返回封装的数据,否则返回new出的数据*/
  2. orElseThrow

    image-20221011223119361

过滤

使用filter方法可以对封装的数据进行判断,若不符合条件则会成为一个无数据的Optional对象。

数据装换

image-20221011223841868

函数式接口

概述

只有一个抽象方法的接口

JDK的函数式接口都加上了@FunctionalInterface注解进行标识。

常见的函数式接口

Consumer

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。

image-20221012161426267

Function计算转换接口

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回

image-20221012161614993

Predicate判断接口

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,把结果返回

image-20221012161753262

Supplier生产型接口

根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把结果返回

image-20221012161859891

常用的默认方法

  • and

    在使用Predicate接口时候可能需要进行条件判断的拼接。而and方法相当于是使用&&来拼接两个判断条件

  • or

    在使用Predicate接口时候可能需要进行条件判断的拼接。而or方法相当于是使用||来拼接两个判断条件

  • negate

    在使用Predicate接口时候可能需要进行条件判断的拼接。而negate方法相当于取反

方法引用

使用lambada时,如果方法体中==只有一个方法==的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。

基本格式

类名或对象名::方法名

语法详解

引用类的静态方法

格式

类名::方法名 //当方法体中只有一行代码,且这行代码是调用了某个类的静态方法,并且我们把重写的抽象方法中的所有参数都按照顺序传入了这个静态方法中。

1
2
Arrays.stream(arr)
.map(Math::abs);

引用对象的实例方法

格式

对象名::方法名 //当方法体中只有一行代码,且这行代码是调用了某个对象的成员方法,并且我们把重写的抽象方法中的所有参数都按照顺序传入了这个成员方法中。

1
2
3
4
5
6
7
8
9
10
11
12
13
list2.stream()
.map(new Function<Phone, String>() {
@Override
public String apply(Phone phone) {
return phone.getName();
}
})
.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});

引用类的实例方法

格式

类名::方法名 //如果我们重写方法时,当方法体中只有一行代码,且这行代码是调用第一个参数的成员方法,并且我们把重写的抽象方法中的剩余参数都按照顺序传入了这个成员方法中。image-20221012192154000

构造器引用

格式

类名::new //如果我们重写方法时,当方法体中只有一行代码,且这行代码是调用了某个类的构造方法

image-20221012192326061

Stream基本数据类型优化

我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。

​ 即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。

​ 所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private static void test27() {

List<Author> authors = getAuthors();
authors.stream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);

authors.stream()
.mapToInt(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
}

并行流

​ 当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。

1
2
3
4
5
6
7
8
9
10
Optional<Integer> reduce1 = list.stream()
.parallel()
.map(Computer::getPrice)
.reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer integer, Integer integer2) {
return integer + integer2;
}
});
System.out.println("电脑价格总和:"+reduce.orElseGet(() -> 3));