Java8—Stream流式编程Optional容器的详细介绍两万字
Posted 刘Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8—Stream流式编程Optional容器的详细介绍两万字相关的知识,希望对你有一定的参考价值。
基于Java8详细介绍了Stream流的含义和大部分API方法的使用,以及Optional容器、并行流等Java8的新特性!
文章目录
- 1 Stream的概述
- 2 流的操作
- 3 流的使用
- 4 并行流
- 5 总结
1 Stream的概述
public interface Stream< T >
extends BaseStream<T,Stream< T >>
Java8开始提供了Stream API,这些API方法是用函数式编程方式在对一批数据集合进行复杂操作的工具,简称“流”。流的特点如下:
- Stream将不同的操作使用不同的方法来描述,比如filter、map、reduce、find、match、sort,它的数据处理类似于数据库的操作。我们只需要编写核心的代码逻辑,这样可以让我们在一定程度上摆脱循环遍历、if判断等控制语句的编写,相当于对集合的操作做了更高层次的抽象。
- 相比于集合的操作,很多流操作本身会返回一个流。对于filter过滤、sorted排序、map收集等等操作,它们都是一个个的动作,因此我们还可以将这些动作链接起来,来表达复杂的数据处理流水线,比如先过滤,然后排序,最后收集输出……
- 就像集合必须存放数据一样,流也需要一个提供数据的源,这个源可以是集合、数组、或者一个函数!如果数据源是有序的,那么生成的流也是有序的!
- 流操作可以单线程顺序执行,也可多线程并行执行。
- 获得流之后,流里面的数据就不能在增加或者删除了,所有的操作都是基于流中现有的数据的操作。和迭代器类似,一个流只能被使用(遍历)一次,如果我们还需要其他按要求,此时只能从数据源那么再一次获取一个新的流。
- 使用普通集合操作元素,需要我们写迭代的代码,这被称为外部迭代,比如for、while、foreach,而流则帮助我们进行了内部数据迭代,这就有了并行迭代优化速度的可能,而我们无需关心它是怎么做的!
- 流元素按需计算,比如要找出大于10 的第一个数字,那么并不需要和所有元素去做比较,只要找出第一个匹配的元素就够了。
- Streams有一个BaseStream.close()方法和实现AutoCloseable,但几乎所有的流实例实际上不需要在使用后关闭。 一般来说,只有来源为IO通道的流(如Files.lines(Path, Charset)返回的流 )才需要关闭。 大多数流都由集合,数组或生成函数支持,这些函数不需要特殊的资源管理。 (如果流确实需要关闭,则可以在try -with-resources语句中将其声明为资源。)
- Stream API通常是接收一个函数式接口为参数,因此对lambda的支持非常好,Stream API配合lambda表达式,我们可以写出非常漂亮且简练的链式编程代码!可以说,要想把流用得好,那么lambda表达式必须先掌握,关于lambda表达式可以看这篇文章:Java8—一万字的lambda表达式的详细介绍与应用案例。
先看看一个常见的普通集合操作和流操作的代码对比,体验流式编程的连续性以及简单性:
public class StreamFrist
public static void main(String[] args)
//学生集合,学生有age-年龄 ,name-名字,score-分数,三个属性
List<Student> students = new ArrayList<>();
students.add(new Student(10, 55, "小花"));
students.add(new Student(13, 100, "小华"));
students.add(new Student(9, 85, "晓华"));
students.add(new Student(8, 70, "肖华"));
//我们需要筛选出成绩大于等于60的学生名字
//使用普通集合操作
ArrayList<String> nameList1 = new ArrayList<>();
for (Student student : students)
if (student.getScore() >= 60)
nameList1.add(student.getName());
//使用流,不同的操作都是链式的,非常适合人类的思维,而不需要考虑迭代、判断操作
List<String> nameList2 = students.stream()
//筛选出成绩大于等于60的学生
.filter(student -> student.getScore() >= 60)
//收集学生名字
.map(Student::getName)
//返回结果
.collect(toList());
2 流的操作
Stream API定义了许多流的操作,它们大概可以分为两类:
- 中间操作
- 中间操作的一个显著特征是会返回另一个Stream流,比如filter、sorted、linit等等,这样的好处是我们可以进行链式编程,形成一个操作流水线!
- 中间操作的另一个隐藏特征是延迟执行,或者称为惰性求值!因为他只是描述了流水线要进行的操作,而并没有真正的执行这条流水线,它需要一个“触发操作”,这就是终端操作!
- 终端操作
- 终端操作的一个显著特征是不会返回另一个Stream流,比如count、collect,相当于从流水线中获取结果,只有存在终端操作,中间操作才会执行。
- 终端操作的另一个隐藏特征是它会终结这个流,此后这个流不能被重复使用,也被称为及早求值!
案例演示:
@Test
public void test()
//学生集合,学生有age-年龄 ,name-名字,score-分数,三个属性
List<Student> students = new ArrayList<>();
students.add(new Student(10, 55, "小花"));
students.add(new Student(13, 100, "小华"));
students.add(new Student(9, 85, "晓华"));
students.add(new Student(8, 70, "肖华"));
//没有终端操作的流,不会执行中间操作
students.stream()
//筛选出成绩大于等于60的学生
.filter(student ->
System.out.println("中间操作" + student.getScore());
return student.getScore() >= 60;
)
//收集学生名字
.map(Student::getName);
//有终端操作的流,才会执行
//没有终端操作的流,不会执行中间操作
students.stream()
//筛选出成绩大于等于60的学生
.filter(student ->
System.out.println("终端操作" + student.getScore());
return student.getScore() >= 60;
)
//收集学生名字
.map(Student::getName)
//collect是一个终端操作
.collect(toList());
3 流的使用
要想使用流,需要做三件事:
- 数据来源,比如集合、数组、函数,用于生成流,这是必须的
- 一系列中间流水线操作,用于对数据进行筛选、过滤、整合等等,中间操作不是必须的;
- 一个终端操作,用于触发流水线中的中间操作的执行,消耗流并且获取结果;
我们可以发现流的使用和Java构建者模式类似,构建者模式使用一系列操作设置属性和配置,最后调用一个build方法时,对象才被真正创建。而流操作同样使用一系列中间操作,最后调用一个终端操作方法,该方法触发中间操作的执行并获取最终的结果!
Stream提供了非常多的API方法,这些方法可以根据不同的作用进行分类讲解!
3.1 获取流
有以下常见方式获取流:
- 从集合
- Java8开始Collection超级接口中,提供了一个stream()默认方法,这个方法可以从调用集合中获取全部集合元素的流,因此全部的Collection体系下面的集合都可以调用stream()方法获取流,流元素就是单个集合元素。
- 注意,Map集合中没有直接获取流的方法!
- 从数组
- 数组的Arrays.stream静态方法接收一个数组,返回一个具有数组全部元素的流,流元素就是单个数组元素。
- 从文件
- Files类中具有读取文件并且生成流的方法,最重要的就是lines方法,用于获取文件的所有行的流。
- 从函数
- Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。从函数生成的流被称为无限流,通常我们只需要截取一部分即可!
- static < T > Stream< T > iterate(T seed, UnaryOperator< T > f),iterate第一个参数是一个初始值,第二个参数是一个依次应用在每个产生的新值上的UnaryOperator,即一元操作器,这个 将上一次生成的值当作参数,生成下一个值。
- static < T > Stream< T > generate(Supplier< T > s),generate方法没有指定初始值,后面的值也不是依据前一个值计算出来的,generate接受一个Supplier,它的值就是通过这个生产者返回的!因此,如果我们需要获取随机数,使用generate就很方便!
- 指定元素
- static < T > Stream< T > of(T… values),返回其元素是指定值的顺序排序流。
- static < T > Stream< T > of(T t),返回包含单个元素的顺序 Stream 。
获取流的案例:
/**
* @author lx
*/
public class CreateTest
/**
* @author lx
*/
class Filter
private int x;
public int getX()
return x;
public void setX(int x)
this.x = x;
public Filter(int x)
this.x = x;
public Filter()
@Override
public String toString()
return "Filter" +
"x=" + x +
'';
/**
* 从集合获取流
*/
@Test
public void test()
List<Filter> filters = new ArrayList<>();
filters.add(new Filter(0));
filters.add(new Filter(3));
filters.add(new Filter(9));
filters.add(new Filter(8));
//从集合
Stream<Filter> stream = filters.stream();
stream.forEach(System.out::println);
/**
* 从数组获取流
*/
@Test
public void test1()
Filter[] filArr = new Filter[]new Filter(1),
new Filter(3),
new Filter(9),
new Filter(8);
//从数组
Stream<Filter> stream = Arrays.stream(filArr);
stream.forEach(System.out::println);
/**
* 从文件获取流
*/
@Test
public void test2()
//读取文件的所有行的流
try (Stream<String> lines = Files.lines(Paths.get("target/classes/lines.txt")))
lines.forEach(System.out::println);
catch (IOException e)
e.printStackTrace();
/**
* iterate获取10个偶数
*/
@Test
public void iterate()
Stream.iterate(0, n -> n + 2)
//取前十个数据(后面会讲,这叫做“筛选”)
.limit(10)
.forEach(System.out::println);
/**
* iterate获取下标
* 采用Stream通过下标遍历集合
*/
@Test
public void iterate2()
ArrayList<Object> objects = new ArrayList<>();
objects.add(1);
objects.add(3);
objects.add(2);
objects.add(4);
Stream.iterate(0, i -> i + 1)
//截取前集合长度的数据
.limit(objects.size())
.forEach(i -> System.out.println(i + "-->" + objects.get(i)));
/**
* generate获取10个随机数
*/
@Test
public void generate()
Stream.generate(Math::random)
.limit(10)
.forEach(System.out::println);
//复杂数据生成
/**
* iterate获取10个斐波那契数
*/
@Test
public void iterateFibonacci()
//斐波那契数列的规律:F(0)=0,F(1)=1,F(2)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
//我们初始化数组new int[]0, 1
//后续的数组的第1个元素是前一个数组的第2个元素,后续的数组的第2个元素是前一个数组的第1、2个元素的和
//这样实际上生成的数组如下:
//new int[]0, 1
//new int[]1, 1
//new int[]1, 2
//new int[]2, 3
//new int[]3, 5
//new int[]5, 8
//new int[]8, 13
//new int[]13, 21
//new int[]21, 34
//取每个数组的第一个元素就是斐波那契数
Stream.iterate(new int[]0, 1,
t -> new int[]t[1], t[0] + t[1])
//生成10个数组
.limit(10)
//获取每个数组的第一个元素(后面会讲,这叫做“映射”)
.map(t -> t[0])
.forEach(System.out::println);
/**
* Stream.of
*/
@Test
public void of()
Stream.of(1, 2, 3, "11").forEach(System.out::println);
3.2 筛选操作
Stream API提供了对流元素的筛选操作,既有普通的条件筛选filter,也有特殊的筛选,比如distinct去重、limit和skip截取!
筛选操作是一个中间操作!
Stream< T > filter(Predicate< ? super T > predicate)
filter是使用最广泛的筛选操作,它接受一个断言,返回由与此给定断言匹配的此流的元素组成的流。
Stream< T > distinct()
返回一个由该流的不同元素(根据元素的equals方法)组成的流。对于有序流,选择不同的元素是稳定的(对于重复的元素,首先在遇到顺序中出现的元素被保留。)对于无序流,不能保证稳定性。
Stream< T > limit(long maxSize)
返回由该流的前maxSize元素组成的流,limit会最多截取流元素的前maxSize个。
Stream< T > skip(long n)
在丢弃流的前n个元素后,返回由该流的前n元素之后的元素组成的流。 如果此流包含少于n元素,那么将返回一个空流。
使用案例:
/**
* @author lx
*/
public class FilterTest
List<Student> students = new ArrayList<>();
@Before
public void test()
students.add(new Student(10, 55, "小花"));
students.add(new Student(13, 100, "小华"));
students.add(new Student(9, 85, "晓华"));
students.add(new Student(8, 70, "肖华"));
students.add(new Student(8, 70, "肖华"));
@Test
public void filter()
System.out.println("filter筛选成绩大于等于70的学生");
//filter筛选成绩大于等于70的学生
students.stream().filter(student -> student.getScore() >= 70).forEach(System.out::println);
System.out.println("filter+distinct筛选成绩大于等于70的学生,且去除重复数据");
//filter+distinct筛选成绩大于等于70的学生,且去除重复数据
students.stream().filter(student -> student.getScore() >= 70).distinct().forEach(System.out::println);
System.out.println("limit最多截取前2个数据");
//limit最多截取前2个数据
students.stream().filter(student -> student.getScore() >= 70).limit(2).forEach(System.out::println);
System.out.println("skip丢弃前2个数据");
//skip丢弃前2个数据
students.stream().filter(student -> student.getScore() >= 70).skip(2).forEach(System.out::println);
System.out.println("skip丢弃前1个数据,limit最多截取前1个数据");
students.stream().filter(student -> student.getScore() >= 70).skip(1).limit(1).forEach(System.out::println);
static class Student
private int age;
private int score;
private String name;
public int getAge()
return age;
public void setAge(int age)
this.age = age;
public int getScore()
return score;
public void setScore(int score)
this.score = scoreJava8 Stream流式编程