流式操作

流(Stream):Java8新增 , 用来处理我们集合的数据(与IO包里的流是完全不同的概念),倒是和Spark Streaming很像(反正是抄的)

如何处理集合中的数据呢(为什么引入流)?

  1. 声明式处理数据;(元素的内部迭代,不需要做外部迭代)【流可以把一些基础操作连接起来,用于表达更加复杂的数据处理的流水线】【类似自然语言,类似sql】
  2. 流内部可以做并行操作。(内部迭代)【底层帮我们,多线程来处理数据,不需要我们来控制】

先看一个小案例:

public class Dish { //菜单

  private final String name; // 菜名
  private final boolean vegetarian; // 素食
  private final int calories; //卡路里
  private final Type type; // MEAT(肉), FISH(鱼), OTHER(其它)

  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<Dish> menu =
          Arrays.asList( new Dish("猪肉", false, 1100, Dish.Type.MEAT),
                  new Dish("牛肉", false, 1700, Dish.Type.MEAT),
                  new Dish("鸡肉", false, 1400, Dish.Type.MEAT),
                  new Dish("薯条", true, 430, Dish.Type.OTHER),
                  new Dish("米饭", true, 350, Dish.Type.OTHER),
                  new Dish("沙拉", true, 120, Dish.Type.OTHER),
                  new Dish("披萨", true, 450, Dish.Type.OTHER),
                  new Dish("虾", false, 600, Dish.Type.FISH),
                  new Dish("大马哈鱼", false, 650, Dish.Type.FISH));

}

需求:从我们的集合中筛选卡路里<400的菜,然后按照卡路里进行排序,返回一个排序好的菜的名字

1.Java8之前所用的方法:遍历集合 使用 外部迭代

@Test
  public void test1(){
    List<String> names = getNamesInJava7(Dish.menu);
    System.out.println(names);
  }
public static List<String> getNamesInJava7(List<Dish> dishes){
    //筛选卡路里小于400的,存入新集合
    List<Dish> lowCaloricDishs = new ArrayList();
    for(Dish dish : dishes){
      if(dish.getCalories() < 400){
        lowCaloricDishs.add(dish);
      }
    }

    //对筛选过的新集合进行排序
    Collections.sort(lowCaloricDishs, new Comparator<Dish>() {
      @Override
      public int compare(Dish o1, Dish o2) {
        return Integer.compare(o1.getCalories(),o2.getCalories());
      }
    });
     //lambda表达式写法,但是其实java8之前还没有lambda表达式
	//Collections.sort(lowCaloricDishs, (o1, o2)->{
	//return Integer.compare(o1.getCalories(),o2.getCalories());
	//});

    //返回菜名的集合
    ArrayList<String> lowCaloricDishName = new ArrayList<>();
    for(Dish dish : lowCaloricDishs){
      lowCaloricDishName.add(dish.getName());
    }

    return lowCaloricDishName;
 }

Java8:集合转化为流(创建流)的api:集合对象.stream()

//集合:List<Dish> dishes  |  集合转化为流:dishes.stream()
  //集合转化为流,就可以做流式操作
  // lambda --- 流
  public static List<String> getNamesInJava8(List<Dish> dishes){
    //声明式处理数据
    Stream<Dish> stream = dishes.stream();
    Stream<Dish> dishStream = stream 
            .filter(d -> d.getCalories() < 400);
    Stream<Dish> sorted = dishStream 
            .sorted(Comparator.comparing(Dish::getCalories));
    Stream<String> map = sorted.map(Dish::getName);
    List<String> collect = map.collect(Collectors.toList());
    return collect;
    //算子  并行的处理  上面的代码可以直接写成下面的方式
//    return dishes.stream() // 1,2,3,4,5->2,3,4,5->3,4,5->4,5->5
//            .filter(d->d.getCalories()<400)//1->2->3->4->5
//            .sorted(Comparator.comparing(Dish::getCalories))//1->2->3->4->5
//            .map(Dish::getName)//1->2->3->4->5
//            .collect(Collectors.toList());//1->1,2->1,2,3->1,2,3,4,->1,2,3,4,5

  }
@Test
  public void test2(){
    //Consumer<String> action
    Stream<String> stream = getNamesInJava8(Dish.menu).stream();
    stream.forEach(System.out::println);
  }

