Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Kotlin call java method with Class<T> argument

Tags:

I want to use Spring RestTemplate in Kotlin like this:

//import org.springframework.web.client.RestTemplate fun findAllUsers(): List<User> {         val restTemplate = RestTemplate()          //has error         val ret = List<User>.javaClass         return restTemplate.getForObject(URI(hostAddress), ret) } 

The RestTemplate.getForObject(URI url, Class<T> responseType) has this signature and I get this error "unresolved reference List" from List in val ret = List<User>.javaClass.

If I use like this val ret = List<User>::class.java I get this error "Only classes are allowed on the left hand side of a class literal"

what's the problem? and what's the proper way for doing this? Is it a bug?

like image 630
mojtab23 Avatar asked Sep 24 '16 17:09

mojtab23


2 Answers

@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>>()) 
like image 113
3 revs Avatar answered Oct 09 '22 08:10

3 revs


You may try

return restTemplate.getForObject(URI(hostAddress), Array<User>::class.java).toList() 
like image 27
Juan Rada Avatar answered Oct 09 '22 08:10

Juan Rada