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