Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pass bounded wildcard type argument in Kotlin?

The class used (in Java, third party API, not changeable):

public class BookmarkablePageLink<T> extends Link<T> {

    public <C extends Page> BookmarkablePageLink(final String id, final Class<C> pageClass)

And now I want to call this from Kotlin:

item.queue(BookmarkablePageLink("link", bookmark.page))

bookmark.page is in Java, and it is: public Class<? extends WebPage> getPage()

None of these work:

item.queue(BookmarkablePageLink("link", bookmark.page))

Error: Not enough information to infer parameter T in constructor Bookmarkable PageLink<T : Any!, C : Page!>(...)

item.queue(BookmarkablePageLink<>("link", bookmark.page))

item.queue(BookmarkablePageLink<Any>("link", bookmark.page))

item.queue(BookmarkablePageLink<Any, *>("link", bookmark.page))

item.queue(BookmarkablePageLink<Any, WebPage>("link", bookmark.page))

item.queue(BookmarkablePageLink<Any, in WebPage>("link", bookmark.page))

item.queue(BookmarkablePageLink<Any, out WebPage>("link", bookmark.page))

item.queue(BookmarkablePageLink<Any, T : WebPage>("link", bookmark.page))

This would be the "hypothetically correct" way to do this in Javaish-speak (just the intention, but it's not real code), but this isn't supported by Kotlin:

item.queue(BookmarkablePageLink<Any, ? extends WebPage>("link", bookmark.page))

My best workaround is this, which is ugly, but works:

item.queue(BookmarkablePageLink<Any, WebPage>("link", bookmark.page as Class<WebPage>))

Surprisingly in Java this was simply:

item.queue(new BookmarkablePageLink<>("link", bookmark.getPage() ));
like image 884
Hendy Irawan Avatar asked May 19 '18 12:05

Hendy Irawan


People also ask

What happens when we use a bounded wildcard?

Use an upper-bounded wild card only when values are to be retrieved and processed and the data structure (ArrayList) won't be changed. This means that the corresponding argument of a call on m2 can be an ArrayList whose elements are of any class that is Integer or a superclass of Integer, including Number and Object.

Which wildcard is known as unbounded wildcard?

The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach: If you are writing a method that can be implemented using functionality provided in the Object class.

How do you use Varargs in Kotlin?

Variable number of arguments (varargs) Only one parameter can be marked as vararg . If a vararg parameter is not the last one in the list, values for the subsequent parameters can be passed using named argument syntax, or, if the parameter has a function type, by passing a lambda outside the parentheses.

What is the difference between upper bounded wildcards and bounded type parameters?

What is the difference between a wildcard bound and a type parameter bound? A wildcard can have only one bound, while a type parameter can have several bounds. A wildcard can have a lower or an upper bound, while there is no such thing as a lower bound for a type parameter.


3 Answers

So far as I understand, BookmarkablePageLink(...) should be approximately equivalent to new BookmarkablePageLink<> in Java, so this is the option which "should" work. All others you tried shouldn't, each for different reasons.

Constructors which have their own type parameters are very rare (before seeing this question I thought they were illegal), so they may be overlooked somewhere in Kotlin compiler. A possible workaround is to make it a function instead:

fun <T, C : Page> makeBookmarkablePageLink(id: String, clazz: Class<C>): BookmarkablePageLink<T> = 
    BookmarkablePageLink<T, C>(id, clazz)

and then

item.queue(makeBookmarkablePageLink("link", bookmark.page))

I'll also note that I'm pretty sure

the "correct" way to do this in Java-speak

is actually wrong; and in fact you can't write down the type parameters in Java explicitly, because the second type parameter is a captured wildcard.

like image 199
Alexey Romanov Avatar answered Nov 15 '22 16:11

Alexey Romanov


I am creating a answer from all the best comments because those already seem very valuable.

Workaround from the question is already a good start:

BookmarkablePageLink<Any, WebPage>("link", bookmark.page as Class<WebPage>)

Also fair is @AlexeyRomanov's intermediate variable (or a similar intermediate function):

val link: BookmarkablePageLink<Any> = BookmarkablePageLink("link", bookmark.page)

Also valuable for all who find this question via Google might be a short summary of Kotlin vs Java handling of type-variance as explained in Kotlin's documentation:

  • in Java the handling is at call-site using wildcards (which you can't use, because call-site is in Kotlin)
  • and in Kotlin the handling is at declaration site using in and out keywords (which you can't use, because your declaration is in Java)

Additionally, Java constructors at call-site only allow to specify type arguments from the class, while in Kotlin the constructor call has two type arguments: one from the class and the other from the constructor. So in Java, we have to say

new BookmarkablePageLink<T>("something", Page.class)

and in Kotlin

BookmarkablePageLink<T, Page>("something", Page::class.java)

despite both calling the same constructor with the same arguments.

Given that Kotlin chose an approach for variant types which is the exact opposite of Java's, I am still happy, that we only need workarounds in so few cases. ;-)

like image 28
Robert Jack Will Avatar answered Nov 15 '22 14:11

Robert Jack Will


Please try

item.queue(BookmarkablePageLink<Any, WebPage>("link", bookmark.page)) 
like image 42
EpicPandaForce Avatar answered Nov 15 '22 16:11

EpicPandaForce