1. 程式人生 > 程式設計 >Java8 Stream flatmap中間操作用法解析

Java8 Stream flatmap中間操作用法解析

stream中的flatmap是stream的一種中間操作,它和stream的map一樣,是一種收集型別的stream中間操作,但是與map不同的是,它可以對stream流中單個元素再進行拆分(切片),從另一種角度上說,使用了它,就是使用了雙重for迴圈。

檢視Stream原始碼中flatmap的方法定義:

<R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)

從方法的定義可以看出,其入參是一個函式式介面,該介面的返回型別應該是Stream< ? extends R > 型別的。

從實際需求中檢視如何使用flatmap:

需求:有一個補習學校,其中有若干老師教學若干門課程,現在學校有關於數學教學的通知要傳達給所有學數學的學生家長,將電子郵件傳送到他們的郵箱中。

注意:一個老師可以教學多個科目,一個老師可以教學多個學生,一個學生可以報名多個科目,一個學生可以有多個家長。

資料結構(均省略get/set,toString,構造器):

// 老師
public class Teacher {
  private String id;
  private String name;
  private String subject;
}
// 學生
public class Student {
  private String id;
  private String name;
  private String techId;
  // 重寫hashCode及equals方法(id及name相同就為同一個學生)
}
// 家長
public class Parents {
  private String id;
  private String name;
  private String chirldId;
  private String email;
}
// 課程
public enum Subject {
  private String value;
  private String desc;
  Subject(String value,String desc) {
    this.value = value;
    this.desc = desc;
  }
  Math("1","數學"),Chinese("2","漢語"),Music("3","音樂"),English("4","英語");
}

實際上的處理也比較簡單:

1、找出教學科目為“數學”的老師;

2、找到這些老師對應的學生;

3、根據學生找到對應的家長。

直接貼程式碼:

public class Test {
  // 模擬資料
  public static Student s1 = new Student("1","zhangsan","001");
  public static Student s2 = new Student("2","lisi","001");
  public static Student s3 = new Student("3","wangwu","001");
  public static Student s4 = new Student("4","zhaoliu","001");
  public static Student s5 = new Student("6","tianqi","001");
  public static Student s6 = new Student("6","002");
  public static Student s7 = new Student("6","003");
  public static Teacher t1 = new Teacher("001","xiaoming",Subject.Math.getValue());
  public static Teacher t2 = new Teacher("002","lihua",Subject.Music.getValue());
  public static Teacher t3 = new Teacher("003","hanmeimei",Subject.Math.getValue());
  public static Teacher t4 = new Teacher("004",Subject.English.getValue());
  public static List<Student> stus = new ArrayList<>();
  public static List<Teacher> teacs = new ArrayList<>();
  static {
    stus.add(s1);
    stus.add(s2);
    stus.add(s3);
    stus.add(s4);
    stus.add(s5);
    stus.add(s6);
    stus.add(s7);
    teacs.add(t1);
    teacs.add(t2);
    teacs.add(t3);
    teacs.add(t4);
  }
 
 
  public static void main(String[] args) {
    // 找到所有數學老師的學生的家長的電話,並找他們開家長會
    List<Parents> collect = teacs.stream()
        // 過濾數學老師
        .filter(t -> Subject.Math.getValue().equals(t.getSubject()))
        // 通過老師找學生
        .flatMap(t -> stus.stream().filter(s -> s.getTechId().equals(t.getId())))
        // 過濾重複的學生(使用student的equals和hashCode方法)
        .distinct()
        // 通過學生找家長(這裡就簡化為建立家長物件)
        .map(s -> {
          Parents p = new Parents();
          p.setId(UUID.randomUUID().toString());
          p.setChirldId(s.getId());
          p.setName(s.getName().toUpperCase() + "'s Parent");
          p.setEmail((int) (Math.random() * 1000000) + "@qq.com");
          return p;
        })
        .collect(Collectors.toList());
    // 列印到控制檯看看
    collect.stream()
        .forEach(System.out::println);
  }
}

執行結果:

Parents{id='3d9312eb-0df5-4ec6-998f-94a32c2253b4',name='LISI's Parent',chirldId='2',telNo='[email protected]'}
Parents{id='7f0b92f5-872d-4671-982d-ef1b48840ce3',name='WANGWU's Parent',chirldId='3',telNo='[email protected]'}
Parents{id='c318bffd-8c6d-4849-8109-9c686c97fb77',name='ZHAOLIU's Parent',chirldId='4',telNo='[email protected]'}
Parents{id='a4ff1bbc-c9b6-4ad2-872c-f4df670c7bb6',name='TIANQI's Parent',chirldId='6',telNo='[email protected]'}

如果不使用stream,寫該部分程式碼的效果,可能還有優化的空間,但是不夠使用stream處理方式更為簡潔,方便:

public class Test {
  public static void main(String[] args) {
    List<Parents> pars = new ArrayList<>();
    Set<Student> targetStudents = new HashSet<>();
    for (Teacher t : teacs) {
      if (t.getSubject().equals(Subject.Math.getValue())) {
        for (Student s : stus) {
          if (s.getTechId().equals(t.getId())) {
            targetStudents.add(s);
          }
        }
      }
    }
    for (Student s : targetStudents) {
      Parents p = new Parents();
      p.setId(UUID.randomUUID().toString());
      p.setChirldId(s.getId());
      p.setName(s.getName().toUpperCase() + "'s Parent");
      p.setEmail((int) (Math.random() * 1000000) + "@qq.com");
      pars.add(p);
    }
    for (Parents p : pars) {
      System.out.println(p);
    }
  }
}

再去看stream中的flatmap方法,它的作用就和他的名字flat一樣,對於呼叫flatmap的流的每一個元素,執行flatmap入參中的函式式方法,由於該函式式方法必須返回一個stream<T>型別的流,這樣對於呼叫flatmap的操作來說,就收集了另一種型別(<T>)的流,並在後續的操作中將<T>型別進行合併,最終產生一個stream<T>的流,而不是一個stream<stream<T>>型別的流。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。