函数式接口
只有一个抽象方法
的接口
我们称之为函数接口。
JDK的函数式接口都加上了@FunctionalInterface
注解进行标识,但无论是否加上该注解只要接口只有一个抽象方法,都是函数式接口。
方法引用
基本格式
类名或对象名::方法名
类名::方法名
只有一行代码,并且这行代码是调用了第一个参数的成员方法,
并且我们要把要重写的抽象方法中剩余的参数按着顺序传入这个成员方法中。
对象名::方法名使用场景为
方法体中只有一行代码,并且这个代码是调用了某个对象的成员方法,并且我们要把抽象方法中
的所有参数都按照顺序传入到这个方法中。
使用场景
如果我们方法体中只有一个方法被调用的话(包括构造方法),我们可以使用方法引用,进一步简化代码。
List<String> collect = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(collect);
使用后
List<String> collect = authors.stream()
.map(Author::getName)
.collect(Collectors.toList());
System.out.println(collect);
构造器引用
类型::new
方法体中只有一行代码,并且这行代码调用了某个类的构造方法。并且我们要把所要重写的参数都按着顺序传入到这个构造方法中。
stream
Java 8 是一个非常成功的版本,这个版本新增的Stream
,配合同版本出现的 Lambda
,给我们操作集合(Collection)提供了极大的便利。
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
stream特性
stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
stream具有延迟执行特性(惰性求值
),只有调用终端操作时,中间操作才会执行。
stream是一次性的(一旦一个流对象经过一个终结操作后,这个流就不会被使用)
debug中可以看到流的执行过程。
常用操作
创建流
中间操作
每次返回一个新的流,可以有多个
终结操作
每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
ctrl+alt+m可以自动封装为函数
创建流的方式
集合对象
集合对象.stream()
数组
Arrays.stream(数组)
Integer arr[]={1,2,3,4,5};
Stream<Integer> stream = Arrays.stream(arr);
stream.distinct().forEach(integer -> System.out.println(integer));
双列集合
先转换为单列集合再创建
map.entrySet().stream();
Map<String, Integer> map = new HashMap<>();
map.put("蜡笔",11);
map.put("蜡笔2",11);
map.put("蜡笔3",11);
Set<Map.Entry<String, Integer>> entries = map.entrySet();
entries.stream().distinct().forEach(stringIntegerEntry -> System.out.println(stringIntegerEntry));
stream中的常用API中间操作
filter
筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作。
map与flatMap
flatMap
接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流
举例说明
比如一个作者类里有
然后需要输出所有作者的所有书籍。
传统需要双重for循环,
flatMap将作者流中的值转换为书籍流,然后将所有书籍流组合成一个大流。
authors.stream()
.flatMap((Function<Author, Stream<Book>>) author -> author.getBooks().stream())
.distinct()
.forEach(book -> System.out.println(book.getName()));
获取所有分类
authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.flatMap(book -> Arrays.stream(book.getCategory().split(",")))
.distinct()
.forEach(s -> System.out.println(s));
先将作者流转换为书籍流,然后将书籍流转换为分类流,最后对分类流进行操作。
二者区别
可以看出flatmap和map的参数差别在于,
map传入一个传入实体返回实体,
flatMap是传入实体返回的却是Stream流,
那既然是流,那么最好返回值本身是一个Stream,或者能被转换成Stream的对象!
flatmap的作用 —— 把嵌套集合,按照子集合的形式,
统一放入到新的一个集合中去,这就叫结果展平
例如,一个年级的学生,按照班级为单位,如今年段长想统计该年段所有的学生信息,一个flatmap就能轻松搞定,无需再for循环取遍历获取了~
distinct
可以去除流中的重复元素,底层依赖的是Object的equals方法来判断是否是相同对象。所以需要重写equals方法。
sorted
sorted,实现排序,中间操作。
如果调用空参的
实体类需要实现Comparable接口
重写方法,实现排序
调用有参的
需要重写里面的方法
limit
可以设置流的最大长度,超出的部分将被抛弃
例如:打印年龄最大的两个作者的姓名。
authors.stream()
.distinct()
.sorted()
.limit(2)
.forEach(author -> System.out.println(author.getName()));
skip
跳过流中的前n个元素了,返回剩下的元素。
authors.stream()
.sorted()
.skip(1)
.forEach(author -> System.out.println(author.getName()));
stream中常用API终结操作
forEach
对流中的元素进行遍历操作,我们通过传入参数去指定对遍历的元素进行什么具体操作。
count
可以获取流中元素的个数
//打印这些作家的所出书籍的数目,去重
long count = authors.stream()
.flatMap(author -> author.getBooks().stream())
.distinct()
.count();
System.out.println(count);
max与min
可以用来获取流中的最值
//获取作品中的最高分和最低分
Optional<Integer> max = authors.stream()
.flatMap(author -> author.getBooks().stream())
.map(book -> book.getScore())
.max((o1, o2) -> o1 - o2);
System.out.println(max.get());
collect
把当前流转换为一个集合
例.获取一个存放所有作者名字的List集合
List<String> collect = authors.stream()
.map(author -> author.getName())
.collect(Collectors.toList());
System.out.println(collect);
例.获取一个所有书名的set集合
Set<Book> collect1 = authors.stream()
.flatMap(author -> author.getBooks().stream())
.collect(Collectors.toSet());
System.out.println(collect1);
例.获取一个map集合,map的key为作者名,value为List
Map<String, List<Book>> collect2 = authors.stream().distinct()
.collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
System.out.println(collect2);
anyMatch
只要有一个满足条件,就返回true
allMatch
如果都符合,结果为true
boolean b = authors.stream()
.allMatch(author -> author.getAge() > 18);
System.out.println(b);
noneMatch
可以判断流中元素是否都不符合匹配条件,如果都不符合结果为true
findAny
获取流中的任意一个,该方法没有办法保证获取的一定是流中的第一个元素。
findFirst
获取流中的第一个元素
reduce
归并
对流中的数据按照你制定的计算方法计算出一个结果
reduce的作用就是把stream()中的元素组合起来,我们可以传入一个初始值,他会按着我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果在和后面的元素计算。
//求所有作者的年龄之和
Integer reduce = authors.stream()
.map(author -> author.getAge())
.reduce(0, (integer, integer2) -> integer + integer2);
System.out.println(reduce);
//求所有作者中,年龄最小的值
Integer reduce1 = authors.stream()
.map(author -> author.getAge())
.reduce(Integer.MAX_VALUE, (integer, integer2) -> integer > integer2 ? integer2 : integer);
System.out.println(reduce1);
stream基本数据类型优化
减少自动装箱拆箱工作
第一个map是转换为integer类型,第二个map会进行装箱与拆箱工作后才能运算,故可以优化此操作。
并行流
parallel可以把串行流改成并行流
peek是一个调试函数
Integer arr[]={1,2,3,4,5,6,7,8,9,0,11};
Stream<Integer> stream1 = Arrays.stream(arr);
Integer reduce = stream1.parallel()
.peek(integer -> System.out.println(Thread.currentThread().getName() + integer))
.reduce(0, (integer, integer2) -> integer + integer2);
System.out.println(reduce);
Optional
到目前为止,臭名昭著的空指针异常是导致Java应用程序失败的最常见原因。以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写更干净的代码。受到Google Guava的启发,Optional类已经成为Java 8类库的一部分。
出现空指针异常的情况,比如mybaits在查询数据库时,如果查询的数据为null,则就会出现空指针异常。
使用optional可以优雅的避免空指针异常,optional就好像是包装类,可以把我们具体的数据封装到Optional对象内部。然后我们调用封装好的方法操作封装进去的数据就可以了。
使用
创建optional对象
我们一般使用optional的静态方法ofNullable来把数据封装成一个Optional对象,无论传入的参数是否为null都不会出现问题。
1.使用ofNullable()
Optional<List<Author>> authors1 = Optional.ofNullable(authors);
authors1.ifPresent(authors2 -> System.out.println(authors2));
of与ofNullable区别
of在使用时,要确保封装的对象不为空。
ofNullable则是两种情况都可以
安全消费值
我们获取到一个Optimal对象后肯定需要对其中的数据进行使用,这时候我们可以使用其中的ifpresent方法来消费其中的值。
这个方法会判断其内封装的数据是否为空,不为空时,才会执行具体的消费代码。下述代码就优雅的避免了空指针问题。
Optional<List<Author>> authors1 = Optional.ofNullable(authors);
authors1.ifPresent(authors2 -> System.out.println(authors2));
获取值
如果我们想要获取值自己处理可以使用get方法获取,不推荐,因为为空时会有异常。
安全获取值
orElseGet
获取数据并设置数据为空时的默认值。如果数据不为空就能够获取到该数据。
如果为空,就根据传入的参数来创建对象作为默认值返回。
下述情况就是如果为空,则返回一个空的list集合。
List<Author> authors2 = authors1.orElseGet(() -> new ArrayList<>());
System.out.println(authors2);
orElseThrow
如果数据为空,则根据传入的参数来创建异常抛出。使用spring时较好。
try {
List<Author> authors3 = authors1.orElseThrow((Supplier<Throwable>) () -> new RuntimeException("数据为空"));
System.out.println(authors3);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
过滤
我们使用**filter
**方法可以对数据进行过滤,如果原本是有数据的,但是不符合条件,也会变成一个无数据的Optional对象。
Optional<List<Author>> authors1 = Optional.ofNullable(authors);
authors1.filter(authors3 -> authors3.get(1).getAge()>10)
.ifPresent(authors32 -> System.out.println(authors32));
判断
我们使用isPresent
方法来判断是否存在数据的判断。如果为空,返回值为flase,如果不为空,返回值为true。但这中方式不能体现Options的好处。更推荐使用ifPresent。
数据转换
Optional还提供了map可以让我们对数据进行转换,并且可以转换得到的数据还是被Optional包装好的,并且保证了我们使用的安全。
例如我们想要获取作家的书籍集合。
Optional<List<Book>> books = authors1.map(authors2 -> authors2.get(1).getBooks());
System.out.println(books);