java8函数式编程Lambda表达式

前言

在开发安卓的时候就通过RxJava进行过函数式编程。java源码中的建造者模式也是类似函数式编程的玩意。
举个例子

1
2
3
4
String url = builder.baseUrl(url)
.param("username", "admin")
.param("password", "admin")
.build();

Lambda表达式-使代码更简洁

通过匿名内部类,我们可以减少类的数量,并且逻辑更清晰。

1
2
3
4
5
6
7
8
9
@Test
public void test() {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("测试");
}
});
}

但是,这明显还不够简洁。Runable只有一个方法run(),每次都要重复相同的代码。
java8提供了Lambda表达式。用于满足这种只有一个方法的接口的简洁写法。
原本5行代码浓缩成了1行!

1
2
3
4
@Test
public void test() {
new Thread(() -> System.out.println("测试"));
}

常见的表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Runable noArguments = () -> System.out.println("无参Lambda");
ActionListener oneArguments = event -> System.out.println("含有一个参数的Lambda");
Runable multiStatement = () -> {
System.out.println("含有多行");
System.out.println("代码的Lambda");
};
BinaryOperator(Long) multiArgument = (x,y) -> {
System.out.println("含有多个参数的Lambda");
return x+y;
}
BinaryOperator(Long) multiArgument = (Long x, Long y) -> {
System.out.println("含有多个指定类型的参数的Lambda");
return x+y;
}

Stream流-新的迭代方式

在java8以前,迭代一般都是通过for或者while实现。
在java5,产生了foreach的迭代方式。
现在,java8提供了新的Stream的迭代方式。

基本使用

collect(toList())创建集合
通过``

1
2
3
4
5
 @Test
public void test() {
List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList());
System.out.println(list);//[1, 2, 3, 4, 5, 6, 7, 8, 9]
}

filter()过滤
有时候需要获取满足条件的集合元素。
比如大于5的元素

1
2
3
4
5
6
7
@Test
public void test() {
List<String> list = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.filter(x -> x>5)
.collect(Collectors.toList());
System.out.println(list);//[6, 7, 8, 9]
}

max()最大值与min()最小值
这里的最大值最小值不只是指长度或数值上的大小。可以进行自定义排序的指标。

1
2
3
4
5
6
@Test
public void test() {
String max = Stream.of("i","love","you").max(Comparator.comparing(String::length)).get();
String min = Stream.of("i","love","you").min(Comparator.comparing(String::length)).get();
System.out.println(max+","+min);// love, i
}

Map转换

map()转换类型
int转换为String类型

1
2
3
4
5
6
7
8
@Test
public void test() {
List<String> list = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.map(x -> "数字"+x)
.collect(Collectors.toList());
System.out.println(list);
//[数字1, 数字2, 数字3, 数字4, 数字5, 数字6, 数字7, 数字8, 数字9]
}

flatMap()转换类型
多个Stream压缩成一个Stream

1
2
3
4
5
6
7
8
9
10
@Test
public void test() {
List<Integer> list1 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).collect(Collectors.toList());
List<Character> list2 = Stream.of('a','b','c').collect(Collectors.toList());

System.out.println(Stream.of(list1, list2)
.flatMap(list -> list.stream())
//.flatMap(Collection::stream) //方法引用
.collect(Collectors.toList()));
}

reduce()一组数据生成一个数据

上面的max()min()都是reduce操作
求和例子,

1
2
3
4
5
6
7
8
@Test
public void test() {
int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.reduce(0, (acc, element) -> acc+element);
// long sum2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9).mapToInt(x -> x).summaryStatistics().getSum();
// 开发时应使用这种方式求和
System.out.println(sum);
}

::方法引用

上面的flatMap方法中,使用了Collection::stream,类似C++的语法。
等价于list -> list.stream()

1
2
3
4
5
6
@Test
public void test() {
List<String> list1 = Stream.of("i", "love","you").collect(Collectors.toList());
String max = list1.stream().max(Comparator.comparing(String::length)).get();
System.out.println(max);
}

collect收集器的使用

上面的例子中有一个collect方法,可以将Stream转化为List等集合对象。

1
2
3
4
5
@Test
public void test() {
List<String> list = Stream.of("i", "love","you").collect(Collectors.toList());
System.out.println(list);
}

** 转化为指定集合类型 **
有时候需要指定特定的集合比如TreeSet之类的。需要手动指定产生集合。

1
2
3
4
5
6
@Test
public void test() {
Set<String> set = Stream.of("i", "love","you")
.collect(Collectors.toCollection(TreeSet::new));
System.out.println(set);
}

** 转化为值 **
有时候需要按照某种顺序找到一个值。比如找一个最长的单词。
这个例子在实际开发中不使用。

1
2
3
4
5
6
7
8
9
@Test
public void reduce() {
String max = Stream.of("i", "love","you")
.collect(Collectors.maxBy(
Comparator.comparing(String::length)
))
.get();
System.out.println(max);
}

** 集合生成字符串 **
有时候需要将集合的所有字符提取出来,组合在一起

1
2
3
4
5
@Test
public void reduce() {
String str = Stream.of("i", "love","you").collect(Collectors.joining("*","(",")"));
System.out.println(str);//(i*love*you)
}

** 数据分块分组 **
partitioningBy将Stream分成两个部分,存储在一个Map中,以truefalse为键。

1
2
3
4
5
6
7
8
@Test
public void test() {
Map<Boolean, List<String>> map = Stream.of("i", "love","you")
.collect(Collectors.partitioningBy(str->str.length()>3));
for(Map.Entry entry : map.entrySet()){
System.out.println(entry.getKey()+","+entry.getValue());
}
}

大部分情况需要分成不止两个部分,可能更多。
groupingBy可以将Stream分成多个部分,下面的例子是将相同长度的字符串存储到一起。

1
2
3
4
5
6
7
8
@Test
public void test() {
Map<Integer, List<String>> map = Stream.of("i", "love","you", "hhh")
.collect(Collectors.groupingBy(String::length));
for(Map.Entry entry : map.entrySet()){
System.out.println(entry.getKey()+","+entry.getValue());
}
}

** 下游收集器 **
使用groupingBy的时候发现有多个重载方法。
groupingBy(Function classifier, Collector downstream)提供了一个下游收集器downstream
可以将classifier收集的流,通过downstream转化。
如下面代码

1
2
3
4
5
6
7
8
9
10
11
12
13
@Test
public void test(){
List<String> list = Stream.of("i","love","you","too").collect(Collectors.toList());
Map<Integer, Long> map = list.stream().collect(
Collectors.groupingBy(String::length, //根据字符串长度转化为map集合
Collectors.counting())); //处理上游的map集合,转化为集合的个数
for(Map.Entry entry : map.entrySet()){
System.out.println(entry.getKey()+","+entry.getValue());
}
// 1, 1
// 3, 2
// 4, 1
}