本文目录
第九章:Stream流与方法引用
1. Stream流
1.1 Stream简介
JDK 1.8开始引入。
流不一定是IO流。得益于lambda编程带来的函数式编程,引入Stream的概念,用于解决集合类的弊端,简化操作。
比如List的遍历,只能用for循环。我们关注于做什么(循环体), 而不是怎么做(for循环语法)。
示例:
我们有一个List,先筛选出姓张的人,再选出名字长度是3的人,再输出。
- 使用list.stream()方法转换为Stream流。
- 流有filter方法,传进去一个Predicate接口的lambda表达式进行筛选。
- 流有forEach接口,传进去消费者接口,将数据输出。
list.stream()
.filter((s -> s.startsWith("张")))
.filter(s -> s.length() == 3)
.forEach(System.out::println);
拼接流式模型:建立一个生产线,按照生产线来生产商品。每个操作都是一个流,从一个流的模型可以转换为另一个流的模型。集合的数据并没有被实时操作,只有最终执行时才会开始执行,这也是lambda延迟执行的特点。
Stream并不会存储元素,而是按需计算。
数据来源可以是集合或者数组。
Stream有两个基础特征:
-
Pipelining: 中间操作都会返回流对象本身,这样多个操作可以串联成一个管道。这样就可以对操作进行优化,比如延迟执行和短路。
-
内部迭代: Stream可以直接调内部迭代的方法。
filter
map
skip
count
forEach
1.2 获取流
-
所有的Collection集合均可通过stream()方法获取流。
-
Stream接口的静态方法of也可以。
static <T> Stream<T> of(T...values)
-
Map<K, V>获取流的方法:
- 先获取Map.Entry<K, V>,存入Set里,再转换为流。
-
数组转换为流:
- Stream.of(一个数组)。
- Stream.of(1, 2, 3, 4, 5)。
1.3 流的常用方法
流有两类方法:
-
延迟方法
- 返回值是Stream流对象,所以可以链式调用。
-
终结方法
- count()和forEach()方法是终结方法。
1.3.1 forEach方法
该方法接收一个Consumer接口,会将每一个元素交给接口去处理。
调用之后就会将流终结。
一般用来遍历流。
list.stream().forEach((str) -> System.out.println(str));
优化为方法引用:list.stream().forEach(System.out::println);
1.3.2 filter方法
这是个过滤方法,传递进来一个Predicate接口,对传进来的数据进行判断。如果满足条件,这个元素将被放进流,否则元素会被舍弃。
1.3.3 map方法
这是个映射方法,传递进来一个Function接口,将当前流中的数据转换为另一种类型的数据。
list.stream().map(s -> Integer.parseInt(s));
优化为方法引用:list.stream().map(Integer::parseInt);
1.3.4 count方法
返回一个long类型的数据,统计Stream中元素的个数。
调用之后就会将流终结。
1.3.5 limit方法
截取前n个元素,n是long类型。如果n大于流中元素的长度,就会返回全部元素组成的流。
这是个延迟方法,只是对流中的元素进行截取,返回一个新的流对象。
list.stream().limit(2);
返回list中前两个元素组成的流。
1.3.5 skip方法
跳过前n个元素。这是个延迟方法,只是对流中的元素进行截取,返回一个新的流对象。如果n大于流中元素的个数,就会返回一个空的流。
1.3.6 contract方法
组合两个流。Stream流的静态方法,可以将两个流合并成一个流。
Stream.concat(stream1, stream2);
1.4 Stream注意事项
Stream流属于管道流,只能被使用一次。第一个流调用完毕方法后,数据就会转到下一个Stream上。此时第一个Stream流已被使用完毕,不能再使用。
2. 方法引用
2.1 简介
我们传递进去的lambda表达式是一种解决方案:拿什么参数、做什么操作。如果其他地方已经存在了这样的方案,就不用重写重复的逻辑。
示例:
接口定义:
package MethodReference;
@FunctionalInterface
public interface PrintAble {
void print(String str);
}
测试类:
package MethodReference;
public class PrintAbleDemo {
public static void fun(String str, PrintAble p) {
p.print(str);
}
public static void main(String[] args) {
String s = "aaa";
fun(s, str -> System.out.println(str));
}
}
分析:
str -> System.out.println(str)
这个lambda表达式的目的是把str传给System.out对象,调用println方法打印。这个对象和方法都是已经存在的。
所以我们可以直接使用System.out::println
来输出字符串。参数被省略。
2.2 语义分析
注意: 传递的参数一定要是方法引用中可以接收的类型,否则会抛异常。
2.3 通过对象名引用成员方法
如果某个类的一个方法实现了我们想要的操作,就可以使用这个类对象来调用这个方法,作为方法引用。
接口定义:
package MethodReference.ObjectMethodReference;
@FunctionalInterface
public interface PrintAble {
void print(String str);
}
类定义:
package MethodReference.ObjectMethodReference;
public class MethodReferObj {
public void toUpper(String str){
System.out.println(str.toUpperCase());
}
}
测试:
package MethodReference.ObjectMethodReference;
public class Demo {
public static void fun(PrintAble p){
p.print("Hello");
}
public static void main(String[] args) {
MethodReferObj obj = new MethodReferObj();
fun(obj::toUpper);
}
}
2.4 通过类名引用静态成员方法
类存在,静态成员方法存在,就可以不新建对象,直接用类::成员方法
来实现方法引用。
2.5 通过super引用父类成员方法
package MethodReference.superDemo;
public class Man extends Human{
@Override
public void sayHello(){
System.out.println("我是子类方法");
}
public void method(Greeting g){
g.meeting();
}
public void show(){
method(super::sayHello);
}
}
存在继承关系、父类有sayHello方法。
2.6 通过this引用本类的方法
package MethodReference.superDemo;
public class Man extends Human{
@Override
public void sayHello(){
System.out.println("我是子类方法");
}
public void method(Greeting g){
g.meeting();
}
public void show(){
method(this::sayHello);
}
}
就是把2.5中的super改为this。
2.7 类的构造器引用
package MethodReference.ConstructReferDemo;
public class Demo {
public static void printName(String s, PersonBuilder p){
Person build = p.build(s);
System.out.println(build.getName());
}
public static void main(String[] args) {
String name = "张三";
//传统lambda表达式
printName(name,(s)->new Person(s));
//方法引用,类名::new
printName(name,Person::new);
}
}
2.8 数组的构造器引用
int[]::new