Bootstrap

【Java8 重要特性】Stream API

Stream API

Java 8 引入了 Stream API,它提供了一种高效且易于使用的处理数据的方式。Stream API 可以以声明式的方式处理数据集合(例如列表或数组)。Stream 操作可以是串行的也可以是并行的,这使得它可以很容易地利用多核架构。

特点

1、Stream 自己不会存储数据。

2、Stream 不会改变源对象。相反,它们会返回一个持有结果的新Stream对象

3、Stream 操作时延迟执行的。这就意味着它们等到有结果时候才会执行

关键概念

Stream:一个流是一系列元素的序列,支持顺序和并行处理。

Intermediate Operations(中间操作):返回一个新的流。这些操作通常允许延迟执行,并且可以链式调用。

Terminal Operations(终端操作):触发实际的计算,产生结果或副作用。一旦执行了终端操作,流就被消费并且不能再被使用。

Stream操作
中间操作(Intermediate Operations)无状态(Stateless)filter(), map(), mapToInt(), mapToLong(), mapToDouble(), flatMap(), flatMapToInt(), flatMapToLong(), flatMapToDouble(), peek(), eunordered()
有状态(Stateful)distinct(), sorted(), sorted(Comparator comparator), limit(), skip()
终端操作(Terminal Operations)非短路操作(Unshort-circuiting)forEach(), forEachOrdered(), toArray(), reduce(), collect(), max(), min(), count()
短路操作(Short-circuiting)anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()

创建流

通过 java.util.Collection.stream() 方法用集合创建流

List<String> list = Arrays.asList("d","a", "b", "c");
// 顺序流
Stream<String> streamList = list.stream();
streamList.forEach(System.out::print);

// 并行流(效率高,线程不安全)
Stream<String> parallelStream = list.parallelStream();
parallelStream.forEach(System.out::print);

使用 java.util.Arrays.stream(T[] array) 方法用数组创建流

int[] array = {1,3,5,6,8};
IntStream streamint = Arrays.stream(array);
streamint.forEach(System.out::print);

使用 Stream 的静态方法: of()iterate()generate()

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
stream.forEach(System.out::print);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::print);

Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::print);

中间操作

操作描述
limit(n)从 list 中取出 n 个元素
skip(n)从 list 第 n 个元素开始,取出所有元素
distinct()去除重复
filter(Predicate<? super T> predicate)筛选条件
map(Function mapper)将元素转换成其他形式或提取信息
sorted()对元素进行排序

limit(n) & skip(n) & distinct()

public static void main(String[] args) {
    List<String> list = new ArrayList<>();
    Collections.addAll(list, "张无忌", "周芷若", "赵敏", "张强", "张三丰","张无忌");
}
list.stream().limit(2).forEach(System.out::println); //张无忌 周芷若
list.stream().skip(3).forEach(System.out::println);// 张强 张三丰 张无忌
list.stream().distinct().forEach(System.out::println); //张无忌 周芷若 赵敏 张强 张三丰

过滤

filter(Predicate<? super T> predicate)

  • 括号中的内容为一个断言型函数式接口,其中的 T 为当前集合中元素的类型。

使用 Stream API 来过滤出年龄大于20岁的用户,并打印这些用户的信息。

class User {
    private String name;
    private int age;
    private String country;
    private char gender;
    public User(String name, int age, String country, char gender) {
        this.name = name;
        this.age = age;
        this.country = country;
        this.gender = gender;
    }
    public int getAge() { return age; }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", country='" + country + '\'' +
                ", gender=" + (gender == 'M' ? "Male" : "Female") +
                '}';
    }
}
public static void main(String[] args) {
    List<User> userList = new ArrayList<>();
    userList.add(new User("欧阳雪",18,"中国",'F'));
    userList.add(new User("Tom",24,"美国",'M'));
    userList.add(new User("Harley",22,"英国",'F'));
    userList.add(new User("向天笑",20,"中国",'M'));
    userList.add(new User("李康",22,"中国",'M'));
    userList.add(new User("小梅",20,"中国",'F'));
    userList.add(new User("何雪",21,"中国",'F'));
    userList.add(new User("李康",22,"中国",'M'));
}
userList.stream()
    .filter((user)->user.getAge()>20)
    .forEach(System.out::println);

输出

User{name='Tom', age=24, country='美国', gender=Male}
User{name='Harley', age=22, country='英国', gender=Female}
User{name='李康', age=22, country='中国', gender=Male}
User{name='李康', age=22, country='中国', gender=Male}

映射

map(Function mapper)

从用户列表中提取出所有用户的年龄,并将这些年龄逐一打印出来。

userList.stream()
    .map((user -> user.getAge()))
    .forEach(System.out::println);

排序

sorted()

userList.stream()
    .filter((user) -> user.getAge() > 20) // 过滤出年龄大于20岁的用户
    .sorted((o1, o2) -> o1.getAge() - o2.getAge()) // 按年龄升序排序
    .forEach(System.out::println); // 打印每个用户的信息
User{name='何雪', age=21, country='中国', gender=Female}
User{name='Harley', age=22, country='英国', gender=Female}
User{name='李康', age=22, country='中国', gender=Male}
User{name='李康', age=22, country='中国', gender=Male}
User{name='Tom', age=24, country='美国', gender=Male}

