Issues with generic type detection of SAM types implemented using lambdas

Oliver Gierke ogierke at pivotal.io
Thu Jan 11 09:10:29 UTC 2018


Hi all,

I hate to bring this up again but as the adoption of Java 8 is growing significantly, this problem now comes up at least every other week and I can really understand the pain of our users. Find one example here [0].

Is there really no chance this is ever going to change?

Cheers,
Ollie

[0] https://github.com/spring-projects/spring-hateoas/issues/688

> Am 19.01.2017 um 08:40 schrieb Oliver Gierke <ogierke at pivotal.io>:
> 
> Hi Maurizio,
> 
> thanks again for you in-depth explanation. I get all that and the design considerations behind the decisions that were made. I just wanted to point out that an ordinary user -- in general client code that uses APIs with SAM types -- *has to implicitly know* whether *any code* (potentially Java 6 based) downstream might attempt generics resolution before she can decide whether to use a Lambda with this SAM type.
> 
> I think that's a very fundamental usability problem because it doesn't even need some arbitrary corner case. If there's a piece of code attempting the resolution anywhere downstream, it just won't work. Never. The only similar scenario (in terms of some parts of the reflection API not working as others) I remember is debug symbols on interface methods. That was changed in Java 8 with the -parameters flag. So I thought, it might be worth bringing this one up here as maybe the context has or will have changed for Java 9 / 10.
> 
> Cheers,
> Ollie
> 
>> Am 19.01.2017 um 02:52 schrieb Maurizio Cimadamore <maurizio.cimadamore at oracle.com>:
>> 
>> 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);
>>>> 		}
>>>> 	}
>>>> }
>> 
> 

-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: Message signed with OpenPGP
URL: <http://mail.openjdk.java.net/pipermail/compiler-dev/attachments/20180111/0500f038/signature.asc>


More information about the compiler-dev mailing list