Anker PowerCor
旅行には必須の大容量モバイルバッテリー!
ペヤング ソースやきそば 120g×18個
とりあえず保存食として買っておけば間違いなし!
モンスターエナジー 355ml×24本 [エナジードリンク]
脳を活性化させるにはこれ!
Bauhutte ( バウヒュッテ ) 昇降式 L字デスク ブラック BHD-670H-BK
メインデスクの横に置くのにぴったりなおしゃれな可動式ラック!
BANDAI SPIRITS ULTIMAGEAR 遊戯王 千年パズル 1/1スケール
もう一人の僕を呼び覚ませ!!
MOFT X 【新型 ミニマム版】 iPhone対応 スマホスタンド
Amazon一番人気のスマホスタンド!カード類も収納出来てかさ張らないのでオススメです!
サンディスク microSD 128GB
スマホからSwitchまで使える大容量MicroSDカード!
スポンサーリンク
目次
Listの順番を担保したMapを作りたい
ソートされたListからMapを作ると順序が変わる
Java8以降でstreamAPIを使い、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オブジェクトを生成することが可能になりました。
お困りの方は、こちらのやり方を参考にしてみてください♪







