抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

java8实战汇总

Lambda表达式

Lambda表达式有三个部分,参数->主体

1
2
(parameters) -> expression
(parameters) ->{ statements; }
1
2
3
4
5
6
7
8
9
() -> {} 
() -> "Raoul"
() -> {return "Mario"; }
// return是一个控制流语句。要使用此Lambda有效,需要使用花括号
(Integer i) -> return "Alan" + i;
(Integer i) -> {return "Alan" + i;}
// “Iron Man”是一个表达式,不是一个语句。要使此Lambda有效,可以去除花括号和分号
(String s) -> {"Iron Man"; }
(String s) -> "Iron Man";

演示代码

1

stream流的操作

❏ 元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。因为集合是数据结构,所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList与LinkedList)。但流的目的在于表达计算,比如你前面见到的filter、sorted和map。集合讲的是数据,流讲的是计算。后面几节会详细解释这个思想。

❏ 源——流会使用一个提供数据的源,比如集合、数组或I/O资源。请注意,从有序集合生成流时会保留原有的顺序。由列表生成的流,其元素顺序与列表一致。

❏ 数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,比如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可以并行执行。

流操作有两个重要的特点。

❏ 流水线——很多流操作本身会返回一个流,这样多个操作就可以链接起来,构成一个更大的流水线。这使得一些优化成为可能,比如处理延迟和短路。流水线的操作可以看作类似对数据源进行数据库查询。

❏ 内部迭代——与集合使用迭代器进行显式迭代不同,流的迭代操作是在后台进行的。

示例代码

1
2
3
4
5
6
7
8
9
10
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french fries", true, 530, Dish.Type.OTHER),
new Dish("rice", true, 350, Dish.Type.OTHER),
new Dish("season fruit", true, 120, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("prawns", false, 300, Dish.Type.FISH),
new Dish("salmon", false, 450, Dish.Type.FISH) );
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
public class Dish {
private final String name;
private final boolean vegetarian;
private final int calories;
private final Type type;
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarian() {
return vegetarian;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
@Override
public String toString() {
return name;
}
public enum Type { MEAT, FISH, OTHER }
}

初次使用stream

1
2
3
4
5
6
7
// toList()是一个静态方法,需要 import static java.util.stream.Collectors.toList;
// 要是不想导包,可以把toList() 改成 Collectors.toList()
List<String> collect = menu.stream().filter(dish -> dish.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(toList());
System.out.println(collect);

上述中Dish::getName相当于Lambda d -> d.getName()

筛选(filter)、提取(map)或截断(limit)功能

流的特性

只能遍历一遍

1
2
3
Stream<Dish> stream = menu.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println);

内部迭代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 用for-each循环外部迭代
ArrayList<String> names = new ArrayList<>();
for (Dish dish : menu) {
names.add(dish.getName());
}
/**
* for-each结构是一个语法糖,其背后逻辑是一个迭代器,Iterator
*/
ArrayList<String> names = new ArrayList<>();
Iterator<Dish> iterator = menu.iterator();
while (iterator.hasNext()){
names.add(iterator.next().getName());
}
// stream的内部迭代
List<String> names = menu.stream().map(Dish::getName).collect(toList());

System.out.println(names);

重构测试

1
2
3
4
5
6
7
8
List<String> highCaloricDishes = new ArrayList<>();
Iterator<String> iterator = menu.iterator();
while(iterator.hasNext()) {
Dish dish = iterator.next();
if(dish.getCalories() > 300) {
highCaloricDishes.add(d.getName());
}
}
1
2
3
4
List<String> highCaloricDishes =
menu.stream()
.filter(dish -> dish.getCalories() > 300)
.collect(toList());

流的操作

从上述代码中,不难看出,stream()之后跟着filter,map,limit,collect等操作,这是java.util.stream.Stream中的Stream接口定义的操作,它们可以分成两类。

❏ 中间操作:filter、map和limit可以连成一条流水线;

❏终端操作: collect触发流水线执行并关闭它。

总结

