概要
前回の【Java】ListをStreamで処理する方法 7選!に続き、Set編です。
基本的にListもSetもCollection型であるため、コーディングに大きな差はありません。
気を付けるべきは、格納されている要素の順序です。
ほとんどの場合、SetというとHashSetを使っていると思います。
HashSetは格納順で要素を保持しない仕様なので、最初に格納した要素を取得しようとしても別の要素を取得してします恐れがあります。
その点についての対策も含めて解説していきたいと思います。
前準備
【Java】ListをStreamで処理する方法 7選! > 前準備を参照下さい。
実装例
ListとSetで処理方法が変わる方法については、以下のように番号を振って、解説を加えておきます。
① -> Listと変わらない方法
② -> 格納順を意識しなくてはならない方法
特定の要素のみを取得する
①特定の要素を取得するだけなので、並び順は関係無くListと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Test void Set_特定の要素のみを取得する() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; List<Product> toys = products.stream() .filter(product -> "玩具".equals(product.category())) .collect(Collectors.toList()); assertThat(toys.get(0).name(), is("プラモデル")); } |
合計を算出する
①全要素の合計であるため、並び順は関係無くListと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Test void Set_合計を算出する() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; int sumAmount = products.stream() .mapToInt(product -> product.price()) .sum(); assertThat(sumAmount, is(1300)); } |
並べ替えた結果を取得する(昇順)
①並び変えるため、元の並び順は関係なくなりますのでListと同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Test void Set_並べ替えた結果を取得する_昇順() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; List<Product> ascOrderOfPrice = products.stream() .sorted(Comparator.comparing(Product::price)) .collect(Collectors.toList()); assertThat(ascOrderOfPrice.get(0).name(), is("ふりかけ")); } |
並べ替えた結果を取得する(降順)
①昇順と同じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
@Test void Set_並べ替えた結果を取得する_降順() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; List<Product> ascOrderOfPrice = products.stream() .sorted(Comparator.comparing(Product::price).reversed()) .collect(Collectors.toList()); assertThat(ascOrderOfPrice.get(0).name(), is("プラモデル")); } |
最初の要素を取得する
②HashSetは格納順が保障されていないので、最初に格納した要素を取得できない場合があります。
格納順を維持したい場合は、LinkedHashSetを使用することをお奨めします。
または、並び替えのキーがあれば、その結果の最初の要素を取得することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
@Test void Set_最初の要素を取得する() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; // Redのケース // Expected: is "靴下" // but: was "ふりかけ" Product first = products.stream() .findFirst() .orElse(null); assertThat(first.name(), is("靴下")); // Greenのケース Product firstOfAscPrice = products.stream() .sorted(Comparator.comparing(Product::price)) .findFirst() .orElse(null); assertThat(firstOfAscPrice.name(), is("ふりかけ")); } |
最後の要素を取得する
②Listではreduceを使用する方法を記載しましたが、HashSetは最後の要素が格納順であることは保証されていないため、並び順を変えるか、LinkedHashsetを使用するのは昇順と同じです。
ここではreverce(逆順)にすることで最後の要素を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
@Test void Set_最後の要素を取得する() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; Product last = products.stream() .sorted(Comparator.comparing(Product::price).reversed()) .findFirst() .orElse(null); assertThat(last.name(), is("プラモデル")); } |
なんらかの値に変換した値を取得する
②変換したあとの値の並び順は保証されていません。
ソートし直すのであればsortedが使用できますが、元々の格納順を維持した結果がほしい場合は、LinkedHashsetを使用することをお奨めします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Test void Set_なんらかの値に変換した値を取得する() { Set<Product> products = new HashSet<>() { { add(new Product("衣類", "靴下", 200)); add(new Product("食品", "ふりかけ", 100)); add(new Product("玩具", "プラモデル", 1000)); } }; List<String> formatedList = products.stream() .map(product -> new Formatter(). format("[%s, %s, %d]", product.category(), product.name(), product.price()) .toString()) .collect(Collectors.toList()); // HashSetは順序が保証されていないので、玩具を含んだ変換後の文字列のみを抽出してテストする assertThat(formatedList.stream() .filter(str -> str.toString().contains("玩具")) .findFirst() .orElse(null), is("[玩具, プラモデル, 1000]")); } |
まとめ
- SetはListと同じCollection型なので、基本的な操作は同じである。
- HashSetは格納順を維持しない。
- 格納順を維持した操作をする場合、LinkedHashsetとしておくか、意図した並び順にソートする必要がある。