java8从入门到精通2:强大的Stream

Stream(流)是什么

流是Java API的新成员,它允许你以声明性方式处理数据集合,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了,不过并行处理在后面写。
先看下面的代码,我有个菜单类,我想返回低热量的菜肴名称的, 并按照卡路里排序

菜单类

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;
    }
    public enum Type { MEAT, FISH, OTHER }
    @Override
    public String toString() {
        return name;
    }
    public static final List menu =
        Arrays.asList( new Dish("猪肉", false, 800, Dish.Type.MEAT),
            new Dish("牛肉", false, 700, Dish.Type.MEAT),
            new Dish("肌肉", false, 400, Dish.Type.MEAT),
            new Dish("炸薯条", true, 530, Dish.Type.OTHER),
            new Dish("米饭", true, 350, Dish.Type.OTHER),
            new Dish("水果", true, 120, Dish.Type.OTHER),
            new Dish("披萨", true, 550, Dish.Type.OTHER),
            new Dish("虾", false, 400, Dish.Type.FISH),
            new Dish("鲫鱼", false, 450, Dish.Type.FISH));
}

使用java7做

public static List getLowCaloricDishesNamesInJava7(List dishes){
    List lowCaloricDishes = new ArrayList<>();
    for(Dish d: dishes){
        //用累加器筛选元素
        if(d.getCalories() < 400){
            lowCaloricDishes.add(d);
        }
    }
    List lowCaloricDishesName = new ArrayList<>();
    //用匿名类对菜肴排序
    Collections.sort(lowCaloricDishes, new Comparator() {
        @Override
        public int compare(Dish d1, Dish d2){
            return Integer.compare(d1.getCalories(), d2.getCalories());
        }
    });
    //处理排序后的菜名列表
    for(Dish d: lowCaloricDishes){
        lowCaloricDishesName.add(d.getName());
    }
    return lowCaloricDishesName;
}

使用java8做

public static List<String> getLowCaloricDishesNamesInJava8(List<Dish> dishes){
    return dishes.stream()//①
        .filter(d -> d.getCalories() < 400)//筛选元素
        .sorted(comparing(Dish::getCalories))//对菜肴排序
        .map(Dish::getName)//处理排序后的菜名列表
        .collect(toList());
}

①处可以做并行处理,只需要把你只需要把stream()换成parallelStream()

以上可以看出,使用java8的stream写要简洁,而且使用声明式方式写的代码更易读,链式调用写起来更方便,我们只需要写我们关心的部分,没有什么中间变量的干扰。stream是流水线操作,内部迭代,所以对集合的操作特别简单,下面看看怎么使用。

Stream的使用

1、筛选
筛选出素食

        List vegetarianMenu =
            menu.stream()
                .filter(Dish::isVegetarian)//①
                .collect(toList());

        vegetarianMenu.forEach(System.out::println);

结果

炸薯条
米饭
水果
披萨

2、去重
筛选出偶数,再去除重复的

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

结果

2
4

3、截断
筛选出热量超过300的前3道菜,

List dishesLimit3 =
    menu.stream()
        .filter(d -> d.getCalories() > 300)
        .limit(3)//①
        .collect(toList());
dishesLimit3.forEach(System.out::println);

结果

猪肉
牛肉
肌肉

limit会按照顺序取前几条,Limit也可以用在set上,这样返回的就是无序的。
4、跳过
筛选出热量超过300的前3道菜,然后跳过前3个

List dishesLimit3 =
    menu.stream()
        .filter(d -> d.getCalories() > 300)
        .skip(3)//①
        .collect(toList());
dishesLimit3.forEach(System.out::println);

结果

炸薯条
米饭
披萨
虾
鲫鱼

5、map:对流中元素应用函数
比如我想统计出所有菜名,然后统计出菜名长度

List<String> dishName = menu.stream()
    .map(Dish::getName)//①
    .collect(toList());
List<Integer> dishNameLengths = menu.stream()
    .map(Dish::getName)//①
    .map(String::length)//①
    .collect(toList());
dishName.forEach(System.out::println);
dishNameLengths.forEach(System.out::println);

结果:

猪肉
牛肉
肌肉
炸薯条
米饭
水果
披萨
虾
鲫鱼
2
2
2
3
2
2
2
1
2

6、flatmap:扁平化
现在有个数组,要统计出里面单词的不重复字母

