3

I have a use case where it would be useful to use reflection to get the parameter and return value types of Function objects1.

But I discovered to following surprising behaviour of function objects resulting from lambda expressions: The object don't have an apply method with specific types.

Example code:

import java.lang.reflect.Type;
import java.lang.reflect.Method;
import java.util.function.Function;

public class Trying {
    public static void main(String[] args) {
        Function<Integer, Double> lambda = i -> i.doubleValue();

        Function<Integer, Double> methodRef = Integer::doubleValue;

        Function<Integer, Double> anon = new Function<Integer, Double>() {
            @Override
            public Double apply(Integer i) {
                return i.doubleValue();
            }
        };

        printType("lambda", lambda);
        printType("methodRef", methodRef);
        printType("anon", anon);
    }
    private static void printType(String name, Function<?, ?> f) {
        System.out.println("### " + name);
        System.out.println("super: " + f.getClass().getSuperclass().getSimpleName());

        for (Type i : f.getClass().getGenericInterfaces()) {
            System.out.println("interface: " + i);
        }

        for (Method m : f.getClass().getDeclaredMethods()) {
            System.out.println("name: " + m.getName() + ", bridge: " + m.isBridge()
                + ", arg type: " + m.getParameterTypes()[0].getSimpleName()
                + ", return type: " + m.getReturnType().getSimpleName());
        }

        System.out.println();
    }
}

This code prints the following:

### methodRef
super: Object
interface: interface java.util.function.Function
name: apply, bridge: false, arg type: Object, return type: Object

### lambda
super: Object
interface: interface java.util.function.Function
name: apply, bridge: false, arg type: Object, return type: Object

### anon
super: Object
interface: java.util.function.Function<java.lang.Integer, java.lang.Double>
name: apply, bridge: false, arg type: Integer, return type: Double
name: apply, bridge: true, arg type: Object, return type: Object
  1. Is there some way to work around this issue and use reflection to get the concrete types of the function objects?
  2. Is the the language specified behaviour, or implementation dependent?
  3. Why do the lambda and method reference objects behave in this way?

1: I want to use this information to create default converters for a databinding API.

Valentin Ruano
  • 2,726
  • 19
  • 29
Lii
  • 11,553
  • 8
  • 64
  • 88
  • [Related Q&A](https://stackoverflow.com/q/30514995/335858). I'm pretty sure it's done on purpose, and that there is no work-around for it. – Sergey Kalinichenko Jun 13 '18 at 20:28
  • 1
    This is nothing specific about Functions or lambdas. This is the generic behaviour of java Generics. Read about type erasure. – gagan singh Jun 14 '18 at 01:30
  • @gagansingh: Note that the anonymous class in the example code have specific types available which can be accessed using reflection. Some generic information, for example about super classes with applied type parameters, is stored in the class files. Read for example about [bridge methods](https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html#bridgeMethods). – Lii Jun 14 '18 at 06:09
  • @Lii thanks for the link. – gagan singh Jun 16 '18 at 16:25
  • Why would it be done "on purpose"? Seems more like an oversight. If you can find generic type arguments of anonymous classes, why not of lambdas too? I'm disappointed in this design decision. – Stefan Reich Nov 20 '20 at 10:12
  • @StefanReich: I think it is an optimisation. They make lambda objects a little cheaper by not having them keep the type arguments around in the generated class. – Lii Nov 20 '20 at 12:44
  • @Lii Yes, possible – Stefan Reich Nov 21 '20 at 11:44

1 Answers1

0

First, I would like to thank you to post a very complete code snippet that just needed a few modification to compile and run. It makes things much easier for us.

My short answer is DON'T DO IT. Writing code that depends on this kind of reflection to work is going to be problematic... there might be tricks to get it to work like using inner classes as you show above but it would bite you in the end, so my advice is to steer away from such a practice.

Instead you could created a "typed" function subclass that addresses the problem using a valid Java idioms like this:

abstract class TypedFunction<T, U> implements Function<T, U> {
  final Class<T> inClass;
  final Class<U> outClass;

  TypedFunction(final Class<T> inClass, final Class<U> outClass) {
    this.inClass = inClass;
    this.outClass = outClass;
  }

  static <TT, UU> TypedFunction<TT, UU> make(
          final Class<TT> inClass, 
          final Class<UU> outClass,
          final Function<? super TT, ? extends UU> func) {
    return new TypedFunction<TT, UU>(inClass, outClass) {
            @Override
            UU apply(final TT in) {
              return func.apply(in);
            }
    };
  }
}

Then the way the using code instantiate those functions like so:

TypedFunction<Integer, Double> lambda = TypedFunction.make(Integer.class, Double.class, i -> i.doubleValue());

TypedFunction<Integer, Double> methodRef = TypedFunction.make(Integer.class, Double.class, Integer::doubleValue);

TypedFunction<Integer, Double> anon = TypedFunction.make(Integer.class, Double.class,  new Function<Integer, Double>() {
        @Override
        public Double apply(Integer i) {
            return i.doubleValue();
        }
};

TypedFunction<Integer, Double> anonSimple = 
        new TypedFunction<Integer, Double>(Integer.class, Double.class) { 
   @Override
   public Double apply(Integer i) {
      return i.doubleValue();
   }
};

And in turn your reflection code to resolve the in and out classes are simply accessing the inClass and outClass fields on the passed TypedFunction<?, ?>. Of course it would return to you the "raw" types rather than the generic forms in case that these happen to be generic typed but I think that we should leave at that.

If including Integer.class and Double.class seems tedious you could create a second-order function whose constructor take on T and U classes references and is itself a function from the underlying Function<T, U> to its TypedFunction<T, U> wrapped version.

Valentin Ruano
  • 2,726
  • 19
  • 29