質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

Q&A

解決済

3回答

620閲覧

Mapインターフェースの引数の順番について

退会済みユーザー

退会済みユーザー

総合スコア0

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

0グッド

0クリップ

投稿2018/03/05 15:41

編集2018/03/05 16:36

生徒をグループ分けするプログラムのなかでMapインターフェースの引数を以下のように入れ替えてみたところ「引数の型が合わない」とエラーになりました。

Map<List<Student>, Integer> // エラー

これはなぜでしょうか?ドキュメントを調べてみるとMapインターフェースの引数はMap<K, V>と書いてありました。実際に利用する場合はMap<Integer, String>だったりMap<String, Integer>だったりと多様性があるはずと記憶していたのでエラーの原因がわかりません。

=は右辺から評価すると考えると右辺に相当するのはstudents.stream().collect(Collectors.groupingBy(Student::getScore));です。キーに生徒のList、値に点数を入れたMAPを作る目的はありませんが「なぜエラーになるのか」の仕組みを知りたいです。

アドバイスのほど、よろしくお願いいたします。

#追記
エラーコードはEclipse内で引数を入れ替えたときに赤い下線が表示されました。内容は以下です。

スレッド [main] (中断中 (型の不一致: Map<Integer,List<Student>> から Map<List<Student>,Integer> には変換できません)) Main.testStream() 行: 21 Main.main(String[]) 行: 11 ``` 追記終了 --- ```Java 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; } @Override public String toString() { return name + ":" + score; } } ``` ```Java public static void main(String[] args) { List<Student> students = new ArrayList<>(); students.add(new Student("Ken", 100)); students.add(new Student("Shin", 60)); students.add(new Student("Takuya", 80)); students.add(new Student("Sakamoto", 100)); Map<Integer, List<Student>> map = students.stream() .collect(Collectors.groupingBy(Student::getScore)); System.out.println(map); } ``` 出力結果 ```Java {80=[Takuya:80], 100=[Ken:100, Sakamoto:100], 60=[Shin:60]} ```

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

umyu

2018/03/05 15:52

表示されたエラーメッセージを質問文に追記してくださいな。
退会済みユーザー

退会済みユーザー

2018/03/05 15:56

Map<Object, List<Student>>  になって型が違うのきがふつふつする
guest

回答3

0

ベストアンサー

A,Bが互換性のない型のとき

List<A>, List<B>が異なる型であること
Map<A,B>, Map<B, A>が異なる型であること

は分かっておられて

students.stream().collect(Collectors.groupingBy(Student::getScore));

がなぜMap<List<Student>, Integer>にならないのかが疑問ということのように見えました。
もしそうだとすると...

上記の式は左から右へ型推論によって次のように「型引数の実際の型が決まっていく」ということでどうでしょうか?(決めるのはもちろんコンパイラーです)

students.stream()
==> Stream<Student>

Student::getScore
==> Function<Student, Integer>
型推論によってFunction<T, R>のT=Student, R=Integerと決まる

Collectors.groupingBy(Student::getScore)
==> Collector<Student, ?, Map<Integer, List<Student>>
型推論によって、Collector<T, ?, Map<K, List<T>のT=Student, K=Integerと決まる

~.collect(Collectors.groupingBy(Student::getScore))
==> Map<Integer, List<Student>>
型推論によって、Map<K, V>のK=Integer, V=List<T>=List<Student>と決まる

代入先の型はMap<List<Student>, Integer>だとcollectの結果の型Map<Integer, List<Student>>と違う型と見做される・・・


と書いてはみたもののasahina1979さんの回答にある点が疑問だったような気もして、質問意図を掴むのが難しかったです・・・
外してたらご容赦を。

投稿2018/03/05 17:20

KSwordOfHaste

総合スコア18392

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/03/05 21:39 編集

Map<String, Integer> map = new HashMap<>(); が 自動推察されてるから Map<List<Student>, Integer> map = students.stream().collect(Collectors.groupingBy(Student::getScore)); も自動推察されるんではないのかとJavaのソースコードを読まないひとに思えてきたよ
退会済みユーザー

退会済みユーザー

2018/03/05 23:21

ふと思ったけど FunctionのRは説明のためにKのほうがよくない? 類推の連鎖的に
KSwordOfHaste

2018/03/05 23:25

ダイヤモンド演算子も本件のような型推論も同様なのですが、型引数の「何番目がどの型か」が位置依存で前向き推論的に推論されてゆくという点のイメージが持ててないのではないかと思いました。
KSwordOfHaste

2018/03/05 23:28 編集

> Kの方が・・・ かも知れませんね。 自分の説明はある程度「気づいている」状況でないと「何言ってるかわからない」かも知れません。もっと丁寧に説明できるのですが、そもそも質問者さんの疑問点がどの辺りなのか確信がなかったのでしごくざっくりとしたコメントにしてみましたw;
退会済みユーザー

退会済みユーザー

2018/03/05 23:40

型推論の仕様がダイヤモンド演算子と見た目的に違いますからね(実際言いたいことを表すならこっちか(笑))
退会済みユーザー

退会済みユーザー

2018/03/06 02:48

>A,Bが互換性のない型のとき 途中省略 >がなぜMap<List<Student>, Integer>にならないのかが疑問ということのように見えました まさに、その通りです!質問の疑問点を伝えられなくて申し訳ありませんでした。みなさんの回答を見て、まだわからない点もあるのですが、1つお尋ねします。 もしかしてgroupingByメソッドの返り値は、groupingByメソッドで得た100, 60, 80, 100をキーとし、studentsリストを値としたMapを返すのでしょうか?だとすれば100がIntegerに相当し"(Ken", 100)がListに相当するので、返り値であるMap<Integer, List<student>>をMap<List<Student>, Integer>に代入しようとすると「型が違う」とエラーが出るのも納得できます。合っているでしょうか?
退会済みユーザー

退会済みユーザー

2018/03/06 04:54

Collectorインターフェース型推論について勉強する必要がありそうです。今回の疑問が自分なりに少しわかりました。
KSwordOfHaste

2018/03/06 05:20 編集

単純な例から知ると理解しやすいと思います。 <T> T intern(Map<T, T> map, T e) {  return map.computeIfAbsent(e, k -> e); } とかくと、このメソッドの第一引数の型としてMap<K,V>のKとVは同じ型(T)という制約が付きます。そういう制約の下で型がチェックされますので、 Map<String, String> map = ... String a = intern(map, "a"); はOKになり、 Map<Integer, String> map = ... String a = intern(map, "a"); はNGになってくれるといった具合です。 この例での型推論は「型引数K,Vに別のジェネリクスからの型引数Tの指定による制約が適用され、TがStringならK,Vも両方Stringでなければならない」というように三段論法的にK,Vが何かを決めてくれる」ものと考えるとよいのではないでしょうか。
退会済みユーザー

退会済みユーザー

2018/03/06 06:23

また少し理解が進みました。まだわからない点がありますが、本題とはずれてしまうので違う機会に質問したいと思います。ありがとうございました。
guest

0

ほぼ int は Integer じゃないから (オートボクシングは継承関係ではない)

Map<List<Student>, Integer> map = students.stream().collect(Collectors.groupingBy(Student::getScore));

がエラーになる理由は順序として Map<KEY,VALUE> が一致しないオブジェクトが出来上がるからですね。

つまり

Map<List<Student>, Integer> map = new HashMap<Integer,List<Student>>();

がエラーになる理由と同じ

追記 ( 3/6 06:50 )

少し分解してあげる・・

Function<Student, Integer> function = (Student s) -> { return s.getScore();}; Collector<Student, ?, Map<Integer, List<Student>>> collector = Collectors.groupingBy(function); Map<Integer, List<Student>> map = students.stream().collect(collector);

どういう形式の Map が返されるかは2行目の Collectorで定義されています。
ソースコードをみたり、eclipse でホバーしたらどういう風に解釈されるのかとかわかると思いますよ。
Collectors.groupingBy の中身はめんどいから自分でのぞいてください。

投稿2018/03/05 15:59

編集2018/03/05 21:49
退会済みユーザー

退会済みユーザー

総合スコア0

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/03/05 16:51

下のコードが右辺と左辺が一致しないのは理解できるのですが、上のコードの右辺をどれだけ眺めても左辺と一致しない理由がわかりません。 students.stream().collect(Collectors.groupingBy(Student::getScore) 右辺のこのコードがどういう動きでMap<KEY, VALUE>と評価されるのでしょうか? groupingByメソッドの引数であるStudent::getScoreはStudentクラスのgetScoreメソッドを呼び出して返り値が100, 60, 80, 100となり、それがmap変数に代入されると考えると、そもそもこれらはIntegerなのかList<Student>なのか?100, 60, 80, 100の要素がどういう風にMapインターフェースに入るのかという部分がわかっていません。 もしよければ、右辺がどういう動きをした結果、Mapインターフェースに参照されるのかを教えていただけないでしょうか?
退会済みユーザー

退会済みユーザー

2018/03/05 21:10

ダイヤモンド演算子と同じく自動で組まれると思ってらっしゃる?
退会済みユーザー

退会済みユーザー

2018/03/06 05:02

右辺のCollectorインターフェースの返り値がMapだということに気づけなかったので、そこが原因のようでした。「ダイヤモンド演算子と同じく自動で組まれる」という部分は今の私には理解ができていません。
退会済みユーザー

退会済みユーザー

2018/03/06 05:12

追記の「Collectorで定義される」というのは型推論で自動的に、そう定義されるということでしょうか?
退会済みユーザー

退会済みユーザー

2018/03/06 05:27

型推論に関しては KSwordOfHaste殿の回答で理解できるかと
guest

0

ドキュメントを調べてみるとMapインターフェースの引数はMap<K, V>と書いてありました。実際に利用する場合はMap<Integer, String>だったりMap<String, Integer>だったりと多様性があるはずと記憶していたのでエラーの原因がわかりません。

ひょっとしてジェネリクスのことを大いに勘違いしている?
Map<K, V>というのは、使うときや作るときにK,Vに具体的な型の名前を入れることで、
メソッドの対応する引数型や返り値型を制限する機能です。
putメソッドは引数がput(K, V)と指定されており、getメソッドの返り値はVとされていますが、
これがジェネリクスによってインスタンスごとに制限されます。

java

1Map<Integer, String> map = new HashMap<Integer, String>(); 2/* 3 * K=Integer, V=Stringなので、このインスタンスに対するputメソッドは 4 * put(Integer, String)として扱われる。 5 * getメソッドの返り値はV、つまりStringとして扱われる。 6 */ 7map.put(1, "A"); // OK 8//map.put("1", "A"); // NG Integerを要求する第1引数に"1"というStringは入れられない 9String s = map.get(1); // OK getメソッドの返り値はV、つまりStringなのでString型変数に入れられる 10// Integer i = map.get(1); // NG String型の返り値をInteger型の変数に入れることはできない 11 12Map<String, Integer> map2 = new HashMap<String, Integer>(); 13/* 14 * K=String, V=Integerなので、このインスタンスに対するputメソッドは 15 * put(String, Integer)として扱われる。 16 * getメソッドの返り値はV、つまりIntegerとして扱われる。 17 */ 18map.put("A", 1); // OK 19//map.put("A", "1"); // NG Integerを要求する第2引数に"1"というStringは入れられない 20Integer s = map.get("A"); // OK getメソッドの返り値はV、つまりIntegerなのでInteger型変数に入れられる 21// String i = map.get("A"); // NG Integer型の返り値をString型の変数に入れることはできない

同じMapという型でも、ジェネリクスが違うとこのように型に関して大きく異なる挙動をします。
これらが同じ型として扱われると非常に扱いづらいうえ、型安全という点でも危険です。
このため、ジェネリクスが違う型同士では代入できないのです。上記例でいえば、

java

1/* 2 * NG 左辺はMap<Integer, String>, 右辺はMap<String, Integer> 3 * 扱う型が異なるため代入できない 4 */ 5// map = map2

以下、Streamの型について言っていると思って最初に回答したもの

java

1Map<Integer, List<Student>> map = students.stream() 2 .collect(Collectors.groupingBy(Student::getScore)); 3

この部分は、もしStreamを使わずに書くとしたら、例としてこうなります。

java

1Map<Integer, List<Student>> map = new HashMap<>(); 2for(Student student : students) { 3 int score = student.getScore(); 4 List<Student> list = map.get(score); 5 if (list == null) { 6 list = new ArrayList<Student>(); 7 map.put(score, list); 8 } 9 list.add(student); 10}

このくだりをまとめてやっているのがcollectメソッドであり、forの中身をどうするか決めているのがCollectors.groupingByメソッドの返り値です。
イメージとしては、

  • students.stream() : for文を構成
  • collect() : まとめろ命令(詳細は引数にゆだねる)
  • Collectors.groupingBy() : for文の中身を構成(最初の1行除く)
  • Student::getScore : グループ分け基準決め int score = student.score()に相当

実際はStreamに適応したやり方でまとめを汎用的に行えるようになっているとは思いますが、
これを一息で書けるようにしたのがStream APIです。

これをやった結果として返ってくるのは、Integerをキー、List<Student>を値にしたMap<Integer, List<Student>>であることには変わりません。これをキー、値の型関係が逆の`Map<List<Student>, Integer>に代入しようとすればエラーになるのは当然です。

投稿2018/03/05 18:04

編集2018/03/05 18:28
swordone

総合スコア20649

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2018/03/06 05:06

collectメソッドのなかでfor文が動いていたとは思いませんでした。それで、いろんなサイトで「for文を禁止してStream APIに書き直す」と書かれているんですね。もっと勉強します。
swordone

2018/03/06 07:28

あくまでイメージですので、本当にcollectでforが動いているとはゆめゆめ覚えませぬようご注意ください。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問