Kotlin call java method with Class argument

前端 未结 4 690
忘了有多久
忘了有多久 2021-02-04 01:39

I want to use Spring RestTemplate in Kotlin like this:

//import org.springframework.web.client.RestTemplate
fun findAllUsers(): List {
          


        
4条回答
  •  孤城傲影
    2021-02-04 02:13

    @edwin's answer is correct, you had an XY Problem where you were actually asking for the wrong thing. Thankfully you had an error in doing so which led you here and @edwin was able to explain the alternative which does the correct behavior.

    Every binding library for Java has some pattern like this that is the same (Class vs. type reference). So this is good thing to learn and look for in your library such as RestTemplate, Jackson, GSON and others. The utility class maybe is ParameterizedTypeReference in one, and TypeReference in another, TypeRef in yet another and so on; but all doing the same thing.

    Now, for anyone wondering what was wrong with the original code, to create an generics erased class reference such as Class the syntax would be:

    val ref = List::class.java
    

    You will notice there is no way to express the generic type of the items in the list. And even if you could, they would be erased anyway due to type erasure. Passing this to the binding library would be like saying "a list of maybe nullable java.lang.Object please" but worse. Imagine Map> where you now lost all information that would make deserializing this possible.

    The following does not compile:

    val ref = List::class.java  // <--- error only classes are allowed on left side
    

    The error message could be clearer to say "only classes without generic parameters are allowed on the left side of ::"

    And your use of javaClass can only be used on an instance, so if you happen to have had a list handy...

    val ref = listOf("one", "two", "three").javaClass  
    

    Then you would end up with a type erased Class which again is the wrong thing to use here, but valid syntax.

    So what does the code @edwin is showing actually do?

    By creating an object with a super type ParameterizedTypeReference the code works around this problem because any class, even if type erased, can see the generics in its superclass and interfaces via the lense of Type. For example:

    val xyz = object : MySuperClass() {}
    

    This instance of an anonymous class has the superclass MySuperClass with generic parameters of SomeGenerics. So if MySuperClass contained this code:

    abstract class MySuperClass protected constructor() {
        val type: Type = (javaClass.genericSuperclass as ParameterizedType)
                                     .actualTypeArguments[0]
    }
    

    You could then add .type to the end of our declaration to access this functionality:

    val typeOfSomeGenerics = object : MySuperClass() {}.type
    

    And now you would have some implementation of Type describing our SomeGenerics class. It would be one of: Class, ParameterizedType, GenericArrayType, TypeVariable, and WildcardType

    And by understanding and working with these, a library that does data binding knows enough to do its job.

    Life is easier in Kotlin:

    In Kotlin, it is easy to write extension functions so that you never have to do this type of code more than once. Just create a helper inline function that uses reified generics to pass through the type and create the ParameterizedTypeReference:

    inline fun  typeRef(): ParameterizedTypeReference = object: ParameterizedTypeReference(){}
    

    And now you can change @edwin's example to:

    val response = restTemplate.exchange(request, typeRef>())
    

提交回复
热议问题