Lambda表达式和流

Lambda表达式和流

函数接口

实现函数功能的接口

image-20200927233555332

Lambda 表达式

语法

1
(parameterList)->{statements}

例如:

1
2
3
4
5
6
(x, y)->{return x+y;}
//or
(x, y)-> x + y

value->System.out.println(value)
()->System.out.println("hello world!")

Streams类

在Java中,Streams类实现了Stream接口。流可以用来加工和传递数据。

使用流的好处:

  • Java 8 中的 Stream 是对集合(Collection)对象功能的增强。

  • 它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。

  • Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。

  • 它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。

  • 通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。

Stream Pipelines

流在移动数据的时候加工里面的元素,称之为管线。它不像Collections可以保留数据,原始数据被加工后就无法重新使用了。

Intermediate and Terminal Operations

Intermediate operation是懒惰的,它直到Terminal Operation被调用后才会执行,即不会改变流的状态,可以继续被其他的Operation调用。

image-20200927235107596

而Terminal Operation是饥渴的,它会加工中间操作然后产生一个结果,这时流已经被破坏而不能使用了。

例如:

1
2
3
IntStream intStream = IntStream.of(values);  //values 是一个整型数组
intStream.reduce(0, (x, y), x + y); //因为是Terminal Operation,流已经被破坏
intStream.reduce(0, (x, y), x + y); //这时再执行会报错

image-20200927235525344

实例1:使用IntStream Operations来简化Arrays和ArrayList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//定义数组values
int[] values = {1,2,3};
//
//输出每个值
IntStream.of(values).forEach(value->System.out.printf("%d ", value));
//其他方式
Arrays.toString(values);
fori;
//
//获取长度
IntStream.of(values).count();
//获取最值
IntStream.of(values).min().getAsInt();
IntStream.of(values).max().getAsInt();
//reduce方法
IntStream.of(values).reduce(0, (x, y)-> x+y); //对values求和
//其他方式
IntStream.of(values).sum();
fori;
//
//输出偶数的10倍,这时先使用Intermediate 再使用Terminal Operation
IntStream.of(values).filter(value->value % 2 == 0).map(vakye->value * 10).sorted().forEach(value->sysout(value));
//求1~9的和
IntStream.range(1,10).sum();

实例2:使用Stream< Integer > Manipulations来处理Integer[]

1
2
3
4
5
6
Integer[] values = {1,2,3};
Arrays.stream(values).collect(Collectors.toList()); //将数组转换成一个集合类,比如列表,映射
//另一种转换方式
Collections.addAll(arrayList, values);
//对元素进行筛选和排序
Arrays.stream(values).filter(value->value > 4).sorted().collect(Collectors.toList());

实例3:使用Stream< String > Manipulations简化Strings, Characters 和 Regular Expressions

1
2
3
4
5
6
7
8
String[] strings = {"red", "blue", "yellow"};
//将字符串转换成大写
Arrays.stream(strings).map(String::toUpperCase).collect(Collectors.toList());
//等价于
System.out.println(Arrays.stream(strings).map(s->s.toUpperCase()).collect(Collectors.toList()));

Arrays.stream(strings).sorted(String.CASE_INSENSITIVE_ORDER).collect(Collectors.toList());//根据字典序排序

String::toUppercase 是一个方法引用,相当于把参数的表达式省略了,但实际上依然是使用具体的实例来引用方法。

其他的方式如下:

image-20200929162129817

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Employee[] list;  //一个对象数组
String toString(); //有一个toString方法

//输出list
list.stream().forEach(System.out::println);
//等价于
list.stream().forEach(obj->sysout(obj));

//使用谓词存储判断条件,然后根据条件筛选并按照薪水高低对员工进行排序
Predicate<Employee> fourToSixThousand = e -> (e.getSalary() >= 4000 && e.getSalary() <= 6000);
list.stream().filter(forToSixThousand).sorted(Comparator.comparing(Employee::getSalary))
.forEach(System.out::println);
//找到第一个符合条件的值
list.stream().filter(fourToSixThousand).findFirst().get());

函数变量和多领域比较

1
2
3
4
5
6
7
8
9
10
11
//接受单个参数,然后返回String类型
Function<Employee, String> byFirstName = Employee::getFirstName;
Function<Employee, String> byLastName = Employee::getLastName;