  1. 一个数据源
  2. 一个中间操作链,形成流水线
  3. 一个终端操作,执行流水线,并生成结果

中间操作

类型 操作 返回类型 操作参数 函数描述 作用
filter Stream<T> Predicate<T> T -> boolean 筛选
map Stream<R> Function<T,R> T -> R 获取
有状态-无界 limit Stream<T> 截选
有状态-无界 skip Stream<T> 跳过
有状态-无界 sorted Stream<T> Comparator<T> (T,T) -> int
有状态-无界 distinct Stream<T> 去重

终端操作

类型 操作 返回类型 作用
forEach void 消费流中的每个元素并对其应用Lambda
count long 返回流中元素的个数
collect (generic) 把流整合成一个集合,比如List,Map,甚至是Iterator
anyMatch boolean
noneMatch boolean
allMatch boolean
findAny Optinal<T>
findFirst Optinal<T>
有状态-有界 reduce Optinal<T>

使用流

筛选

谓词筛选
1
2
List<Dish> collect = menu.stream().filter(Dish::isVegetarian).collect(toList());
System.out.println(collect);
筛选各异的元素

流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流。例如,以下代码会筛选出列表中所有的偶数,并确保没有重复(使用equals方法进行比较)

1
2
3
4
5
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct()
.forEach(System.out::println);

切片

谓词切片

takeWhiledropWhile,这两个是java9引入的新方法。

引入原因:倘若数据源已经按照热量从小到大排序了,还要筛选出热量小于320的数据,filter筛选会去遍历所有数据,这样比较消耗性能。所以引入了takeWhiledropWhile

takeWhile:通过谓词去选取出符合的数据,遭遇到第一个不符合的元素时,停止处理

dropWhile:和taskWhile是相反的操作,它会删除掉符合要求的数据

截短流

流支持limit(n)方法,该方法会返回另一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。

跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。请注意,limit(n)和skip(n)是互补的

映射

流支持map方法,它会接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)

流的扁平化

案例

对于一张单词表,如何返回一张列表,列出里面各不相同的字符?

例如,给定单词列表[“Hello”,“World”],要返回列表[“H”, “e”, “l”, “o”, “W”, “r”, “d”]

尝试1:不行

1
2
3
4
5
6
7
List<String> words = Arrays.asList("hello", "world");
List<String[]> collect =
words.stream()
.map(word -> word.split(""))
.distinct()
.collect(toList());
System.out.println("words = " + words);

尝试2:不行

1
2
3
4
5
6
7
8
List<String> words = Arrays.asList("hello", "world");
List<Stream<String>> collect =
words.stream()
.map(word -> word.split(""))
.map(Arrays::stream)
.distinct()
.collect(toList());
System.out.println("words = " + words);

解释Arrays::stream

1
2
3
4
String[] arrayOfWords = {"Goodbye", "World"};
List<String> streamOfwords = Arrays
.stream(arrayOfWords) // 这是Stream<String>类型的
.collect(toList());

尝试3:使用flatMap,成功

flatMap
1
2
3
4
5
6
7
8
List<String> words = Arrays.asList("hello", "world");
List<String> collect =
words.stream()
.map(word -> word.split(""))
.flatMap(Arrays::stream)
.distinct()
.collect(toList());
System.out.println("words = " + words);

查找和匹配

另一个常见的数据处理套路是看看数据集中的某些元素是否匹配一个给定的属性。Stream API通过allMatch、anyMatch、noneMatch、findFirst和findAny方法提供了这样的工具。

anyMatch

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”

比如↓,你可以用它来看看菜单里面是否有素食可选择

1
2
3
4
if(menu.stream().anyMatch(Dish::isVegetarian)){
System.out.println("The menu is (somewhat) vegetarian friendly! ! ");
}
//anyMatch方法返回一个boolean
allMatch

allMatch方法会看流中的元素是否都能匹配给定的谓词。

比如↓,你可以用它来看看菜品是否有利健康(即所有菜的热量都低于1000卡路里):

1
boolean isHealthy = menu.stream().allMatch(dish -> dish.getCalories() < 1000);
noneMatch

它可以确保流中没有任何元素与给定的谓词匹配

比如↓,你可以用noneMatch重写前面的例子

