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)
source to share
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
.
source to share