How to keep the parameter types of lambdas?

Viewed 53

This is a odd Java reflection issue, that happens because of type erasure:

<P, R> void printIt(Function<P, R> function) {
        inner(function);
    }

    private void inner(Object function) {
        for (Method method : function.getClass().getDeclaredMethods()) {
            System.out.println(method);
        }
    }

    @Test
    public void testAnyHandler() {
        printIt((String a) -> "Hello");
    }

the result is:

public java.lang.Object com.dariodario.sqlsmile.QueryTest$$Lambda$1/1607460018.apply(java.lang.Object)

which is really a shame... Is there any way to keep the parameters?

1 Answers

Not really, this data is not kept by the compiler. You would either create a separate class for lambda:

interface StringFunc extends Function<String, String> {}

And then you can get StringFunc super interface to get Function<String, String>.

Or just use classes instead of lambdas:

    printIt(new Function<String, String>() {
        @Override
        public String apply(String s) {
            return s;
        }
    });

Lambdas also generate synthetic method in the class they were declared in:

    Function<String, String> x = (String a) -> a;
    printIt(x);

    for (Method declaredMethod : Test.class.getDeclaredMethods()) {
        if (declaredMethod.isSynthetic()) {
            System.out.println(declaredMethod);
        }
    }
}

will print

public java.lang.Object org.gotofinal.Test$$Lambda$14/0x0000000800066840.apply(java.lang.Object)
private static java.lang.String org.gotofinal.Test.lambda$main$0(java.lang.String)

But I don't see option to get declaration class of lambda (but can be extracted from class name) and how to match method with a lambda + it's implementation specific so might not work on every version of jvm. Also this method will include types of local variables passed to lambda:

    int dd = "dddd".length() + args.length;
    Function<String, String> x = (String a) -> a + dd;

will print:

private static java.lang.String org.gotofinal.Test.lambda$main$0(int,java.lang.String)

So probably your best bet is to just pass type as separate argument:

public <F, T> Whatever method(Class<F> typeF, Class<T> typeT, Function<F, T> func)
Related