//先根据姓排序,再根据名排序
Comparator<Employee> lastThenFirst = Comparator.comparing(byLastName).thenComparing(byFirstName);

//进行排序
list.stream().sorted(lastThenFirst).forEach(System.out::println);
//倒序排
list.stream().sorted(lastThenFirst.reversed()).forEach(System.out::println);

使用map和distinct来产生唯一元素:

1
list.stream().map(Employee::getLastName).distinct().sorted().forEach(System.out::println);

groupingBy方法对对象进行分类

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
53
54
55
56
57
58
59
60
61
62
63
//这里使用的是一个比较常用的实例,根据某个数值范围来将数据分成许多个小段
public class Solution {
public static class Person{
private String name;
private int age;

public Person(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}

public String byAgeRange(){
//设定最高年龄和最低年龄,然后按照<=59 ... 90~99, 100的标准划分
if(getAge() <= 59){
return "<=59";
} else if(getAge() == 100){
return "100";
} else {
int ten = getAge() / 10;
return String.format(ten + "0~" + ten + "9");
}
}
}

public static void main(String[] args) {
Person[] perons = {
new Person("chen", 10),
new Person("wen", 78),
new Person("shu", 100),
new Person("guo", 31)
};
List<Person> list = Arrays.stream(perons).collect(Collectors.toList());
list.add(new Person("fan", 65));

Map<String, List<Person>> groupByName = list.stream().collect(Collectors.groupingBy(Person::byAgeRange));
System.out.println(groupByName);
}

}

输出结果:

1
{100=[Person{name='shu', age=100}], 60~69=[Person{name='fan', age=65}], 70~79=[Person{name='wen', age=78}], <=59=[Person{name='chen', age=10}, Person{name='guo', age=31}]}

将结果映射成double类型

1
2
list.stream().mapToDouble(Employee::getSalary).reduce(0, (value1, value2)->value1 + value2);
list.stream().mapToDouble(Employee::getSalary).average().getAsDouble();

实例4:从文件中创建字符流

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
//将某个文件中的单词做成一个字典。
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

public class StreamOfLines {
public static void main(String[] args) throws IOException {
Pattern pattern = Pattern.compile("\\s+");

Map<String, Long> wordCounts =
Files.lines(Paths.get("src/main/resources/test.txt")) //将文件转换成一行行字符串组成的流
.map(line -> line.replaceAll("(?!')\\p{P}", "")) //取代所有标点符号(\\p{P})
.filter(line -> line.compareTo("") != 0) //把空行筛选掉
.flatMap(line -> pattern.splitAsStream(line)) //将分解得到的多个流汇聚成一股流
.collect(Collectors.groupingBy(String::toLowerCase,
TreeMap::new, Collectors.counting())); //将流转换成Map结构

wordCounts.entrySet() //将map转换成key-value pair(称作entry)存储在set中
.stream()
.collect(
Collectors.groupingBy(entry -> entry.getKey().charAt(0), TreeMap::new, Collectors.toList())) //将首字母相同的单词放在同一个列表中,元素类型为entry
.forEach((letter, wordList) -> //按层级进行输出
{
System.out.printf("%n%C%n", letter);
wordList.stream().forEach(word -> System.out.printf(
"%13s: %d%n", word.getKey(), word.getValue()
));
});
}

实例5:使用流来统计随机结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.security.SecureRandom;
import java.util.function.Function;
import java.util.stream.Collectors;

public class RandomIntStream {
public static void main(String[] args) {
SecureRandom random = new SecureRandom();

System.out.printf("%-6s%s%n", "Face", "Frequency");
random.ints(600_000, 1, 7) //返回IntStream
.boxed() //将IntStream转化为Stream<Integer>,以使用方法collect
.collect(Collectors.groupingBy(Function.identity(), //等价于digit -> digit,即返回自身
Collectors.counting()))
.forEach((face, frequency) ->
System.out.printf("%-6d%d%n", face, frequency));
}
}

注:Java不允许原始数据放入集合中,而infinite stream(比如IntStream)的元素个数是未知的,因此需要使用boxed将其转换为Stream< Integer >。


Lambda表达式和流
http://example.com/2023/01/10/Lambda表达式和流/
作者
Chen Shuwen
发布于
2023年1月10日
许可协议