Java8之Stream

什么是Stream?
Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是 Stream API(java.util.stream.*)。
Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用Stream API来并行执行操作。简而言之,Stream API提供了一种高效且易于使用的处理数据的方式。
总结一句话就是:流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“集合讲的是数据,流讲的是计算!”

创建流

要对流进行操作,首先要创建流,有6种方法可以获取流。

1. 通过集合创建流

@Test
public void testCollectionStream() {
	List<String> strs = Arrays.asList("a", "b", "c", "d");
	//创建普通流
	Stream<String> stream = strs.stream();
	//创建并行流(即多个线程处理)
	Stream<String> streamParallel = strs.parallelStream();

	stream.forEach(System.out::println);

	streamParallel.forEach(System.out::println);
}

输出结果

a
b
c
d
c
d
a
b

2. 通过数组创建流

@Test
public void testArrayStream() {
	int[] arr = new int[]{1, 2, 3, 4};
	IntStream intStream = Arrays.stream(arr);
	intStream.forEach(System.out::println);

	System.out.println("========");
	Integer[] arr2 = new Integer[]{1, 2, 3, 4};
	Stream<Integer> stream = Arrays.stream(arr2);
	stream.forEach(System.out::println);
}

输出结果

1
2
3
4
========
1
2
3
4

3. 通过Stream.of方法创建流

@Test
public void testStreamOf() {
    Stream<Integer> stream = Stream.of(1, 2, 3);
    stream.forEach(System.out::println);
}

输出结果

1
2
3

4. 创建规律的无限流

@Test
public void testUnlimitStream() {
    Stream<Integer> stream = Stream.iterate(0, x -> x + 2).limit(3);
    stream.forEach(System.out::println);
}

输出结果

0
2
4

5. 创建无限流

@Test
public void testUnlimitStream2() {
    Stream<String> stream = Stream.generate(() -> "number" + new Random().nextInt()).limit(3);
    stream.forEach(System.out::println);
}

输出结果

number996910758
number715217789
number-1287821385

6. 创建空流

@Test
public void testEmptyStream() {
    Stream<Integer> stream = Stream.empty();
    stream.forEach(System.out::println);
}

操作流

Stream操作分类
分为两大类,中间操作和终结操作。
中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作。

  • 无状态是指元素的处理不受之前元素的影响,
  • 有状态是指该操作只有拿到所有元素之后才能继续下去。

终结操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作。

  • 短路是指遇到某些符合条件的元素就可以得到最终结果,
  • 非短路是指必须处理完所有元素才能得到最终结果。

关于stream的操作分类见下图(图片来源于网络,侵删)。。。
image
一些常用的方法见下表。

方法 参数 作用
filter Predicate<? super T> predicate 过滤
map 转换
flatMap 转换+扁平化
distinct 去重
sorted 排序
limit 限量
skip 跳过
forEach 遍历
anyMatch/allMatch 匹配

1. filter

filter接收一个Predicate函数接口参数,boolean test(T t);即接收一个参数,返回boolean类型。

@Test
public void testFilter() {
    Integer[] arr = new Integer[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    Arrays.stream(arr).filter(x -> x > 3 && x < 8).forEach(System.out::println);
}

输出结果

4
5
6
7

2. map

map接收一个Function<T, R>函数接口,R apply(T t);即接收一个参数,并且有返回值。

 @Test
 public void testMap() {
  String[] arr = new String[]{"yes", "YES", "no", "NO"};
  Arrays.stream(arr).map(x -> x.toLowerCase()).forEach(System.out::println);
  System.out.println("==============================");
  Arrays.stream(arr).forEach(System.out::println);
 }

输出结果

yes
yes
no
no
==============================
yes
YES
no
NO

注意:map操作并未改变原有数组内的元素。

3. flatMap

flatMap接收一个Function<T, R>函数接口: Stream flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);即入参为集合类型,返回Stream类型。

@Test
public void testFlatMap() {
    String[] arr1 = {"a", "b"};
    String[] arr2 = {"e", "f"};
    String[] arr3 = {"h", "j"};
    // Stream.of(arr1, arr2, arr3).flatMap(x -> Arrays.stream(x)).forEach(System.out::println);
    Stream.of(arr1, arr2, arr3).flatMap(Arrays::stream).forEach(System.out::println);
}
flatMap会将lambda表达式函数中所有返回的stream进行合并,然后对合并后的流进行处理。

输出结果

a
b
e
f
h
j

为了加深对于flatMap的理解,再举一个例子。

private static class Klass {
    private int field;

    public Klass(int field) {
        this.field = field;
    }

    @Override
    public String toString() {
        return "field=" + field;
    }
}

private static class KlassGroup {
    private List<Klass> group = new ArrayList<>();

    public KlassGroup(Klass... objList) {
        for (Klass item : objList) {
            this.group.add(item);
        }
    }

    public List<Klass> getKlassList() {
        return group;
    }
}

现在有一个groupList对象列表,列表中的元素仍然是一个列表,我们的需求是将内层列表中的元素取出来,放到一个ArrayList中。

