Kotlin call java method with Class argument

前端 未结 4 692
忘了有多久
忘了有多久 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:06

    I think you need to actually use the RestTemplate.exechange method that has a signature that accepts a ParameterizedTypeReference<T> where T can be the generic type of your response (in your case a List<User>)

    Suppose I have an endpoint that returns a list of Jedi names in JSON format, as follows

    ["Obi-wan","Luke","Anakin"]
    

    And I would like to invoke this endpoint and get a List<String> as the result, with the list containing the Jedi names from the JSON.

    Then I can do this:

    val endpoint = URI.create("http://127.0.0.1:8888/jedi.json")
    val request = RequestEntity<Any>(HttpMethod.GET, endpoint)
    val respType = object: ParameterizedTypeReference<List<String>>(){}
    val response = restTemplate.exchange(request, respType)
    val items: List<String> = response.body;
    println(items) //prints [Obi-wan, Luke, Anakin]
    

    Notice that my ParameterizedTypeReference has a type argument of List<String>. That's what does the trick.

    And that worked for me when I tried it.

    0 讨论(0)
  • 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<T> 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<String, List<Int>> where you now lost all information that would make deserializing this possible.

    The following does not compile:

    val ref = List<String>::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<T> 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<SomeGenerics>() {}
    

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

    abstract class MySuperClass<T> 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<SomeGenerics>() {}.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 <reified T: Any> typeRef(): ParameterizedTypeReference<T> = object: ParameterizedTypeReference<T>(){}
    

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

    val response = restTemplate.exchange(request, typeRef<List<String>>())
    
    0 讨论(0)
  • 2021-02-04 02:13

    You may try

    return restTemplate.getForObject(URI(hostAddress), Array<User>::class.java).toList()
    
    0 讨论(0)
  • 2021-02-04 02:25

    First, you will make a new class - UserList.kt (the name is our owner)

    Inside:

    class UserList : MutableList<User> by ArrayList()
    

    After that, you can call your method that has a RestTemplate

    val response: List < Item > ? = restTemplate.getObject("yourEndpoint", UserList::class.java)
    

    It may be returns null, so you need to take care of that.

    0 讨论(0)
提交回复
热议问题