终端操作

操作描述备注
forEach()遍历流中每一个元素,会关闭流forEach 不能修改自己包含的本地变量值,也不能用 break/return 之类的关键字提前结束循环
findFirst()返回流中的第一个元素在顺序流中,它总是返回第一个元素;在并行流中,它也会尝试找到第一个元素,但可能会有性能开销,因为它需要协调各个线程的结果。
findAny()返回流中的任意一个元素在顺序流中,它通常返回第一个元素;在并行流中,它会从任何可用的线程中返回一个元素,这可以提高性能,因为不需要等待所有线程完成。
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");
// 使用 findFirst() 获取第一个元素
Optional<String> first = list.stream()
    .filter(s -> s.startsWith("B"))
    .findFirst();
// 输出结果
first.ifPresent(System.out::println); // 输出: Banana
List<String> list = Arrays.asList("Apple", "Banana", "Cherry", "Date");

// 使用 findAny() 获取任意一个元素
Optional<String> any = list.parallelStream() // 使用并行流
    .filter(s -> s.length() > 5)
    .findAny();

// 输出结果
any.ifPresent(System.out::println); // 可能输出: Banana, Cherry 或其他符合条件的元素

注意事项

  • 在顺序流中,findFirst()findAny() 通常会有相同的行为,即都返回第一个符合条件的元素。
  • 在并行流中,findFirst() 可能会有更高的性能开销,因为它需要确保返回的是第一个元素,而 findAny() 则更灵活,可以从任意一个线程中返回一个元素,从而可能提高性能。
操作描述
allMatch()要求 Stream 中所有元素都满足条件才返回 true
anyMatch()只要有1个元素满足就返回 true
noneMatch()要求所有的元素都不满足条件才返回 true
//每个学生年纪都为18才返回true
boolean flag = list.stream().allMatch(e -> e.getAge() == 18);
//任意一个学生是18,则返回true
boolean flag = list.stream().anyMatch(e -> e.getAge() == 18);
//学生年纪没有一个是18 ,则返回true
boolean flag = list.stream().noneMatch(e -> e.getAge() == 18);
操作描述
reduce将整个数据流的值规约为一个值,其中count、min、max底层就是使用reduce,也可用于字符串连接
min最值操作 需要自定义比较器,返回数据流中最小的值
max最值操作 需要自定义比较器,返回数据流中最大的值
count返回一个 long 类型的值,表示流中的元素数量
toArray数组操作 将数据流的元素转换成数组

reduce

签名:Optional<T> reduce(BinaryOperator<T> accumulator)

  • 不需要提供初始值。从流中的第一个元素开逐步应用二元操作 accumulator,直到处理完所有元素
  • 如果流为空,则返回 Optional.empty();否则,返回一个包含结果的 Optional<T>

签名:T reduce(T identity, BinaryOperator<T> accumulator)

  • 需要提供一个初始值 identity。它会从这个初始值开始,逐步应用二元操作 accumulator 来累积结果
  • 即使流为空,也会返回初始值 identity
// 字符串连接 concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat);
// 集合累加求和 另一写法reduce(0,Integer::sum)
int sum2 = Stream.of(1, 2, 3, 4, 5).reduce((t, u) -> t + u).get();
// 输出最大值 另一写法reduce(Integer::max)
int max = Stream.of(1, 2, 3, 4, 5).reduce((t, u) -> t >= u ? t : u).get();

int sum3 = Stream.of(1, 2, 3, 4, 5).reduce(0, (t, u) -> t + u);

第 2 行、.reduce("", String::concat):将字符串连接起来。初始值是空字符串

String::concat 是一个方法引用,它接受两个字符串参数并返回它们的连接结果

第 4 行、.reduce((t, u) -> t + u):对流中的所有整数进行累加。此处未提供初始值,返回 Optional<Integer>

.get():从 Optional<Integer> 中获取实际的整数值

第 6 行、.reduce((t, u) -> t >= u ? t : u):找出流中的最大值。此处未提供初始值,返回 Optional<Integer>

.get():从 Optional<Integer> 中获取实际的整数值

第 8 行、.reduce(0, (t, u) -> t + u):使用 reduce 方法对流中的所有整数进行累加。这里提供了初始值 0,并且 BinaryOperator(t, u) -> t + u,表示将当前值 t 和下一个值 u 相加。

min、max

最值操作 需要自定义比较器,返回数据流中最大、最小的值

//查年纪最大的
Optional<Student> max =
    list.stream().max(Comparator.comparing(Student::getAge));
if (max.isPresent()) {
    Student student = max.get();
}

toArray

数组操作 将数据流的元素转换成数组

List<String> list = Arrays.asList("a","b","c","a");
//返回数组
String[] newArr = list.stream().toArray(String[]::new);

将 Stream 转换为 list

语法:Stream 对象名.collect(Collectors.toList());

// 使用Stream API过滤出年龄大于20岁的学生
List<Student> list1 = list.stream()
    .filter(t -> t.getAge() > 20)
    .collect(Collectors.toList());
// 打印过滤后的学生列表
list1.forEach(System.out::println);
;