Identity of Lambdas (was Re: JEP 186: Collection Literals)
Peter Levart
peter.levart at gmail.com
Sat Jan 18 09:09:21 PST 2014
On 01/18/2014 12:54 PM, Millies, Sebastian wrote:
> Surprising, at least when one thinks of lambdas as functions, not objects.
>
> I was under the impression that lambdas could not be used as hash keys, because they had no
> well-defined identity. The source for this belief was Stuart Mark's answer at
> http://stackoverflow.com/questions/15221659/java-8-lambda-expression-and-first-class-values
> from which I quote:
>
> <quote>
> Given that lambdas are converted into objects, they inherit (literally) all the characteristics of objects. In particular, objects:
>
> have various methods like equals, getClass, hashCode, notify, toString, and wait
> have an identity hash code
> can be locked by a synchronized block
> can be compared using the == and != and instanceof operators
>
> and so forth. In fact, all of these are irrelevant to the intended usage of lambdas. Their behavior is essentially undefined. You can write a program that uses any of these, and you will get some result, but the result may differ from release to release (or even run to run!).
> </quote>
>
> In Peter's example, the only thing that seems to matter would be that if a lambda has an identity hash code, that will not change while the JVM exists. But will the antecedent always be true in the future (function types etc.?)
> Are there some minimal guarantees on the identities of lambdas, and where are they described?
>
> I took Stuart's remark "Their behavior is essentially undefined" to mean: Don't do these things with lambdas! Would that be sound advice?
It depends. I think that lambdas in Java will always be objects, because
they map to reference types which are objects. Unless some future
release breaks compatibility. This could happen and that's why invoking
Object methods on lambdas is not recommended. It should be noted that
there surely exists code which is perfectly legal since it's written in
pre-JDK8 times but happens to use interfaces classified as functional
interfaces in JDK8. Pairing such code with lambdas might work today, but
is not guaranteed to work tomorrow...
But returning to my example. Whatever future brings, my example might
still work then. It only uses one assumption and that is:
if (lambda1.hashCode() == lambda2.hashCode() &&
lambda1.equasl(lambda2)) {
// assume lambda2 produces the same result as lambda1
}
...so unless invoking hashCode and equals on lambdas starts to throw
exceptions or starts to state that all lambdas are equal, the example
should still work. It might not be as optimal as before though.
Regards, Peter
> -- Sebastian
>
> -----Original Message-----
> From: lambda-dev-bounces at openjdk.java.net [mailto:lambda-dev-bounces at openjdk.java.net] On Behalf Of Peter Levart
> Sent: Saturday, January 18, 2014 12:17 PM
> To: Remi Forax; lambda-dev at openjdk.java.net
> Subject: [Spam]: Re: JEP 186: Collection Literals
>
>
> On 01/16/2014 12:43 AM, Remi Forax wrote:
>> We can also use the Builder Pattern of Ruby now that we have a lambda syntax
>> new ArrayList<>(builder -> builder.add(1).add(2).add(3));
>>
>> Rémi
> This one is interesting, because it shows what can be achieved with lambdas today. It enables expressions that evaluate into singleton objects (like non-capturing lambdas). If the lambda body is non-capturing, it evaluates into constant singleton in current implementation. So objects produced with such lambdas can be cached (using lambda object as a weak key). Here's a sample test that exercises
> that:
>
> public class Test {
> public static void main(String[] args) {
> for (int i = 0; i < 3; i++) {
> List<Double> list = immutableList(b -> b._(3.14)._(0.33333));
> System.out.println(list + " : " + System.identityHashCode(list));
> }
> System.out.println();
> for (int i = 0; i < 3; i++) {
> double x = i;
> List<Double> list = immutableList(b -> b._(3.14)._(0.33333)._(x));
> System.out.println(list + " : " + System.identityHashCode(list));
> }
> System.out.println();
> for (int i = 0; i < 3; i++) {
> Map<String, Integer> map = immutableMap(b -> b._("aa", 1)._("bb", 2)._("cc", 3));
> System.out.println(map + " : " + System.identityHashCode(map));
> }
> System.out.println();
> for (int i = 0; i < 3; i++) {
> int x = i;
> Map<String, Integer> map = immutableMap(b -> b._("aa", 1)._("bb", 2)._("cc", x));
> System.out.println(map + " : " + System.identityHashCode(map));
> }
> }
> }
>
> which prints:
>
> [3.14, 0.33333] : 1706377736
> [3.14, 0.33333] : 1706377736
> [3.14, 0.33333] : 1706377736
>
> [3.14, 0.33333, 0.0] : 868693306
> [3.14, 0.33333, 1.0] : 989110044
> [3.14, 0.33333, 2.0] : 321001045
>
> {aa=1, bb=2, cc=3} : 1044036744
> {aa=1, bb=2, cc=3} : 1044036744
> {aa=1, bb=2, cc=3} : 1044036744
>
> {aa=1, bb=2, cc=0} : 1915318863
> {aa=1, bb=2, cc=1} : 295530567
> {aa=1, bb=2, cc=2} : 1324119927
>
>
>
> Regards, Peter
>
>
> P.S. Here's what I used to compile above test:
>
> public interface Builder<T> extends Consumer<T> {
> default Builder<T> _(T t) {
> accept(t);
> return this;
> }
> }
>
> public interface BiBuilder<T, U> extends BiConsumer<T, U> {
> default BiBuilder<T, U> _(T t, U u) {
> accept(t, u);
> return this;
> }
> }
>
> public class Collections2 {
>
> public static <T> ArrayList<T> arrayList(Consumer<Builder<T>>
> producer) {
> ArrayList<T> list = new ArrayList<>();
> producer.accept(list::add);
> return list;
> }
>
> // cache of immutable lists per producer
> private static final Map<Consumer<? extends Builder<?>>, List<?>> INTERNED_IMMUTABLE_LISTS = new WeakHashMap<>();
>
> public static <T> List<T> immutableList(Consumer<Builder<T>>
> producer) {
> synchronized (INTERNED_IMMUTABLE_LISTS) {
> // check if already interned
> @SuppressWarnings("unchecked")
> List<T> list = (List<T>)
> INTERNED_IMMUTABLE_LISTS.get(producer);
> if (list != null) return list;
> }
>
> // count elements produced
> final int[] count = new int[1];
> producer.accept(t -> count[0]++);
>
> // construct new list
> ArrayList<T> aList = new ArrayList<>(count[0]);
> producer.accept(aList::add);
>
> synchronized (INTERNED_IMMUTABLE_LISTS) {
> // recheck (highly unlikely)
> @SuppressWarnings("unchecked")
> List<T> list = (List<T>)
> INTERNED_IMMUTABLE_LISTS.get(producer);
> if (list != null) return list;
> // put it into cache and return
> list = Collections.unmodifiableList(aList);
> INTERNED_IMMUTABLE_LISTS.put(producer, list);
> return list;
> }
> }
>
> public static <K, V> HashMap<K, V> hashMap(Consumer<BiBuilder<K,
> V>> producer) {
> HashMap<K, V> map = new HashMap<>();
> producer.accept(map::put);
> return map;
> }
>
> // cache of immutable maps per producer
> private static final Map<Consumer<? extends BiBuilder<?, ?>>, Map<?, ?>> INTERNED_IMMUTABLE_MAPS = new WeakHashMap<>();
>
> public static <K, V> Map<K, V> immutableMap(Consumer<BiBuilder<K,
> V>> producer) {
> synchronized (INTERNED_IMMUTABLE_MAPS) {
> // check if already interned
> @SuppressWarnings("unchecked")
> Map<K, V> map = (Map<K, V>) INTERNED_IMMUTABLE_MAPS.get(producer);
> if (map != null) return map;
> }
>
> // count elements produced
> final int[] count = new int[1];
> producer.accept((k, v) -> count[0]++);
>
> // construct new map
> HashMap<K, V> hMap = new HashMap<>(count[0] * 4 / 3);
> producer.accept(hMap::put);
>
> synchronized (INTERNED_IMMUTABLE_MAPS) {
> // recheck (highly unlikely)
> @SuppressWarnings("unchecked")
> Map<K, V> map = (Map<K, V>) INTERNED_IMMUTABLE_MAPS.get(producer);
> if (map != null) return map;
> // put it into cache and return
> map = Collections.unmodifiableMap(hMap);
> INTERNED_IMMUTABLE_MAPS.put(producer, map);
> return map;
> }
> }
> }
>
>
>
>
> Software AG – Sitz/Registered office: Uhlandstraße 12, 64297 Darmstadt, Germany – Registergericht/Commercial register: Darmstadt HRB 1562 - Vorstand/Management Board: Karl-Heinz Streibich (Vorsitzender/Chairman), Dr. Wolfram Jost, Arnd Zinnhardt; - Aufsichtsratsvorsitzender/Chairman of the Supervisory Board: Dr. Andreas Bereczky - http://www.softwareag.com
>
>
More information about the lambda-dev
mailing list