Javaのジェネリクスについて質問です。

Writer: admin Type: regalmach Date: 2019-01-05 00:00
Javaのジェネリクスについて質問です。public final class Collectorsのpublic static <T,K> Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier)これのパラメータ型TとKはどのように決定されるかわかる方いらっしゃいますか?例えばpublic <T> T hoge(T hoge) だったらhogeに渡す型でTがパラメータ化されるし、public class Hoge<T> だったらnew Hoge<Moge>()のように明示しなければいけないと思います。上の例の場合、Stream<Moge>#Collector(Collector.grouingBy(arg->{}))のような形で利用することとなると思いますが、FunctionのT, Kの型がどのように判定されるのかがわからないです。以上、よろしくお願いいたします。補足Map<String, List<Moge> ret = ~のように指定したところ、KがString, TがMogeにパラメータ化されました。戻り値の型から推測しているということでしょうか。共感した0###Streamで多用されるラムダ式では冗長と感じる変数の型などの部分を削っている場合が多いので、型が何なのか分からなくなってしまう場合がありますが、APIリファレンスを追っかけていけば解決できます。地域をキーにそこに住んでいる顧客のリストを値とするMapを生成する (地域で顧客をグルーピングする) 以下のコードを例に説明してみます。 (Customer#getCityはStringを返すように宣言されているとします)import static java.util.stream.Collectors.groupingBy;Stream<Customer> customers;Map<String, List<Customer>> map;map = customers.collect(groupingBy(c -> c.getCity()));//map = customers.collect(groupingBy(Customer::getCity));(英語的にすんなり読めそうだからなのかコメントアウトしている方が一般的のようで、APIリファレンスのCollectorsにあるサンプルコードでも、メソッド参照の方が使われています。またEffective Javaでもメソッド参照の方が推奨されていました。)最初にAPIリファレンスのインタフェースStream<T>のcollectメソッドの宣言の引用です。https://docs.oracle.com/javase/jp/9/docs/api/java/util/stream/Strea...----------<R,A> R collect(Collector<? super T,A,R> collector)----------引数はCollectorオブジェクトです。CollectorはStream#collectの引数として利用する為に作られたインタフェースだと思うのですが、APIリファレンスの宣言と型引数の部分を引用します。(上のリンク先で引数のCollectorをクリックするとCollectorが何をしているのかの説明も含め、仕様が読めます)----------インタフェースCollector<T,A,R>型パラメータ:T - リダクション操作の入力要素の型A - リダクション操作の可変蓄積の型(通常は実装詳細として隠蔽される)R - リダクション操作の結果の型----------T -> 説明に入力要素の型とありますが、これはcollectメソッドが呼び出されたStreamオブジェクトの実型引数の型です。一番上のコードで言えばCustomer型です。(本当はcollectの宣言の引数で ? super T となっているので、Customerが継承しているすべてのクラス、又は実装しているインタフェースの型でも良いのですが、説明を簡単にする為にここではTをCustomer型で通します)A -> Collectorsのメソッドを使ってストリームを扱う上で、これが何なのかを理解していなくても、問題ありません。一番上のコードで言えばMapの値となるList<Customer>ですが、Collectorインタフェースを自前で実装するのでなければ、この型引数の実型引数を指定する場面はないと思います。R -> collectメソッドの戻り値の型で一番上のコードで言えばMap<String, List<Customer>>です。ここまでで以下が分かります。customers.collect(groupingBy(c -> c.getCity()));の groupingBy(c -> c.getCity()) の戻り値のCollector<? super T,A,R> オブジェクトのTがCustomer型で、Aは無視で、結果Rの型はまだ不明。次にCollectors#groupingByの宣言です。https://docs.oracle.com/javase/jp/9/docs/api/java/util/stream/Colle...----------public static <T,K> Collector<T,?,Map<K,List<T>>> groupingBy(Function<? super T,? extends K> classifier)----------引数が一つのgroupingByはストリームの要素を同じキーの値ごとにリストにまとめてマップに格納して返しますが、要素からキーの値を決定するFunctionオブジェクトを引数に取ります。このgroupingByの宣言で不明だったRの型が Map<K,List<T>> である事がわかります。TはCustomer型であることが判明しているので残りはK、つまりキーの型だけです。Kは引数のFunctionの2つ目の型引数と同じ型です。FunctionのAPIからの引用です。https://docs.oracle.com/javase/jp/9/docs/api/java/util/function/Fun...----------インタフェースFunction<T,R>型パラメータ:T - 関数の入力の型R - 関数の結果の型 @FunctionalInterfacepublic interface Function<T,R>1つの引数を受け取って結果を生成する関数を表します。これは、apply(Object)を関数メソッドに持つ関数型インタフェースです。----------applyメソッドの宣言はR apply (T t)関数型インタフェースはオーバーライドできるメソッドは一つだけです。コードにおいて関数型インタフェースのオブジェクト参照を記述する部分は、その関数インタフェースの唯一のオーバーライド可能メソッドの実装を記述したラムダ式やメソッド参照で置き換えることができます。例です。Function<Customer, String> f1 = Customer::getCity;Function<Customer, String> f2 = c -> c.getCity();customers.collect(groupingBy(f2));よって下のコードのcustomers.collect(groupingBy(c -> c.getCity()));ラムダ式c -> c.getCity()この部分はapplyメソッドのCustomer型引数をcとして、そのgetCity()の値を返す事を意味しているので、Rの型はStringである事が判明します。前述の通りこのRは Map<K,List<T>> のKと同じ型なのでcustomers.collect(groupingBy(c -> c.getCity()));の戻り値の型はMap<String, List<Customer>>となり、TがCustomer、KがStringであることが分かります。以上、ジェネリクスの境界をごまかし、ラムダ式とメソッド参照に関しては説明していませんが、もしそれらに関して検索しても分からない部分があれば、返信にてどうぞ。お疲れ様でした。ナイス0
###この質問は投票によってベストアンサーに選ばれました!

 

TAG