LoginSignup
57

More than 5 years have passed since last update.

Java7 vs. Java8 Stream

Last updated at Posted at 2015-08-17

はじめに

業務でもようやくJava8に移行するような話が出てきているので、復習を兼ねてJava8のStream APIの使い方を幾つかのユースケースごとにまとめてみました。それぞれ、Java7でのコードススタイルとJava8のStreamを用いたコードスタイルでのサンプルコードを記載しています。

リストの操作

リスト(java.util.List)の操作はStream APIを利用することでより簡潔に記述できるようになります。以降のサンプルコードでは、次の惑星クラスと、


    //惑星
    public static class Planet {
        private final String name;
        private final int diameter;
        private final String[] satellites;

        public Planet(String name, int diameter, String... satelites) {
            this.name = name;
            this.diameter = diameter;
            this.satellites = satelites;
        }

        //名前
        public String getName() {
            return name;
        }

        //直径(km)
        public int getDiameter() {
            return diameter;
        }

        //衛星のリスト
        public List<String> getSatellites() {
            return Arrays.asList(satellites);
        }
    }

この惑星クラスを要素とする、下のリスト(planets)が定義されているものとします。

    //衛星は最大2つまでとします。
    //昔は冥王星も惑星でした。。。
    List<Planet> planets = Arrays.asList(
            new Planet("水星", 4879), 
            new Planet("金星", 12103),
            new Planet("地球", 12756, "月"),
            new Planet("火星", 6794, "フォボス", "ダイモス"), 
            new Planet("木星", 142984, "イオ", "エウロパ"),
            new Planet("土星", 120536, "タイタン", "レア"), 
            new Planet("天王星", 51118, "チタニア", "オベロン"), 
            new Planet("海王星", 49572, "トリトン", "プロテウス")
    );

すべての要素に何かの処理を行う

例えば、すべての惑星の名前を出力するコードは以下のようになります。

Java7:

            for (Planet p : planets) {
                System.out.println(p.getName());
            }

Java8:

            planets.stream().forEach(p -> System.out.println(p.getName()));

StreamのforEachメソッドはすべて要素に指定の処理を適用します。もしくは、Streamではないですが、以下のようにも書けます。

            planets.forEach(p -> System.out.println(p.getName()));

条件に一致する要素のみを抽出する

例えば、直径10,000km以上の惑星のみのリストを生成するコードは以下のようになります。

Java7:

            List<Planet> filtered = new ArrayList<>();
            for (Planet p : planets) {
                if (p.getDiameter() > 10000) {
                    filtered.add(p);
                }
            }

Java8:

            List<Planet> filtered = planets.stream()
                    .filter(p -> p.getDiameter() > 10000)
                    .collect(Collectors.toList());

抽出して得られるリストは以下の通りです。

        [金星,地球,木星,土星,天王星,海王星]

条件に一致する要素の数を数える

例えば、直径10,000km以上の惑星の数を数えるコードは以下のようになります。

Java7:

            long count = 0;
            for (Planet p : planets) {
                if (p.getDiameter() > 10000) {
                    count++;
                }
            }

Java8:

            long count = planets.stream()
                    .filter(p -> p.getDiameter() > 10000)
                    .count();

Streamのfilterメソッドは指定された条件(述語)に一致する要素のみを抽出したStreamを生成します。

条件に一致する要素が一つ以上あるか調べる

例えば、直径10,000km以上の惑星が存在するかを調べるコードは以下のようになります。

Java7:

            boolean found = false;
            for (Planet p : planets) {
                if (p.getDiameter() > 10000) {
                    found = true;
                    break;
                }
            }

Java8:

            boolean found = planets.stream()
                    .anyMatch(p -> p.getDiameter() > 10000);

StreamのanyMatchメソッドは指定された述語に一致する要素が1つでもあれば true を返します。

すべての要素が条件に一致するか調べる

例えば、すべての惑星が直径10,000km以上かを調べるコードは以下のようになります。

Java7:

            boolean matchedAll = true;
            for (Planet p : planets) {
                if (!(p.getDiameter() > 10000)) {
                    matchedAll = false;
                    break;
                }
            }

Java8:

            boolean matchedAll = planets.stream()
                    .allMatch(p -> p.getDiameter() > 10000);        

StreamのallMatchは指定された述語にすべての要素が一致する場合のみ true を返します。

それぞれの要素を異なる要素に変換(写像)したリストを生成する

例えば、リストの各惑星インスタンスから惑星名(String)のリストを生成するコードは以下のようになります。

Java7:

            List<String> mappedList = new ArrayList<>();
            for (Planet p : planets) {
                mappedList.add(p.getName());
            }

Java8:

            List<String> mappedList = planets.stream()
                    .map(p -> p.getName())
                    .collect(Collectors.toList());

Streamのmapメソッドは、Streamの各要素を別の型の要素にマッピングしたStreamを生成します。上の例では、惑星インスタンス(Planet型)を惑星名(String型)に変換します。

メソッド参照を利用して、以下のようにより簡潔に記述することもできます。

            List<String> mappedList = planets.stream()
                    .map(Planet::getName)
                    .collect(Collectors.toList());

各要素を異なる複数要素に変換した結果をマージしたリストを生成する

少しわかりにくいですが、上の例はリストの各要素の1:1での変換であるのに対し、1:Nに変換することです。例えば、各惑星に対応する衛星(複数)をマージして1つのリストとして生成するコードは以下のようになります。

Java7:

            List<String> sattelites = new ArrayList<>();
            for (Planet p : planets) {
                sattelites.addAll(p.getSatellites());
            }

Java8:

            List<String> sattelites = planets.stream()
                    .flatMap(p -> p.getSatellites().stream())
                    .collect(Collectors.toList());  

