Is it possible to access extension functions from Java code?
I defined the extension function in a Kotlin file.
package com.test.extensions
import com.test.model.MyModel
/**
*
*/
public fun MyModel.bar(): Int {
return this.name.length()
}
Where MyModel is a (generated) java class. Now, I wanted to access it in my normal java code:
MyModel model = new MyModel();
model.bar();
However, that doesn't work. The IDE won't recognize the bar() method and compilation fails.
What does work is using with a static function from kotlin:
public fun bar(): Int {
return 2*2
}
by using import com.test.extensions.ExtensionsPackage so my IDE seems to be configured correctly.
I searched through the whole Java-interop file from the kotlin docs and also googled a lot, but I couldn't find it.
What am I doing wrong? Is this even possible?
Perhaps like this:
// CallExtensionFunction.java
package com.example.groundup;
public class CallExtensionFunction {
public static void main(String[] args) {
MyModel myModel = new MyModel();
int bar = MyModelKt.bar(myModel);
System.out.println(bar);
}
}
// MyModell.kt
package com.example.groundup
fun MyModel.bar(): Int {
return this.name.length
}
class MyModel() {
val name = "Hugo"
}
The extension function is provided in the corresponding singleton with the suffix "Kt" as a static method.
Writing a JUnit 5 parameterized test and need to pass functions to the test using Arguments.of(), but there are 2 compile errors that I don't know how to fix. Any help would be appreciated.
The method of(Object...) in the type Arguments is not applicable for the arguments (boolean, String::length)
The target type of this expression must be a functional interface
public static Stream<Arguments> some() {
return Stream.of(Arguments.of(true, String::length));
}
#ParameterizedTest
#MethodSource
public <T> void some(final T input, final Function<String, Integer> length) {
}
The following works as expected.
public void sample() {
some(true, String::length);
}
Wrap the arguments in a helper method
Similar to the answer "wrap it in a class", but possibly less intrusive, is to use a helper method to pass the functional interface as a java.lang.Object.
For example, the first raw method reference, Math::ciel, in this parameterized test:
#ParameterizedTest
#MethodSource("testCases")
void shouldExerciseMethod(Function<Double, Double> method, Double expected) {
assertEquals(expected, method.apply(1.5d), 1.0E-8d);
}
static Stream<Arguments> testCases() {
return Stream.of(Arguments.of(Math::ceil, 2.0d),
Arguments.of(Math::floor, 1.0d));
}
causes this compilation error:
java: method of in interface org.junit.jupiter.params.provider.Arguments cannot be applied to given types;
required: java.lang.Object[]
found: Math::ceil,double
reason: varargs mismatch; java.lang.Object is not a functional interface
which you can get around by passing the arguments through a helper method:
static <T, U> Arguments args(Function<T, U> method, U expected) {
return Arguments.of(method, expected);
}
so:
static Stream<Arguments> testCases() {
return Stream.of(args(Math::ceil, 2.0d),
args(Math::floor, 1.0d));
}
My attempts to make the idiom more general using varargs failed with variations on the same error, so I have ended up overloading it whenever I need another signature.
The function needs to be wrapped in a class.
public static class P {
private final Function<String, Integer> mFunction;
public P(final Function<String, Integer> function) {
mFunction = function;
}
public Function<String, Integer> function() {
return mFunction;
}
}
public static Stream<Arguments> some() {
return Stream.of(Arguments.of(3, "abc", new P(String::length)));
}
#ParameterizedTest
#MethodSource
public <T> void some(final int expect, final String input, final P p) {
assertEquals(expect, p.function().apply(input));
}
I liked #adrian-redgers solution, but I think overloading a method for each signature needed is a bit overkill.
You only really need to convert the functional interface to an object. So the solution I implemented was:
/**
* Helps to use {#link org.junit.jupiter.params.provider.Arguments#of(Object...)}, as functional
* interfaces cannot be converted into an object directly.
*/
public class ArgumentsWrapper {
private ArgumentsWrapper() {
throw new IllegalStateException(
ArgumentsWrapper.class + " util class cannot be instantiated");
}
public static <T, U> Function<T, U> wrap(Function<T, U> function) {
return function;
}
}
Then, it can be used as:
public static Stream<Arguments> testMapAlarmTypeConfigWithLanguage() {
return Stream.of(
// Statically imported ArgumentsWrapper#wrap
Arguments.of(null, wrap(AlarmTypeConfig::getNameInEnglish)),
Arguments.of("en-us", wrap(AlarmTypeConfig::getNameInEnglish)),
Arguments.of("es-es", wrap(AlarmTypeConfig::getNameInSpanish)));
}
Is it possible to extend public abstract class UiObject2Condition?
Doing in this way
class NoChildCondition: UiObject2Condition<Boolean>() {
override fun apply(obj: UiObject2?): Boolean {
return obj?.childCount == 0
}
}
causes an error: public open fun apply(obj: UiObject2?): Boolean defined in circlecomplete.ciom.uitests.NoChildCondition' has no access to 'public/*package*/ abstract fun apply(p0: UiObject2!): Boolean! defined in androidx.test.uiautomator.UiObject2Condition', so it cannot override it.
Note that UiObject2Condition is an empty subclass of androidx.test.uiautomator.Condition<UiObject2, R> and Condition is not public.
Implementing apply method causes a compilation error. Not implementing it compiles but causes a runtime error.
UIAutomator version: 2.2.0
UiObject2Condition decompilation:
package androidx.test.uiautomator;
public abstract class UiObject2Condition<R> extends Condition<UiObject2, R> {
public UiObject2Condition() {
}
}
Condition decompilation:
package androidx.test.uiautomator;
abstract class Condition<T, R> {
Condition() {
}
abstract R apply(T var1);
}
Creating androidx.test.uiautomator package and adding class into it works fine.
package androidx.test.uiautomator
class NoChildCondition: UiObject2Condition<Boolean>() {
override fun apply(obj: UiObject2?): Boolean {
return obj?.childCount == 0
}
}
When implementing a twitter4j.StatusListner in Kotlin, I get the following IllegalAccessError and associated stack trace:
Exception in thread "main" java.lang.IllegalAccessError: tried to access class twitter4j.StreamListener from class rxkotlin.rxextensions.TwitterExampleKt$observe$1
at rxkotlin.rxextensions.TwitterExampleKt$observe$1.subscribe(TwitterExample.kt:50)
at io.reactivex.internal.operators.observable.ObservableCreate.subscribeActual(ObservableCreate.java:40)
at io.reactivex.Observable.subscribe(Observable.java:10700)
at io.reactivex.Observable.subscribe(Observable.java:10686)
at io.reactivex.Observable.subscribe(Observable.java:10615)
at rxkotlin.rxextensions.TwitterExampleKt.main(TwitterExample.kt:8)
Produced by the following code:
val twitterStream = TwitterStreamFactory().instance
// See https://stackoverflow.com/questions/37672023/how-to-create-an-instance-of-anonymous-interface-in-kotlin/37672334
twitterStream.addListener(object : StatusListener {
override fun onStatus(status: Status?) {
if (emitter.isDisposed) {
twitterStream.shutdown()
} else {
emitter.onNext(status)
}
}
override fun onException(e: Exception?) {
if (emitter.isDisposed) {
twitterStream.shutdown()
} else {
emitter.onError(e)
}
}
// Other overrides.
})
emitter.setCancellable { twitterStream::shutdown }
If I don't use Rx, it makes the exception a bit simpler:
twitterStream.addListener(object: twitter4j.StatusListener {
override fun onStatus(status: Status) { println("Status: {$status}") }
override fun onException(ex: Exception) { println("Error callback: $ex") }
// Other overrides.
})
Exception in thread "main" java.lang.IllegalAccessError: tried to access class twitter4j.StreamListener from class rxkotlin.rxextensions.TwitterExampleKt
at rxkotlin.rxextensions.TwitterExampleKt.main(TwitterExample.kt:14)
However, if I implement a Java wrapper function, no error is thrown and the behaviour is as expected:
Wrapper -
public class Twitter4JHelper {
public static void addStatusListner(TwitterStream stream, StatusListener listner) {
stream.addListener(listner);
}
}
Revised implementation -
val twitterStream = TwitterStreamFactory().instance
val listner = object: StatusListener {
override fun onStatus(status: Status?) {
if (emitter.isDisposed) {
twitterStream.shutdown()
} else {
emitter.onNext(status)
}
}
override fun onException(e: Exception?) {
if (emitter.isDisposed) {
twitterStream.shutdown()
} else {
emitter.onError(e)
}
}
// Other overrides.
}
Twitter4JHelper.addStatusListner(twitterStream, listner)
emitter.setCancellable { twitterStream::shutdown }
This revised solution comes from a blog post, which I think tries to explain the cause but Google translate is not being my friend. What is causing the IllegalAccessError? Is there a purely Kotlin based solution, or will I have to live with this workaround?
Yep that's not going to work.
addListener method takes a StreamListener param and StreamListener is non-public (package private). I would definitely raise a bug against Kotlin compiler for this.
The code Kotlin compiler generates is:
TwitterStream twitterStream = (new TwitterStreamFactory()).getInstance();
twitterStream.addListener((StreamListener)(new StatusListener() {
// ..overrides ...
}));
StatusListener already implements StreamListener so I don't see why the cast is required.
I worked around this by using a java utility class:
public class T4JCompat {
public static void addStatusListener(TwitterStream stream, StatusListener listener) {
stream.addListener(listener);
}
public static void removeStatusListener(TwitterStream stream, StatusListener listener) {
stream.removeListener(listener);
}
}
You can call these methods from Kotlin and things work as expected.
This code sample cannot be compiled and shows an internal error.
open class TestClass {
open inner class Back {
open fun dd() { }
}
}
class Manager: TestClass() {
private val test = object : Back() {
override fun dd() { }
}
}
Cause:
Error generating constructors of class null with kind IMPLEMENTATION
What does it mean?
The example provided refers to KT-11833 and now compiles. Checked it with Kotlin version 1.1.0-beta-22.