3

As a follow-up question strongly related to Holgers' solution, why does uncommenting the override break the working code below?

public static interface StringFunction<N extends Number> extends Function<String, N> {

  // @Override
  // N apply(String t);

}

This works only, if the comments above are not removed:

public static <N extends Number> StringFunction<N> create(Class<N> type) throws Throwable {
  MethodType methodType = MethodType.methodType(type, String.class);
  MethodHandles.Lookup lookup = MethodHandles.lookup();
  MethodHandle handle = lookup.findConstructor(type, 
    MethodType.methodType(void.class, String.class));
  StringFunction<N> f = (StringFunction<N>) LambdaMetafactory.metafactory(lookup, "apply", 
        MethodType.methodType(StringFunction.class), 
        methodType.generic(), handle, methodType).getTarget().invokeExact();
  return f;
}

public static void main(String[] args) throws Throwable {
  System.out.println(create(Byte.class).apply("1"));
  System.out.println(create(Short.class).apply("2"));
  System.out.println(create(Integer.class).apply("3"));
  System.out.println(create(Long.class).apply("4"));
}

The runtime complains about:

Exception in thread "main" java.lang.AbstractMethodError:
Method LambdaFun$$Lambda$1.apply(Ljava/lang/String;)Ljava/lang/Number; is abstract
  at LambdaFun$$Lambda$1/856419764.apply(Unknown Source)
  at LambdaFun.main(LambdaFun.java:28)
Community
  • 1
  • 1
Sormuras
  • 8,491
  • 1
  • 38
  • 64
  • 2
    You do realize that `LambdaMetafactory` is primarily only for compiler writers, not ordinary java code, right? By all means have fun trying it out, but it's a tool for experts, and using it requires deep understanding of how VM linkage works. So you shouldn't be surprised that using it requires a high degree of precision and the resulting error messages for misuse are not trivially interpreted. – Brian Goetz Dec 11 '14 at 16:11

1 Answers1

4

When using generic interfaces with the metafactory, you need to understand how Generics work on the bytecode level.

When declaring an interface method like

public static interface StringFunction<N extends Number> extends Function<String, N> {
    @Override
    N apply(String t);
  }

the raw type StringFunction will have a method with the signature of Number apply(String) that you have to implement. It further contains a compiler generated bridge method overriding the the inherited method Object apply(Object) which will delegate to the abstract method (which is a good thing as otherwise, e.g. when the interface was compiled under Java 7 or earlier, we would have to declare all required bridge methods explicitly using altMetafactory, compare «this answer»).

So you have to change your factory method to:

public static <N extends Number> StringFunction<N> create(Class<N> type) throws Throwable {
    MethodType methodType = MethodType.methodType(type, String.class);
    MethodHandles.Lookup lookup = MethodHandles.lookup();
    MethodHandle handle = lookup.findConstructor(type, 
      MethodType.methodType(void.class, String.class));
    StringFunction<N> f = (StringFunction<N>) LambdaMetafactory.metafactory(lookup, "apply", 
      MethodType.methodType(StringFunction.class), 
      methodType.changeReturnType(Number.class), handle, methodType).getTarget().invokeExact();
    return f;
}

to make it work. Note that we now keep the String argument type that is fixed now and only change the return type to its lower bound using methodType.changeReturnType(Number.class).


Further, note how this code may hide errors regarding Generics. You were using Integer.class at places supposed to be replaced by the parameter type but it doesn’t break immediately as your example code never tries to assign the value returned by such a function, e.g. StringFunction<Short> to a variable of that type, so you don’t notice that a StringFunction<Short> returns an Integer. I have fixed that in my example code, so that, e.g. StringFunction<Short> really return a Short.

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765