Identity of Lambdas (was Re: JEP 186: Collection Literals)

Millies, Sebastian Sebastian.Millies at softwareag.com
Sat Jan 18 03:54:06 PST 2014


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?

-- 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