1
boolean isHealthy = menu.stream().noneMatch(dish -> dish.getCalories() >= 1000);
findAny

findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用

比如↓,你可能想找到一道素食菜肴。可以结合使用filter和findAny方法来实现这个查询

Optional

1
Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian).findAny();
findFirst

有些流由一个出现顺序(encounter order)来指定流中项目出现的逻辑顺序(比如由List或排序好的数据列生成的流)。对于这种流,你可能想要找到第一个元素。为此有一个findFirst方法,它的工作方式类似于findAny

1
2
3
4
5
6
List<Integer> someNumbers = Arrays.asList(1, 2, 3, 4, 5);
Optional<Integer> firstSquareDivisibleByThree =
someNumbers.stream()
.map(n -> n * n)
.filter(n -> n % 3 == 0)
.findFirst(); // 9

归约-reduce

此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。这样的查询可以被归类为归约操作(将流归约成一个值)。用函数式编程语言的术语来说,这称为折叠(fold),因为你可以将这个操作看成把一张长长的纸(你的流)反复折叠成一个小方块,而这就是折叠操作的结果

求和

在研究如何使用reduce方法之前,先来看看如何使用for-each循环来对数字列表中的元素求和

1
2
3
4
int sum = 0;
for (int x : numbers) {
sum += x;
}

从上述代码看,for-each的求和过程并不复杂,那为什么还要引入reduce呢?

for-each代码复用率低,reduce对重复应用的模式做了抽象

1
2
// 引入reduce求和
int sum = numbers.stream().reduce(0,(a,b)->a+b);

reduce接收的参数:

  1. 初始值,即对应for-each中的sum

  2. BinaryOperator<T>用来将两个元素结合起来产生一个新的值。上述代码中是一个lambda表达式

(a,b)->a+b

所以,当有累乘的业务时,我们可也写成:

1
int product = numbers.stream().reduce(1,(a,b)->a*b);
  • 引入java8静态方法sum进行求和
1
int sum = numbers.stream().reduce(0,Integer::sum);
  • 无初始值

reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象

1
int sum = numbers.stream().reduce((a,b)->a+b);
最大值和最小值
1
2
Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);
归约方法的优势与并行化

从for-each循环求和来看,这种迭代求和方式一旦并行,问题时致命的。而reduce提供了更加安全,简便的实现方式。

1
2
3
// use stream to sum all elements in parallel
// 使用流对所有元素并行求和
int sum = numbers.parallelStream().reduce(0,Integer::sum);

原始类型流特化

1
int calories = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);

像上述代码,直接写成int calories = menu.stream().map(Dish::getCalories).sum();不是更好?但这却是不可能的,因为map方法生成的是Stream<T>类型的数据,没办法通过sum()方法来实现求和,不过Stream API提供了原始类型流特化,专门处理数值流的方法

IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本

映射到数值流

将流转换为特化版本的常用方法是mapToInt、mapToDouble和mapToLong

这些方法和前面说的map方法的工作方式一样,只是它们返回的是一个特化流,而不是Stream

IntStream还支持其他的方便方法,如max、min、average等

1
2
3
4
int sum = 
menu.stream() //Stream<Dish>
.mapToInt(Dish::getCalories) // IntStream
.sum(); // 如果IntStream是空的话,返回的是0
转换回对象流

同样,一旦有了数值流,你可能会想把它转换回非特化流。例如,IntStream上的操作只能产生原始整数:IntStream的map操作接受的Lambda必须接受int并返回int(一个IntUnaryOperator)。但是你可能想要生成另一类值,比如Dish

1
2
3
IntStream intStream = menu.stream()
.mapToInt(Dish::getCalories);
Stream<Integer> stream = intStream.boxed();
默认值OptionalInt

求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果

Optional类,这是一个可以表示值存在或不存在的容器。Optional可以用Integer、String等参考类型来参数化。对于三种原始流特化,也分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong

