ねこじゃすり
猫を魅了する魔法の装備品!
【最新機種】GoPro hero11 Black
最新機種でVlogの思い出を撮影しよう!
ペヤング ソースやきそば 120g×18個
とりあえず保存食として買っておけば間違いなし!
ドラゴンクエスト メタリックモンスターズギャラリー メタルキング
みんな大好き経験値の塊をデスクに常備しておこう!
BANDAI SPIRITS ULTIMAGEAR 遊戯王 千年パズル 1/1スケール
もう一人の僕を呼び覚ませ!!
MOFT X 【新型 ミニマム版】 iPhone対応 スマホスタンド
Amazon一番人気のスマホスタンド!カード類も収納出来てかさ張らないのでオススメです!
サンディスク microSD 128GB
スマホからSwitchまで使える大容量MicroSDカード!
スポンサーリンク
目次
Listの順番を担保したMapを作りたい
ソートされたListからMapを作ると順序が変わる
Java8以降でstream
APIを使い、List
オブジェクトからMap
オブジェクトを生成したいというケースは良くあると思います。
今回の例ではとてもシンプルなList
オブジェクトですが、実際にはModel
などをList
の要素として持つ形になると思います。
そんな時に、すでにソートされたList
(DBからの取得結果etc…)をstream
で回してMap
オブジェクトにしようとする際に以下のような書き方をする場合が多いと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class Sample { public static void main( String... args) { System.out.println("=====List====="); List<String> numberList = Arrays.asList("One","Two", "three", "For", "Five", "Six", "Seven", "Eight", "Nine", "Ten"); numberList.stream() .forEach(System.out::println); System.out.println("=====Map====="); numberList.stream() .collect(Collectors.toMap( number -> number, number -> number ) ) .keySet() .forEach(System.out::println); } } |
しかし、この実装方法で作成したMap
からkeySet()
を呼び出してをさらにstream
でループ処理しようとした際に、List
の順序が担保されないMap
オブジェクトが生成されてしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
=====List===== One Two three For Five Six Seven Eight Nine Ten =====Map===== Eight Five Six One Nine For Seven Ten Two three |
原因
toMap
を使う場合、引数にMap
クラスを何も指定しないとHashMap
クラスで生成されます。
そしてご存知の通りHashMap
クラスは要素の一意性をkey
のハッシュ値で管理するので元々順序を保証してくれません。
勘違いしやすいケース
以下のようなとてもシンプルなList
の場合はぱっと見順序を保持していそうですが、たまたまハッシュ値と順序が一緒なだけなので、要素がオブジェクトになった途端順序がバラバラになってしまいます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.util.Arrays; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; public class Sample { public static void main( String... args) { System.out.println("=====List====="); List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7,8,9,10); numberList.stream() .forEach(System.out::println); System.out.println("=====Map====="); numberList.stream() .collect(Collectors.toMap( number -> number, number -> number) ) .keySet() .forEach(System.out::println); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
=====List===== 1 2 3 4 5 6 7 8 9 10 =====Map===== 1 2 3 4 5 6 7 8 9 10 |
試しにリストをハッシュとは逆順にすると、、、、
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
import java.util.Arrays; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; public class Sample { public static void main( String... args) { System.out.println("=====List====="); List<Integer> numberList = Arrays.asList(10,9,8,7,6,5,4,3,2,1); numberList.stream() .forEach(System.out::println); System.out.println("=====Map====="); numberList.stream() .collect(Collectors.toMap( number -> number, number -> number) ) .keySet() .forEach(System.out::println); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
=====List===== 10 9 8 7 6 5 4 3 2 1 =====Map===== 1 2 3 4 5 6 7 8 9 10 |
はい残念。
解決方法
なので、
1 2 3 |
public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) |
ではなく、
1 2 3 4 5 |
public static <T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapFactory) |
を使い、第四引数にてLinkedHashMap
クラスを渡し、明示的に使用するMap
クラス指定することで解決が可能となります。
ちなみに、LinkedHashMap
クラスとは追加した順番を保持してくれる便利なMap
クラスとなっています。
今回は実際にこちらを使って追加した順番を担保するサンプルを作ってみようと思います。
手順
collect関数の引数を修正
修正方法はとても簡単です。
以下のようにCollectors.toMap
の第三引数と第四引数を以下のように追加するだけでOKです。
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 |
import java.util.Arrays; import java.util.List; import java.util.LinkedHashMap; import java.util.stream.Collectors; public class Sample { public static void main( String... args) { System.out.println("=====List====="); List<String> numberList = Arrays.asList("One","Two", "three", "For", "Five", "Six", "Seven", "Eight", "Nine", "Ten"); numberList.stream() .forEach(System.out::println); System.out.println("=====Map====="); numberList.stream() .collect(Collectors.toMap( number -> number, number -> number, (u, v) -> v, // 追加 LinkedHashMap::new // 追加 ) ) .keySet() .forEach(System.out::println); } } |
確認
実行すると以下のような形になると思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
=====List===== One Two three For Five Six Seven Eight Nine Ten =====Map===== One Two three For Five Six Seven Eight Nine Ten |
完璧ですね♪
TreeMapでは怪しい
LinkedHashMapではなく、TreeMapでもいけるという記事をいくつか見ましたので確かめてみました、。
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 |
import java.util.Arrays; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; public class Sample { public static void main( String... args) { System.out.println("=====List====="); List<String> numberList = Arrays.asList("One","Two", "three", "For", "Five", "Six", "Seven", "Eight", "Nine", "Ten"); numberList.stream() .forEach(System.out::println); System.out.println("=====Map====="); numberList.stream() .collect(Collectors.toMap( number -> number, number -> number, (u, v) -> v, TreeMap::new ) ) .keySet() .forEach(System.out::println); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
=====List===== One Two three For Five Six Seven Eight Nine Ten =====Map===== Eight Five For Nine One Seven Six Ten Two three |
むむっ。
TreeMap
クラスは通常のHashMap
クラスと同様にハッシュによる順序に自動ソートされるのでうまくいきませんでした。
しかし、引数を渡さないtoMap
と同様に、List
の値によってはうまく順序を保っているように見えてしまうので注意が必要です。
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 |
import java.util.Arrays; import java.util.List; import java.util.TreeMap; import java.util.stream.Collectors; public class Sample { public static void main( String... args) { System.out.println("=====List====="); List<Integer> numberList = Arrays.asList(1,2,3,4,5,6,7,8,9,10); numberList.stream() .forEach(System.out::println); System.out.println("=====Map====="); numberList.stream() .collect(Collectors.toMap( number -> number, number -> number, (u, v) -> v, TreeMap::new ) ) .keySet() .forEach(System.out::println); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
=====List===== 1 2 3 4 5 6 7 8 9 10 =====Map===== 1 2 3 4 5 6 7 8 9 10 |
終わりに
以上のように簡単にList要素の順番に担保されたMapオブジェクトを生成することが可能になりました。
お困りの方は、こちらのやり方を参考にしてみてください♪