一. 例子

如果我们现在有一个需求,要对饭店菜单中的菜品做如下处理:

        1.找出卡路里小于4000的菜品

        2.对找出的菜品进行排序

        3.获取排序后的菜品名字

实体类:

public class Dish {  
    private String name;  
    private boolean vegetarian;  
    private int calories;  
    private Type type;  
  
    // getter and setter  
}

在java8之前是这样的:

private List<String> beforeJava7(List<Dish> dishList) {  
        List<Dish> lowCaloricDishes = new ArrayList<>();  
  
        //1.筛选出卡路里小于400的菜品
        for (Dish dish : dishList) {  
            if (dish.getCalories() < 400) {  
                lowCaloricDishes.add(dish);  
            }  
        }  
  
        //2.对筛选出的菜品进行排序  
        Collections.sort(lowCaloricDishes, new Comparator<Dish>() {  
            @Override  
            public int compare(Dish o1, Dish o2) {  
                return Integer.compare(o1.getCalories(), o2.getCalories());  
            }  
        });  
  
        //3.获取排序后菜品的名字  
        List<String> lowCaloricDishesName = new ArrayList<>();  
        for (Dish d : lowCaloricDishes) {  
            lowCaloricDishesName.add(d.getName());  
        }  
  
        return lowCaloricDishesName;  
    }

使用stream之后:极大提升代码感官和简洁度

private List<String> afterJava8(List<Dish> dishList) {  
        return dishList.stream()  
                .filter(d -> d.getCalories() < 400)  //筛选出卡路里小于400的菜品 
                .sorted(comparing(Dish::getCalories))  //根据卡路里进行排序  
                .map(Dish::getName)  //提取菜品名称  
                .collect(Collectors.toList()); //转换为List  
    }

将菜品根据type分类,这种情况在stream出现之前一定是头皮发麻的

private static Map<Type, List<Dish>> beforeJdk8(List<Dish> dishList) {  
    Map<Type, List<Dish>> result = new HashMap<>();  
  
    for (Dish dish : dishList) {  
        //不存在则初始化  
        if (result.get(dish.getType())==null) {  
            List<Dish> dishes = new ArrayList<>();  
            dishes.add(dish);  
            result.put(dish.getType(), dishes);  
        } else {  
            //存在则追加  
            result.get(dish.getType()).add(dish);  
        }  
    }  
  
    return result;  
}

但我们使用stream之后一行就可以搞定

private static Map<Type, List<Dish>> afterJdk8(List<Dish> dishList) {  
    return dishList.stream().collect(groupingBy(Dish::getType));  
}

二.用法简介

以上例子可以看出stream api的强大

流的操作主要分为两种

1.中间操作

        一个流可以后面跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的filter、map等

        1.1 filter筛选

// 通过使用filter方法进行条件筛选,filter的方法参数为一个条件 
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);  
 Stream<Integer> stream = integerList.stream().filter(i -> i > 3);

         1.2 distinct去除重复元素

// 通过distinct方法快速去除重复的元素
List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);  
Stream<Integer> stream = integerList.stream().distinct();

        1.3  limit返回指定流个数

// 通过limit方法指定返回流的个数,limit的参数值必须>=0,否则将会抛出异常
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);  
 Stream<Integer> stream = integerList.stream().limit(3);

        1.4  skip跳过流中的元素 

// 通过skip方法跳过流中的元素,上述例子跳过前两个元素,所以打印结果为2,3,4,5,skip的参数值必须 
 // >=0,否则将会抛出异常
 List<Integer> integerList = Arrays.asList(1, 1, 2, 3, 4, 5);  
 Stream<Integer> stream = integerList.stream().skip(2);

        1.5 map流映射 

// 通过map方法可以完成映射,该例子完成中String -> Integer的映射,之前上面的例子通过map方法完成了 
// Dish->String的映射
List<String> stringList = Arrays.asList("Java 8", "Lambdas",  "In", "Action");  
Stream<Integer> stream = stringList.stream().map(String::length);

        1.6  flatMap流转换

// map(w -> w.split(" "))的返回值为Stream<String[]>,我们想获取Stream<String>,可以通过flatMap // 方法完成Stream ->Stream的转换
List<String> wordList = Arrays.asList("Hello", "World");  
List<String> strList = wordList.stream()  
        .map(w -> w.split(" "))  
        .flatMap(Arrays::stream)  
        .distinct()  
        .collect(Collectors.toList());

         1.7 元素匹配

