函数式编程
一句话概述:
函数式编程:函数式编程是一种思想,面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们数学中的函数。它主要关注的是对数据进行了什么操作。
函数式接口:运用了策略模式,指拥有一个方法的接口,这个接口在策略模式中充当抽象策略角色,抽取了要实现的方法,而每一个匿名内部类都是接口实现,在策略模式中对应具体策略
Lambda表达式:Lambda表达式是对函数式编程思想的实现,因为有了lambda表达式才让过去由创建类实例后再调用方法解决实际问题过渡到直接调用方法解决实际问题
stream流:是对函数式编程和Lambda表达式的充分应用,是java8提供的新特性,用于更好解决并发和数据处理的api
1、概述
1.1、 为什么学函数式编程
- 能够看懂公司里的代码
- 大数量下处理集合效率高
- 代码可读性高
- 消灭嵌套地狱
//查询未成年作家的评分在70以上的书籍 由于洋流影响所以作家和书籍可能出现重复,需要进行去重
List<Book> bookList = new ArrayList<>();
Set<Book> uniqueBookValues = new HashSet<>();
Set<Author> uniqueAuthorValues = new HashSet<>();
for (Author author : authors) {
if (uniqueAuthorValues.add(author)) {
if (author.getAge() < 18) {
List<Book> books = author.getBooks();
for (Book book : books) {
if (book.getScore() > 70) {
if (uniqueBookValues.add(book)) {
bookList.add(book);
}
}
}
}
}
}
System.out.println(bookList);
List<Book> collect = authors.stream()
.distinct()
.filter(author -> author.getAge() < 18)
.map(author -> author.getBooks())
.flatMap(Collection::stream)
.filter(book -> book.getScore() > 70)
.distinct()
.collect(Collectors.toList());
System.out.println(collect);
1.2 、函数式编程思想
面向对象思想需要关注用什么对象完成什么事情,而函数式编程思想类似于数学中的函数,它主要关注的是对数据进行了什么操作。
1.3 、函数式编程的优点
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
2、函数式编程接口
2.1、什么是函数式编程接口
所谓的函数式编程接口指只有一个抽象方法的接口
**注意:**接口中default 声明的方法不是抽象方法,而是接口默认方法,可以选择不实现
2.2、函数式接口中的设计模式
2.2.1、策略模式的简介
该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
2.2.2、策略模式的角色
①抽象策略(Strategy)类:这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所要实现的功能。
②具体策略(Concrete Strategy)类:实现了抽象策略定义的接口,提供具体的算法实现或行为。这些策略可以反复交替使用,具体用哪个看环境
③环境(Context)类:持有一个策略类的引用,最终给客户端调用。在不同条件下使用的策略不同
2.2.3、函数式接口中的设计模式分析
函数式接口是对策略模式的应用,其中接口对应抽象策略类,是对具体主题的抽取,而匿名内部类则是承担了具体策略类角色,而环境实现了对具体策略中方法的调用
2.3、JDK中常用函数式接口
JDK的函数式接口都加上了**@FunctionalInterface** 注解进行标识。但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
2.3.1、Consumer 消费型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
2.3.2、Function函数型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算或转换,把结果返回
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
2.3.3、Predicate判断型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
2.3.4、Supplier 生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回
@FunctionalInterface
public interface Supplier<T> {
T get();
}
2.3 、常用的默认方法
2.3.1、and
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而and方法相当于是使用&&来拼接两个判断条件
例如:打印作家中年龄大于17并且姓名的长度大于1的作家。
@Test
public void and() {
// 打印作家中年龄大于17并且姓名的长度大于1的作家。
List<Author> authors = MockData.getAuthors();
authors.stream().filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge() > 17;
}
}.and(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length() > 1;
}
})).forEach(System.out::println);
}
}
2.3.2、or
我们在使用Predicate接口时候可能需要进行判断条件的拼接。而or方法相当于是使用||来拼接两个判断条件。
例如:打印作家中年龄大于17或者姓名的长度小于2的作家。
// 打印作家中年龄大于17或者姓名的长度小于2的作家。
List<Author> authors = getAuthors();
authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge()>17;
}
}.or(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getName().length()<2;
}
})).forEach(author -> System.out.println(author.getName()));
2.3.3、negate
Predicate接口中的方法。negate方法相当于是在判断添加前面加了个! 表示取反
例如:打印作家中年龄不大于17的作家。
// 打印作家中年龄不大于17的作家。
List<Author> authors = getAuthors();
authors.stream()
.filter(new Predicate<Author>() {
@Override
public boolean test(Author author) {
return author.getAge()>17;
}
}.negate()).forEach(author -> System.out.println(author.getAge()));
3、 Lambda表达式
定义:Lambda是JDK8中一个语法糖。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的实现。让我们不用关注是什么对象。而是更关注我们用什么方法对数据进行了什么操作。
核心原则:可推导可省略
基本格式:(参数列表)->{代码}
3.1、匿名内部类简化语法
3.2、方法||参数引用简化语法
我们在使用lambda时,如果方法体中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。语法如下:
对象::实例方法名
类::静态方法名
类::实例方法名
类::参数get方法
3.3、使用演示
**技巧:**alt+enter可以自动转换为lambda方式
现有方法定义如下,其中Function是一个接口。先使用匿名内部类的写法调用该方法。
public static void foreachArr(IntConsumer consumer){
int[] arr = {1,2,3,4,5,6,7,8,9,10};
for (int i : arr) {
// 调用具体实现类的accept方法
consumer.accept(i);
}
}
@Test
public void test04(){
// 调用foreachArr方法,传入具体实现类
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
// lambda写法
foreachArr(value -> System.out.println(value));
}
4、Stream流
定义:是对函数式编程思想的体现和对lambda表达式的充分运用,是为了更好操作数据而提供的api
特点:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新 Stream 。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
使用步骤:
- 第一步 创建 Stream:创建 Stream一个数据源(如:集合、数组),获取一个流
- 第二步 中间操作:中间操作一个中间操作链,对数据源的数据进行处理
- 第三步 终止操作 :一旦执行终止操作, 就执行中间操作链 ,并产生结果 。之后,不会再被使用
流的分类:
案例数据准备:
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
</dependencies>
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author {
//id
private Long id;
//姓名
private String name;
//年龄
private Integer age;
//简介
private String intro;
//作品
private List<Book> books;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Book {
//id
private Long id;
//书名
private String name;
//分类
private String category;
//评分
private Integer score;
//简介
private String intro;
}
private static List<Author> getAuthors() {
//数据初始化
Author author = new Author(1L,"蒙多",33,"一个从菜刀中明悟哲理的祖安人",null);
Author author2 = new Author(2L,"亚拉索",15,"狂风也追逐不上他的思考速度",null);
Author author3 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
Author author4 = new Author(3L,"易",14,"是这个世界在限制他的思维",null);
//书籍列表
List<Book> books1 = new ArrayList<>();
List<Book> books2 = new ArrayList<>();
List<Book> books3 = new ArrayList<>();
books1.add(new Book(1L,"刀的两侧是光明与黑暗","哲学,爱情",88,"用一把刀划分了爱恨"));
books1.add(new Book(2L,"一个人不能死在同一把刀下","个人成长,爱情",99,"讲述如何从失败中明悟真理"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
books2.add(new Book(3L,"那风吹不到的地方","哲学",85,"带你用思维去领略世界的尽头"));
books2.add(new Book(4L,"吹或不吹","爱情,个人传记",56,"一个哲学家的恋爱观注定很难把他所在的时代理解"));
books3.add(new Book(5L,"你的剑就是我的剑","爱情",56,"无法想象一个武者能对他的伴侣这么的宽容"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
books3.add(new Book(6L,"风与剑","个人传记",100,"两个哲学家灵魂和肉体的碰撞会激起怎么样的火花呢?"));
author.setBooks(books1);
author2.setBooks(books2);
author3.setBooks(books3);
author4.setBooks(books3);
List<Author> authorList = new ArrayList<>(Arrays.asList(author,author2,author3,author4));
return authorList;
}
4.1 、创建流
4.1.1、单列集合
集合对象.stream()或者集合对象.parallelStream()
default Stream stream() : 返回一个顺序流
default Stream parallelStream() : 返回一个并行流
List<Author> authors = getAuthors();
Stream<Author> stream = authors.stream();
4.1.2、数组
Arrays.stream(数组)
或者使用Stream.of
来创建
重载形式,能够处理对应基本类型的数组:
public static IntStream stream(int[] array)public static LongStream stream(long[] array)
public static DoubleStream stream(double[] array)
Integer[] arr = {1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
Stream<Integer> stream2 = Stream.of(arr);
4.1.3、双列集合
转换成单列集合后再创建
Map<String,Integer> map = new HashMap<>();
map.put("蜡笔小新",19);
map.put("黑子",17);
map.put("日向翔阳",16);
Stream<Map.Entry<String, Integer>> stream = map.entrySet().stream();
4.2、中间操作
多个中间操作 可以连接起来形成一个 流水线 ,除非流水线上触发终止操作,否则 中间操作不会执行任何的处理 !而在 终止操作时一次性全部处理,称为“惰性求值” 。
4.2.1、筛选与切片
4.2.1.1、filter
打印所有姓名长度大于1的作家的姓名
@Test
public void filter(){
List<Author> authors = MockData.getAuthors();
authors.stream().filter(author ->
author.getName().length() > 1
).forEach(System.out::println);
}
4.2.1.2、distinct
打印所有作家的姓名,并且要求其中不能有重复元素。
@Test
public void distinct(){
List<Author> authors = MockData.getAuthors();
authors.stream().distinct().forEach(System.out::println);
}
4.2.1.3、limit
最多取两条数据
@Test
public void limit(){
List<Author> authors = MockData.getAuthors();
authors.stream().limit(2).forEach(System.out::println);
}
4.2.1.4、skip
跳过头个元素
@Test
public void skip(){
List<Author> authors = MockData.getAuthors();
authors.stream().skip(1).forEach(System.out::println);
}
4.2.2、映射
4.2.2.1、map
接受一个函数,每个元素执行函数,并将执行后的返回值又转换为流,此时流的类型为流中元素的类型
所以map的作用在于:可以把对流中的元素进行计算或转换类型。
将作者们的名字提取出来作为流并输出
原来为Author类型的流最后变为String类型了
// 将作者们的名字提取出来作为流
authors.stream().map(new Function<Author, String>() {
@Override
public String apply(Author author) {
return author.getName();
}
}).forEach(name-> System.out.println(name));
// 简写
authors.stream().map(author -> author.getName())
.forEach(name-> System.out.println(name));
将所有作者年龄+10并输出
@Test
public void map(){
List<Author> authors = MockData.getAuthors();
// 将作者的年龄+10
authors.stream().map(author -> author.getAge()+10).forEach(System.out::println);
}
4.2.2.2、mapToInt
基本数据类型优化
我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。
例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapToDouble等。
@Test
public void mapToInt() {
List<Author> authors = MockData.getAuthors();
// 将作者的年龄类型由Integer类型转换为int避免平凡的装箱拆箱提升效率
authors.stream().map(author -> author.getAge())
.map(new Function<Integer, Object>() {
@Override
public Object apply(Integer integer) {
// 需要拆箱做加法,返回时又要装箱
return integer + 10;
}
}).forEach(System.out::println);
authors.stream().map(author -> author.getAge())
.mapToInt(new ToIntFunction<Integer>() {
@Override
public int applyAsInt(Integer value) {
// 只用拆箱不用装箱了
return value + 10;
}
}).forEach(System.out::println);
}
4.2.2.3、flatMap
可以合并多个流为一个流
打印所有书籍的名字。要求对重复的元素进行去重。
若使用map可以看到只能返回一个List 类型的流,不能产生Book类型的流
// 使用map
authors.stream()
.map(author -> author.getBooks());
这里使用flatMap(),能够将多个Book类型的流合并为一个Book类型的流
@Test
public void flatMap(){
List<Author> authors = MockData.getAuthors();
// 打印所有书籍的名字。要求对重复的元素进行去重。
authors.stream()
// 这里每个作者的书籍都是一个list集合,转为流后会变成多个Book类型的流
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getName())
.distinct().forEach(System.out::println);
}
打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
authors.stream()
.flatMap(author -> author.getBooks().stream())
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(System.out::println);
4.2.3、排序
4.2.3.1、sorted
**注意:**若要比较对象需要让对象实现Comparable接口,自定义比较策略
比如我希望通过年龄来比较
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode//用于后期的去重使用
public class Author implements Comparable<Author>{
//id
private Long id;
//姓名
private String name;
//年龄
private Integer age;
//简介
private String intro;
//作品
private List<Book> books;
@Override
public int compareTo(Author author) {
return this.age - author.getAge();
}
}
@Test
public void sorted(){
List<Author> authors = MockData.getAuthors();
// 对作者进行排序 其策略是按年龄
authors.stream().sorted().forEach(System.out::println);
}
4.2.3.2、sorted(Comparator com)
@Test
public void sorted_v2(){
List<Author> authors = MockData.getAuthors();
// 对作者进行排序 其策略是按年龄
authors.stream().sorted((o1, o2) -> o1.getAge() - o2.getAge()).forEach(System.out::println);
}
4.3、终结操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的值,例如: List 、 Integer ,甚至是 void
流进行了终止操作后,不能再次使用。
4.3.1、匹配与查找
4.3.1.1、count
打印这些作家的所出书籍的数目,注意删除重复元素。
@Test
public void count(){
List<Author> authors = MockData.getAuthors();
// 打印这些作家的所出书籍的数目,注意删除重复元素。
Long count = authors.stream()
.flatMap(author -> author.getBooks().stream()).distinct().count();
System.out.println(count);
}
4.3.1.2、max&min
分别获取这些作家的所出书籍的最高分和最低分并打印。
@Test
public void minAndmax(){
List<Author> authors = MockData.getAuthors();
//分别获取这些作家的所出书籍的最高分和最低分并打印。
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max(Comparator.comparingInt(score -> score));
Optional<Integer> min = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max((score1,score2)->score2-score1);
System.out.println(max.get());
System.out.println(min.get());
}
4.3.1.3、allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。
判断是否所有的作家都是成年人
@Test
public void allMatch() {
List<Author> authors = MockData.getAuthors();
// 判断是否所有的作家都是成年人
boolean res = authors.stream().map(author -> author.getAge()).allMatch(age->age>=18);
System.out.println(res);
}
4.3.1.4、anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型。
判断是否有年龄在29以上的作家
@Test
public void anyMatch() {
List<Author> authors = MockData.getAuthors();
// 判断是否有年龄在29以上的作家
boolean res = authors.stream().map(author -> author.getAge()).anyMatch(age->age>29);
System.out.println(res);
}
4.3.1.5、noneMatch
可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false
判断作家是否都没有超过100岁的。
@Test
public void noneMatch() {
List<Author> authors = MockData.getAuthors();
// 判断作家是否都没有超过100岁的。
boolean res = authors.stream().map(author -> author.getAge()).noneMatch(age->age>100);
System.out.println(res);
}
4.3.1.6、findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
获取任意一个年龄大于18的作家,如果存在就输出他的名字
@Test
public void findAny() {
List<Author> authors = MockData.getAuthors();
// 获取任意一个年龄大于18的作家,如果存在就输出他的名字
Optional<Author> optionalAuthor = authors.stream()
.filter(author -> author.getAge()>18)
.findAny();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
}
4.3.1.7、findFirst
获取流中的第一个元素。
获取一个年龄最小的作家,并输出他的姓名。
@Test
public void findFirst() {
List<Author> authors = MockData.getAuthors();
//获取一个年龄最小的作家,并输出他的姓名。
Optional<Author> optionalAuthor = authors.stream()
.sorted((o1, o2) -> o1.getAge() - o2.getAge())
.findFirst();
optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
}
4.3.2、归约
定义:归并流的操作的作用是从一个序列的元素重复应用合并操作,最后产生一个单一的结果返回。这里,我们将探讨 Stream.reduce() 常用的功能并举例说明。
关键概念:初始值的定义(Identity),累加器(Accumulator),组合器(Combiner)
- Identity : 定义一个元素代表是归并操作的初始值,如果Stream 是空的,也是Stream 的默认结果
- Accumulator: 定义一个带两个参数的函数,第一个参数是上个归并函数的返回值,第二个是Stream 中下一个元素。
- Combiner: 调用一个函数来组合归并操作的结果,当归并是并行执行或者当累加器的函数和累加器的实现类型不匹配时才会调用此函数。
**技巧:**map和reduce常常一起使用
4.3.2.1、reduce(T identity, BinaryOperator accumulator);
源码:reduce两个参数的重载形式内部的计算方式如下:
T result = identity;
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
案例一、使用reduce求所有作者年龄的和
@Test
public void reduce() {
List<Author> authors = MockData.getAuthors();
// 使用reduce求所有作者年龄的和
Integer sum = authors.stream()
.distinct()
.map(author -> author.getAge())
// identity为0 然后result = identity 然后遍历每流中元素 element,在遍历的过程中执行操作result = result + element
.reduce(0, (result, element) -> result + element);
System.out.println(sum);
}
案例二、使用reduce求所有作者中年龄的最大值
@Test
public void reduce02() {
List<Author> authors = MockData.getAuthors();
// 使用reduce求所有作者中年龄的最大值
// 方式一
Integer maxAge = authors.stream().map(author -> author.getAge())
.reduce(authors.stream().map(author -> author.getAge()).findFirst().get(), (result, element) -> element > result ? element : result);
System.out.println(maxAge);
// 方式二
maxAge = authors.stream().map(author -> author.getAge())
.reduce(Integer.MIN_VALUE, (result, element) -> element > result ? element : result);
System.out.println(maxAge);
}
案例三、使用reduce求所有作者中年龄的最小值
@Test
public void reduce03() {
List<Author> authors = MockData.getAuthors();
Integer min = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, (result, element) -> result > element ? element : result);
System.out.println(min);
}
4.3.4.2、reduce(BinaryOperator accumulator)
源码:reduce一个参数的重载形式内部的计算
所谓的一个参数重载就是没有提供默认的identity,所以默认第一次遍历的时候将 result = element,而不是result = idenrity 然后再进行后续操作
boolean foundAny = false;
T result = null;
for (T element : this stream) {
if (!foundAny) {
foundAny = true;
result = element;
}
else
result = accumulator.apply(result, element);
}
return foundAny ? Optional.of(result) : Optional.empty();
案例:如果用一个参数的重载方法去求最小值代码如下:
// 使用reduce求所有作者中年龄的最小值
List<Author> authors = getAuthors();
Optional<Integer> minOptional = authors.stream()
.map(author -> author.getAge())
.reduce((result, element) -> result > element ? element : result);
minOptional.ifPresent(age-> System.out.println(age));
4.3.4.3、reduce(identity,accumulator,combiner);
源码:reduce三个参数的重载形式内部的计算
暂无…待续
案例一、当使用parallelStream()并行流的时候需要将流合并为统一类型的流
List<Integer> ages = Arrays.asList(25, 30, 45, 28, 32);
int computedAges = ages.parallelStream().reduce(0, a, b -> a + b, Integer::sum);
案例二、当使用Stream()时,下面的代码无法编译,其原因是,流中包含的是User 对象,但是累加函数的参数分别是数字和user 对象,而累加器的实现是求和,所以编译器无法推断参数 user 的类型。可以把代码改为如下可以通过编译
List<User> users = Arrays.asList(new User("John", 30), new User("Julie", 35));
int computedAges = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge());
可以改为:
int result = users.stream().reduce(0, (partialAgeResult, user) -> partialAgeResult + user.getAge(), Integer::sum);
assertThat(result).isEqualTo(65);
4.3.3、汇总
Collector接口中方法的实现决定了如何对流执行收集的操作 如收集到 List 、 Set 、Map) 。
另外,Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
4.3.3.1、Collectors.toList()
// 获取一个存放所有作者名字的List集合。
List<Author> authors = getAuthors();
List<String> nameList = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(nameList);
4.3.3.2、Collectors.toSet()
// 获取一个所有书名的Set集合。
List<Author> authors = getAuthors();
Set<Book> books = authors.stream()
.flatMap(author -> author.getBooks().stream())
.collect(Collectors.toSet());
System.out.println(books);
4.3.3.3、Collectors.toMap()
// 获取一个Map集合,map的key为作者名,value为List<Book>
List<Author> authors = getAuthors();
Map<String, List<Book>> map = authors.stream()
.distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
System.out.println(map);
4.4、注意事项
- 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
- 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
- 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)
5、optional类
Optional类主要用来处理空指针异常
5.1、创建对象
5.1.1、Optional.of(T t)
创建一个 Optional 实例, t 必须非空
@Test
public void of() {
Author author = new Author();
// of需要传入对象不为空
Optional optional = Optional.of(author);
optional.ifPresent(new Consumer() {
@Override
public void accept(Object o) {
System.out.println(o);
}
});
// 若传入为null会爆空指针异常
Optional optional2 = Optional.of(null);
}
底层源码:
of方法里面会调用构造器
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
这个构造器要求传入值不可为空
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
当使用这个对象时,需要提供一个消费者,具体决定如何消费
public void ifPresent(Consumer<? super T> consumer) {
// 这里面进行了非空判断
if (value != null)
consumer.accept(value);
}
5.1.2、Optional.empty()
创建一个空的 Optional 实例
@Test
public void empty() {
Author author = new Author();
// 获取空的optional
Optional optional = Optional.empty();
}
5.1.3、Optional.ofNullable(T t)
t 可以为 null
可以看到这个方法可以接受null,并且在执行的时候不会报空指针异常
@Test
public void ofNullable() {
Author author = new Author();
// 封装对象为optional类
Optional optional1 = Optional.ofNullable(author);
// 这个方法可以接受空对象
Optional optional2 = Optional.ofNullable(null);
optional1.ifPresent(System.out::println);
optional2.ifPresent(System.out::println);
}
底层源码:
在底层中会先对对象进行判断,若为空那么调用empty()方法返回空的optional,否则调用of()方法
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
5.2、对象判断
5.2.1、boolean isPresent()
判断是否包含对象
5.2.2、void ifPresent(Consumer<? super T> consumer)
如果有值,就执行 Consumer接口的实现代码,并且该值会作为参数传给它。
5.3、获取对象
5.3.1、T get():
如果调用对象包含值,返回该值,否则抛异常
@Test
public void get() {
Author author = new Author();
// 封装对象为optional类
Optional optional1 = Optional.ofNullable(author);
// 这个方法可以接受空对象
Optional optional2 = Optional.ofNullable(null);
Author author1 = (Author) optional1.get();
System.out.println(author1);
// No value present
Author author2 = (Author) optional2.get();
System.out.println(author2);
}
底层源码:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
5.3.2、T orElse(T other)
如果有值则将其返回,否则返回指定的 other 对象。
@Test
public void orElse() {
Author author = new Author();
// 接受空对象
Optional optional2 = Optional.ofNullable(null);
// 若没有值用指定对象代替
Author author1 = (Author) optional2.orElse(author);
System.out.println(author1 == author); // true
}
底层源码:
public T orElse(T other) {
return value != null ? value : other;
}
5.3.3、T orElseGet(Supplier<? extends T> other)
如果有值则将其返回,否则返回由Supplier 接口实现提供的对象。
@Test
public void orElseGet() {
Author author = new Author();
// 接受空对象
Optional optional2 = Optional.ofNullable(null);
// 若没有值用指定对象代替
Author author1 = (Author) optional2.orElseGet(() -> author);
System.out.println(author1 == author); // true
}
底层源码:
可见在调用方法时会传入一个具体的提供者,决定具体的提供方案,在没有值得时候会调用get方法获取对象
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
5.3.4、T orElseThrow(Supplier<? extends X> exceptionSupplier)
如果有值则将其返回,否则抛出由 Supplier 接口实现提供的异常 。
@Test
public void orElseThrow() {
Author author = new Author();
// 接受空对象
Optional optional2 = Optional.ofNullable(null);
// 若没有值用指定对象代替
try {
Author author1 = (Author) optional2.orElseThrow(new Supplier() {
@Override
public Object get() {
return new NullPointerException("author不能为空");
}
});
} catch (Throwable e) {
System.out.println(e.getMessage());//author不能为空
}
}
底层源码:
可见在调用方法时会传入一个具体的异常提供者,决定具体的抛出异常方案,在没有值得时候会调用get方法抛出异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
5.4、常规操作返回optional
5.4.1、 过滤
我们可以使用filter方法对数据进行过滤。如果原本是有数据的,但是不符合判断,也会变成一个无数据的Optional对象。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
authorOptional.filter(author -> author.getAge()>100).ifPresent(author -> System.out.println(author.getName()));
底层源码:
可见在调用方法时会传入一个具体的判断者,决定具体的判断方法
public Optional<T> filter(Predicate<? super T> predicate) {
// 判断者为空
Objects.requireNonNull(predicate);
// 没有值
if (!isPresent())
return this;
else
// 检测合格返回本身,检测不合格返回空optional
return predicate.test(value) ? this : empty();
}
5.4.2、判断
我们可以使用isPresent方法进行是否存在数据的判断。如果为空返回值为false,如果不为空,返回值为true。但是这种方式并不能体现Optional的好处,更推荐使用ifPresent方法。
Optional<Author> authorOptional = Optional.ofNullable(getAuthor());
if (authorOptional.isPresent()) {
System.out.println(authorOptional.get().getName());
}
底层源码:
当调用isPresent()方法时,会进行this.value!=null的判断,而this就是调用者authorOptional
public boolean isPresent() {
return this.value != null;
}
5.4.3、数据转换
Optional还提供了map可以让我们的对数据进行转换,并且转换得到的数据也还是被Optional包装好的,保证了我们的使用安全。
例如我们想获取作家的书籍集合。
@Test
public void optionalMap() {
Optional<Author> authorOptional = Optional.ofNullable(new Author());
Optional<List<Book>> optionalBooks = authorOptional.map(author -> author.getBooks());
optionalBooks.ifPresent(books -> System.out.println(books));
}
底层源码:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
6、高级操作
6.1、创建并行流
当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
6.1.2、方式一
parallel方法可以把串行流转换成并行流。
private static void test28() {
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Integer sum = stream.parallel()
.peek(new Consumer<Integer>() {
@Override
public void accept(Integer num) {
System.out.println(num+Thread.currentThread().getName());
}
})
.filter(num -> num > 5)
.reduce((result, ele) -> result + ele)
.get();
System.out.println(sum);
}
6.1.3、方式二
也可以通过parallelStream直接获取并行流对象。
List<Author> authors = getAuthors();
authors.parallelStream()
.map(author -> author.getAge())
.map(age -> age + 10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);