最近看了下Lambda表达式和Stream流的课程,觉得非常实用,转发推荐给大家:
bilibili三更草堂课程地址:
https://www.bilibili.com/video/BV1Gh41187uR?p=5&spm_id_from=pageDriver&vd_source=e1d4c66f1603bebf0c80d15561835936
1、概述
- 能否看懂代码
- 大数据量下处理集合效率高
- 代码可读性高
- 消灭嵌套地狱
概念:
面向对象思想需要关注用什么对象完成什么事情。而函数式编程思想就类似于我们教学中的函数。它主要关注的是对数据进行了什么操作。
优点:
- 代码简洁,开发快速
- 接近自然语言,易于理解
- 易于"并发编程"
2、Lambda表达式
2.1概述
Lambda是JDK8中一个语法糖。他可以对某些匿名内部类的写法进行简化。它是函数式编程思想的一个重要体现。让我们不用关注是什么对象。而是更关注我们对数据进行了什么操作。
2.2核心原则
可推导可省略
2.3基本格式
(参数列表)->{代码}
只关注参数列表和方法体,其他不重要的内容多了徒增烦恼。
例一
创建线程并启动时可以使用匿名内部类的写法:
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("你知道吗 我比你想象的 更想在你身边");
}
}).start();
匿名内部类并且只有一个实现方法 可以使用Lambda的格式:
new Thread(()->{
System.out.printIn("你知道吗 我比你想象的 更想在你身边");
}).start();
例二
现有方法定义如下,其中IntBinaryOperator是一个接口,先使用匿名内部类的写法调用该方法。
import java.util.function.IntBinaryOperator;
public static void main(String[] args) {
int i=calculateNum(new IntBinaryOperator() {
@Override
public int applyAsInt(int left, int right) {
return left+right;
}
});
System.out.println(i);
}
public static int calculateNum(IntBinaryOperator intBinaryOperator){
int a=10;
int b=20;
return intBinaryOperator.applyAsInt(a,b);
}
Lambda写法:
import java.util.function.IntBinaryOperator;
public static void main(String[] args) {
int i=calculateNum((left, right) -> left - right);
System.out.println(i);
}
public static int calculateNum(IntBinaryOperator intBinaryOperator){
int a=10;
int b=20;
return intBinaryOperator.applyAsInt(a,b);
}
例三
现有方法定义如下,其中IntPredicate是一个接口。先使用匿名内部类的写法调用该方法。
public static void printNum(IntPredicate predicate){
int[] arr={1,2,3,4,5,6,7,8,9,10};
for (int i : arr) {
if(predicate.test(i)){
System.out.println(i);
}
}
}
public static void main(String[] args) {
printNum(new IntPredicate() {
@Override
public boolean test(int value) {
return value%2==0;
}
});
}
Lambda写法:
public static void main(String[] args) {
printNum(value -> value%2==0);
}
例四
现有方法定义如下,其中Function是一个接口。先使用匿名内部类的写法调用该方法。
public static <R> R typeConver(Function<String,R> function){
String str="1235";
return function.apply(str);
}
public static void main(String[] args) {
Integer i=typeConver(new Function<String, Integer>() {
@Override
public Integer apply(String s) {
return Integer.parseInt(s);
}
});
System.out.println(i);
}
Lambda:
public static void main(String[] args) {
String i=typeConver((String s) -> s+"最伟大的作品");
System.out.println(i);
}
例五
现有方法定义如下,其中IntConsumer是一个接口,先使用匿名内部类的写法调用该方法。
public static void main(String[] args) {
foreachArr(new IntConsumer() {
@Override
public void accept(int value) {
System.out.println(value);
}
});
}
public static void foreachArr(IntConsumer intConsumer){
int[] arr = {1,2,3,4,5,6,7,8,9,10};
for (int a:arr){
intConsumer.accept(a);
}
}
Lambda:
public static void main(String[] args) {
foreachArr(System.out::println);
}
2.4省略规则
- 参数类型可以省略
- 方法体只有一句代码时 大括号return和唯一一句代码的分号可以省略
- 方法只有一个参数时小括号也可以省略
- 以上这些规则都记不住也可以省略不记
3、Stream流
3.1概念
Java8的Stream使用的是函数式编程模式,如同他的名字一样,他可以被用来对集合或数组进行链状流式的操作。可以方便的让我们对集合或数组操作。
3.2案例数据准备
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
</dependencies>
@Data
@NoArgsConstructor
@AllArgsConstructor
//用于去重
@EqualsAndHashCode
public class Author {
private Long id;
private String name;
private Integer age;
private String intro;
private List<Book> bookList;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
//用于去重
@EqualsAndHashCode
public class Book {
private Long id;
private String name;
/**
* 分类
*/
private String category;
/**
* 评分
*/
private Integer score;
/**
* 简介
*/
private String intro;
}
/**
* 数据源
* @return
*/
private static List<Author> getAuthors(){
Author author1=new Author(1L,"王小波",45,"王小波(1952年5月13日—1997年4月11日),男,中国当代学者、作家。代表作品有《黄金时代》《白银时代》《青铜时代》《黑铁时代》等。",null);
Author author2=new Author(2L,"余华",62,"余华,1960年4月3日生于浙江杭州,浙江省嘉兴市海盐县人,中国当代作家,中国作家协会委员会委员。",null);
Author author3=new Author(3L,"余秋雨",54,"余秋雨,男,1946年8月23日出生于浙江省余姚县桥头镇(今属浙江省慈溪市),中国当代作家、学者 [1] 。",null);
Author author4=new Author(3L,"余秋雨",54,"余秋雨,男,1946年8月23日出生于浙江省余姚县桥头镇(今属浙江省慈溪市),中国当代作家、学者 [1] 。",null);
List<Book> books1=new ArrayList<>();
List<Book> books2=new ArrayList<>();
List<Book> books3=new ArrayList<>();
books1.add(new Book(1L,"沉默的大多数","随笔集,分类测试1,分类测试2",80,"《沉默的大多数》是陕西师范大学出版社于2009年7月发行的图书,作者王小波。主要讲述了作者在该作品中倾注了其对中国民众命运的关注,以反讽和幽默的手法直面生活,从一个轻松的角度来解析身边复杂的事态。【详细>>】"));
books1.add(new Book(2L,"黄金时代","小说集,分类测试2",90,"收入五篇小说:《黄金时代》《三十而立》《似水流年》《革命时期的爱情》《我的阴阳两界》"));
books1.add(new Book(3L,"我的精神家园","杂文集",80,"包括《黄金时代》《白银时代》《青铜时代》"));
author1.setBookList(books1);
books2.add(new Book(4L,"活着","长篇小说,分类测试1",90,"意大利格林扎纳·卡佛文学奖"));
books2.add(new Book(4L,"活着","长篇小说,分类测试1",90,"意大利格林扎纳·卡佛文学奖"));
books2.add(new Book(5L,"文城","长篇小说,分类测试2,分类测试3",80,"2021年度长篇小说五佳作品"));
author2.setBookList(books2);
books3.add(new Book(6L,"千禧之旅","散文集,分类测试2",90,"2000年,出版散文集《千年一叹》《千禧之旅》,其中,在《千年一叹》中,作者记述了考察世界古老的经历。"));
books3.add(new Book(7L,"山居笔记","散文集,分类测试3",90,"1995年,出版散文集《山居笔记》。"));
books3.add(new Book(7L,"山居笔记","散文集,分类测试3",90,"1995年,出版散文集《山居笔记》。"));
author3.setBookList(books3);
author4.setBookList(books3);
return new ArrayList<>(Arrays.asList(author1,author2,author3,author4));
}
3.3快速入门
3.3.1 需求
调用getAuthors方法获取到作家的集合,现在打印所有年龄小于60的作家名字和年龄,注意去重。
3.3.2 实现
public static void main(String[] args) {
//获取数据
List<Author> authorList=getAuthors();
authorList.forEach(System.out::println);
//打印所有年龄小于60的作家名字和年龄,注意去重。
authorList.stream()//转换流
.distinct()//去重
.filter(author -> author.getAge()<60)//过滤
.forEach(author -> System.out.println(author.getName() + " "+author.getAge()));//打印
}
Stream流 debug
3.4常用操作
3.4.1创建流
单列集合:集合对接.stream()
List<Author> authorList=getAuthors();
Stream<Author> authorStream=authorList.stream();
数组:Arrays.stream(数组)或者使用Stream.of来创建
Integer[] arr={1,2,3,4,5};
Stream<Integer> stream=Arrays.stream(arr);
Stream<Integer> stream1=Stream.of(arr);
stream.distinct()
.filter(integer -> integer<=3)
.forEach(System.out::println);
双列集合:转换成单列集合后再创建
Map<String,Integer> map= new HashMap<>();
map.put("蜡笔小新",14);
map.put("十八岁男大学生",18);
map.put("风华正茂社畜码农",24);
Set<Map.Entry<String,Integer>> entrySet=map.entrySet();
Stream<Map.Entry<String,Integer>> system=entrySet.stream();
system.filter(entry->entry.getValue()>=18).forEach(entry -> System.out.println(entry.getKey()+" "+entry.getValue()));
3.4.2中间操作
filter
可以 流中的元素进行条件过滤,符合过滤条件的才能继续留在流中。
打印作家名字大于2的作家姓名
List<Author> authorList=getAuthors();
//打印作家名字大于2的作家姓名
authorList.stream().filter(author -> author.getName().length()>2).forEach(author -> System.out.println(author.getName()));
map
可以吧流中的元素进行计算或者转换。
List<Author> authorList=getAuthors();
//打印所有作家名字
// authorList.forEach(author -> System.out.println(author.getName()));
authorList.stream().map(Author::getName).forEach(System.out::println);
authorList.stream().map(Author::getAge).map(age->age+10).forEach(System.out::println);
distinct
可以去除流中的重复元素
注意:distinct方法是依赖Object的equals方法来判断是否显示相同对象的。所以需要注意重写equals方法。
//打印所有作家名字 要求不能有重复元素
List<Author> authorList=getAuthors();
authorList.stream()
.distinct()
.map(Author::getName).forEach(System.out::println);
sorted
可以对流中的元素排序。
注意:如果调用空参的sorted()方法,需要流中的元素是实现了Comparable。
//打印所有作家年龄 按照降序排序 要求不能有重复元素
List<Author> authorList=getAuthors();
//空参
authorList.stream()
.distinct()
.sorted()
.map(Author::getAge).forEach(System.out::println);
//有参
authorList.stream()
.distinct()
.sorted((o1, o2) -> o2.getAge()-o1.getAge())
.map(Author::getAge).forEach(System.out::println);
//有参 排序顺序
authorList.stream()
.distinct()
.sorted(Comparator.comparing(Author::getAge).reversed())
.map(Author::getAge).forEach(System.out::println);
limit
可以设置流的最大长度,超出的部分将被抛弃。
//对流中的元素按照年龄进行降序排序 并且要求不能有重复的元素,然后打印其中年龄最大的两个作家的姓名。
List<Author> authorList=getAuthors();
authorList.stream()
.distinct()
.sorted(Comparator.comparing(Author::getAge).reversed())
.limit(2).forEach(author -> System.out.println(author.getName()+" "+author.getAge()));
skip
跳过流中的前n个元素,返回剩下的元素
//打印除了年龄最大的作家的其他作家,要求不能有重复元素,并且按照年龄降序排序
List<Author> authorList=getAuthors();
authorList.stream()
.distinct()
.sorted(Comparator.comparing(Author::getAge).reversed())
.skip(1).forEach(author -> System.out.println(author.getName()+" "+author.getAge()));
flatMap
map只能把一个对象转换成另外一个对象来作为流中的元素,而flatMap可以把一个对象转换成多个对象作为流中的元素。
debug 可以看到转化过程
//打印所有书籍名称 要求对重复的元素进行去重
List<Author> authorList=getAuthors();
authorList.stream()
//(Function<Author, Stream<Book>>) 可以省略 把一个对象转换成多个对象作为流中的元素
.flatMap((Function<Author, Stream<Book>>) author -> author.getBookList().stream())
.distinct()
.forEach(book -> System.out.println(book.getName()));
//打印现有数据的所有分类 要求对分类进行去重 不能出现这种带,号的格式:哲学,爱情
List<Author> authorList=getAuthors();
authorList.stream()
//(Function<Author, Stream<Book>>) 可以省略 把一个对象转换成多个对象作为流中的元素
.flatMap((Function<Author, Stream<Book>>) author -> author.getBookList().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(System.out::println);
3.4.3终结操作
forEach
对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作。
//输出所有作家的名字
List<Author> authorList=getAuthors();
authorList.stream().map(Author::getName).distinct().forEach(System.out::println);
count
可以用来获取当前流中元素的个数
打印这些作家的所出书籍的数目,注意删除重复元素。
//打印这些作家的所出书籍的数目,注意删除重复元素。
List<Author> authorList=getAuthors();
val count = authorList.stream()
.flatMap(author -> author.getBookList().stream())
.distinct()
.count();
System.out.println(count);
max&min
可以用来求流中的最值。
分别获取这些作家的书籍中的最高分和最低分并打印。
//分别获取这些作家的书籍中的最高分和最低分并打印。
List<Author> authorList=getAuthors();
Integer max = authorList.stream().flatMap(author -> author.getBookList().stream())
.map(Book::getScore)
.max(Comparator.comparingInt(score -> score)).get();
System.out.println(max);
Integer min = authorList.stream().flatMap(author -> author.getBookList().stream())
.map(Book::getScore)
.min(Comparator.comparingInt(score -> score)).get();
System.out.println(min);
collect
把当前流转换成一个集合。
获取一个存放所有作者名字的List集合。
//获取一个存放所有作者名字的List集合。
List<Author> authorList=getAuthors();
List<String> nameList=authorList.stream().map(Author::getName).distinct().collect(Collectors.toList());
System.out.println(nameList);
获取一个所有书名的Set集合。
//获取一个所有书名的Set集合。
List<Author> authorList=getAuthors();
Set<String> nameSet=authorList.stream().flatMap(author -> author.getBookList().stream())
.map(Book::getName).collect(Collectors.toSet());
System.out.println(nameSet);
获取一个map集合,map的key为作者名,value为List
//获取一个map集合,map的key为作者名,value为List<Book>
List<Author> authorList=getAuthors();
Map<String,List<Book>> map=authorList.stream().distinct().collect(Collectors.toMap(Author::getName, Author::getBookList));
System.out.println(map);
查找与匹配
anyMatch
可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型
例子:判断是否有年龄60岁以上的作家
//获取一个map集合,map的key为作者名,value为List<Book>
List<Author> authorList=getAuthors();
Map<String,List<Book>> map=authorList.stream().distinct().collect(Collectors.toMap(Author::getName, Author::getBookList));
System.out.println(map);
allMatch
可以用来判断是否都符合匹配条件,结果为boolean类型,如果都符合结果为true,否则结果都为false。
例子:判断是否所有作家都是成年人
//判断是否所有的作家都是成年人
List<Author> authorList=getAuthors();
boolean allMatch = authorList.stream().allMatch(author -> author.getAge() >= 18);
System.out.println(allMatch);
noneMatch
可以判断流中的元素是否都不符合匹配条件,如果都不符合结果为true,否者结果为false。
例子:判断作家是否都没有超过100岁的。
//判断作家是否都没有超过100岁的。
List<Author> authorList=getAuthors();
boolean noneMatch = authorList.stream().noneMatch(author -> author.getAge() >= 100);
System.out.println(noneMatch);
findAny
获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。
例子:获取任意一个大于18的作家,如果存在就输出他的名字。
//获取任意一个大于18的作家,如果存在就输出他的名字。
List<Author> authorList=getAuthors();
Optional<Author> any = authorList.stream().filter(author -> author.getAge() >= 20).findAny();
any.ifPresent(author -> System.out.println(author.getName()));
findFirst
获取流中的第一个元素。
例子:获取一个年龄最小的作家,并输出他的姓名。
//获取一个年龄最小的作家,如果存在就输出他的名字
List<Author> authorList=getAuthors();
authorList.stream().sorted((o1, o2) -> o1.getAge()-o2.getAge()).findFirst().ifPresent(author -> System.out.println(author.getName()));
authorList.stream().min(Comparator.comparingInt(Author::getAge)).
ifPresent(author -> System.out.println(author.getName()));
reduce归并
对流中的数据按照你制定的计算方式计算出一个结果。(缩减操作)
reduce的作用是把Stream中的元素给组合起来,我们可以传入一个初始值,他会按照我们的计算方式依次拿流中的元素和在初始化值的基础上进行计算,计算结果再和后面的元素计算。
reduce两个参数的重载形式内部的计算方式如下:
T result = identity
for (T element : this stream)
result = accumulator.apply(result, element)
return result;
其中identity就是我们可以通过方法传入的初始值,accumulate的apply具体进行什么计算也是我们通过方法参数来确认的。
例子:
使用reduce求所有作者年龄的和
//使用reduce求所有作者年龄的和
List<Author> authorList=getAuthors();
Integer reduce = authorList.stream().distinct().map(Author::getAge).
reduce(0, Integer::sum);
System.out.println(reduce);
使用reduce求所有作者中年龄的最大值
//使用reduce求所有作者中年龄的最大值
List<Author> authorList=getAuthors();
Integer reduce = authorList.stream().distinct().map(Author::getAge).
reduce(Integer.MIN_VALUE, Integer::max);
System.out.println(reduce);
reduce = authorList.stream().distinct().map(Author::getAge).
reduce(Integer.MIN_VALUE, (result, element) -> result>element?result:element);
System.out.println(reduce);
使用reduce求所有作者中年龄的最小值
//使用reduce求所有作者中年龄的最小值
List<Author> authorList=getAuthors();
Integer reduce = authorList.stream().distinct().map(Author::getAge).
reduce(Integer.MAX_VALUE, Integer::min);
System.out.println(reduce);
reduce = authorList.stream().distinct().map(Author::getAge).
reduce(Integer.MAX_VALUE, (result, element) -> result>element?element:result);
System.out.println(reduce);
reduce一个参数重载形式内部的计算:
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> authorList=getAuthors();
Optional<Integer> minOptional = authorList.stream().distinct().map(Author::getAge).
reduce(new BinaryOperator<Integer>() {
@Override
public Integer apply(Integer result, Integer element) {
return result>element?element:result;
}
});
minOptional.ifPresent(System.out::println);
3.5注意事项
- 惰性求值(如果没有终结操作、中间操作是不会得到执行的结果的)
- 流是一次性的(一旦一个流对象经过一个终结操作后,这个流就不能再被使用)
- 不会影响原数据(我们在六中可以多数据做很多处理,但正常情况下是不会影响原来集合中的元素的,这往往也是我们期望的)
List<Author> authorList=getAuthors();
Stream<Author> authorStream = authorList.stream();
authorStream.forEach( e -> System.out.println(e.getName()));
//流是一次性的 连续用两次会 Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
// authorStream.forEach( e -> System.out.println(e.getName()));
//需要创建新的流
authorList.stream().forEach( e -> System.out.println(e.getName()));
4、Optional
4.1概述
编程时出现最多的就是空指针异常,所以在很多情况下我们需要做各种非空的判断。例:
List<Author> authorList=getAuthors();
authorList.add(null);
if(CollectionUtils.isNotEmpty(authorList)){
authorList.forEach(
a -> {
if(a==null){return;}
System.out.println(a.toString());
}
);
}
常用集合工具包-CollectionUtils包
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.4</version>
</dependency>
</dependencies>
尤其是属性是对象或者集合中含有空对象或是参数时,这种判断会更多,显得代码臃肿不堪。
所以JDK8中引入了Optional,养成使用Optional的习惯后可以写出更优雅的代码避免空指针异常,并且在很多函数式编程相关的API中也都用到了Optional。
4.2使用
4.2.1创建对象
Optional就好像是包装类,可以把我们具体数据封装Optional兑现内部,然后我们去使用Optional中封装好的方法操作封装进去的数据可以非常优雅的避免空指针异常。
一般使用Optional的静态方法ofNullable来吧数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题。
public static void main(String[] args) {
List<Author> authorList=getAuthor();
Optional<List<Author>> optionalAuthors=Optional.ofNullable(authorList);
optionalAuthors.ifPresent(authors -> authors.forEach(System.out::println));
}
public static List<Author> getAuthor(){
Author author1=new Author(1L,"王小波",45,"王小波(1952年5月13日—1997年4月11日),男,中国当代学者、作家。代表作品有《黄金时代》《白银时代》《青铜时代》《黑铁时代》等。",null);
Author author2=new Author(2L,"余华",62,"余华,1960年4月3日生于浙江杭州,浙江省嘉兴市海盐县人,中国当代作家,中国作家协会委员会委员。",null);
List<Author> authorList=new ArrayList<>(Arrays.asList(author1,author2));
authorList.add(null);
return null;
}
可能要加一行代码来封装代码比较麻烦,如果改造下getAuthor方法,让返回值就是封装好的在使用时更方便。
实际开发中数据基本是从数据库中获取的,Mybatis从3.5版本也支持Optional了。可以dao方法的返回值类型定义成Optional类型,Mybatis会封装成Optional类型的数据直接返回。
还有两种使用较少的方法Optional.of和Optional.empty,例:
public static void test1(){
List<Author> authorList=getAuthor();
//推荐 为空不影响程序运行
Optional<List<Author>> optionalAuthors=Optional.ofNullable(authorList);
//Optional.of确认传入参数不为空才能使用,或者做处理
assert authorList != null;
optionalAuthors=Optional.of(authorList);
//Optional.empty 确认是空值 需要封装返回
optionalAuthors=Optional.empty();
optionalAuthors.ifPresent(authors -> authors.forEach(System.out::println));
}
4.2.2安全消费值
对获取Optional对象后对其数据进行使用消费,可以使用ifPresent方法,其中数据不为空才会执行,这样能有效避免空指针异常。
private static void test2() {
Optional<List<Author>> optionalAuthors=getAuthorOptional();
optionalAuthors.ifPresent(o-> System.out.println(o.toString()));
}
public static Optional<List<Author>> getAuthorOptional(){
return Optional.ofNullable(getAuthor());
}
4.2.3获取值
如想获取值进行处理可以使用get()方法,但不推荐,因为Optional内部的数据为空会出现异常。
4.2.4安全获取值
orElseGet方法未获取值可以设置默认值
private static void test3() {
Optional<Author> authorOptionalNull=getAuthorOptionalNull();
//有值打印值 没有默认值 new对象@Builder
Author author=authorOptionalNull.orElseGet(()->Author.builder().name("获取名称失败").build());
System.out.println(author.getName());
}
public static Optional<Author> getAuthorOptionalNull(){
// return Optional.of(new Author(1L,"王小波",45,"王小波(1952年5月13日—1997年4月11日),男,中国当代学者、作家。代表作品有《黄金时代》《白银时代》《青铜时代》《黑铁时代》等。",null));
return Optional.empty();
}
orElseThrow方法未获取值可以设置抛出异常
private static void test4() {
try {
Optional<Author> authorOptionalNull=getAuthorOptionalNull();
//有值打印值 没有默认值抛出异常
Author author=authorOptionalNull.orElseThrow(()->new RuntimeException("获取名称失败"));
System.out.println(author.getName());
} catch (RuntimeException e) {
e.printStackTrace();
}
}
4.2.5过滤
可以使用filter方法过滤不满足条件的数据
private static void test5() {
Optional<Author> optionalAuthor=getAuthorOptionalOf();
Optional<Author> optional=optionalAuthor.filter(author -> author.getAge() > 50);
optional.ifPresent(o -> System.out.println(o.toString()));
}
public static Optional<Author> getAuthorOptionalOf(){
return Optional.of(new Author(1L,"王小波",45,"王小波(1952年5月13日—1997年4月11日),男,中国当代学者、作家。代表作品有《黄金时代》《白银时代》《青铜时代》《黑铁时代》等。",null));
}
4.2.6判断
可以用isPresent方法先判断是否存在值,在get获取。推荐直接使用ifPresent方法
private static void test6() {
Optional<Author> optionalAuthor=getAuthorOptionalOf();
if(optionalAuthor.isPresent()){
System.out.println(optionalAuthor.get().toString());
}
optionalAuthor.ifPresent(author -> System.out.println(author.toString()));
}
4.2.7数据转换
Optional还提供map可以进行数据转换,并且转换得到的数据仍然是被Optional包装好的,包装数据使用安全,例:
private static void test7() {
Optional<List<Author>> optionalAuthors=getAuthorBookOptional();
Optional<List<Book>> optionalBooks=optionalAuthors.map(authors -> authors.get(0).getBookList());
optionalBooks.ifPresent(books -> System.out.println(books.toString()));
}
public static Optional<List<Author>> getAuthorBookOptional(){
Author author1=new Author(1L,"王小波",45,"王小波(1952年5月13日—1997年4月11日),男,中国当代学者、作家。代表作品有《黄金时代》《白银时代》《青铜时代》《黑铁时代》等。",null);
List<Book> books1=new ArrayList<>();
books1.add(new Book(1L,"沉默的大多数","随笔集,分类测试1,分类测试2",80,"《沉默的大多数》是陕西师范大学出版社于2009年7月发行的图书,作者王小波。主要讲述了作者在该作品中倾注了其对中国民众命运的关注,以反讽和幽默的手法直面生活,从一个轻松的角度来解析身边复杂的事态。【详细>>】"));
books1.add(new Book(2L,"黄金时代","小说集,分类测试2",90,"收入五篇小说:《黄金时代》《三十而立》《似水流年》《革命时期的爱情》《我的阴阳两界》"));
books1.add(new Book(3L,"我的精神家园","杂文集",80,"包括《黄金时代》《白银时代》《青铜时代》"));
author1.setBookList(books1);
List<Author> authorList=new ArrayList<>();
authorList.add(author1);
return Optional.of(authorList);
}
5、函数式接口
5.1概述
只有一个抽象方法的接口我们称之为函数式接口。
jdk的函数式接口都加上了**@FunctionalInterface**注解进行标识,但是无论是否加上该注解只要接口中只有一个抽象方法,都是函数式接口。
5.2常见函数式接口
-
Consumer消费接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数进行消费。
-
Function计算转换接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数计算和转换,把结果返回。
-
Predicate判断接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中对传入的参数条件判断,返回判断结果。
-
Supplier生产型接口
根据其中抽象方法的参数列表和返回值类型知道,我们可以在方法中创建对象,把创建好的对象返回。
5.3常用的默认方法
-
and
我们在使用Predicate接口时候可能需要进行条件判断的拼接,而and方法相当于是使用&&来拼接两个判断条件。例如:
打印作家中年龄大于17并且姓名长度大于2的作家
//打印作家中年龄大于17并且姓名长度大于2的作家 List<Author> authorList=getAuthors(); authorList.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()>2; } })).forEach(e -> System.out.println(e.toString())); authorList.stream().filter(((Predicate<Author>) author -> author.getAge() > 17).and(author -> author.getName().length()>2)).forEach(e -> System.out.println(e.toString()));
-
or
我们在使用Predicate接口时候可能需要进行条件的拼接。而or方法相当于是使用||来拼接两个判断条件的。
例:打印作家中年龄大于17或者姓名的长度小于2的作家。
//打印作家中年龄大于17或者姓名的长度小于2的作家 List<Author> authorList=getAuthors(); authorList.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(e -> System.out.println(e.toString())); authorList.stream().filter(((Predicate<Author>)auth -> auth.getAge()>17).or((Predicate<Author>)auth -> auth.getName().length()<2)).forEach(e -> System.out.println(e.toString()));
-
negate
Predicate接口中的方法,negate方法相当于是在半段添加前面加了个!,表示取反
例如:
打印作家年龄不大于50的作家:
// 打印作家年龄不大于50的作家
List<Author> authorList=getAuthors();
authorList.stream().filter(((Predicate<Author>)auth -> auth.getAge()>50).negate()).forEach(e -> System.out.println(e.toString()));
6、方法引用
我们在使用lambda时,如果方法中只有一个方法的调用的话(包括构造方法),我们可以用方法引用进一步简化代码。
6.1推荐用法
我们在使用lambda时不需要考虑什么时候用方法引用,用那种方法引用,方法引用的格式是什么,只需要在写完lambda方法发现方法体只有一行代码,并且是方法的调用时使用快捷键尝试是否能够转换成方法引用即可。
当我们方法引用使用的多了慢慢的也可以直接写出方法引用。
6.2基本格式
类名或者对象名::方法名
6.3语法详解(了解)
6.3.1引用类的静态方法
其实就是引用类的静态方法。格式:
类名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的静态方法,并且我们把要重写的抽象方法中所有的参数都按照顺序传入了这个静态方法中,这个时候我们就可以引用类的静态方法。
例如:
如下代码就可以用方法引用进行简化
List<Author> authorList=getAuthors();
Stream<Author> authorStream=authorList.stream();
authorStream.map(author -> author.getAge()).
map(age -> String.valueOf(age));
注意,如果我们所重写的方法是没有参数的,调用的方法也是没有参数的相当于符合以上规则。
优化后:
List<Author> authorList=getAuthors();
Stream<Author> authorStream=authorList.stream();
authorStream.map(Author::getAge).
map(String::valueOf);
6.3.2引用对象的实例方法
格式:
对象名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个对象的成员方法,并且我们把重写的抽象方法中所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用对象的实例方法。
例如:
List<Author> authorList=getAuthors();
Stream<Author> authorStream=authorList.stream();
StringBuilder sb=new StringBuilder();
authorStream.map(Author::getName).
forEach(sb::append);
System.out.println(sb);
6.3.3引用类的实例方法
格式
类名::方法名
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了第一个参数的成员方法,并且我们把要重写的抽象方法中剩余的所有的参数都按照顺序传入了这个成员方法中,这个时候我们就可以引用类的实例方法。
例如:
interface UseString{
String use(String str,int start,int length);
}
public static String subAuthorName(String str,UseString useString){
int start = 0;
int length = 1;
return useString.use(str,start,length);
}
public static void main(String[] args) {
System.out.println(
subAuthorName("晚上好呀", new UseString() {
@Override
public String use(String str, int start, int length) {
return str.substring(start,length);
}
})
);
System.out.println(
subAuthorName("晚上好呀", String::substring)
);
}
6.3.4构造器使用
如果方法体中的一行代码是构造器的话就可以使用构造器引用。
格式:
类名::new
使用前提
如果我们在重写方法的时候,方法体中只有一行代码,并且这行代码是调用了某个类的构造方法,并且我们把要重写的抽象方法中的所有的参数都按照顺序传入了这个构造方法中,这个时候我们就可以引用构造器。
例如:
List<Author> authorList=getAuthors();
authorList.stream()
.map(Author::getName)
.map(StringBuilder::new)
.map(sb->sb.append("-三更").toString())
.forEach(System.out::println);
7、高级用法
基本数据类型优化
很多Stream的方法由于都使用了泛型,所以涉及到的参数和返回值都是引用数据类型。
即使我们操作的是整形小数,但是实际用的都是他们的包装类,JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便,但是你一定要知道装箱和拆箱肯定要消耗时间的,如果大量数据不断重复装箱拆箱的时候,就不能无视这个时间损耗了。
针对这部分时间损耗进行优化,Stream还提供了很多专门针对基本数据类型的方法。
例如:mapToInt,mapToLong,mapToDouble,flatMapToInt,flatMapTodouble等。
List<Author> authorList=getAuthors();
authorList.stream()
.map(Author::getAge)
.map(age->age+10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
//转换后
authorList.stream()
.mapToInt(Author::getAge)
.map(age->age+10)
.filter(age->age>18)
.map(age->age+2)
.forEach(System.out::println);
并行流
当流中有大量元素时,可以使用并行流去提高操作的效率,其实并行流是把任务分配给多个线程去完成,如果我们去用代码实现的话其实会非常的复杂,并且要求对并发编程有足够的理解和认识。如果使用Stream的话,只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。
parallel方法可以把串行流转换成并行流。
Stream<Integer> stream=Stream.of(1,2,3,4,5,6,7,8,9,10);
Integer integer = stream
.parallel()
.peek(num-> System.out.println(num+Thread.currentThread().getName()))
.filter(num -> num > 5)
.reduce(Integer::sum)
.get();
System.out.println(integer);
也可以通过parallelStream直接获取并行流对象。
List<Author> authorList=getAuthors();
int asInt= authorList.parallelStream()
.mapToInt(Author::getAge)
.peek(num -> System.out.println(num + Thread.currentThread().getName()))
.filter(num -> num > 5)
.reduce(Integer::sum).getAsInt();
System.out.println(asInt);