// allMatch匹配所有
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
if (integerList.stream().allMatch(i -> i > 3)) {  
    System.out.println("值都大于3");  
}  



// anyMatch匹配其中一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
if (integerList.stream().anyMatch(i -> i > 3)) {  
    System.out.println("存在大于3的值");  
}  
//等同于
for (Integer i : integerList) {  
    if (i > 3) {  
        System.out.println("存在大于3的值");  
        break;  
    }  
}  



// noneMatch全部不匹配
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
if (integerList.stream().noneMatch(i -> i > 3)) {  
    System.out.println("值都小于3");  
}

2.终端操作

        一个流有且只能有一个终端操作,当这个操作执行后,流就被关闭了,无法再被操作,因此一个流只能被遍历一次,若想在遍历需要通过源数据在生成流。终端操作的执行,才会真正开始流的遍历。如下面即将介绍的count、collect等

        2.1 统计流中元素个数

// 通过count
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
Long result = integerList.stream().count();  


// 通过counting
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
Long result = integerList.stream().collect(counting());

        2.2 查找

// findFirst查找第一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findFirst();



// findAny随机查找一个
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
Optional<Integer> result = integerList.stream().filter(i -> i > 3).findAny();

        2.3 reduce将流中的元素组合起来

// 假设我们对一个集合中的值进行求和
    //1.8之前
    int sum = 0;  
    for (int i : integerList) {  
        sum += i;  
    } 

    // stream
    // 第一种
    int sum = integerList.stream().reduce(0, (a, b) -> (a + b));
    // 第二种
    int sum = integerList.stream().reduce(0, Integer::sum);

        2.4 获取最大值/最小值

// 第一种
Optional<Integer> min = menu.stream().map(Dish::getCalories).min(Integer::compareTo);
Optional<Integer> max = menu.stream().map(Dish::getCalories).max(Integer::compareTo);



// 第二种
OptionalInt min = menu.stream().mapToInt(Dish::getCalories).min();
OptionalInt max = menu.stream().mapToInt(Dish::getCalories).max();


// 第三种
Optional<Integer> min = menu.stream().map(Dish::getCalories).collect(minBy(Integer::compareTo));
Optional<Integer> max = menu.stream().map(Dish::getCalories).collect(maxBy(Integer::compareTo));



// 第四种
Optional<Integer> min = menu.stream().map(Dish::getCalories).reduce(Integer::min);
Optional<Integer> max = menu.stream().map(Dish::getCalories).reduce(Integer::max);

        2.5 求和

// 通过summingInt
int sum = menu.stream().collect(summingInt(Dish::getCalories));


// 通过reduce
int sum = menu.stream().map(Dish::getCalories).reduce(0, Integer::sum);


// 通过sum
int sum = menu.stream().mapToInt(Dish::getCalories).sum();

        2.6 通过averagingInt求平均值

// 如果数据类型为double、long,则通过averagingDouble、averagingLong方法进行求平均
double average = menu.stream().collect(averagingInt(Dish::getCalories));

        2.7 通过summarizingInt同时求总和、平均值、最大值、最小值

// 如果数据类型为double、long,则通过summarizingDouble、summarizingLong方法
IntSummaryStatistics intSummaryStatistics = menu.stream().collect(summarizingInt(Dish::getCalories));  
double average = intSummaryStatistics.getAverage();  //获取平均值  
int min = intSummaryStatistics.getMin();  //获取最小值  
int max = intSummaryStatistics.getMax();  //获取最大值  
long sum = intSummaryStatistics.getSum();  //获取总和

        2.8 通过groupingBy进行分组

// 简单分组
Map<Type, List<Dish>> result = dishList.stream().collect(groupingBy(Dish::getType));  



// 在collect方法中传入groupingBy进行分组,其中groupingBy的方法参数为分类函数。还可以通过嵌套使用 // groupingBy进行多级分类
Map<Type, List<Dish>> result = 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;  
        })));

        2.9 通过partitioningBy进行分区

// 分区是特殊的分组,它分类依据是true和false,所以返回的结果最多可以分为两组
Map<Boolean, List<Dish>> result = menu.stream().collect(partitioningBy(Dish :: isVegetarian));

// 等同于
Map<Boolean, List<Dish>> result = menu.stream().collect(groupingBy(Dish :: isVegetarian));



// 更明显的例子
List<Integer> integerList = Arrays.asList(1, 2, 3, 4, 5);  
Map<Boolean, List<Integer>> result = integerList.stream().collect(partitioningBy(i -> i < 3));