1
2
3
4
5
6
OptionalInt maxCalories = 
menu.stream()
.mapToInt(Dish::getCalories)
.max();
System.out.println("maxCalories = " + maxCalories);
int max = maxCalories.orElse(1);
数值范围
1
2
3
4
5
6
7
8
9
IntStream.range(1, 10)        
.filter(value -> value % 2 == 0)
.forEach(System.out::println);
// ↑结果:2,4,6,8
IntStream.rangeClosed(1, 10)
.filter(value -> value % 2 == 0)
.forEach(System.out::println);
// ↑结果:2,4,6,8,10
// 总结range范围[x,y),rangeClosed范围[x,y]

尝试:获取勾股数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Stream<int[]> pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.filter(b -> Math.sqrt(a*a + b*b) % 1 == 0)
.mapToObj(
b -> new int[]{a, b, (int)Math.sqrt(a * a + b * b)})
);
pythagoreanTriples.limit(5).forEach(t->System.out.println(t[0]+","+ t[1] + "," + t[2]));



Stream<double[]> pythagoreanTriples =
IntStream.rangeClosed(1, 100).boxed()
.flatMap(a ->
IntStream.rangeClosed(a, 100)
.mapToObj(
b -> new double[]{a, b, Math.sqrt(a * a + b * b)})
)
.filter(t -> t[2] % 1 == 0);
pythagoreanTriples.limit(5).forEach(t->System.out.println(t[0]+","+ t[1] + "," + t[2]));

Optional简介

Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。

上述代码中,findAny存在什么元素都没有找到的可能,所以java8引入了Optional<T>,用来避免返回null的问题

测试

测试1:filter

根据示例代码结合filter使用流筛选出前两个荤菜

1
2
3
4
5
List<Dish> collect = 
menu.stream()
.filter(dish -> dish.getType().equals(Dish.Type.MEAT))
.limit(2)
.collect(toList());

测试2:map

根据示例代码结合映射

(1)给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4, 5],应该返回[1, 4, 9, 16,25]。

(2) 给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2,4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代表数对

(3) 如何扩展前一个例子,只返回总和能被3整除的数对

1
2
3
4
5
// (1)给定一个数字列表,如何返回一个由每个数的平方构成的列表呢?例如,给定[1, 2, 3, 4, 5],应该返回[1, 4, 9, 16,25]。
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> collect = numbers.stream().map(n -> n * n)
.collect(toList());
System.out.println("collect = " + collect);

1
2
3
4
5
6
7
8
9
// (2)给定两个数字列表,如何返回所有的数对呢?例如,给定列表[1, 2, 3]和列表[3, 4],应该返回[(1, 3), (1, 4), (2, 3), (2,4), (3, 3), (3, 4)]。为简单起见,你可以用有两个元素的数组来代表数对
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =
numbers1.stream()
.flatMap(i -> numbers2.stream()
.map(j -> new int[]{i, j})
)
.collect(toList());

1
2
3
4
5
6
7
8
9
10
11
// (3)如何扩展前一个例子,只返回总和能被3整除的数对
List<Integer> numbers1 = Arrays.asList(1, 2, 3);
List<Integer> numbers2 = Arrays.asList(3, 4);
List<int[]> pairs =
numbers1.stream()
.flatMap(i ->
numbers2.stream()
.filter(j -> (i + j) % 3 == 0)
.map(j -> new int[]{i, j})
)
.collect(toList());

测试3:reduce

根据示例代码,结合reduce,数一数流中有多少个菜

1
int count = menu.stream().map(d -> 1).reduce(0, (a, b) -> a + b);

等价于

1
long count = menu.stream().count();

综合测试

源代码

1
2
3
4
5
6
7
8
9
10
11
12
Trader raoul = new Trader("Raoul", "Cambridge");
Trader mario = new Trader("Mario", "Milan");
Trader alan = new Trader("Alan", "Cambridge");
Trader brian = new Trader("Brian", "Cambridge");
List<Transaction> transactions = Arrays.asList(
new Transaction(brian, 2011, 300),
new Transaction(raoul, 2012, 1000),
new Transaction(raoul, 2011, 400),
new Transaction(mario, 2012, 710),
new Transaction(mario, 2012, 700),
new Transaction(alan, 2012, 950)
);
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
public class Trader{
private final String name;
private final String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
public String getName(){
return this.name;
}
public String getCity(){
return this.city;
}
public String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}
public class Transaction{
private final Trader trader;
private final int year;
private final int value;
public Transaction(Trader trader, int year, int value){
this.trader = trader;
this.year = year;
this.value = value;
}
public Trader getTrader(){
return this.trader;
}
public int getYear(){
return this.year;
}
public int getValue(){
return this.value;
}
public String toString(){
return "{" + this.trader + ", " +
"year: "+this.year+", " +
"value:" + this.value +"}";
}
}

