JDK1.8 Lambda/Stream

前言

Oracle的发布周期缩短,即以后每半年一个版本,JDK11马上都要发布出来,但现在实验室和生成环境使用的还是JDK8。JDK8新特性听到最多的就是LambdaStream了,为了更好的了解后续版本的特性,我们先来了解学习一下JDK8中的这两个特性。

Lambda表达式

首先要知道的是,lambda是用来简化我们的代码量的。直观上来看它可以让一个让一个表达式作为参数。如:

1
2
3
ArrayList<Stu> data = new ArrayList<>();
data.add(new Stu(18,"Job"));
data.stream().forEach(s-> System.out.println(s.name+s.age));

注意上面forEach()填入了一个"s-> System.out.println(s.name+s.age)"这样的表达式,先不管这个表达式是什么意思,但是我们知道有些方法是可以以表达式的方式作为参数进行使用。

那么就是说forEach是接受一个表达式参数的,点开发现它是接受一个叫做Consumer<? super T> action

1
void forEach(Consumer<? super T> action);

那Consumer又是什么呢?点开发现这是一个被FunctionalInterface修饰的接口。这个jdk1.8新引入的。被这个修饰的接口称为函数式接口,里面只能有一个自定义方法。如Consumer有一个自定义方法accept()

1
2
3
4
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

那么forEach的接受的参数就是一个实现了Consumer接口的类。那我们也可以像下面这样做:

1
2
3
4
5
6
7
ArrayList<Stu> data = new ArrayList<>();
data.add(new Stu(18,"Job"));
data.stream().forEach(new Consumer<Stu>() {
@Override
public void accept(Stu stu) {
System.out.println(stu.name + stu.age);
}

上面的这种写法不就是很熟悉的匿名内部类实现吗!既然可以这样做,为什么还要弄成什么lambda表达式?前面也提到了,Lambda表达式就是为了简化我们代码量的。那么继续来看内部类的实现是如何变成简短的表达式的。

首先函数式接口只有一个一个自定义的方法,像上面的Consumer接口中的accept方法。new Consumer<Stu>()这段系统肯定是帮你生成好的,所以可以不用写。既然只有一个方法,那么public void accept(Stu stu)这段是不是没有什么必要了,因为实现的方法体也只能对应到唯一一个方法中。参数类型Stu也不需要,java内部就可以识别,所以只要一个参数名s即可。简化之后就差不多是下面的式子了。

1
2
(参数)-> 方法体
s-> System.out.println(s.name+s.age)

Stream

这里的Stream不是我们在IO上看到的Stream,简单来说这个是用来处理集合的。还是上面那个实例,对ArrayList进行遍历。

1
2
3
4
5
6
7
8
9
10
11
12
//Jdk1.8之前遍历
for(Stu s:data){
System.out.println(s.name+s.age);
}
//或者用迭代器来做
Iterator<Stu> iterator = data.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}

//JDK1.8,一句就可以完成
data.stream().forEach(s-> System.out.println(s.name+s.age));

那如果想要针对sex字段(Male,Female)值,映射成中文(男,女)forEach就做不了吧,的确是的。但是stream还提供了map这个方法,map是一个接受Function接口实现的。Function和Consumer都是1.8自带的函数式接口,这样可以减少我们自己去生成没什么作用的函数式接口。

jdk1.8已经生成了好多个函数式接口,常用有如下四个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//接受一个类型,不返回值,那基本就是做一个输出
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}

//不接受参数,直接返回一个值,可能是用来提供某个随机值。因为它没有参数
@FunctionalInterface
public interface Supplier<T> {
T get();
}

//接受两个参数,一个是用来计算,另一个是返回类型,可以说是一个映射,和stream中的map是一个意思
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}

//根据一个参数,去判断这个参数的某个属性
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}

stream中好多方法就是接受一个如上面函数接口来实现功能,如Stream中的map接受一个Function接口的实现,Skip接受Predicate的接口实现。下面列出Stream常用的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52

//根据年龄和名字进行排序,然后再输出,sorted这里要接受一个comparator的函数式接口的实现
data.stream().sorted((s1,s2)->{
if(s1.age==s2.age)
return s1.name.compareTo(s2.name);
else
return s1.age-s2.age;
}).forEach(s-> System.out.println(s.name+s.age));


//根据年龄的奇偶来输出一个中文,那就是一个简单的映射,用map。map是一个接受Function接口实现的
data.stream().map(p->{
if(p.age%2==1)
return "奇数";
else
return "偶数";
}).forEach(p-> System.out.println(p));


//map用了只能使用映射的值,如果还要实现,一种方式是在return的时候就把 "奇数偶数"+age+name重新封装成一个newStu类,然后返回
//第二中就是使用flatMap来实现,flatMap接受一个s之后,再生成一个stream返回
data.stream().flatMap(s->{
ArrayList<NewStu> stuArrayList = new ArrayList<>();
if(s.age%2==1)
stuArrayList.add(new NewStu(s.age,s.name,"奇数"));
else
stuArrayList.add(new NewStu(s.age,s.name,"偶数"));
return stuArrayList.stream();
}).forEach(ns-> System.out.println(ns.age+ns.name+ns.joValue));


//filter接受一个参数,根据参数值进行筛选,它需要接受一个predicate的接口实现
data.stream().filter(s->{
if(s.age>=16 && s.age<=20)
return true;
return false;
}).forEach(s-> System.out.println(s.age+s.name));


//limit和skip的结合使用,skip忽略前面几个,limit容纳几个。集合就是输出第几个到第几个
data.stream().skip(2).limit(4).forEach(s-> System.out.println(s.age+s.name));


//distinct,根据hashcode和equals来去重的,如果是基本数据类型,比较的就是值,如果是类的话,要看是否是同一个实列对象
data.stream().distinct().forEach(System.out::println);


//allMatch——检查是否匹配所有元素 ,anyMatch——检查是否至少匹配一个元素,noneMatch——检查是否没有匹配的元素
//findFirst——返回第一个元素,findAny——返回当前流中的任意元素,count——返回流中元素的总个数
//max——返回流中最大值,min——返回流中最小值
Optional<Stu> sMax = data.stream().min((s1, s2)->(s1.age-s2.age));
System.out.println(sMax.get().age);

惰性计算

1
2
3
4
5
6
7
8
9
10
11
12
13

//惰性计算:如果没有执行integerStream.forEach,System.out.println("大于十八");不会输出。
//即如果这个流没有被使用,它是不会去执行的,所以叫做惰性
Stream<Integer> integerStream = data.stream().map(p->{
if(p.age>=18) {
System.out.println("大于十八");
return p.age;
}

return null;
});

integerStream.forEach(p-> System.out.println(p));