javakotlinreflectionkotlin-reflect

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.


Solution

  • 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"
        }
    }