Java8新特性(三)(持续更新中)

3 篇文章 0 订阅
1 篇文章 0 订阅

这篇主要是关于Stream的介绍及使用

2.10 java.util.stream.Stream接口

Stream表示能应用在一组元素上一次执行的操作序列。
Stream操作分为中间操作或者最终操作两种,最终操作返回一特定类型的计算结果,而中间操作返回Stream本身,这样就可以将多个操作依次串起来(链式编程)。
Stream的创建需要指定一个数据源,比如 java.util.Collection的子类,List或者Set, 但是Map不支持。
Stream的操作可以串行执行或者并行执行。

Stream作为Java8的一大亮点,它与java.io包里的InputStream和OutputStream 是完全不同的概念。Java8中的Stream是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作,或者大批量数据操作。
Stream的API结合Lambda表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行操作

2.10.1 Stream对象的构建:
// 1.使用值构建
Stream<String> stream = Stream.of("a", "b", "c");
// 2.使用数组构建
String[] strArray = new String[] {"a", "b", "c"};
Stream<String> stream = Stream.of(strArray);
Stream<String> stream = Arrays.stream(strArray);
// 3.利用集合构建(不支持Map集合)
List<String> list = Arrays.asList(strArray);
Stream<String> stream = list.stream();

对于基本数值类型,目前有三种对应的Stream包装类型:IntStream、LongStream、DoubleStream。
当然也可以用泛型:Stream、Stream 、Stream,但是自动拆箱装箱会很耗时,所以特别为这三种基本数值型提供了对应的Stream。
Java8中还没有提供其它基本类型数值的Stream

2.10.2 数值Stream的构建:
IntStream stream1 = IntStream.of(new int[]{1, 2, 3});
//[1,3)
IntStream stream2 = IntStream.range(1, 3);
//[1,3]
IntStream stream3 = IntStream.rangeClosed(1, 3);
2.10.3 Stream转换为其它类型:
Stream<String> stream = Stream.of("hello","world","tom");
// 1. 转换为Array
String[] strArray  = stream.toArray(String[]::new);
// 2. 转换为Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set<String> set3 = stream.collect(Collectors.toSet());
Set<String> set4 = stream.collect(Collectors.toCollection(HashSet::new));
// 3. 转换为String
String str = stream.collect(Collectors.joining()).toString();

特别注意 : 一个 Stream 只可以使用一次,上面的代码为了简洁而重复使用了多次。
这个代码直接运行会抛出异常的:
java.lang.IllegalStateException: stream has already been operated upon or closed

2.10.4 Stream操作

当把一个数据结构包装成Stream后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。

Intermediate:中间操作
map (mapToInt, flatMap 等)、filter、distinct、sorted、peek、limit、skip、parallel、sequential、unordered

Terminal: 最终操作
forEach、forEachOrdered、toArray、reduce、collect、min、max、count、anyMatch、allMatch、noneMatch、findFirst、findAny、iterator

Short-circuiting: 短路操作
anyMatch、allMatch、noneMatch、findFirst、findAny、limit


map/flatMap映射: 把Stream中的每一个元素,映射成另外一个元素。

例子:

//转换大写
Stream<String> stream = Stream.of("hello","world","tom");
List<String> output = stream.
		 		map(String::toUpperCase).
			  	collect(Collectors.toList());

//把stream中的值都返回其二进制的字符串形式
Stream<Integer> numList = Stream.of(1,2,3);
	List<String> output = numList.
			 		map(Integer::toBinaryString).
				  	collect(Collectors.toList());


//也可以直接使用forEach循环输出
Stream<String> wordList = Stream.of("hello","world","tom");
wordList.map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);

注:这里最后调用的forEach方法是Iterable接口中的默认方法

default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
}

注:Stream中也有一个forEach方法,也可以直接遍历到每个数据
wordList.forEach(System.out::println);

Stream接口中的抽象方法:
void forEach(Consumer<? super T> action);

而Consumer接口中的方法为:
public void accept(T t);

这个方法刚好和System.out对象的println方法类似,所以:
Consumer c = System.out::println;

例子:
计算平方数

List<Integer> nums = Arrays.asList(1, 2, 3, 4);
List<Integer> squareNums = 
			nums.stream().
			map(n -> n * n).
			collect(Collectors.toList());

map生成的是个1:1映射,每个输入元素,都按照规则转换成为另外一个元素。

还有一些场景,是一对多映射关系的,这时需要 flatMap。
map和flatMap的方法声明是不一样的

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

例子:

//stream1中的每个元素都是一个List集合对象
Stream<List<Integer>> stream1 = Stream.of(
				 Arrays.asList(1),
				 Arrays.asList(2, 3),
				 Arrays.asList(4, 5, 6)
);
Stream<Integer> stream2 = stream1.flatMap(e->e.stream());
			