List<KlassGroup> groupList = Arrays.asList(
        new KlassGroup(new Klass(1), new Klass(2), new Klass(3)),
        new KlassGroup(new Klass(4), new Klass(5), new Klass(6)),
        new KlassGroup(new Klass(7), new Klass(8), new Klass(9)),
        new KlassGroup(new Klass(10))
);

我们可以用两层for循环来做,像这样。

List<Klass> result2 = new ArrayList<>();
for (KlassGroup group : groupList) {
    for (Klass klass : group.getKlassList()) {
        result2.add(klass);
    }
}

如果用flatMap来写的话,有这样。

List<Klass> result3 = groupList.stream()
        .flatMap(it -> it.getKlassList().stream())
        .collect(Collectors.toList());

filter方法、map方法以及flatMap方法均为懒操作,只有等到需要结果的时候才执行。

4. distinct

@Test
public void testDistinct() {
    List<String> list = new ArrayList<String>() {
        {
            add("user1");
            add("user2");
            add("user2");
            add("user2");
        }
    };
    list.stream().distinct().forEach(System.out::println);
}

输出结果

user1
user2

5. sorted

@Test
public void testSorted1() {
    String[] arr1 = {"abc", "a", "bc", "abcd"};
    // 按照字符长度排序
    System.out.println("lambda表达式");
    Arrays.stream(arr1).sorted((x, y) -> {
        if (x.length() > y.length())
            return 1;
        else if (x.length() < y.length())
            return -1;
        else
            return 0;
    }).forEach(System.out::println);
    // Comparator.comparing是一个键提取的功能
    System.out.println("方法引用");
    Arrays.stream(arr1).sorted(Comparator.comparing(String::length)).forEach(System.out::println);
}

输出结果

lambda表达式
a
bc
abc
abcd
方法引用
a
bc
abc
abcd

6. reversed

/**
 * 倒序
 * reversed(),java8泛型推导的问题,所以如果comparing里面是非方法引用的lambda表达式就没办法直接使用reversed()
 * Comparator.reverseOrder():也是用于翻转顺序,用于比较对象(Stream里面的类型必须是可比较的)
 * Comparator.naturalOrder():返回一个自然排序比较器,用于比较对象(Stream里面的类型必须是可比较的)
 */
@Test
public void testSorted2_() {
    String[] arr1 = {"abc", "a", "bc", "abcd"};
    System.out.println("reversed(),这里是按照字符串长度倒序排序");
    Arrays.stream(arr1).sorted(Comparator.comparing(String::length).reversed()).forEach(System.out::println);
    System.out.println("Comparator.reverseOrder(),这里是按照首字母倒序排序");
    Arrays.stream(arr1).sorted(Comparator.reverseOrder()).forEach(System.out::println);
    System.out.println("Comparator.naturalOrder(),这里是按照首字母顺序排序");
    Arrays.stream(arr1).sorted(Comparator.naturalOrder()).forEach(System.out::println);
}

输出结果

reversed(),这里是按照字符串长度倒序排序
abcd
abc
bc
a
Comparator.reverseOrder(),这里是按照首字母倒序排序
bc
abcd
abc
a
Comparator.naturalOrder(),这里是按照首字母顺序排序
a
abc
abcd
bc

7. limit从流中获得前n个数据

/**
 * limit,限制从流中获得前n个数据
 */
@Test
public void testLimit() {
    Stream.iterate(1, x -> x + 2).limit(3).forEach(System.out::println);
}

输出结果

1
3
5

8. forEach

@Test
public void testForEach() {
    List<String> list = new ArrayList<String>() {
        {
            add("a");
            add("b");
        }
    };
    list.stream().forEach(System.out::println);
}

输出结果

a
b

9. redeuce

@Test
public void testReduce() {
    Optional<Integer> optional = Stream.of(1, 2, 3).filter(x -> x > 1).reduce((x, y) -> x + y);
    System.out.println(optional.get());
}
5

10.collect

collect是一个非常常用的操作,可以将流转换为其他形式。
用户实体类

@Data
@AllArgsConstructor
@ToString
public class User {
    private String name;
    private Integer age;
    private Integer salary;
}

toList、toSet、toMap

@Test
public void testCollect() {
    List<User> users = Arrays.asList(new User("张三", 19, 1000),
            new User("张三", 58, 2000),
            new User("李四", 38, 3000),
            new User("赵五", 48, 4000)
    );
    List<String> collect = users.stream().map(x -> x.getName()).collect(Collectors.toList());
    Set<String> collect1 = users.stream().map(x -> x.getName()).collect(Collectors.toSet());
    Map<Integer, String> collect2 = users.stream().collect(Collectors.toMap(x -> x.getAge(), x -> x.getName()));
    System.out.println(collect);
    System.out.println(collect1);
    System.out.println(collect2);
}

输出结果

[张三, 张三, 李四, 赵五]
[李四, 张三, 赵五]
{48=赵五, 19=张三, 38=李四, 58=张三}

总结

  • Stream 自己不会存储元素。
  • Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
  • Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

参考文章链接:

posted @ 2023-10-17 18:24  梦醒时风  阅读(88)  评论(0)    收藏  举报