[Java][华为云Java编程创造营][学习笔记][第三阶段][06_Java集合][05_Stream]
【摘要】 5,Java Stream 5.1,Java8的新特性Stream概述Stream的概述流Stream是数据渠道,用于操作数据源集合、数组等所生成的元素序列。集合讲的是数据,流讲的是计算。Stream不是数据结构,不会保存数据。Stream不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。Stream是惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到...
5,Java Stream
5.1,Java8的新特性Stream概述
-
Stream的概述
- 流Stream是数据渠道,用于操作数据源集合、数组等所生成的元素序列。
- 集合讲的是数据,流讲的是计算。
-
Stream不是数据结构,不会保存数据。
-
Stream不会修改原来的数据源,它会将操作后的数据保存到另外一个对象中。
-
Stream是惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算。
Stream流
- Stream就好像一个高级的迭代器,但只能遍历一次,就好像一江春水向东流;在流的过程中,对流中的元素执行一些操作,比如"过滤掉长度大于10的字符串"、"获取每个字符串的首字母"等。
- 假设我们现在要统计一个
List<Person>
里面的男性个数,那么代码我们通常是这样写的。
import java.util.Iterator;
public class Test
{
public int countMan()
{
int count = 0;
Iterator<Person> iterator = personList.iterator();
while (iterator.hasNext())
{
Person person = iterator.next();
if (person.getGender().equal("男"))
{
count++;
}
}
return count;
}
}
循环迭代遍历的弊端
-
Java8让我们可以更加专注于做什么(What),而不是怎么做(How)。现在,我们仔细体会一下上例代码。可以发现:
- 循环的语法就是"怎么做"
- 循环的循环体才是"做什么"
-
为什么使用循环?因为要进行遍历,但循环是遍历的唯一方式吗?遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。
使用Stream流解决问题
- 我们要做的事情是,获取到Stream,告诉Stream我们要做什么,然后问Stream要结果。
- 实际上,Stream并没有new一个新的List。
- Steam在这里主要做了两件事情。
- 第一,把所有人获取到
- 第二,把"男性"过滤出来。
- 优化后的代码
public class Test
{
public int countMan()
{
return personList.stream().filter(person -> person.getGender().equal("男")).count();
}
}
5.2,Stream的操作流程和创建
-
Stream操作的三个步骤:
- 创建Stream
- 一个数据源(集合、数组),获取一个流。
- 中间操作
- 一个中间操作链,对数据源的数据进行处理。
- 终止操作
- 一个终止操作,执行中间操作链。并产生结果。
- 创建Stream
Stream操作分类 | ||
---|---|---|
中间操作 | 无状态 | Filter() map() peek() flatMap() |
中间操作 | 有状态 | distinct() sorted() limit() skip() |
结束操作 | 非短路操作 | forEach() toArray() reduce() max() min() count() collect() |
结束操作 | 短路操作 | findFirst() findAny() allMatch() anyMatch() noneMatch() |
- 无状态:指元素的处理不受之前元素的影响。
- 有状态:指该操作只有拿到所有元素之后才能继续下去。
- 非短路操作:指必须处理所有元素才能得到最终结果。
- 短路操作:指遇到某些符合条件的元素就可以得到最终结果,如A||B,只要A为true,则无需判断B的结果。
5.3,Stream的中间操作
- 中间操作:中间操作会返回一个新的流,一个流可以后面跟随零个或多个intermediate操作。
- 中间操作目的主要是打开流,做出某种程度的数据映射/过滤,然后会返回一个新的流,交给下一个操作使用。这类操作都是惰性的,就是说,仅仅调用到这类方法,并没有真正开始流的遍历。而是在终端操作开始的时候才真正开始执行。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test
{
public static void main(String[] args)
{
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
//filter():返回结果生成新的流中只包含满足筛选条件的数据
List<Integer> result = nums.stream().filter(n -> n > 3).collect(Collectors.toList());
System.out.println(result);//[4, 5]
}
}
5.4,Stream的串行流和并行流
-
Stream的串行流
- 所有的Collection集合都可以通过stream默认方法获取流:list.stream();
- Stream接口的静态方法of可以获取数组对应的流:Steam.of(6,1,5,4,3);
-
Stream的并行流
- 并行流是把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流。
- 获取并行流的两种方式:
- 直接获取并行的流:
- ArrayList<Integer> list = new ArrayList<>();
- Stream<Integer> stream = list.parallelStream();
- 将串行流转成并行流:
- ArrayList<Integer> list2 = new ArrayList<>();
- Stream<Integer> stream1 = list2.stream().parallel();
- 直接获取并行的流:
-
stream和parallelStream的简单区分:stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:
-
如果流中的数据量足够大,并行流可以加快处理速度。除了直接创建并行流,还可以通过
parallel()
把顺序流转换成并行流。
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class Test
{
public static void main(String[] args)
{
//初始化1千万条随机数据
System.out.println("初始化测试数据开始...");
List<UUID> datas = Stream.iterate(UUID.randomUUID(),
t -> UUID.randomUUID()).limit(10000000).collect(Collectors.toList());
System.out.println("初始化测试数据结束...");
//开始测试串行流
System.out.println("测试串行流开始");
long streamStartTime = System.nanoTime();
datas.stream().sorted().count();
long streamEndTime = System.nanoTime();
System.out.println("测试串行流结束");
//开始测试并行流
System.out.println("测试并行流开始");
long parallelStreamStartTime = System.nanoTime();
datas.parallelStream().sorted().count();
long parallelStreamEndTime = System.nanoTime();
System.out.println("测试并行流结束");
System.out.println("测试完毕,计算结果...");
BiFunction<Long, Long, Long> workMillionTime = (start, end) -> TimeUnit.NANOSECONDS.toMillis(end - start);
long streamTime = workMillionTime.apply(streamStartTime, streamEndTime);//计算串行耗时
long paramStreamTime = workMillionTime.apply(parallelStreamStartTime, parallelStreamEndTime);//计算并行耗时
//串行并行耗时测试结果
System.out.println(String.format("串行流耗时:%d,并行流耗时:%d", streamTime, paramStreamTime));
/*
* 输出结果
* 初始化测试数据开始...
初始化测试数据结束...
测试串行流开始
测试串行流结束
测试并行流开始
测试并行流结束
测试完毕,计算结果...
串行流耗时:4295,并行流耗时:1437
* */
}
}
5.5,Stream的filter过滤操作
- Stream流中的常用方法filter:用于对Stream流中的数据进行过滤,filter方法的参数Predicate是一个函数式接口,所以可以传递Lambda表达式,对数据进行过滤。
import java.util.stream.Stream;
public class Test
{
public static void main(String[] args)
{
//创建Stream流
Stream<String> stream = Stream.of("aa", "bb", "cc", "dd", "apple");
stream.filter(name -> name.startsWith("a")).forEach(name -> System.out.println(name));
}
/*
* 输出结果
* aa
apple
* */
}
5.6,Stream的sorted排序
- Stream的sorted()排序,能够以自然顺序或用Compactor接口定义的排序规则来排序一个流,Comparator能用Lambda表达式来初始化,还能够逆序一个已经排序的流。
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
public class Test
{
public static void main(String[] args)
{
List lists = new ArrayList();
lists.add(10);
lists.add(9);
lists.add(1);
lists.add(0);
lists.add(3);
lists.add(6);
lists.stream().sorted().forEach(i -> System.out.print(i + ","));//0,1,3,6,9,10,
System.out.println();
lists.stream().sorted(Comparator.reverseOrder()).forEach(i -> System.out.print(i + ","));//10,9,6,3,1,0,
}
}
- Stream的sorted()自定义排序:用Comparable接口定义的排序规则来排序一个流:
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
class Student implements Comparable<Student>
{
private int id;
private String name;
private int age;
public Student(int id, String name, int age)
{
this.id = id;
this.name = name;
this.age = age;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
Student student = (Student) obj;
if (this == student)
{
return true;
}
else
{
return (this.name.equals(student.name) && (this.age == student.age));
}
}
@Override
public int compareTo(Student o)
{
return name.compareTo(o.getName());
}
@Override
public int hashCode()
{
int hashno = 7;
hashno = 13 * hashno + (name == null ? 0 : name.hashCode());
return hashno;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
public int getAge()
{
return age;
}
public void setAge(int age)
{
this.age = age;
}
}
public class Test
{
public static void main(String[] args)
{
List<Student> list = new ArrayList<>();
list.add(new Student(1, "Mahesh", 12));
list.add(new Student(2, "Suresh", 15));
list.add(new Student(3, "Nilesh", 10));
System.out.println("---Natural Sorting by Name---");
List<Student> slist = list.stream().sorted().collect(Collectors.toList());
slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
/*
* 输出结果
* ---Natural Sorting by Name---
Id: 1,Name: Mahesh,
Id: 3,Name: Nilesh,
Id: 2,Name: Suresh,
* */
System.out.println("---Natural Sorting by Name in reverse order---");
slist = list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());
slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
/*
* 输出结果
* ---Natural Sorting by Name in reverse order---
Id: 2,Name: Suresh,Age: 15
Id: 3,Name: Nilesh,Age: 10
Id: 1,Name: Mahesh,Age: 12
* */
System.out.println("---Sorting using Comparator by Age---");
slist = list.stream().sorted(Comparator.comparing(Student::getAge)).collect(Collectors.toList());
slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
/*
* 输出结果
* ---Sorting using Comparator by Age---
Id: 3,Name: Nilesh,Age: 10
Id: 1,Name: Mahesh,Age: 12
Id: 2,Name: Suresh,Age: 15
* */
System.out.println("---Sorting user Comparator by Age with reverse order---");
slist = list.stream().sorted(Comparator.comparing(Student::getAge).reversed()).collect(Collectors.toList());
slist.forEach(e -> System.out.println("Id: " + e.getId() + ",Name: " + e.getName() + ",Age: " + e.getAge()));
/*
* 输出结果
* ---Sorting user Comparator by Age with reverse order---
Id: 2,Name: Suresh,Age: 15
Id: 1,Name: Mahesh,Age: 12
Id: 3,Name: Nilesh,Age: 10
* */
}
}
5.7,Stream的limit限制条目
- Stream流中的常用方法limit:用于截取流中的元素,limit方法可以对流进行截取,只取用前n个。
import java.util.stream.Stream;
public class Test
{
public static void main(String[] args)
{
//获取Stream流
String[] arr = {"aa", "bb", "cc", "dd", "ee"};
Stream<String> stream = Stream.of(arr);
//使用limit对Stream流中的元素进行截取
stream.limit(3).forEach(name -> System.out.println(name));
/*
* 输出结果
* aa
bb
cc
* */
}
}
5.8,Steam的distinct去重
- Stream流的distinct方法对自定义对象去重,定义的对象需要实现hashcode()和equals()方法:
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Test
{
public static void main(String[] args)
{
List<String> list = Arrays.asList("aa", "bb", "cc", "aa");
long l = list.stream().distinct().count();
System.out.println("No. of distinct elements: " + l);
String output = list.stream().distinct().collect(Collectors.joining(","));
System.out.println(output);
}
/*
* 输出结果
* No. of distinct elements: 3
aa,bb,cc
* */
}
import java.util.ArrayList;
import java.util.List;
class Book
{
private String name;
private int price;
public Book(String name, int price)
{
this.name = name;
this.price = price;
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
{
return false;
}
Book book = (Book) obj;
if (this == book)
{
return true;
}
else
{
return (this.name.equals(book.name) && (this.price == book.price));
}
}
@Override
public int hashCode()
{
int hashno = 7;
hashno = 13 * hashno + (name == null ? 0 : name.hashCode());
return hashno;
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public int getPrice()
{
return price;
}
public void setPrice(int price)
{
this.price = price;
}
}
public class Test
{
public static void main(String[] args)
{
List<Book> list = new ArrayList<>();
list.add(new Book("CoreJava", 200));
list.add(new Book("CoreJava", 200));
list.add(new Book("SpringMVC", 200));
long l = list.stream().distinct().count();
System.out.println("No. of distinct books:" + l);
list.stream().distinct().forEach(b -> System.out.println(b.getName() + "," + b.getPrice()));
}
/*
* 输出结果
* No. of distinct books:2
CoreJava,200
SpringMVC,200
* */
}
5.9,Stream的skip跳过元素
- Stream流中的常用方法skip:用于跳过元素,如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流。
import java.util.stream.Stream;
public class Test
{
public static void main(String[] args)
{
//获取一个Stream流
String[] arr = {"aa", "bb", "cc", "dd", "ee"};
Stream<String> stream = Stream.of(arr);
//使用skip方法跳过前四个元素
stream.skip(4).forEach(name -> System.out.println(name));
}
/*
* 输出结果
* ee
* */
}
5.10,Stream的mapTo统计
- Stream的mapTo返回一个Stream,其中包括将给定函数应用于此流的元素的结果。
import java.util.Arrays;
import java.util.List;
public class Test
{
public static void main(String[] args)
{
List<String> list = Arrays.asList("1", "2", "3", "4");
list.stream().mapToInt(num -> Integer.parseInt(num)).filter(num -> num % 3 == 0)
.forEach(i -> System.out.print(i + ","));
/*
* 输出结果
* 3,
* */
}
}
映射(map)
- 映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。
- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
5.11,Stream的collect规约操作
- Stream的collect是一个把stream规约成一个value的规约操作。
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Test
{
public static void main(String[] args)
{
List<Integer> skipNum = IntStream.range(1, 100).skip(85).boxed()
.collect(Collectors.toList());
System.out.println(skipNum);
}
/*
* 输出结果
* [86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
* */
}
收集(collect)
- collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。
- collect主要依赖
java.util.stream.Collectors
类内置的静态方法。
归集(toList/toSet/toMap)
- 因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。
toList
、toSet
和toMap
比较常用,另外还有toCollection
、toConcurrentMap
等复杂一些的用法。
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)