//输出1 2 3 4 5 6
stream2.forEach(e->System.out.println(e));

flatMap 把 stream1 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终新的 stream2 里面已经没有 List 了,都是直接的数字。

例子:

Stream<String> stream1 = Stream.of("tom.Li","lucy.Liu");
//flatMap方法把stream1中的每一个字符串都用[.]分割成了俩个字符串
//最后返回了一个包含4个字符串的stream2
Stream<String> stream2 = stream1.flatMap(s->Stream.of(s.split("[.]")));
stream2.forEach(System.out::println);

输出结果:

	tom
	Li
	lucy
	Liu
forEach 遍历

接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。
forEach 是 terminal 操作,执行完stream就不能再用了
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
list.stream().forEach(System.out::println);
filter 过滤

对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。
通过一个predicate接口来过滤并只保留符合条件的元素,该操作属于中间操作,所以我们可以在过滤后的结果来应用其他Stream操作(比如forEach)。forEach需要一个函数来对过滤后的元素依次执行。forEach是一个最终操作,所以我们不能在forEach之后来执行其他Stream操作
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
list.stream().filter(s->s.length()>4).forEach(System.out::println);

注意:System.out::println 这个是lambda表达式中对静态方法的引用

peek

对每个元素执行操作并返回一个新的 Stream
注意:调用peek之后,一定要有一个最终操作,否则代码不执行
peek是一个intermediate 操作
例子:

List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> list2 = list.stream()
				 .filter(e -> e.length() > 3)
				 .peek(e -> System.out.println("符合条件的值为: " + e))
				 .collect(Collectors.toList());
//打印结果为 2
System.out.println(list2.size());

最后list2中就存放的筛选出来的元素

findFirst

总是返回 Stream 的第一个元素,或者空,返回值类型:Optional。
如果集中什么都没有,那么list.stream().findFirst()返回一个Optional对象,但是里面封装的是一个null。
注:只是返回并没有移除
例子:

List<String> list = Arrays.asList("test","hello","world");
Optional<String> first = list.stream().findFirst();
System.out.println(first.orElse("值为null"));
sort 排序
排序是一个中间操作,返回的是排序好后的Stream。如果你不指定一个自定义的Comparator则会使用默认排序。
对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类操作: map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
list.stream().sorted().filter(s->s.startsWith("j")).forEach(System.out::println);
//按照字符串的长短排序
list.stream().sorted((s1,s2)->s1.length()-s2.length()).forEach(System.out::println);

需要注意的是,排序只创建了一个排列好后的Stream,而不会影响原有的数据源,排序之后原数据list是不会被修改的:

Match 匹配
Stream提供了多种匹配操作,允许检测指定的Predicate是否匹配整个Stream。所有的匹配操作都是最终操作,并返回一个boolean类型的值。

//allMatch
//所有元素匹配成功才返回true 否则返回false
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
boolean allMatch = list.stream().allMatch((s)->s.startsWith("j"));
System.out.println(allMatch);

//任意一个匹配成功就返回true 否则返回false
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
boolean anyMatch = list.stream().anyMatch((s)->s.startsWith("j"));
System.out.println(anyMatch);

//没有一个匹配的就返回true 否则返回false
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
boolean noneMatch = list.stream().noneMatch((s)->s.startsWith("j"));
System.out.println(noneMatch);
Count 计数
计数是一个最终操作,返回Stream中元素的个数,返回值类型是long。
例子:

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
long count = list.stream().filter(s->s.startsWith("j")).count();
System.out.println(count);
Reduce 规约/合并

这是一个最终操作,允许通过指定的函数来将stream中的多个元素规约合并为一个元素.
它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max等都是特殊的reduce。
例如:

//1~10之间的数字累加
IntStream stream = IntStream.rangeClosed(1, 10);
Integer sum = stream.reduce(0, (a, b) -> a+b); 

Integer sum = stream.reduce(0, Integer::sum);

也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。
OptionalInt min = stream.reduce((a, b) -> a<b?a:b);

// 字符串连接,concat = "ABCD"
String concat = Stream.of("A", "B", "C", "D").reduce("", String::concat); 
Optional<String> opStr = Stream.of("A", "B", "C", "D").reduce(String::concat); 

思考:reduce方法中的参数是BinaryOperator接口类型的,其抽象方法为:public T apply(T t1, T t1);
String::concat表示使用的是public String concat(String str){…}
那么lambda表达式是怎么把concat方法匹配给apply方法的?

之前讲的一种特殊情况,类名::非静态方法