String[] words = {"Hello", "world"};
        List word2 = Arrays.stream(words)
            .map(word->word.split(""))
            .flatMap(Arrays::stream)//①
            .distinct()
            .collect(toList());

word2.forEach(System.out::print);

结果

HelowrdD

7、检查anyMatch,allMatch,noneMatch

//至少一个匹配
boolean isVegetarian =  menu.stream().anyMatch(Dish::isVegetarian);
//都匹配
boolean isHealthy =  menu.stream().allMatch(d->d.getCalories()<1000);
//都不匹配
boolean isNotHealthy =  menu.stream().noneMatch(d->d.getCalories()<1000);

8、查找:findAny,findFirst

//找到任何一个就立即返回
Optional dish = menu.stream().findAny();
//找到第一个元素
Optional dishone = menu.stream().findFirst();

9、归约:reduce
归约就是把一个流归成一个值,比如把一个List的结果计算为一个值,术语叫折叠。
对List中的数字求和,初始值0

int sum = numbers.stream().reduce(0, (a, b) -> a + b);
或者
int sum = numbers.stream().reduce(0, Integer::sum);

最大值和最小值

Optional<Integer> max = numbers.stream().reduce(Integer::max);
Optional<Integer> min = numbers.stream().reduce(Integer::min);

10、创建流

//由值创建流
Stream stringStream = Stream.of("张三", "李四", "王五", "马六");
stringStream.forEach(System.out::println);
//空流
Stream emptyStream = Stream.empty();
//数组创建流
int[] ints = {1,2,3,4,5};
int sum = Arrays.stream(ints).sum();
//由函数创建无限流,使用iterate,或generate
Stream.iterate(0,n->n+2).limit(10).forEach(System.out::println);

11、归约汇总
前面有reduce归约,这里的归约就是reduce的一些特殊情况,求平均值,最大,最小或者总计等。下面是一个求经常用到的值得例子。

IntSummaryStatistics menuStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));

System.out.println(menuStatistics);

输出

IntSummaryStatistics{count=9, sum=4300, min=120, average=477.777778, max=800}

除了summarizingInt计算int型,还有summarizingDouble,summarizingLong计算对应的值,最后返回的类里面就是我们需要的值。
12、连接字符串,joining

String menuNames = menu.stream().map(Dish::getName).collect(joining(","));
System.out.println(menuNames);

结果

猪肉,牛肉,肌肉,炸薯条,米饭,水果,披萨,虾,鲫鱼

13、分组groupingBy
把菜单按照类型分组,返回的结果就是个map,里面是统计结果

Map> dishTypes = menu.stream().collect(groupingBy(Dish::getType));

System.out.println(dishTypes);

结果

{MEAT=[猪肉, 牛肉, 肌肉], FISH=[虾, 鲫鱼], OTHER=[炸薯条, 米饭, 水果, 披萨]}

不仅可以分一组,还可以分组以后再分组,也就是多级分组

public enum CaloricLevel {
        DIET,
        NORMAL,
        FAT
    }

Map<Dish.Type, Map<CaloricLevel, List<Dish>>> dishCaloricLevel = menu.stream()
    .collect(groupingBy(Dish::getType,
    groupingBy(dish -> {
        if (dish.getCalories() <= 400) {return CaloricLevel.DIET;}
        else if (dish.getCalories() <= 700) { return CaloricLevel.NORMAL; }
        else { return CaloricLevel.FAT; }
    })
));

System.out.println(dishCaloricLevel);

结果

{FISH={NORMAL=[鲫鱼], DIET=[虾]}, MEAT={FAT=[猪肉], NORMAL=[牛肉], DIET=[肌肉]}, OTHER={NORMAL=[炸薯条, 披萨], DIET=[米饭, 水果]}}

14、分区partitioningBy
分区是分组的特殊情况,只能分两组

Map<Boolean, List> map = menu.stream()
    .collect(partitioningBy(Dish::isVegetarian));

System.out.println(map);

结果

{false=[猪肉, 牛肉, 肌肉, 虾, 鲫鱼], true=[炸薯条, 米饭, 水果, 披萨]}

可以看出,true这一组,就是谓词(isVegetarian)返回的这一组。

总结

以上是对stream的常见使用,其实还有更为强大的功能呢,后面我会写自定义收集器,并行和并发操作,在学会stream的基础上再学会使用java8更强大的编程技能,告别以前的java7时代的啰嗦编程,欢迎关注。

你可能感兴趣的:(java8,java8,stream)