一 前言
今天继续看第二部分,首先看了第四章,记录流的一些内容。
首先为什么要引入流。说一个场景:有一个菜单,要查找热量低的菜,并按热点大小排序。这个场景如果用sql很简单,但是在Java中用集合操作,以往的方法都是for循环处理,不知道你烦不烦,我每次处理这种需要循环挺烦的。尤其是嵌套循环。
二 流与集合
上面说的这个业务场景,我们一般的处理方式是这样的
# 定义一个菜单集合,存储数据
public class Dish {
private final String name;
private final boolean vegetarion;
private final int calories;
private final Type type;
public enum Type{
MEAT, FISH, OTHER
}
public Dish(String name, boolean vegetarion, int calories, Type type) {
this.name = name;
this.vegetarion = vegetarion;
this.calories = calories;
this.type = type;
}
public String getName() {
return name;
}
public boolean isVegetarion() {
return vegetarion;
}
public int getCalories() {
return calories;
}
public Type getType() {
return type;
}
}
# 初始化集合数据
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.先筛选
List<Dish> lowCaloricDishes = new ArrayList<>();
for(Dish d: dishes){
if(d.getCalories() < 400){
lowCaloricDishes.add(d);
}
}
# 2.排序
List<String> lowCaloricDishesName = new ArrayList<>();
List<String> lowCaloricDishesName = new ArrayList<>();
Collections.sort(lowCaloricDishes, new Comparator<Dish>() {
public int compare(Dish d1, Dish d2){
return Integer.compare(d1.getCalories(), d2.getCalories());
}
});
# 3. 排序后生成新的菜单集合
for(Dish d: lowCaloricDishes){
lowCaloricDishesName.add(d.getName());
}
流处理方式如下:
menu.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList());
很显然,流式处理太简单了。
2.1 流简介
Java8 中流(接口定义在java.util.stream.Stream)的概念:
从支持数据处理操作的源生成的元素序列
- 元素序列——和集合一样,有一组可访问的有序序列。
- 源——流需要一个数据源,如集合,数组,文件等。
- 数据处理操作——流的数据处理类似于SQL,以及函数式编程语言的操作。如Scala中的高阶算子等。
2.2 流与集合的区别
书中举了一个很好的例子,集合类似于存在DVD的中的电影(以字节或者帧的形式实际存储),流类似于在线看电影(按需计算所需内容,这里类似于云计算服务器租用的商业模式)。
集合是系统已经为对象分配了堆栈,核心是存储。流则是按需计算,核心是计算。
三 Java 8 中流的基本使用
书中有这样一个例子,对交易数据的分析。
## 定义一个交易人员类,存储交易人员数据
public class Trader{
private String name;
private 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 void setCity(String newCity){
this.city = newCity;
}
public String toString(){
return "Trader:"+this.name + " in " + this.city;
}
}
## 定义一个交易过程类
public class Transaction{
private Trader trader;
private int year;
private 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 +"}";
}
}
## 数据初始化
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)找出2011年发生的所有交易,并按交易额顺序排列(从低到高);
(2)交易员都在哪些不同的城市工作过;
(3)查找所有来自于剑桥的交易员,并按顺序排列;
(4)返回所有交易员的姓名字符串,并按字母顺序排序;
(5)有没有交易员在米兰工作?
(6)打印生活在剑桥的交易员的所有交易额;
(7)所有交易中,最高的交易额是多少?
(8)找到交易额最小的交易。
# (1)找出2011年发生的所有交易,并按交易额顺序排列(从低到高)
List< Transaction > tr2011=transactions.stream.filter(x->x.getYear()==2011)
.sorted((a,b)->a. getValue().compareTo(b.getVaule())
.collect(toList());
# (2) 交易员都在哪些不同的城市工作过
List<String> cities=transactions.stream.map(x->x.getTrader().getName())
.distinct()
.collect(toList());
# (3)查找所有来自于剑桥的交易员,并按顺序排列
List<Trader> traders= transactions.stream.map(x->x.getTrader())
.filter(x->x.getCity ().equlas("Cambridge"))
.distinct()
.sorted(comparing(Trader::getName()))
.collect(toList());
# (4)返回所有交易员的姓名字符串,并按字母顺序排序
String names=transactions.stream.map(x->x.getTrader().getName())
.distinct()
.reduce("",(x,y)->x+y) ;
# (5)有没有交易员在米兰工作?
bolean ismilan=transactions.stream.anyMatch(x->x.getTrader().getCity ().equlas("Milan"));
# (6) 打印生活在剑桥的交易员的所有交易额
transactions.stream .filter(x->x.getTrader().getCity().
equlas("Cambridge"))
.forEach(x->System.out.println(x.getValue());
# (7)所有交易中,最高的交易额是多少?
Option<Integer> highValue=transactions.stream .map(x- >x.getValue()) .reduce(Integer:max);
# (8)找到交易额最小的交易。
int min=transactions.stream.min(comparing(Transaction::getValue()))
四 数值流的使用
Java的数据类型有引用类型和值类型两种,而泛型只能绑定引用类型,这样就存在装箱和拆箱的过程(这个过程是有性能消耗的),所以Java8 Stream定义了IntStream、LongStream和DoubleStream等数值流。
## 对象流映射数值流,maptoInt,maptoDouble,maptoLong,
## menu对象在前面定义了。例如
IntStream intstream=menu.stream().maptoInt(Dish::getValue);
## 数值流转换对象流
Stream<Integer> stream=intstream.boxed();
## 默认值OptionalInt
## Optional是为了处理空值或者不存在值。
例子:计算1到100之间自然数的勾股数,即a * a+b * b=c * c;
## 1.定义a,取值范围为[1,100]
IntStream a1=IntStream.rangeClosed(1,100);
## 2.转换为对象流
Stream<Integer> a2=a1.boxed();
## 3.把每个a的值映射三元数流
a2.flatMap(a->{
//定义b值,范围为[a,100]
IntStream b1 =IntStream.rangeClosed(a,100);
//判断a*a+b*b的平方根是否为整数
IntStream b2=b1.filter(b->Math.sqrt(a*a+b*b)%1==0);
// 转换a2为数值流
b2.mapToObj(b->new int[]{a,b,(int)Math.sqrt(a*a+b*b)})
})
//打印前5组勾股数
a1.limit(5).forEach(t->System.out.println(t[0]+t[1]+t[2]));
五 无限流的使用
Stream提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。
斐波那契数列生成
Stream.iterate(new int[]{0,1},t->new int[]{t(1),t(0)+t(1)})
.limit(10)
.map(t->t[0])
.forEach(System.out::println);
## 打印结果
0,1,1,2,3,5,8,13,21,34...
参考文章
[1]《Java 8实战》