BinaryOperator<String> bo = String::concat;

例子:

List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
Optional<String> reduce = list.stream().sorted((s1,s2)->s2.length()-s1.length()).filter(s->s.startsWith("j")).map(s->s+"_briup").reduce((s1,s2)->s1+"|"+s2);
System.out.println(reduce.orElse("值为空"));
//打印结果为: javascript_briup|javap_briup|java_briup

整个代码有点长,可以换行看下:

Optional<String> reduce    =  list.stream()
				  .sorted((s1,s2)->s2.length()-s1.length())
				  .filter(s->s.startsWith("j"))
				  .map(s->s+"_briup")
				  .reduce((s1,s2)->s1+"|"+s2);

	1.先调用stream方法
	2.再排序,按照字符串的长度进行排序,长的在前短的再后
	3.再过滤,字符串必须是以字符'j'开头的
	4.再进行映射,把每个字符串后面拼接上"_briup"
	5.再调用reduce进行合并数据,使用"|"连接字符串
	6.最后返回Optional<String>类型数据,处理好的字符串数据就封装在这个对象中				  
limit/skip

limit 返回 Stream 的前面 n 个元素;skip 则是跳过前 n 个元素只要后面的元素
例子:

List<String> list = Arrays.asList("test","javap","hello","world","java","tom","C","javascript");
list.stream().limit(5).forEach(System.out::println);
list.stream().skip(5).forEach(System.out::println);
min/max/distinct

例子:
找出字符文件中字符字符最长的一行

BufferedReader br = new BufferedReader(new FileReader("src/com/briup/test/a.txt"));
int maxLen = br.lines().
	   	mapToInt(String::length).
	   	max().
	   	getAsInt();

System.out.println(maxLen);	 	

注意:lines方法把文件中所有行都返回并且转换为一个Stream类型对象,因为每行读出的String类型数据,同时String::length是使用方法引用的特殊方式(因为泛型的缘故),max()方法执行后返回的时候OptionalInt类型对象,所以接着调用了getAsInt方法来获得这次运行结果的int值

例子:
找出全文的单词,转小写,去掉空字符,去除重复单词并排序

BufferedReader br = new BufferedReader(new FileReader("src/com/libo/test/xxx.txt"));
br.lines().
   flatMap(s->Stream.of(s.split(" "))).
   filter(s->s.length()>0).
   map(s->s.toLowerCase()).
   distinct().
   sorted().
   forEach(System.out::println);
Stream.generate
通过Supplier接口,可以自己来控制Stream的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,由于它是无限的,在管道中,必须利用limit之类的操作限制Stream大小。可以使用此方式制造出海量的测试数据

public static<T> Stream<T> generate(Supplier<T> s);

例子:
生成100个随机数并由此创建出Stream实例

Stream.generate(()->(int)(Math.random()*100)).limit(100).forEach(System.out::println);

或者:

Stream.generate(()->new Random().nextInt(100)).limit(100).forEach(System.out::println);
Stream.iterate
iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(假设是 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,f(f(f(seed))) 第四个,以此类推。
该方法的声明为:

public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f)

UnaryOperator接口继承了Function接口:

public interface UnaryOperator<T> extends Function<T, T>

例子:
生成一个等差数列

Stream.iterate(0, n -> n + 3).
			limit(10). 
			forEach(System.out::println);

打印结果:

0 3 6 9 12 15 18 21 24 27 
java.util.stream.Collectors类
Collectors类的主要作用就是辅助进行各类有用的操作。
例如把Stream转变输出为 Collection,或者把Stream元素进行分组。
例子:
把Stream中的元素进行过滤然后再转为List集合

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");
List<String> result = list.stream().filter(s->s.length()>4).collect(Collectors.toList());

分组:按照字符串的长度分组

List<String> list = Arrays.asList("test","hello","world","java","tom","C","javascript");

相同长度的字符串放到一个List集合中作为Map的value,字符串的长度作为Map的Key

Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(String::length));

或者

Map<Integer, List<String>> collect = list.stream().collect(Collectors.groupingBy(s->s.length()));

输出map中的key和value

collect.forEach((k,v)->{
	System.out.println(k+" : "+v);
});

分割
按照某一条件,把数据分割为两部分,一部分数据符合该条件(true),另一部分不符合该条件(false)
例如:
按照字符串是否包含java进行划分,把数据分为两部分,一部分包含java字样,另一部分则不包含java字样

Map<Boolean, List<String>> collect = 
		list.stream().collect(Collectors.partitioningBy(s->s.indexOf("java")!=-1));
for(Boolean b:collect.keySet()){
	System.out.println(b+" : "+collect.get(b).size());
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值