Lukáš Moravec
Lukáš Moravec

Reputation: 65

How to use Kotlin reflection from Java

Is it possible to use Kotlin reflection from Java?

I want to get KCallable from Kotlin function in Java and use its method callBy to call method with default arguments.

Example in Kotlin:

fun test(a: String = "default", b: String): String {
    return "A: $a - B: $b";
}

fun main() {
    val callable: KCallable<*> = ::test
    val parameterB = callable.parameters[1]

    val result = callable.callBy(mapOf(
        parameterB to "test"
    ))

    println(result)
}

Is it even possible? If so, how to get instance of KCallable from Java code?

EDIT:

I cannot use @JvmOverloads as suggested, because the number of arguments, default arguments and their positions can be arbitrary.

The known information for calling is:

EDIT 2:

Example of not working @JvmOverloads here:

fun test(a: String = "default", b: String = "default"): String {
    return "A: $a - B: $b";
}

Here calling with one String value is ambiguous.

Upvotes: 3

Views: 1340

Answers (1)

If file, where test function was declared, is Utils.kt, then it will be compiled into UtilsKt class.

As documentation states:

Normally, if you write a Kotlin function with default parameter values, it will be visible in Java only as a full signature, with all parameters present. If you wish to expose multiple overloads to Java callers, you can use the @JvmOverloads annotation.

So, after adding this annotation:

@JvmOverloads 
fun test(a: String = "default", b: String): String {
    return "A: $a - B: $b";
}

test method may be called from java with a single parameter:

public class ReflectionInterop {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Method test = UtilsKt.class.getDeclaredMethod("test", String.class);
        String result = (String) test.invoke(null, "test"); //null used because method is compiled as static, so no instance needed to call it
        System.out.println(result); //Will print "A: default - B: test"
    }
}

EDIT

If you are looking for a way to overcome limitations of convenient java interop, then you indeed need to get an instance of KCallable in your Java code.

I believe it is impossible without auxilary Kotlin function:

fun testReflection(): KCallable<*> = ::test

Its usage in Java is pretty simple:

public class ReflectionInterop {
    public static void main(String[] args) {
        KCallable<?> test = UtilsKt.testReflection(); //Assuming it is located in the same `Utils.kt` file
        KParameter parameterB = test.getParameters().get(1);
        String result = (String) test.callBy(new HashMap<>() {{
            put(parameterB, "test");
        }});
        System.out.println(result); //Will print "A: default - B: test"
    }
}

Upvotes: 1

Related Questions