Stream API①

Java本格入門のStreamAPIのところを読んだので、それのメモ。


StreamAPIは大量データを逐次処理する「ストリーム処理」を効率的に記述するための手段。

作成:コレクションや配列からStreamを作成

中間操作:StreamからStreamを作成

終端操作:Streamからコレクションや配列への変換、要素の処理や集計など

という流れで処理を行える。


for文やwhile文はStreamに置き換えていくのがよい(可読性が失われない範囲で)

Streamの作成

・ListやSetから
        //listを作成
        List<Integer> list = Arrays.asList(1,2,3,4);

        //作成したlistをstreamに変換し、メソッドチェーンで終端操作につなげて出力。
        list.stream().forEach(System.out::println);
        →1 2 3 4(実際には縦に表示される)
・配列から
        //配列を作成
        Integer[] array = {1,2,3,4,5,6};

        //ArraysクラスからStreamを作成し、メソッドチェーンで終端操作につなげて出力
        Arrays.stream(array).forEach(System.out::println);
        →1 2 3 4

配列の中身が決まっているなら、StreamのofメソッドでStreamを作れる。

        //ofメソッドの中にArrays.sreamがある
        Stream<Integer> stream = Stream.of(1,2,3,4);
        stream.forEach(System.out::println);
        →1 2 3 4
・Mapから
        Map<Integer, String> map = new HashMap<>();
        map.put(1, "yamada");
        map.put(2, "tanaka");
        map.put(3, "sasaki");
        
        //entrySet()でSetを取得し、SetからStreamを作成、
        //そのままメソッドチェーンで終端操作につなげて出力。
        //キーと値の取得
        map.entrySet().stream()
        .forEach(e -> System.out.println(e.getKey() +  ":" + e.getValue()));
        // 1:yamada
        // 2:tanaka
        // 3:sasaki

        //キーの取得(上記のe.getKey()以外のやり方)
        map.keySet().stream()
        .forEach(System.out::println);
        // 1
        // 2
        // 3

        //値の取得(上記のe.getValue()以外のやり方)
        map.values().stream()
        .forEach(System.out::println);
        // yamada
        // tanaka
        // sasaki
・数値範囲から

開始と終了を指定して、範囲で数値を扱えるIntStreamというものがある。

        //rangeはfor(int i=1; i<5; i++)と同じ
        IntStream.range(1, 5)
        .forEach(System.out::println);
        // 1
        // 2
        // 3
        // 4

        //rangeClosedはfor(int i=1; i<=5; i++)と同じ
        IntStream.rangeClosed(1, 5)
        .forEach(System.out::println);
        // 1
        // 2
        // 3
        // 4
        // 5

       //「n回処理する」という場合はこう書ける。
       // for文より読みやすい!
        int count = 3;
        IntStream.range(1, count)
        .forEach(System.out::println);
        // 1
        // 2

Stremの中間操作

要素を置き換える中間操作

メソッドにmapという文字が入っている中間操作は要素を置き換えることを目的としている。

・map
要素を別の値に置き換える。
戻り値はStream<変換後の型>

・mapToInt
要素をint値に置き換える。
戻り値はintStream


他にもmapToDouble, mapToLong, flatMapなどがある。



Student.java

public class Student {
    
    private String name;
    private int score;

    public Student(String name, int score){
        this.name = name;
        this.score = score;
    }

    public String getName(){
        return name;
    }

    public int getScore(){
        return score;
    }

    public String toString(){
        return name + ":" + score;
    }
}

Main.java

public class Main {
    
    public static void main(String[] args) {
        
        List<Student> students = new ArrayList<>();
        students.add(new Student("yamada", 100));
        students.add(new Student("sasaki", 90));
        students.add(new Student("yoyogi", 67));

        //mapでスコアを取り出し(メソッド参照)、出力している。
        students.stream()
            .map(Student::getScore)
            .forEach(System.out::println);
    }
}
flatMap(複数StudentをGroupでまとめた時)

Goup.java

public class Group {
    public List<Student> students = new ArrayList<>();

    public void add(Student student){
        students.add(student);
    }

    public List<Student> getStudents(){
        return students;
    }
}

Main.java

public class Main {
    
    public static void main(String[] args) {
        List<Group> groups = new ArrayList<>();

        Group group1 = new Group();
        group1.add(new Student("yamada", 100));
        group1.add(new Student("gouda", 30));
        groups.add(group1);

        Group group2 = new Group();
        group2.add(new Student("takahashi", 89));
        group2.add(new Student("sigime", 24));
        groups.add(group2);

        //通常のmapで二つのグループをまとめる。
        //戻り値がListなので、次の処理単位はListになる
        Stream<List<Student>> mappedStream = groups.stream()
            .map(g -> g.getStudents());

        //flatMapで二つのグループをまとめる。
        //戻り値はStudent(group1とgroup2がまとまっている)で、次の処理単位はStudentになる
        Stream<Student> flatMappedStream = groups.stream()
            .flatMap(g -> g.getStudents().stream());


        //-------スコア順にstudentを並べる処理-----------

        //flatMapを使わない場合
        //group1とgroup2をまとめるために、Listを用意しておかないといけない
        List<Student> allStudents = new ArrayList<>();
        groups.stream()
            .forEach(g -> allStudents.addAll(g.getStudents()));

        allStudents.stream()
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(s -> System.out.println(s.getName() + ":" +s.getScore()));

        //flatMapを使う場合
        //group1とgroup2をまとめたList型の変数を用意しなくていい。
        groups.stream()
            .flatMap(g -> g.getStudents().stream())
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(s -> System.out.println(s.getName() + ":" +s.getScore()));
    }
}

要素を絞り込む中間操作

・filter - 条件に合致した要素のみに絞り込む
・limit - 指定した件数に絞り込む
・distinct - ユニークな要素のみに絞り込む

public class Main {
    
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student("yamada", 100));
        students.add(new Student("harada", 50));
        students.add(new Student("gouda", 87));

        //filterを使って70以上に絞り込む
        students.stream()
            .filter(s -> s.getScore() > 80)
            .forEach(s -> System.out.println(s.getName()));
        // yamada
        // gouda

        //limitを使って2件だけに絞り込む
        students.stream()
            .limit(2)
            .forEach(System.out::println);
        // yamada:100
        // harada:50

        List<Integer> numbers = new ArrayList<>();
        numbers.add(1);
        numbers.add(2);
        numbers.add(3);
        numbers.add(2);

        //distinctを使ってユニークな要素のみに絞り込む。(重複が除外される)
        numbers.stream()
            .distinct()
            .forEach(System.out::println);
        // 1
        // 2
        // 3
    }
}

要素を並べかえる中間操作

・sorted

Comparator(2つの引数を受けて、intを返すオブジェクト)を渡す。

public class Main {
    
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();

        students.add(new Student("yamada", 100));
        students.add(new Student("harada", 50));
        students.add(new Student("gouda", 87));

        //scoreが高い順に並べる
        students.stream()
            .sorted((s1, s2) -> s2.getScore() - s1.getScore())
            .forEach(s -> System.out.println(s.getName() + ":" + s.getScore()));
        // yamada:100
        // gouda:87
        // harada:50
    }
}

まとめ

Sreamは慣れれば簡単だし便利だと思うけど、書き方によっては可読性が下がりそう。
長くなってしまったので、後半は次に回す。