kotlinのrefiedキーワード

6783 ワード

kotlinのこのキーワードを言う前にJavaの汎用型を簡単に話して、私達はプログラミングの中で、多重化と効率的な目的のため、よく汎用型を使います.汎用はJVMの下部でタイプ消去のメカニズムをとることによって実現され,Kotlinも同様である.
汎用型
汎用はJava SE 1.5ならではの特性であり、汎用の本質はパラメトリックタイプであり、汎用クラス、汎用インタフェース、汎用メソッドに分けられる.汎用型がない場合はObjectへの参照のみでパラメータの任意化が可能となり、明示的な強制型変換が必要となる欠点があるが、強制変換はコンパイル期間中はチェックを行わず、実行時に問題を残しやすいため、汎用型の利点はコンパイル時にタイプが安全であり、すべての強制変換が自動的かつ暗黙的であることである.コードの再利用率を向上させ、実行時にClassCastExceptionが発生しないようにします.
JDK 1.5には、コンパイル時に強いタイプがタイプチェックを行うことを可能にする汎用型が導入されている.JDK 1.7における汎用インスタンス化タイプは自動推定能力を備えており、例えばList mList = new ArrayList()List mList = new ArrayList<>()と書くことができる
タイプ消去
汎用はタイプ消去によって実現され、コンパイラはコンパイル時にすべての汎用タイプに関する情報を消去する.すなわち、実行時には汎用タイプに関する情報は一切存在しない.例えば、Listは実行時にリスト1つだけで表され、Java 1.5以前のバージョンと互換性を持つことを目的とする.
fun test() {
        val mList= ArrayList()
        mList.add("123")
        Log.v("tag",mList[0])
    }

バイトコードは次のとおりです.
public final test()V
   L0
    LINENUMBER 18 L0
    NEW java/util/ArrayList
    DUP
    INVOKESPECIAL java/util/ArrayList. ()V
    ASTORE 1
   L1
    LINENUMBER 19 L1
    ALOAD 1
    LDC "123"
    INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z
    POP
   L2
    LINENUMBER 20 L2
    LDC "tag"
    ALOAD 1
    ICONST_0
    INVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Object;
    CHECKCAST java/lang/String
    INVOKESTATIC android/util/Log.v (Ljava/lang/String;Ljava/lang/String;)I
    POP
   L3
    LINENUMBER 21 L3
    RETURN
   L4
    LOCALVARIABLE mList Ljava/util/ArrayList; L1 L4 1
    LOCALVARIABLE this Lcom/github/coroutinesdemo/Test; L0 L4 0
    MAXSTACK = 3
    MAXLOCALS = 2
