Come funziona la parola chiave reificata in Kotlin?


144

Sto cercando di capire lo scopo della reifiedparola chiave, apparentemente ci sta permettendo di fare una riflessione sui generici .

Tuttavia, quando lo lascio fuori funziona altrettanto bene. Qualcuno ha cura di spiegare quando questo fa la differenza ?


Sei sicuro di sapere cos'è la riflessione? Inoltre, hai sentito della cancellazione dei tipi?
Mibac,

3
I parametri di tipo generico vengono cancellati in fase di esecuzione, leggi sulla cancellazione di tipo se non l'hai già fatto. I parametri di tipo reificati sulle funzioni inline non solo incorporano il corpo del metodo, ma anche il parametro di tipo generico che ti consente di fare cose come T :: class.java (che non puoi fare con i normali tipi generici). Mettendo un commento perché non ho tempo di dare una risposta completa in questo momento ..
F. George,

Permette di accedere al tipo generico concreto di una funzione senza fare affidamento sulla riflessione e senza passare il tipo come argomento.
BladeCoder

Risposte:


368

TL; DR: Che cosa è reifiedbene per

fun <T> myGenericFun(c: Class<T>) 

Nel corpo di una funzione generica come myGenericFun, non è possibile accedere al tipo Tperché è disponibile solo in fase di compilazione ma cancellato in fase di esecuzione. Pertanto, se si desidera utilizzare il tipo generico come classe normale nel corpo della funzione, è necessario passare esplicitamente la classe come parametro, come mostrato in myGenericFun.

Se si crea una inlinefunzione con un reified T , tuttavia, è Tpossibile accedere al tipo di anche in fase di esecuzione e quindi non è necessario passarlo Class<T>ulteriormente. È possibile lavorare con Tcome se fosse una classe normale, ad esempio, si potrebbe voler controllare se una variabile è un esempio di T che si può facilmente fare poi: myVar is T.

Tale inlinefunzione con reifiedtipo ha Til seguente aspetto:

inline fun <reified T> myGenericFun()

Come reifiedfunziona

È possibile utilizzare solo reifiedin combinazione con una inlinefunzione . Tale funzione fa sì che il compilatore copi il bytecode della funzione in ogni luogo in cui la funzione viene utilizzata (la funzione viene "incorporata"). Quando si chiama una funzione inline con tipo reificato, il compilatore conosce il tipo effettivo utilizzato come argomento di tipo e modifica il bytecode generato per utilizzare direttamente la classe corrispondente. Pertanto chiamate come myVar is Tdiventano myVar is String(se l'argomento tipo fosse String) nel bytecode e in fase di esecuzione.


Esempio

Diamo un'occhiata a un esempio che mostra quanto reifiedpossa essere utile . Vogliamo creare una funzione di estensione per la Stringchiamata toKotlinObjectche tenti di convertire una stringa JSON in un semplice oggetto Kotlin con un tipo specificato dal tipo generico della funzione T. Possiamo usare com.fasterxml.jackson.module.kotlinper questo e il primo approccio è il seguente:

a) Primo approccio senza tipo reificato

fun <T> String.toKotlinObject(): T {
      val mapper = jacksonObjectMapper()
                                                        //does not compile!
      return mapper.readValue(this, T::class.java)
}

Il readValuemetodo prende un tipo che dovrebbe analizzare il JsonObject. Se proviamo a ottenere il Classparametro del tipo T, il compilatore si lamenta: "Impossibile utilizzare 'T' come parametro del tipo reificato. Usa invece una classe."

b) Soluzione alternativa con Classparametro esplicito

fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, c.java)
}

Per ovviare al problema, Classof Tpuò essere impostato come parametro del metodo, che viene quindi utilizzato come argomento per readValue. Funziona ed è un modello comune nel codice Java generico. Può essere chiamato come segue:

data class MyJsonType(val name: String)

val json = """{"name":"example"}"""
json.toKotlinObject(MyJsonType::class)

c) La via di Kotlin: reified

L'uso di una inlinefunzione con reifiedparametro type consente Tdi implementare la funzione in modo diverso:

inline fun <reified T: Any> String.toKotlinObject(): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, T::class.java)
}

Non c'è bisogno di prendere il Classdi in Tpiù, Tpuò essere usato come se fosse una classe normale. Per il client il codice è simile al seguente:

json.toKotlinObject<MyJsonType>()

Nota importante: lavorare con Java

Una funzione incorporata con reifiedtipo non è richiamabile dal codice Java .


6
Grazie per la tua risposta esaustiva! Questo ha davvero senso. Solo una cosa che mi chiedo, perché è necessario reified se la funzione è inline comunque? Vorrebbe lasciare il tipo di cancellazione e incorporare comunque la funzione? Questo mi sembra uno spreco, se inline la funzione potresti anche inline il tipo in uso o sto vedendo qualcosa di sbagliato qui?
hl3mukkel,

6
Grazie per il tuo feedback, in realtà dimentico di menzionare qualcosa che potrebbe darti la risposta: una normale funzione inline può essere chiamata da Java ma una con un parametro di tipo reificato non può! Penso che questo sia un motivo per cui non tutti i parametri di tipo di una funzione inline vengono automaticamente reificati.
s1m0nw1

Cosa succede se la funzione è un mix di parametri reificati e non reificati? Ciò rende comunque non idoneo il fatto di essere chiamato da Java, perché non reimpostare automaticamente tutti i parametri di tipo? Perché è necessario che kotlin sia stato reificato specificatamente per tutti i parametri di tipo?
Vairavan,

1
cosa succede se i chiamanti superiori nello stack non devono necessariamente json.toKotlinObject <MyJsonType> (), ma json.toKotlinObject <T> () per oggetti diversi?
sette

1

SEMPLICE

* reificato è dare il permesso di usare al momento della compilazione (per accedere a T all'interno della funzione)

per esempio:

 inline fun <reified T:Any>  String.convertToObject(): T{

    val gson = Gson()

    return gson.fromJson(this,T::class.java)

}

usando come:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
  println(user.name)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.