3

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:

  • names of arguments
  • their type
  • their value

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.

1 Answers1

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"
    }
}
  • Thank you very much, but it is not what I was asking. I really need Kotlin reflection method `callBy`, because `@JvmOverloads` fails in some scenarious, like second `test` method with `a` parameter of type `Int`. – Lukáš Moravec Oct 27 '20 at 08:01
  • You mean there is another `fun test(a: Int = 11, c: String): String = "A: $a - C: $c"` function in the same file? Well, it really complicates the task, if you want to be able to call both of them from Java, using default value for `a`. It even complicates simple (non-reflective) calling of them from kotlin with a single argument (cause to avoid ambiguity you will have to specify its name). I believe you should update the question with this additional clause. As a simple workaround I may suggest to just move second `test` to another file. – Михаил Нафталь Oct 27 '20 at 09:11
  • Thank you for your time and patience, I will update my question. The problem is just showed with additional function exactly as you specified. The real problem with with `@JvmOverloads` is positioning parameters which can be use default parameters before or after non-defaults, so `@JvmOverloads` can not help here. – Lukáš Moravec Oct 27 '20 at 10:41
  • Thank you very much, auxiliary function is the way to go! :) – Lukáš Moravec Oct 27 '20 at 12:53