Stream简介:
java8随着lambda表达式的加入,又加入了Stream(流),什么是流呢?书上是这样定义的“从支持数据处理操作的源生成的元素序列”,
元素序列——就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。
源——流会使用一个提供数据的源,如集合、数组或输入/输出资源。
数据处理操作——流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。
话不多说,让我们看一个实例。找出了卡路里大于350的所有饮料
List<Drink> drinkList = Arrays.asList(
new Drink("cocaCalo", Drink.Type.CARBONIC_ACID,300),
new Drink("sprite", Drink.Type.CARBONIC_ACID,330),
new Drink("fanta", Drink.Type.FRUIT_JUICE,350),
new Drink("mintueMaid", Drink.Type.FRUIT_JUICE,430),
new Drink("coconut", Drink.Type.FRUIT_JUICE,370),
new Drink("yiLi", Drink.Type.MILK,550),
new Drink("wangZai", Drink.Type.MILK,500),
new Drink("IcedRedTea", Drink.Type.TEA,280),
new Drink("greenTea", Drink.Type.TEA,250)
);
//使用Stream写法
List<Drink> list = drinkList.stream().filter(d -> d.getCalorie() > 350).collect(toList());
//使用传统的写法
List<Drink> newList = new ArrayList<>();
for (int i=0;i<drinkList.size();i++){
if(drinkList.get(i).getCalorie()>350){
newList.add(drinkList.get(i));
}
}
Stream语法:
1.stream()
使用流的时候,要先把数据源转换成流,Collection接口提供了一个stream()方法,可以把集合转换成流。
Stream<Drink> stream = drinkList.stream();
2.筛选filter()
Stream<Drink> stream2 = drinkList.stream().filter(d -> d.getCalorie() > 350);
filter()接受一个谓词Predicate,Predicate是一个函数式接口,函数式接口可以使用lambda表达四“d -> d.getCalorie() > 350”,如果对lambda表达式
还没有了解的同学可以看一下上一篇介绍的lambda表达式。
3.映射map()
Stream<String> stream3 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName());
map()的参数是Function函数式接口,“d -> d.getName()”这个lanbda表达式把每一个Drink对象映射成对象的名字
4.截短流limit()
Stream<String> stream4 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName()).limit(3);
limit()的参数是一个long参数3,获取流中的3个元素。
5.跳过流元素skip()
Stream<Drink> stream5 = drinkList.stream().filter(d -> d.getCalorie() > 350).skip(2);
6.collect()
我们可以看到以上的方法返回的类型都是Straen<T>类型,没有返回集合类型,因此我们称以上的流操作为中间操作。
我们要把流又变成集合就需要使用终端操作,collect()方法就是一个终端操作,它接受的参数是一个收集器
List<String> list1 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName()).skip(2).limit(3).collect(toList());
7.count()
long count = drinkList.stream().filter(d -> d.getCalorie() > 500).count();
count()方法也是一个终端操作,返回了卡路里大于500的饮料个数。
以上是非常使用流过程中很常见的操作,还有一些操作我们结合一些例子来讲解。
8.flatMap():流扁平化。就是把多个流化成一个流
把["Hello","World"]变成[H, e, l, o, W, r, d];
List<String> list2 = Arrays.asList("Hello", "World");
List<String> list3 = list2.stream().map(str -> str.split("")).flatMap(Arrays::stream).distinct().collect(toList());
list3 = [H, e, l, o, W, r, d];
首先通过map()把"Hello"映射成H,e,l,l,o,"World"映射成W,o,r,l,d。但是类型是Stream<String[]>,我们还需要变成Stream<String>,
因此使用flatMap(),跟map()一样,参数是Function函数式接口,Arrays::stream,这是方法引用,就是Arrays类的stream()方法,这个方法可以把一个数组
转变成流 String[] strs = new String[]{"Hello"};Stream<String> stream7 = Arrays.stream(strs);。因此通过flatMap()操作,我们就把String[]变成
Stream<String>,然后再通过distinct()方法把重复的去掉,这个跟MySQL数据库的distinct()方法一样。
9.查找和匹配
题目:找出一瓶属于茶类的一瓶饮料。
9.1 findAny();
Optional<Drink> tea = drinkList.stream().filter(d -> Drink.Type.TEA.equals(d.getType())).findAny();
tea.ifPresent(t -> System.out.println(t));
这里返回一个Optional类,这里简单的介绍一下Optional类,Optional<T>类(java.util.Optional)是一个容器类,代表一个值存在或不存在。
也是在java8中提出来的,用来取代null的,我们会在下一篇详细的介绍Optional;Optional类的isPresent()方法,如果存在就打印出这个饮料。
9.2 findFrist()
Optional<Drink> result = drinkList.stream().filter(d -> d.getCalorie() > 450).findFirst();
result.ifPresent(System.out::println);
9.3 allMatch,匹配所有元素
boolean b1 = drinkList.stream().anyMatch(d -> d.getCalorie() > 500); //b1 = true
9.4 anyMatch,匹配任意一个元素
boolean b2 = drinkList.stream().allMatch(d -> d.getCalorie() > 500); //b2 = false
9.5 noneMatch,一个元素都不匹配
boolean b3 = drinkList.stream().noneMatch(d -> d.getCalorie() > 500); //b3 = false
10.reduce(),归约操作。
List<Integer> intList = Arrays.asList(1, 2, 3, 4, 5);
Integer sum = intList.stream().reduce(0, (a, b) -> a + b); //sum = 15;
归约操作就是将Stream的每个元素都用加法运算符反复迭代来得到结果,跟下面的方法一样。
int sum = 0;
for (int x : numbers) {
sum += x;
}
Integer max = intList.stream().reduce(0, (a, b) -> a > b ? a : b); //max=5
Integer min = intList.stream().reduce(0,(a, b) -> a > b ? b : a); //min=0,这里不太准确,因为我们给了一个初始变量0,它比数组所有的数都小。
Optional<Integer> min = intList.stream().reduce((a, b) -> a > b ? b : a);
min1.ifPresent(System.out::println);//打印出1,因此我们可以知道,如若不给一个初始值给reduce(),就会返回一个Optional对象,因为不知道有没有这个值。
11.数值流
题目:求出所有饮料的所有卡路里的总和;
Integer calorieSum = drinkList.stream().map(d -> d.getCalorie()).reduce(0, Integer::sum); //calorieSum = 3360;这里有一个暗含的装箱成本,
每个Integer都要拆成原始类型再求和。
int calorieSum1 = drinkList.stream().mapToInt(d -> d.getCalorie()).sum();//使用mapToInt就可以把流转换成一个IntStream。然后直接使用sum()方法就可以求和。还有max(),min()等方法
数值流又IntStream,DoubleStream,LongStream(),我们可以通过mapToInt(),mapToDouble(),把对象流转变成数值流。我们也可以通过boxed()方法把数值流转变成对象流
下面我们通过一个例子来好好总结一下数值流的应用。相信“勾股定理”大加一定会有深刻印象,(3,4,5)就是一组勾股数。
题目:打印出从1-100之间的5组勾股数。
IntStream还有range(n,m)和rangeClosed(n,m)两个方法,就是产生数值流。区别就是rangeClosed()包含m,range()不包含m.
Stream<int[]> gouGuShu = IntStream.rangeClosed(1, 100).boxed()
.flatMap(i -> IntStream.rangeClosed(i, 100).filter(j -> (Math.sqrt(i * i + j * j)) % 1 == 0)
.mapToObj(j -> new int[]{i, j, (int) Math.sqrt(i * i + j * j)}));
List<int[]> gouGuShuList = gouGuShu.limit(5).collect(Collectors.toList());
gouGuShuList.forEach(g -> System.out.println(Arrays.toString(g)));
打印出
[3, 4, 5]
[5, 12, 13]
[6, 8, 10]
[7, 24, 25]
[8, 15, 17]
12.收集器
在前面介绍collect()方法的时候,我们就说他的参数是一个收集器。
List<String> list1 = drinkList.stream().filter(d -> d.getCalorie() > 350).map(d -> d.getName()).skip(2).limit(3).collect(toList());
toList()方法是java.util.stream.Collectors类的一个方法。返回一个返回一个把流转换成List的收集器。还有toSet()等。
long count = drinkList.stream().filter(d -> d.getCalorie() > 500).collect(counting());
也可以写成:
long count = drinkList.stream().filter(d -> d.getCalorie() > 500).count();
找出卡路里最大饮料
Optional<Drink> maxCalorieDrink = drinkList.stream().collect(maxBy(Comparator.comparing(Drink::getCalorie)));
卡路里最小的饮料
Optional<Drink> maxCalorieDrink = drinkList.stream().collect(minBy(Comparator.comparing(Drink::getCalorie)));
把所有饮料的卡路里求和
Integer sumCalories = drinkList.stream().collect(summingInt(Drink::getCalorie));
求出所有饮料的卡路里平均值
Double averageCalorie = drinkList.stream().collect(averagingInt(Drink::getCalorie));
把所有饮料的名字都连接起来
String drinkName = drinkList.stream().map(d -> d.getName()).collect(Collectors.joining("+"));
以上的所有收集器都可以使用reducing()方法来造出来
Integer sumCalories1 = drinkList.stream().collect(reducing(0, Drink::getCalorie, (a, b) -> a + b));
把饮料按各个类型分组
Map<Drink.Type, List<Drink>> drinkByType = drinkList.stream().collect(groupingBy(Drink::getType));
可以得到如下分组:
{
TEA=[Drink{name='IcedRedTea', type=TEA, Calorie=280}, Drink{name='greenTea', type=TEA, Calorie=250}],
FRUIT_JUICE=[Drink{name='fanta', type=FRUIT_JUICE, Calorie=350}, Drink{name='mintueMaid', type=FRUIT_JUICE, Calorie=430}, Drink{name='coconut', type=FRUIT_JUICE, Calorie=370}],
CARBONIC_ACID=[Drink{name='cocaCalo', type=CARBONIC_ACID, Calorie=300}, Drink{name='sprite', type=CARBONIC_ACID, Calorie=330}],
MILK=[Drink{name='yiLi', type=MILK, Calorie=550}, Drink{name='wangZai', type=MILK, Calorie=500}]
}
按卡路里的高低来分大于400,算是高,小于400算是低。
Map<String, List<Drink>> drinkByCalorie = drinkList.stream().collect(groupingBy(drink -> {
if (drink.getCalorie() > 400) return "Hiht";
else return "Low";
}));
得到如下的分组:
{Low=[Drink{name='cocaCalo', type=CARBONIC_ACID, Calorie=300}, Drink{name='sprite', type=CARBONIC_ACID, Calorie=330}, Drink{name='fanta', type=FRUIT_JUICE, Calorie=350}, Drink{name='coconut', type=FRUIT_JUICE, Calorie=370}, Drink{name='IcedRedTea', type=TEA, Calorie=280}, Drink{name='greenTea', type=TEA, Calorie=250}],
Hiht=[Drink{name='mintueMaid', type=FRUIT_JUICE, Calorie=430}, Drink{name='yiLi', type=MILK, Calorie=550}, Drink{name='wangZai', type=MILK, Calorie=500}]
}
多级分组,按饮料的类型和卡路里的高低分组
Map<String, Map<Drink.Type, List<Drink>>> drinkByCalorieAndType = drinkList.stream().collect(groupingBy(drink -> {
if (drink.getCalorie() > 400) return "Higt";
else return "Low";
}, groupingBy(Drink::getType)));
得到如下分组:
{
Low={
TEA=[Drink{name='IcedRedTea', type=TEA, Calorie=280}, Drink{name='greenTea', type=TEA, Calorie=250}],
FRUIT_JUICE=[Drink{name='fanta', type=FRUIT_JUICE, Calorie=350}, Drink{name='coconut', type=FRUIT_JUICE, Calorie=370}],
CARBONIC_ACID=[Drink{name='cocaCalo', type=CARBONIC_ACID, Calorie=300}, Drink{name='sprite', type=CARBONIC_ACID, Calorie=330}]
},
Higt={FRUIT_JUICE=[Drink{name='mintueMaid', type=FRUIT_JUICE, Calorie=430}],
MILK=[Drink{name='yiLi', type=MILK, Calorie=550}, Drink{name='wangZai', type=MILK, Calorie=500}]
}
}
这一篇我们介绍的都是顺序流,在下一篇我们会介绍并行流。