How to pass bounded wildcard type argument in Kotlin?
Asked Answered
E

3

12

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() ));
Endora answered 19/5, 2018 at 12:59 Comment(12)
<Any, T : WebPage>Savdeep
@Zoe: doesn't work - Unresolved reference: T then Unexpected type specificationEndora
Hmm... why would someone downvote a question that has no valid answer yet and is not trivially found even after trying to read the official docs? Strange, but that's life..Endora
Your code doesn't seem to match: Java call has 2 type parameters and 3 parameters, declaration has 1 and 2. And what's getParams type?Justus
Thanks @AlexeyRomanov, I've removed the params parameter as it is not relevant to this problem. However regarding the 1 vs 2 type parameter, that is because the class has 1 type parameter and the constructor has 1 type parameter, totalling 2 type parameters, so they actually match.Endora
I kinda wonder if public BookmarkablePageLink(final String id, final Class<? extends Page> pageClass) worksGlochidium
@Glochidium that's an API I cannot change. Besides, it would defeat the purpose if I have to manipulate Java code in order to call it from Kotlin...Endora
No, I'm only curious because the template argument doesn't seem necessary based on the snippet. item.queue<WebPage>(BookmarkablePageLink("link", bookmark.page)) should work.Glochidium
There's T and there's C, two type arguments are needed.Endora
Oh... Now I see it, your constructor has a template argument. In that case I really don't know why it's not just a wildcard like I initially said. Ah well. At least now I know that despite being Wicket 7, its api is a messGlochidium
Java is simpler, as it is less safe ;-). Kotlin deals with this at compile-time and java during runtime. Casting is the way to goShayna
@Glochidium I wholeheartedly disagree that Wicket's API is a mess. Having used Wicket for several years, Wicket did a lot of API design right. Use of type parameters in Wicket makes the API very safe, although at rare times the type system gets in the way. Which IMO not the API's fault, but how language treats the type parameters. And we still have the option to "skip" these checks if we're sure what we're doing anyway.Endora
T
3

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. ;-)

Tyrant answered 27/5, 2018 at 18:14 Comment(0)
J
6

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.

Justus answered 22/5, 2018 at 10:9 Comment(14)
You're right that "the correct way in Java-speak" is wrong, because it is not Java. I've edited the question to clarify it. The Java way to write it is as I put in the question "Surprisingly in Java this was simply"Endora
BookmarkablePageLink(...) gives me: Error: Not enough information to infer parameter T in constructor Bookmarkable PageLink<T : Any!, C : Page!>(...) So, no, Kotlin's missing diamond operator is not equivalent to <> in Java. Maybe somewhat similar, but Kotlin's definitely stricter.Endora
That's why I said "should", not "is".Justus
Actually, that it fails to infer T rather than C is useful information; what is the type item.queue expects?Justus
Can you also try val link: BookmarkablePageLink<Any> = BookmarkablePageLink("link", bookmark.page); item.queue(link)? This would specify only T = Any, which seems to be what you want.Justus
If that works, then item.queue(BookmarkablePageLink("link", bookmark.page) as BookmarkablePageLink<Any>) also might work thanks to kotlinlang.org/docs/reference/….Justus
item.queue() is irrelevant actually .. you can replace it with someObjectList.add() or even let it go altogether, doesn't matterEndora
Yup, the val link works... But it's not a convenient workaround.Endora
no, ... as BookmarkablePageLink<Any> doesn't work, Kotlin still cannot infer T. Probably because it needs to get what the actual type is first before it comes to the "as".Endora
If you need it once, val link seems to be OK to me (though maybe someone will still come up with a better option); if more often, you could define fun linkForClass(id: String, clazz: Class<out Page>): BookmarkablePageLink<Any> = BookmarkablePageLink(id, clazz) (modify depending on what will vary in your code).Justus
Thanks, these are all decent workarounds. Although, this proves that at least in this case, Java's syntax is more compact than Kotlin's.Endora
youtrack.jetbrains.com/issue/KT-17061 would remove need for workarounds, but I have no idea if (or when) it'll be implemented. Also raising this issue at discuss.kotlinlang.org could be useful.Justus
I would agree with comment "Java's syntax is more compact than Kotlin's" if you add "at the cost of type unsafe". Kotlin made a programmer to choose type parameter, while Java somehow makes decision what programmer wants. It proves with val link example work - just take type parameter for class. And I absolutely agree that type parameter for constructor is very strange.Colbert
"what the actual type is first before it comes to the "as"" Kotlin can sometimes use the type in as for inference, I linked the change note about it. This case apparently isn't covered, but it might be worth to report and see if it gets fixed (certainly likely to be faster than KT-17061).Justus
T
3

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. ;-)

Tyrant answered 27/5, 2018 at 18:14 Comment(0)
G
2

Please try

item.queue(BookmarkablePageLink<Any, WebPage>("link", bookmark.page)) 
Glochidium answered 21/5, 2018 at 9:53 Comment(3)
item.queue() has no type parameters. This definitely won't work.Endora
That's one the things I tried. It's not working because given WebPage type argument doesn't match the actual: ? extends WebPageEndora
That's weird. I guess it really is item.queue(BookmarkablePageLink<Any, WebPage>("link", bookmark.page as Class<WebPage>)) then. :(Glochidium

© 2022 - 2024 — McMap. All rights reserved.