StreamのflatMapメソッドは、各要素から生成されるStreamを1つに統合したStreamを生成します。

Java7,8いずれの結果でも、以下の衛星からなる(フラットな)リストが生成されます。

        [月,フォボス,ダイモス,イオ,エウロパ,タイタン,レア,チタニア,オベロン,トリトン,プロテウス]

各要素を合成して1つの値に集約する

これも少しわかりにくいですが、関数型言語での畳み込み関数のことです。例えば、各惑星の直径の合計を求めるコードは以下のようになります。

Java7:

            int totalDiameter = 0;
            for (Planet p : planets) {
                totalDiameter += p.getDiameter();
            }

Java8:

             int totalDiameter = planets.stream()
                     .map(p -> p.getDiameter())
                     .reduce((accum, p) -> accum + p)
                     .get();

Streamのreduceメソッドが関数型言語での畳み込み関数(fold関数)に対応します。Streamでは左からの畳み込み関数しかありません。reduceメソッドは、結果が存在しない場合があるため(たとえば空のStreamの場合など)、Optional型を返すことになっています。そのため、最後のgetメソッドで値を取得する必要があります。

リストをフィルタリングしてマッピングして簡約する

いわゆる、map-reduceパターンです。例えば、衛星を持つ惑星を抽出(filter)し、その惑星を惑星名に変換(map)し、惑星名のリストを生成(reduce)するコードは以下のようになります。

Java7:

            List<String> planetWithSattelites = new ArrayList<>();
            for (Planet p : planets) {
                if (p.getSatellites().size() > 0) {
                    planetWithSattelites.add(p.getName());
                }
            }

Java8:

            List<String> planetWithSattelites = planets.stream()
                    .filter(p -> p.getSatellites().size() > 0)
                    .map(Planet::getName)
                    .collect(Collectors.toList());  

マップの操作

Streamではないですが、Java8ではMapの繰り返し処理も簡単にできます。

すべてのキーと値のペアに何らかの処理を行う

例えば、次のMapのすべてのキーと値を出力するコードは以下のようになります。

        Map<String, String> map = new HashMap<>();
        map.put("1", "one");
        map.put("2", "two");
        map.put("3", "three");      

Java7:

            for (Map.Entry<String, String> entry : map.entrySet()) {
                String key = entry.getKey();
                String value = entry.getValue();
                System.out.println(String.format("key=%s, value=%s" ,key, value));
            }

Java8:

            map.forEach((key, value) -> {
                System.out.println(String.format("key=%s, value=%s" ,key, value));
            });     

テキストファイルの操作

次のテキストファイルを題材とします。

/tmp/sample.txt
Java
Scala
C
C#
JavaScript
Groovy

テキストファイルの全ての行に何かの処理を行う

たとえば、テキストファイルの全行を出力するコードは以下のようになります。

Java7:

            try (BufferedReader br = Files.newBufferedReader(Paths.get("/tmp/sample.txt"), Charset.forName("utf-8"))) {
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }
            }

Java8:

            try (Stream<String> stream = Files.lines(Paths.get("/tmp/sample.txt"), Charset.forName("utf-8"))) {
                stream.forEach(line -> System.out.println(line));
            }

Fileから生成したStreamの場合は、明示的にStreamをクローズする必要があります。よって、上のようにtry-with-resources文を利用しています。

以下のように書くこともできます。ただし、この場合は全行をメモリに展開することに注意が必要です。

            for (String line : Files.readAllLines(Paths.get("/tmp/sample.txt"), Charset.forName("utf-8"))) {
                System.out.println(line);
            }

テキストファイルから条件に一致する行を抽出する

たとえば、テキストファイルから"Java"という単語を含む行のみを抽出して出力するコードは以下のようになります。

Java7:

            try (BufferedReader br = Files.newBufferedReader(Paths.get("/tmp/sample.txt"), Charset.forName("utf-8"))) {
                String line;
                while ((line = br.readLine()) != null) {
                    if (line.contains("Java")) {
                        System.out.println(line);
                    }
                }
            }

Java8:

            try (Stream<String> stream = Files.lines(Paths.get("/tmp/sample.txt"))) {
                stream.filter(line -> line.contains("Java")).forEach(System.out::println);
            }

出力結果は以下のようになります。

Java
JavaScript

ディレクトリの操作

/tmp/test に以下のディレクトリとファイルが存在するものとします。

$ ls -a /tmp/test
.   ..  .dir2   .file2  .file3  dir1    file1

ディレクトリの全てのサブディレクトリとファイルを抽出する

たとえば、ディレクトリ /tmp/test 配下のすべてのファイルとディレクトリ名を出力するコードは以下のようになります。

Java7:

            File dir = Paths.get("/tmp/test").toFile();
            for (File file : dir.listFiles()) {
                System.out.println(file);
            }

Java8:

            try (Stream<Path> stream = Files.list(Paths.get("/tmp/test"))) {
                stream.forEach(System.out::println);
            }

ディレクトリから条件に一致するファイルのみを抽出する

たとえば、/tmp/test から"."から始まる隠しファイルのみを抽出して出力するコードは以下のようになります。

Java7:

            File dir = Paths.get("/tmp/test").toFile();
            for (File file : dir.listFiles()) {
                if (!file.isFile()) {
                    continue;
                }
                if (file.getName().startsWith(".")) {
                    System.out.println(file.getName());
                }
            }

Java8:

            try (Stream<Path> stream = Files.list(Paths.get("/tmp/test"))) {
                stream
                .filter(path -> path.toFile().isFile())
                .map(path -> path.getFileName().toString())
                .filter(name -> name.startsWith("."))
                .forEach(System.out::println);
            }

出力結果は以下のようになります。

.file2
.file3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
57