问题:

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
// (1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
List<Transaction> tr2011 = transactions.stream()
.filter(transaction -> transaction.getYear() == 2011)
.sorted(Comparator.comparing(Transaction::getValue))
.collect(Collectors.toList());
System.out.println("tr2011 = " + tr2011);
// (2) 交易员都在哪些不同的城市工作过?
List<String> cities = transactions.stream()
.map(transaction -> transaction.getTrader().getCity())
.distinct()
.collect(Collectors.toList());
// Set<String> cities = transactions.stream()
// .map(transaction -> transaction.getTrader().getCity())
// .collect(Collectors.toSet());
// System.out.println("cities = " + cities);
System.out.println("cities = " + cities);

// (3) 查找所有来自于剑桥的交易员,并按姓名排序。
List<Trader> cambridgeTrader = transactions.stream()
.map(Transaction::getTrader)
.filter(trader -> trader.getCity().equals("Cambridge"))
.distinct()
.sorted(Comparator.comparing(Trader::getName))
.collect(Collectors.toList());
System.out.println("cambridgeTrader = " + cambridgeTrader);
// (4) 返回所有交易员的姓名字符串,按字母顺序排序。
// String reduce = transactions.stream()
// .map(transaction -> transaction.getTrader().getName())
// .distinct()
// .sorted()
// .reduce("", (n1, n2) -> n1 + n2);
String reduce = transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.collect(Collectors.joining());

System.out.println("reduce = " + reduce);
// (5) 有没有交易员是在米兰工作的?
boolean anyOneInMilan = transactions.stream()
.anyMatch(transaction -> transaction.getTrader().getCity().equals("Milan"));
System.out.println("anyOneInMilan = " + anyOneInMilan);
// (6) 打印生活在剑桥的交易员的所有交易额。
transactions.stream()
.filter(transaction -> transaction.getTrader().getCity().equals("Cambridge"))
.map(Transaction::getValue)
.forEach(System.out::println);
// (7) 所有交易中,最高的交易额是多少?
Optional<Integer> max = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::max);
Optional<Transaction> max1 = transactions.stream().max(Comparator.comparing(Transaction::getValue));
System.out.println("max = " + max);
System.out.println("max1 = " + max1);

// (8) 找到交易额最小的交易。
Optional<Integer> min = transactions.stream()
.map(Transaction::getValue)
.reduce(Integer::min);
Optional<Transaction> min1 = transactions.stream().min(Comparator.comparing(Transaction::getValue));
System.out.println("min = " + min);
System.out.println("min1 = " + min1);

测试树形数据

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
public static void main(String[] args) {
ArrayList<Zz> source = new ArrayList<>();
for (int i = 1; i < 10; i++) {
Zz z1 = new Zz();
z1.setId(Long.valueOf(i));
int i1 = i % 3 == 2 ? i - 1 : 0;
z1.setPid(Long.valueOf(i1));
source.add(z1);
}
System.out.println(source);

List<Zz> tree = source.stream()
.filter(item -> item.getPid().equals(0))
.map(item -> {
List<Zz> children = new ArrayList<>();
item.setChildren(children);
getChildren(children, item.getId(), source);
return item;
})
.collect(Collectors.toList());
System.out.println(tree);
}

private static void getChildren(List<Zz> children, Long id, ArrayList<Zz> source) {
source.stream()
.filter(item -> item.getPid().equals(id))
.map(item -> {
children.add(item);
List<Zz> child = new ArrayList<>();
item.setChildren(child);
getChildren(children, item.getId(), source);
return item;
})
.collect(Collectors.toList());
}

函数式编程Function

待续

评论