訂正: 型引数に指定した具象型を得るための条件(A)が不適切だったので訂正しました。また若干の誤記を訂正しました。
KotlinにせよJavaにせよメソッドの引数に指定できるのは「値」です。しかし「型名」は文法上特別な意味を持つ構文要素でしてそれは「値」にはなれません。型が指定できる特別な場所にしか書けないのです。
val a: Int
とかclass MyClass : MutableSet<Long> ...
とはかけますが、
val a = Int
とかsomeFunction(Int)
とは書けません。Int
は型名であって「値」ではないからです。
そこで型に関する情報を「値としてやりとりする」ために登場するのがjava.lang.Class, kotlin.reflect.KClass, java.lang.reflect.Typeなどの型のメタ情報を保持するクラス(メタクラス)です。
Intに関する型情報を渡すなら
Int.javaClass
=> Class<Int>のインスタンス
Int::class
=> KClass<Int>のインスタンス
などと記述できます。しかしJVMのtype erasureの仕様のためgenericsの場合単純にMutableSet<Long>.javaClass
のようには書けません。また
kotlin
1val o = mutableSetOf(1L, 2L)
2val clazz = o.javaClass
としても、clazzがMutableSet
を継承する具象クラスであることはわかるのですが、その具象クラスの型引数に何の型が指定されていたかまではわからない仕様になっています。
そこで実際にどのような型引数が用いられているか実行時にわかるようなメタクラスとしてTypeを用います。ただし、型引数の実際の型がわかるためには条件があり
A) 「型引数に具象型を指定したGenericsを継承したクラスを定義し、そのクラス情報からsuperclassとして参照したときのTypeからしか型引数がわからない」(訂正:元の説明には余計な条件を含んでいましたので記述を改めました)
ということになっています。言葉だとわかりにくいですね。
Kotlin
1class A : MutableSet<Long>
2...
3val typeOfSuperA = MutableSetOfLong::class.supertypes[0]
4println(typeOfSuperA) // ==> kotlin.collections.HashSet<kotlin.Long> /* = java.util.HashSet<kotlin.Long> */
gsonで用意されているTypeTokenはそのままではAの制約のためtypeを参照しても期待通りの内容になってません。正しくはTypeTokenの派生クラスを定義しそのtypeをを参照しなくてはならないのです。そういう事情があるためTypeTokenは直接インスタンスを生成できないよう「コンストラクターがprotectedになっている」のです。
Can not access '<init>' it is public/package/ in TypeToken
このエラーが出るのはそういう理由です。
結局どう書けばよいかといえば、TypeTokenの派生クラスを定義すればよいのですが、そうしたものに一々名前を付けるのは煩雑なので、無名クラスのインスタンス化の構文を使うのが一番簡潔です。
val type: Type = object: TypeToken<MutableSet<Long>>() {}.type
ということで
val longSet:MutableSet<Long> = Gson().fromJson(setString, object: TypeToken<MutableSet<Long>>() {}.type)
と記述するとお望みの動作になります。しかし・・・せっかく型推論が導入されているKotlinで同じ型MutableSet<Long>
を代入文の左辺にも右辺にも書かなければならないのは業腹です。
それについてはstackoverflowにraified type parameterを使ったうまい方法がありました。
https://stackoverflow.com/questions/33381384/how-to-use-typetoken-generics-with-gson-in-kotlin
Kotlin
1// まずこういう関数を定義しておく
2inline fun <reified T> Gson.fromJson(json: String) =
3 this.fromJson<T>(json, object: TypeToken<T>() {}.type)
4
5// 使い方
6fun foo() {
7 val setString = "[1, 2]"
8
9 val longSet1 = Gson().fromJson<MutableSet<Long>>(setString)
10 // or
11 val longSet2: MutableSet<Long> = Gson().fromJson(setString)
12}
これなら概ね満足できると思います。
ただ一点「自前でこの関数を定義しなくてはならない」という不満だけが残ります。自分はJackson(のkotlin対応ライブラリーのjackson-module-kotlin)にはこれににたextensionが用意されているのを見つけました。しかしGsonやそのKotlin用ラッパーの中にこうした便利な定義があるかどうかまでは残念ながらわかりません。
その他:
mutableSetOfでどんなインスタンスを作っているのか
こうすればわかります。
println(mutableSetOf(1L)::class)
=> java.util.LinkedHashSet
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/11/16 06:25
2018/11/16 14:25
2018/11/17 02:47