I'd like to better understand java/kotlin interop relative to inline value classes. I have some example code below with my attempts for interop, a few notes:
- You can directly import inline value classes in java, i.e.
import kotlin.Result - You cannot use their constructor methods in java, i.e.
Result.success - You cannot use their methods once constructed in java, i.e.
myResult.isSuccess
I created some workaround below. They work... but seem to have their own edge-cases and oddities. I have a few questions:
"boxing" seems to be necessary to preserve type information. Is a way to enforce boxing other than
?(which has the negative of triggering unnecessary null checking). I tried something likefun <T, R: Result<T>> success(value: T): R = Result.success(value as T) as R-- it compiled but threw ajava.lang.NoSuchMethodErrorerror at runtime (!!)is there some other way that this can be done? Are there any modules/libraries that help with kotlin/java inline value interop?
it seems to be a bug that the java <-> kotlin interop here causes the
isFailuremethods to loose the success/failure status when called from Java (without explicit?boxing) but I'd appreciate any thoughts!
Boxing as defined here, from which I can see that you can box by using generic and interfaces, but I don't see how I can use those to force a box AND perform logic and type checking.
Kotlin:
@file:JvmName("Inline")
package play
// Note: doesn't compile without `?`:
// incompatible types: no instance(s) of type variable(s) T exist so that
// Object conforms to Result<String>
@JvmName("success") fun <T> success(value: T): Result<T>?
= Result.success(value)
@JvmName("failure") fun <T> failure(t: Throwable): Result<T>?
= Result.failure(t)
// Note: java compiles without `?` BUT "failures" report isSuccess=true (!?!)
@JvmName("isSuccess") fun <T> isSuccess(r: Result<T>?): Boolean
= r!!.isSuccess
@JvmName("isFailure") fun <T> isFailure(r: Result<T>?): Boolean
= r!!.isFailure
java test code:
package play;
import static org.junit.Assert.assertTrue;
import kotlin.Result;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class InlineTest {
@Test
public void testResult() {
Result<String> rString = Inline.success("hi");
assertTrue(Inline.isSuccess(rString));
assertSuccess(rString);
Result<Integer> rInt = Inline.success(42);
assertTrue(Inline.isSuccess(rInt));
Result<Throwable> fail = Inline.failure(new Exception("test"));
// Note: fails unless isFailure using `?` type
assertTrue(Inline.isFailure(fail));
}
// just proves you can pass them to java functions
<T>void assertSuccess(Result<T> r) {
assertTrue(Inline.isSuccess(r));
}
}
Normally, methods of the companion object of a
value classcan be accessed from Java as usual. However,Result.successandResult.failureare marked@InlineOnly.This is expected. Properties and methods of inline classes are compiled to static methods taking a parameter of the wrapped type, to avoid boxing. They also have a mangled name, so you cannot access them from Java.
The
NoSuchMethodErroris a bug of either the Java or Kotlin compiler (I'm not sure). It should either not compile your code, or compile it without throwing aNoSuchMethodErrorat runtime. This is possibly related to KT-26131.What happens here is that the
successyou wrote gets compiled toinstead of
The Java code looks for a method that matches the latter, which doesn't exist.
isFailurealways returns false when using the unboxedResult<T>, becauseisFailuredetermines whether the result is a failure by checking if it is an instance of an internalFailuretype. Obviously, the instance ofResultyou passed in from Java is notFailure. If you use the boxedResult<T>?, then an extra unbox operation will be generated.Some Java pseudocode to illustrate the difference:
If you have control over the inline class, an alternative way to use it in Java is to have it implement an interface, like my answer demonstrates here.
Otherwise, I'd just write my own non-inline wrapper, that wraps the inline class.