Jak działa słowo kluczowe reified w Kotlinie?

144

Próbuję zrozumieć cel reifiedsłowa kluczowego, najwyraźniej pozwala nam to na refleksję na temat leków generycznych .

Jednak kiedy to pominę, działa równie dobrze. Czy ktoś chciałby wyjaśnić, kiedy to rzeczywiście robi różnicę ?

hl3mukkel
źródło
Czy na pewno wiesz, czym jest odbicie? Czy słyszałeś o usuwaniu typów?
Mibac
3
Ogólne parametry typu są usuwane w czasie wykonywania, przeczytaj o usuwaniu typów, jeśli jeszcze tego nie zrobiłeś. Reifikowane parametry typu w funkcjach wbudowanych nie tylko wstawiają treść metody, ale także parametr typu ogólnego, umożliwiając wykonywanie takich czynności jak T :: class.java (czego nie można zrobić w przypadku normalnych typów ogólnych). Dodam jako komentarz, ponieważ nie mam teraz czasu na sformułowanie pełnej odpowiedzi ..
F. George,
Pozwala uzyskać dostęp do konkretnego typu generycznego funkcji bez polegania na refleksji i bez konieczności przekazywania typu jako argumentu.
BladeCoder

Odpowiedzi:

368

TL; DR: Co jest reifieddobre dla

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

W treści funkcji ogólnej, takiej jak myGenericFun, nie można uzyskać dostępu do typu, Tponieważ jest on dostępny tylko w czasie kompilacji, ale jest usuwany w czasie wykonywania. Dlatego jeśli chcesz użyć typu ogólnego jako normalnej klasy w treści funkcji, musisz jawnie przekazać klasę jako parametr, jak pokazano w myGenericFun.

Jeśli utworzysz inlinefunkcję z reified T , typ Tmożna uzyskać nawet w czasie wykonywania, a zatem nie musisz Class<T>dodatkowo przekazywać . Można pracować z Ttak jakby to było normalne klasy, na przykład może chcesz sprawdzić, czy zmienna jest instancją T , które można łatwo zrobić wtedy: myVar is T.

Taka inlinefunkcja z reifiedtypem Twygląda następująco:

inline fun <reified T> myGenericFun()

Jak reifieddziała

Możesz używać tylko reifiedw połączeniu z inlinefunkcją . Taka funkcja powoduje, że kompilator kopiuje kod bajtowy funkcji w każde miejsce, w którym funkcja jest używana (funkcja jest „wstawiana”). Kiedy wywołujesz funkcję wbudowaną z typem reified, kompilator zna rzeczywisty typ używany jako argument typu i modyfikuje wygenerowany kod bajtowy, aby bezpośrednio używał odpowiedniej klasy. Dlatego wywołania takie jak myVar is Tget myVar is String(jeśli argument typu był String) w kodzie bajtowym iw czasie wykonywania.


Przykład

Spójrzmy na przykład, który pokazuje, jak pomocne reifiedmoże być. Chcemy utworzyć funkcję rozszerzającą dla Stringwywoływanej, toKotlinObjectktóra próbuje przekonwertować ciąg JSON na zwykły obiekt Kotlin o typie określonym przez typ ogólny funkcji T. Możemy com.fasterxml.jackson.module.kotlindo tego użyć, a pierwsze podejście jest następujące:

a) Pierwsze podejście bez typu zreifikowanego

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

readValueMetoda zajmuje typ, który to ma do analizowania JsonObjectdo. Jeśli spróbujemy pobrać Classparametr typu T, kompilator narzeka: „Nie można użyć 'T' jako parametru typu reified. Zamiast tego użyj klasy.”

b) Obejście z jawnym Classparametrem

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

Aby obejść ten problem, Classof Tmożna ustawić jako parametr metody, który następnie zostanie użyty jako argument funkcji readValue. To działa i jest częstym wzorcem w ogólnym kodzie Java. Można to nazwać w następujący sposób:

data class MyJsonType(val name: String)

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

c) Droga Kotlina: reified

Użycie inlinefunkcji z reifiedparametrem typu Tumożliwia inną implementację funkcji:

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

Nie ma potrzeby brać dodatkowego Classudziału T, Tmożna z niej korzystać tak, jakby to była zwykła klasa. Dla klienta kod wygląda następująco:

json.toKotlinObject<MyJsonType>()

Ważna uwaga: praca z Javą

Funkcja wstawiana z reifiedtypem nie jest wywoływana z kodu Java .

s1m0nw1
źródło
6
Dziękuję za wyczerpującą odpowiedź! To naprawdę ma sens. Zastanawiam się tylko nad jedną rzeczą, dlaczego jest potrzebna reifikacja, jeśli funkcja i tak jest wstawiana? Czy mimo to pozostawiłoby to wymazanie typu i wbudowane w funkcję? Wydaje mi się, że to trochę marnotrawstwo, jeśli wstawisz funkcję, możesz również umieścić w niej używany typ, czy też widzę tutaj coś nie tak?
hl3mukkel
6
Dziękuję za twoją opinię, właściwie zapomniałem wspomnieć o czymś, co może dać ci odpowiedź: normalną funkcję inline można wywołać z Javy, ale taką z parametrem typu reified nie! Myślę, że jest to powód, dla którego nie każdy parametr typu funkcji wbudowanej jest automatycznie reifikowany.
s1m0nw1
A co, jeśli funkcja jest połączeniem parametrów zreifikowanych i niezreifikowanych? To sprawia, że ​​i tak nie można go wywoływać z języka Java, dlaczego nie ponownie określić wszystkich parametrów typu automatycznie? Dlaczego kotlin musi jawnie reifikować wszystkie parametry typu?
Vairavan,
1
co się stanie, jeśli wyższe wywołujące w stosie nie potrzebują json.toKotlinObject <MyJsonType> (), ale json.toKotlinObject <T> () dla różnych obiektów?
siódmy
1

PROSTY

* reified to udzielenie pozwolenia na użycie w czasie kompilacji (aby uzyskać dostęp do funkcji T inside de)

na przykład:

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

    val gson = Gson()

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

}

używając takich jak:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
  println(user.name)
poiu
źródło