Java流库(上篇——基础用法)
为什么使用java流库?
它让我们在做某一操作时,不需要再编写具体的实现,而是只要告诉它想要做什么,然后把剩下的交给他就可以了。
流是如何工作的?
流的使用大体分为三步
- 创建流
- 转换流(操作流)
- 终止流(终止之后,这个流将不可使用)
创建流
//1、组合任意元素构建成流
Stream<String> stream1 = Stream.of("hello","world");
stream1.forEach(System.out::print);
System.out.println();
//2、创建一个不包含任何元素的流
Stream<Object> stream2 = Stream.empty();
stream2.forEach(System.out::println);
//3、创建一个无限流,流中的数据由具体的Supplier函数式接口实例返回,limit表示返回前10个数据到流中
Stream<Integer> generate = Stream.generate(() -> (int)(Math.random()*10)).limit(10);
generate.forEach(System.out::println);
//4、通过迭代器创建一个无限流,这里流中包含的元素为0+1+1+1+1+1...+1,,limit表示返回前10个数据到流中
Stream<Integer> iterate = Stream.iterate(0, n -> n + 1).limit(10);
iterate.forEach(System.out::println);
上述代码演示了四种创建流的方式。分别是of()
、empty()
、generate()
、iterate()
。他们都是Stream
流对象中的方法。但是值得注意的是,在Stream
对象之外,也有很多类提供了创建一个流的方法。
关于流需要注意的一些事项:
- 流本身不存储任何数据
- 对流的一系列操作绝对不会修改原数据 (假设将list集合转换为流并在流中做了修改操作,对原list集合不会产生任何影响)
- 对流所做的操作并非是立即执行,往往是在对流做终止操作时才会执行。
其他创建流的方式举例
Arrays.stream
Integer[] cs = new Integer[10];
Arrays.fill(cs,8);
Stream<Integer> stream = Arrays.stream(cs);
stream.forEach(System.out::println);
示例中展示如何将数组转换为一个流,其中Arrays.stream()
方法是可重载方法。还可以如下表示: Stream<Integer> stream = Arrays.stream(cs,3,8);
代表通过指定了范围的数组中的多个元素创建流。
Scanner.tokens
还可以通过扫描器的tokens()
方法将接收到的数据转为一个流
Stream<String> scanner = new Scanner("你好吗").tokens();
scanner.forEach(System.out::println);
其实创建流的方式还有很多很多,总之不一定非得通过Stream
接口创建流,java中很多其他类中也都封装了创建流的方法。
操作流
现在已经知道如何去创建一个流了,那到底该如何去使用呢?Stream
接口中同样提供了很多操作流的方法。
map方法
map
方法主要用来转换流中的元素,比如将一个流中的小写字母转为大写字母
Stream<String> hello = Stream.of("hello", "world");
Stream<String> stream = hello.map(String::toUpperCase);
stream.forEach(System.out::print);
输出:HELLOWORLD
再或者说,将流中的数据加密
Stream<String> hello = Stream.of("hello", "world");
Stream<String> stream = hello.map(s -> Base64.getEncoder().encodeToString(s.getBytes()));
stream.forEach(System.out::print);
简单来说,在map
方法中,我们可以做一些针对元素的操作。map
方法的参数是一个函数式接口Function
,所以我们可以传入操作元素的自定义方法。
filter方法
这个方法的作用是从一个流中过滤出符合条件的数据
Stream<String> hello = Stream.of("hello", "world");
Stream<String> stream = hello.filter(s -> s.equals("hello"));
stream.forEach(System.out::print);
输出:hello
flatMap方法
flatMap
方法的作用在于它可以将方法中产生的多个流中的元素合并到一个流中,打个比方:
Stream<String> hello = Stream.of("aa", "bb");
Stream<Stream<String>> stream = hello.map(s -> cs());
stream.forEach((x) -> x.forEach(System.out::println));
可以看到hello.map(s -> cs())
方法返回的每个结果都是一个流,所以前面的变量类型是Stream<Stream<String>>
。这样处理起来或许很麻烦。现在假如使用flatMap
方法:
Stream<String> hello = Stream.of("aa", "bb");
Stream<String> stream = hello.flatMap(s -> cs());
stream.forEach(System.out::println);
现在对流进行处理过后返回的就是Stream<String>
类型了。所以总结一下:flatMap
可以将当前操作产生的一个一个的流类型的结果连接到一起组成一个流。
limit方法
这个方法在前面的代码示例中出现过,可以在原有流的基础上产生一个包含原有流中前n个元素的流
Stream<String> hello = Stream.of("aa", "bb");
Stream<String> limit = hello.limit(1);
limit.forEach(System.out::println);
输出:aa
这里limit
方法的作用就是产生一个包含aa
的新流
skip方法
这个方法的作用和limit
正好相反,skip
方法可以产生一个不包含前n
个元素的新流。这里将上面的代码做一下改动:
Stream<String> hello = Stream.of("aa", "bb");
Stream<String> limit = hello.skip(1);
limit.forEach(System.out::println);
输出:bb
takeWhile
条件成立,则获取流中所有的元素
Stream<String> hello = Stream.of("aa", "bb");
Stream<String> limit = hello.takeWhile(x -> x.equals("aa"));
limit.forEach(System.out::println);
输出:aa
dropWhile
与takeWhile
相反,条件成立,则从不成立的第一个元素开始获取之后的所有元素
tream<String> hello = Stream.of("aa", "bb");
Stream<String> limit = hello.dropWhile(x -> x.equals("aa"));
limit.forEach(System.out::println);
输出:bb
distinct
这个方法产生的新流会剔除重复的元素
Stream<String> hello = Stream.of("aa", "bb","aa","aa");
Stream<String> limit = hello.distinct();
limit.forEach(System.out::print);
输出:aabb
concat(Stream a,Stream b)
将第一个参数流b,连接到第一个参数流a
Stream<String> limit = Stream.concat(cs(),cs());
limit.forEach(System.out::print);
其中cs()
方法返回一个流,这里需要注意一点:concat
第一个参数不可以为无限流,否则第二个参数永远无法完成连接。
sorted
显而易见这是一个排序方法,并且可以传入Comparator实例自定义排序规则
public static Stream<Integer> cs2(){
Stream<Integer> hello = Stream.of(4,2,5,1,5,8,3);
return hello;
}
public static void main(String[] args) {
Stream<Integer> limit = cs2().sorted();
limit.forEach(System.out::print);
}
输出:1234558
传入Comparator实例:
Stream<Integer> limit = cs2().sorted(Comparator.reverseOrder());
limit.forEach(System.out::print);
输出:8554321
传入一个倒序排列的规则,正确打印从大到小的顺序~
peek
peek
的意思是,每当从流中获取一个元素时,都会执行我们传入的Consumer
函数式接口实例方法
Stream<Integer> ss = cs2().peek(System.out::println);
ss.forEach(System.out::println);
上面的示例代码中,每当循环语句输出一条流中的元素,peek
方法中的Consumer
接口实例就会执行一次方法中的打印语句。由此可见,流是被惰性处理的,只有在使用时才会真正执行。
终止流
从流中获取一个答案就会终止一个流,通俗一点说,已经通过流达目的了,自然也就可以结束了。并且这个答案并非是一个流类型的值。
举个例子:
Optional<Integer> ss = cs2().max(Integer::compareTo);
System.out.println(ss.orElse(-1));
在这个示例中,max()
方法就是一个终止操作,其实更应该把这叫做约简。参数是一个比较器的函数式接口,说明可以自定义比较规则。同样的还有min()
方法,用法都大同小异,就不举例说明了。
下面是其他终止流的一些操作:
- findFirst:返回第一个元素或符合某种条件的第一个元素
- findAny:返回任意元素或符合某种条件的任意元素,这个方法不要求元素的顺序,所以在并行处理方面很适合
- anyMatch:如果符合某种条件则返回true
Optional<Integer> first = cs2().findFirst();
Optional<Integer> any = cs2().findAny();
boolean b = cs2().anyMatch(n -> n > 7);
System.out.println(first.orElse(-1));
System.out.println(any.orElse(-1));
System.out.println(b);
输出: 4 4 false
转载自:https://juejin.cn/post/7234513100383699001