Issues with generic type detection of SAM types implemented using lambdas

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Jan 11 14:09:35 UTC 2018


I think that's your best bet. The name should contain $$Lambda$ followed 
by some number. Of course this is very impl specific, and it would be 
possible for other runtimes to do different things (e.g. the name is not 
specified anywhere).

Maurizio


On 11/01/18 13:49, Oliver Gierke wrote:
> While I am currently investigating how to actually detect a lambda defined instance: is there a more reliable way than checking the class name to contain "$Lambda$"?
>
> Cheers,
> Ollie
>
>> Am 11.01.2018 um 12:48 schrieb Oliver Gierke <ogierke at pivotal.io>:
>>
>> I fear that's quite a mouthful of internals thrown into a user's face, especially as everyone has literally learned for so many years that "SAM type -> Lambda", but I guess it's the best reference I can make.
>>
>> Thanks, Maurizio!
>>
>>> Am 11.01.2018 um 12:18 schrieb Maurizio Cimadamore <maurizio.cimadamore at oracle.com>:
>>>
>>> I think I still stand by the answers provided.
>>>
>>> Brian's comment here is particularly insightful:
>>>
>>> http://mail.openjdk.java.net/pipermail/compiler-dev/2015-January/009268.html
>>>
>>> E.g. it's not much that we're saying "works as designed" - it's more that we see this as some kind of short-term relief that is unavoidably cause much worse problems later down the road.
>>>
>>> So, I see two options: we add all the required attributes/annotations, and commit to never change the implementation strategy again (ouch!); or we leave freedom for the implementation to come up with more efficient runtime strategies - which unfortunately works against the use case you are presenting.
>>>
>>> That said, I agree with what's said in the evaluation you point at - the fact that many tools out there (IDEs) suggest you to turn all anon inner classes into lambdas might lead to people doing the refactoring w/o really understanding the implications for the framework they're working on.
>>>
>>> I wonder whether some other resolution - e.g. an explicit mechanism to opt out of the functional interface business would help? E.g. if a functional interface is marked with @NoFunctionalInterface (provisional name of course), then the compiler (and the IDE) should refuse to consider this interface a suitable target type for a lambda expression/method reference. Would that mitigate the pain?
>>>
>>> Maurizio
>>>
>>> On 11/01/18 09:10, Oliver Gierke wrote:
>>>> 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);
>>>>>>>> 		}
>>>>>>>> 	}
>>>>>>>> }



More information about the compiler-dev mailing list