Issues with generic type detection of SAM types implemented using lambdas
Maurizio Cimadamore
maurizio.cimadamore at oracle.com
Thu Jan 19 01:52:38 UTC 2017
Hi Oliver,
this request seems to be similar to other requests expressed in the past:
Signature attribute on lambda method:
http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009220.html
Annotations on lambdas:
http://mail.openjdk.java.net/pipermail/compiler-dev/2015-July/009662.html
The recurring theme is that we don't want to make promises on
implementation details - that is how is a lambda implemented. For now, a
lambda is implemented as a (dynamically generated) anonymous class
instance - so certain requests seem to make sense. But as the
implementation will get better at sharing, having to expose details such
as signature attributes and such will become an obstacle to further
improvements.
For instance, the runtime does NOT have to distinguish between a
Supplier<String> and a Supplier<Object> - example:
class Test {
void test() {
Supplier<String> ss = this::m;
Supplier<Object> si = this::m;
}
String m() { return ""; }
}
This gives the following bytceode:
void test();
descriptor: ()V
flags:
Code:
stack=1, locals=3, args_size=1
0: aload_0
1: invokedynamic #2, 0 // InvokeDynamic
#0:get:(LTest;)Ljava/util/function/Supplier;
6: astore_1
7: aload_0
8: invokedynamic #3, 0 // InvokeDynamic
#1:get:(LTest;)Ljava/util/function/Supplier;
13: astore_2
14: return
BootstrapMethods:
0: #18 invokestatic
java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#19 ()Ljava/lang/Object;
#20 invokevirtual Test.m:()Ljava/lang/String;
#21 ()Ljava/lang/String;
1: #18 invokestatic
java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#19 ()Ljava/lang/Object;
#20 invokevirtual Test.m:()Ljava/lang/String;
#19 ()Ljava/lang/Object;
As you can see, both invokedynamic share the same SAM type - the erased
java/util/function/Supplier. The only difference between the two indys
is the method type passed as 3rd static argument - in one case ()String
is passed, in the other ()Object is passed - that dictates the signature
of the dynamically generated method implementing the sam type.
In other words, if the lambda metafactory used the same instance for
both method references, everything would still work (in fact, the
generated bytecode is the same for both classes!).
Maurizio
On 18/01/17 18:06, Oliver Gierke wrote:
> Hi again,
>
> didn't get much response but it looks like there are some improvements for lambdas planned for at least JDK10 [0]. Is what I asked for below maybe a candidate for inclusion into those efforts?
>
> Is there maybe a better place to ask for this?
>
> Cheers,
> Ollie
>
> [0] https://www.infoq.com/news/2017/01/java10-lambda-leftovers
>
>> Am 23.12.2016 um 12:22 schrieb Oliver Gierke <ogierke at pivotal.io>:
>>
>> Hi all,
>>
>> Lambda based implementations of SAM types currently don't support inspecting the type for generic type parameters. This can cause unexpected surprise as some high-level API taking a SAM type as parameter is usually an indicator to users, that they can be used with Lambdas. If the object passed in is then inspected for generic types somewhere down the call stack this causes issues. Handing in a dedicated implementation of the SAM type is a workaround bit I think that's highly confusing and can be a source of errors hard to understand and debug.
>>
>> I've added an example below.
>>
>> Cheers,
>> Ollie
>>
>> public class LambdaTypeDetectionSample {
>>
>> public static void main(String[] args) {
>>
>> Function<Integer, String> lambdaFunction = i -> i.toString();
>> Function<Integer, String> oldschoolFunction = new Function<Integer, String>() {
>>
>> public String apply(Integer t) {
>> return t.toString();
>> }
>> };
>>
>> printTypeArguments(oldschoolFunction);
>>
>> // Yields:
>> // java.util.function.Function<java.lang.Integer, java.lang.String> is a class sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl
>> // class java.lang.Integer
>> // class java.lang.String
>>
>> printTypeArguments(lambdaFunction);
>>
>> // Yields:
>> // interface java.util.function.Function is a class java.lang.Class
>> }
>>
>> private static void printTypeArguments(Function<?, ?> function) {
>>
>> Type type = function.getClass().getGenericInterfaces()[0];
>>
>> System.out.println(type + " is a " + type.getClass());
>>
>> if (type instanceof ParameterizedType) {
>>
>> ParameterizedType functionInterface = (ParameterizedType) type;
>> Arrays.stream(functionInterface.getActualTypeArguments()).forEach(System.out::println);
>> }
>> }
>> }
More information about the compiler-dev
mailing list