付诸实战
在本节中,我们会将迄今学到的关于流的知识付诸实践。我们来看一个不同的领域:执行交易的交易员。
你的经理让你为八个查询找到答案。
-
找出2011年发生的所有交易,并按交易额排序(从低到高)。
-
交易员都在哪些不同的城市工作过?
-
查找所有来自于剑桥的交易员,并按姓名排序。
-
返回所有交易员的姓名字符串,按字母顺序排序。
-
有没有交易员是在米兰工作的?
-
打印生活在剑桥的交易员的所有交易额。
-
所有交易中,最高的交易额是多少?
-
找到交易额最小的交易。
ps: 多像数据库查询。换言之,个人以后如果想实现 database,就可以使用 stream 来实现大部分的这种需求。
领域:交易员和交易
(可以理解对应数据库中的表,设计中的实体)
以下是我们要处理的领域,一个 Traders 和 Transactions 的列表:
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)
);
Trader和Transaction类的定义:
- Trader
public class Trader {
private String name;
private String city;
public Trader(String n, String c){
this.name = n;
this.city = c;
}
//Getter & Setter & ToString
}
- Transaction
public class Transaction {
private Trader trader;
private Integer year;
private Integer value;
public Transaction(Trader trader, Integer year, Integer value) {
this.trader = trader;
this.year = year;
this.value = value;
}
}
1. 找出2011年发生的所有交易,并按交易额排序(从低到高)。
List<Transaction> tr2011 = transactions.stream()
// 筛选出2011年发生的所有交易
.filter(transaction -> transaction.getYear() == 2011)
// 按照交易额从低到高排序
.sorted(Comparator.comparing(Transaction::getValue))
// 转为集合
.collect(Collectors.toList());
太棒了,第一个问题我们很轻松的就解决了!
首先,将transactions集合转为流,然后给filter传递一个谓词来选择2011年的交易,接着按照交易额从低到高进行排序,最后将Stream中的所有元素收集到一个List集合中。
2. 交易员都在哪些不同的城市工作过?
List<String> cities = transactions.stream()
// 提取出交易员所工作的城市
.map(transaction -> transaction.getTrader().getCity())
// 去除已有的城市
.distinct()
// 将Stream中所有的元素转为一个List集合
.collect(Collectors.toList());
是的,我们很简单的完成了第二个问题。
首先,将transactions集合转为流,然后使用map提取出与交易员相关的每位交易员所在的城市,接着使用distinct去除重复的城市(当然,我们也可以去掉distinct,在最后我们就要使用collect,将Stream中的元素转为一个Set集合。
collect(Collectors.toSet())),我们只需要不同的城市,最后将Stream中的所有元素收集到一个List中。
3. 查找所有来自于剑桥的交易员,并按姓名排序。
List<Trader> traders = transactions.stream()
// 从交易中提取所有的交易员
.map(Transaction::getTrader)
// 进选择位于剑桥的交易员
.filter(trader -> "Cambridge".equals(trader.getCity()))
// 确保没有重复
.distinct()
// 对生成的交易员流按照姓名进行排序
.sorted(Comparator.comparing(Trader::getName))
.collect(Collectors.toList());
从交易中提取所有的交易员,然后进选择位于剑桥的交易员确保没有重复,接着对生成的交易员流按照姓名进行排序。
4. 返回所有交易员的姓名字符串,按字母顺序排序。
String traderStr = transactions.stream()
// 提取所有交易员姓名,生成一个 Strings 构成的 Stream
.map(transaction -> transaction.getTrader().getName())
// 只选择不相同的姓名
.distinct()
// 对姓名按字母顺序排序
.sorted()
// 逐个拼接每个名字,得到一个将所有名字连接起来的 String
.reduce("", (n1, n2) -> n1 + " " + n2);
这些问题,我们都很轻松的就完成!首先,提取所有交易员姓名,生成一个 Strings 构成的 Stream并且只选择不相同的姓名,然后对姓名按字母顺序排序,最后使用reduce将名字拼接起来!
请注意,此解决方案效率不高(所有字符串都被反复连接,每次迭代的时候都要建立一个新的 String 对象)。
性能提升方案
下一章中,你将看到一个更为高效的解决方案,它像下面这样使用 joining (其内部会用到 StringBuilder ):
String traderStr = transactions.stream()
.map(transaction -> transaction.getTrader().getName())
.distinct()
.sorted()
.collect(joining());
5. 有没有交易员是在米兰工作的?
boolean milanBased = transactions.stream()
// 把一个谓词传递给 anyMatch ,检查是否有交易员在米兰工作
.anyMatch(transaction -> "Milan".equals(transaction.getTrader() .getCity()));
依旧很简单把一个谓词传递给 anyMatch ,检查是否有交易员在米兰工作。
6. 打印生活在剑桥的交易员的所有交易额。
transactions.stream()
// 选择住在剑桥的交易员所进行的交易
.filter(t -> "Cambridge".equals(t.getTrader().getCity()))
// 提取这些交易的交易额
.map(Transaction::getValue)
// 打印每个值
.forEach(System.out::println);
首先选择住在剑桥的交易员所进行的交易,接着提取这些交易的交易额,然后就打印出每个值。
所有交易中,最高的交易额是多少?
Optional<Integer> highestValue = transactions.stream()
// 提取每项交易的交易额
.map(Transaction::getValue)
// 计算生成的流中的最大值
.reduce(Integer::max);
首先提取每项交易的交易额,然后使用reduce计算生成的流中的最大值。
8. 找到交易额最小的交易。
Optional<Transaction> smallestTransaction = transactions.stream()
// 通过反复比较每个交易的交易额,找出最小的交易
.reduce((t1, t2) -> t1.getValue() < t2.getValue() ? t1 : t2);
是的,第八个问题很简单,但是还有更好的做法!
流支持 min 和 max 方法,它们可以接受一个 Comparator 作为参数,指定计算最小或最大值时要比较哪个键值:
Optional<Transaction> smallestTransaction = transactions.stream()
.min(comparing(Transaction::getValue));
个人感受
-
使用 stream 编码变得非常优雅
-
题目的设计难度是平级的,就是没有难度上升,解决问题的人会感觉兴致平平。可能使用递进式的难度体验会更好。
-
其中提到了两个技巧,一个关于性能(4),一个关于编码的优雅性(8)。对于编码就应该有精益求精的精神。这样才能有所进步。
参考资料
《java8 实战》