可以声明式的编程(不需要我们显示的迭代数据,外部迭代数据)(内部迭代数据) ,【stream()集合变成了流,Stream类有很多api(即算子),链式操作】

并行的数据处理 (简单的并行 | 分布式并行)


java流式API设计 java流式操作_stream

流(Stream):算子(api、方法)算子是指过程(上图中的大矩形是指集合)

算子分为两种:(java8和spark都有这两种概念)

中间操作 :所有Stream类里面的算子中,当一个流调用了一个算子的时候,如果能返回另外一个流那么则是中间操作,否则就是一个终端操作。中间操作是具有延迟加载的

终端操作:所有Stream类里面的算子中,当一个流调用了一个算子的时候,不能返回流。

中间操作具有延迟加载,程序执行由谁来触发呢?------由终端操作来进行触发

所有没有终端操作的流式操作,都不会进行程序的执行(即所谓的延迟加载)

@org.junit.Test
public void test2(){
   Stream<Dish> distinctList = Dish.menu.stream()
		   		.filter(d->{
		   			System.out.println("我有没有输出");
		   			return d.getCalories()>300;
                  })
		   		.distinct();//去重
//	   distinctRDD.forEach(System.out::println);
//这里没有终端操作,中间操作的程序不会执行,控制台不会输出
}

为什么流要做延迟加载?

Stream<Dish> stream = Dish.menu.stream();
stream.map(d->{
	System.out.println("map->"+d);
	return d ;
})
.filter(d->{
	System.out.println("filter->"+d);
	return d.getCalories() < 500 ;
})
.limit(2) //限制
.forEach(System.out::println);
//输出
map->猪肉
filter->猪肉
map->牛肉
filter->牛肉
map->鸡肉
filter->鸡肉
map->薯条
filter->薯条
薯条
map->米饭
filter->米饭
米饭
//单线程,先处理1,处理完后处理2,再3,limit(2)截断作用,所有4、5不执行
//stream()-->parallelStream()
//输出
map->牛肉
filter->牛肉
map->虾
filter->虾
map->鸡肉
filter->鸡肉
map->沙拉
filter->沙拉
map->薯条
filter->薯条
map->大马哈鱼
filter->大马哈鱼
map->猪肉
filter->猪肉
map->披萨
filter->披萨
map->米饭
filter->米饭
薯条
米饭

java流式API设计 java流式操作_java_02

stream()单线程(确切的说是PC中默认的最大线程数,单核/多核)

parallelStream()并行的


总结:

中间操作:(.)表示传入的是一个接口

操作

返回类型

函数式接口

函数描述符

作用

filter(.)

Stream

Predicate

T -> boolean

过滤

map(.)

Stream

Function<T,R>

T -> R

映射(转换)

limit(数字)

Stream

限制

sorted(.)

Stream

Comparator

(T,T)->int

排序

distinct()

Stream

去重(对流里的每个元素去重)

skip(数字)

Stream

跳过

终端操作

注: 收集器就是把流中的所有数据收集起来(list,set)

forEach()

遍历流中每一个元素

count()

返回流中元素的个数【long】

collect()

里面接收一个收集器(toList())

补充一下(网上很多说的都不准确):函数式接口是只有一个有效的抽象方法的接口。函数式接口中还可以有默认方法、静态方法。


集合和流的关系:

集合描述的是数据集【数据的整体】, 流主要描述的是计算方式,就是把一个集合的状态转变为另外一个状态)

流血缘关系链:

java8流式操作里面 血缘关系链形成的流不具有重复消费功能

spark血缘关系链是可以重复消费的

@org.junit.Test
public void test2(){
   Stream<Dish> distinctList = Dish.menu.stream()
		   		.filter(d->d.getCalories()>300)
		   		.distinct();
   distinctList.forEach(System.out::println);
   distinctList.forEach(System.out::println);    //这行会产生错误
   //若想再终端操作,只能重新进行中间操作,再终端操作,费时【不具有重复消费功能】
}

skip(n)算子:跳过n个元素 -------- (中间操作)Stream skip(long n)

总结:

1.如果大家在写代码的时候能够使用匿名内部类的地方,那么就可以尝试使用lambda表达式,方法引用

2.假如大家要对集合做一些数据分析,那么就可以考虑用流