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() ));
Hendy Irawan
  • 20,498
  • 11
  • 103
  • 114
  • @Zoe: doesn't work - `Unresolved reference: T` then `Unexpected type specification` – Hendy Irawan May 19 '18 at 19:48
  • 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.. – Hendy Irawan May 19 '18 at 19:50
  • 1
    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? – Alexey Romanov May 19 '18 at 19:53
  • 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. – Hendy Irawan May 20 '18 at 04:45
  • I kinda wonder if `public BookmarkablePageLink(final String id, final Class extends Page> pageClass)` works – EpicPandaForce May 21 '18 at 09:44
  • @EpicPandaForce 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... – Hendy Irawan May 21 '18 at 09:46
  • No, I'm only curious because the template argument doesn't seem necessary based on the snippet. `item.queue(BookmarkablePageLink("link", bookmark.page))` should work. – EpicPandaForce May 21 '18 at 09:51
  • There's T and there's C, two type arguments are needed. – Hendy Irawan May 21 '18 at 10:22
  • 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 mess – EpicPandaForce May 21 '18 at 11:30
  • Java is simpler, as it is less safe ;-). Kotlin deals with this at compile-time and java during runtime. Casting is the way to go – tieskedh May 21 '18 at 12:02
  • @EpicPandaForce 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. – Hendy Irawan May 21 '18 at 12:07

3 Answers3

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.

Alexey Romanov
  • 167,066
  • 35
  • 309
  • 487
  • 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" – Hendy Irawan May 22 '18 at 10:30
  • `BookmarkablePageLink(...)` gives me: Error: Not enough information to infer parameter **T** in `constructor Bookmarkable PageLink(...)` So, no, Kotlin's missing diamond operator is not equivalent to `<>` in Java. Maybe somewhat similar, but Kotlin's definitely stricter. – Hendy Irawan May 22 '18 at 10:37
  • That's why I said "should", not "is". – Alexey Romanov May 22 '18 at 10:42
  • Actually, that it fails to infer `T` rather than `C` is useful information; what is the type `item.queue` expects? – Alexey Romanov May 22 '18 at 10:43
  • 1
    Can you also try `val link: BookmarkablePageLink = BookmarkablePageLink("link", bookmark.page); item.queue(link)`? This would specify only `T = Any`, which seems to be what you want. – Alexey Romanov May 22 '18 at 10:50
  • If that works, then `item.queue(BookmarkablePageLink("link", bookmark.page) as BookmarkablePageLink)` also might work thanks to http://kotlinlang.org/docs/reference/whatsnew12.html#information-from-explicit-casts-is-used-for-type-inference. – Alexey Romanov May 22 '18 at 11:00
  • item.queue() is irrelevant actually .. you can replace it with someObjectList.add() or even let it go altogether, doesn't matter – Hendy Irawan May 22 '18 at 11:55
  • Yup, the `val link` works... But it's not a convenient workaround. – Hendy Irawan May 22 '18 at 11:59
  • no, `... as BookmarkablePageLink` 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". – Hendy Irawan May 22 '18 at 12:01
  • 1
    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): BookmarkablePageLink = BookmarkablePageLink(id, clazz)` (modify depending on what will vary in your code). – Alexey Romanov May 22 '18 at 12:29
  • Thanks, these are all decent workarounds. Although, this proves that at least in this case, Java's syntax is more compact than Kotlin's. – Hendy Irawan May 22 '18 at 12:50
  • https://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 https://discuss.kotlinlang.org/ could be useful. – Alexey Romanov May 22 '18 at 13:17
  • 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. – Ircover May 23 '18 at 14:00
  • "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). – Alexey Romanov May 26 '18 at 07:38
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. ;-)

Robert Jack Will
  • 10,333
  • 1
  • 21
  • 29
2

Please try

item.queue(BookmarkablePageLink<Any, WebPage>("link", bookmark.page)) 
EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428