AbstractMethodException with LambdaMetafactory

As a follow-up question, closely 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 only works 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)

      

+3


source to share


1 answer


When using common interface

with a metafile, you need to understand how Generics work at the bytecode level.

When declaring a method interface

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 a signature Number apply(String)

that you must implement. Next, it contains a compiler-generated modem method that overrides the inherited method Object apply(Object)

, which will delegate the method abstract

(which is good, otherwise, for example, when it interface

was compiled for Java 7 or earlier, we would have to declare all the necessary bridge methods explicitly using altMetafactory

, compare "this answer " ).

So, you need 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 are now retaining the type of the argument String

, which we fixed, and only change the return type to its lower bound using methodType.changeReturnType(Number.class)

.


Also, note how this code can hide errors regarding Generics. You have used Integer.class

in places that need to be replaced with a parameter type

, but it does not break immediately as your example code never tries to assign the return value of such a function, for example. StringFunction<Short>

to a variable of this type, so you won't notice what it StringFunction<Short>

returns Integer

. I fixed this in my example code so that, for example, StringFunction<Short>

do indeed return a Short

.

+4


source







All Articles