INVOKEVIRTUAL java/util/ArrayList.add (Ljava/lang/Object;)Z list.add("123")は実際には"123"Objectとして集合に格納されているINVOKEVIRTUAL java/util/ArrayList.get (I)Ljava/lang/Objectは、listの例から読み出され、Objectに変換されてから使用される.String型変換
汎用消去バイトコードにコンパイルするときにまずタイプチェックを行い、タイプ消去を行います(つまり、クラス、変数、メソッドを含むすべてのタイプパラメータが限定タイプで置き換えられます.タイプ変数に制限がある場合、元のタイプは最初の境界のタイプで置き換えられます.例えばclass Test{}の元のタイプはComparableです).
タイプ消去とマルチステート性が競合すると、サブクラスでブリッジメソッド解決が生成され、汎用メソッドを呼び出す戻りタイプが消去されると、メソッドを呼び出すときに強制タイプ変換が挿入されます.
タイプ消去の問題
タイプ消去には一連の問題がありますが、ここでは展開しません.
  • 汎用読取時に自動タイプ変換の問題が発生するため、汎用メソッドを呼び出す戻りタイプが消去されると、このメソッドを呼び出す際に強制タイプ変換
  • が挿入される.
  • 汎用型パラメータは基本型ではなく、消去後のObjectは参照型であり基本型ではない
  • である.
  • 特定の汎用パラメータタイプの運転時タイプチェックができず、CHECKCAST java/lang/String
  • は汎用クラスのオブジェクトを投げ出すことも捕獲することもできない.異常は実行時に捕獲され、投げ出されるため、コンパイル時に汎用情報が消去され、消去後の2つのcatchは同じものになる.汎用情報は、コンパイル時に元のタイプ(例えば、catch(T)が限定子の場合に元のタイプThrowableとなる)に置き換えられているため、catch句では汎用変数を使用できません.catch句で使用できる場合、異常なキャプチャ優先順位
  • に違反します.
    fun Int.toCase():T?{
            return (this as T)
        }

    上記のコードは、タイプを変換する際にチェックされていないため、実行時にクラッシュする可能性があります.コンパイラは、取得したデータが所望のタイプでない場合、この関数がクラッシュすると警告するinstanceof ArrayList>をプロンプトします.
     fun testCase() {
            1.toCase()?.substring(0)
        }

    TypeCastExceptionエラーが発生するため、データを安全に取得するにはclass情報を明示的に渡す必要があります.
     fun  Int.toCase(clz:Class):T?{
            return if (clz.isInstance(this)){
                this as? T
            }else{
                null
            }
        }
      fun testCase() {
         1.toCase(String::class.java)?.substring(0)
        }

    しかし、これはclassを伝達する方法を表示することによって煩雑で煩雑であり、特に多種類のパラメータを伝達する必要があり、タイプ消去メカニズムに基づいて実行時にTのタイプ情報を得ることができないため、セキュリティ変換オペレータasまたはunchecked castに使用される.
        fun  Bundle.putCase(key: String, value: T, clz:Class){
            when(clz){
                Long::class.java -> putLong(key,value as Long)
                String::class.java -> putString(key, value as String)
                Char::class.java -> putChar(key, value as Char)
                Int::class.java -> putInt(key, value as Int)
                else -> throw IllegalStateException("Type not supported")
            }
        }

    では、このような伝達パラメータを排除した優雅な実現はありますか??
    refiedキーワード
    refiedキーワードの使用は簡単です.
  • 汎用型の前にas?修飾
  • を追加する.
  • メソッドの前にreifiedを追加し、上記のコード
        inline fun  Int.toCase():T?{
            return if (this is T) {
                this
            } else {
                null
            }
        }
    testCase()メソッド呼び出しをJavaコードに変換する方法を改善します.
     public final void testCase() {
          int $this$toCase$iv = 1;
          int $i$f$toCase = false;
          String var10000 = (String)(Integer.valueOf($this$toCase$iv) instanceof String ? Integer.valueOf($this$toCase$iv) : null);
          // inline   
          String var1;
          if (var10000 != null) {
               //     
             var1 = var10000;
             $this$toCase$iv = 0;
             if (var1 == null) {
                throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
             }
             var10000 = var1.substring($this$toCase$iv);
             Intrinsics.checkExpressionValueIsNotNull(var10000, "(this as java.lang.String).substring(startIndex)");
          } else {
             var10000 = null;
          }
                // reified    
          var1 = var10000;
          System.out.println(var1);
       }
    Inlineの役割はここではもう言わないでください.noinlineとcrossinlineは何ですか.ここで見てもいいです . 汎用型は実行時にタイプによって消去されますが、inline関数ではタイプが消去されないことを指定できます.inline関数はコンパイル期間中にバイトコードcopyを呼び出す方法に移行するので、コンパイラは現在の方法で汎用型に対応する具体的なタイプが何なのかを知ってから、汎用型を特定のタイプに置き換えて消去されない目的を達成します.inline関数では、reifiedキーワードを使用して、この汎用型をコンパイル時に特定のタイプ
  • に置き換えることができます.

    Gsonでjsonデータを解析するとき,汎型inline構造をどのように解析して得たのか.TypeTokenは,getType()法により我々が使用する汎用クラスの汎用パラメータタイプを得ることができるが,反射解析を用いる場合,Gsonがオブジェクトインスタンスを構築する際に呼び出されるのはデフォルトの無パラメトリック構造方法であるため,JavaのClassバイトコードに格納されている汎用パラメータ情報に依存し,Javaの汎用メカニズムはコンパイル中に消去されるが,しかしJavaはコンパイル時にバイトコード内の命令セット以外の場所に一部の汎用情報を保持し、インタフェース、クラス、メソッド定義上のすべての汎用、メンバー変数宣言での汎用はタイプ情報を保持し、他の場所の汎用情報は消去され、これらの情報はclassバイトコードの定数プールに保存され、汎用コードを使用するとsignature署名フィールドが生成され、署名signatureフィールドによってこの定数プールのアドレスが指定され、JDKはこれらの汎用情報を読み取る方法を提供し、反射を利用して汎用パラメータの具体的なタイプを得ることができます.例えば:
    (mList.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[0]

    一般Gson解析:
    inline fun  Gson.fromJson(jsonStr: String) = 
            fromJson(json, T::class.java)

    Moshi解析を使用する場合:
    inline fun  Moshi.fromJson(jsonStr: String) = Moshi.Builder().add(KotlinJsonAdapterFactory()).build().adapter(T::class.java).fromJson(jsonStr)