82

From J. Bloch

A ... source of memory leaks is listeners ... The best way to ensure that callbacks are garbage collected promptly is to store only weak references to them, for instance, by storing them only as keys in a WeakHashMap.

So, why there isn't any WeakSet in the Java Collections framework?

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Stan Kurilin
  • 15,614
  • 21
  • 81
  • 132
  • 1
    Stas, can you accept mart's upvoted, correct answer instead of Martin's downvoted, incorrect answer? – toolforger Dec 18 '17 at 17:55
  • While Joshua Bloch wrote a lot of reasonable advice for Java programmers, this seems to be an awful exception. Storing listeners into a `WeakHashMap` does never “ensure that callbacks are garbage collected promptly”, but rather makes them horribly non-deterministic. The garbage collector will only run when there is insufficient memory, hence, such weak listeners may be dangling around an arbitrary long time *and still getting executed*, but even worse, such listeners might spuriously disappear when you still need them, as it now needs an actually unrelated strong reference to keep them alive. – Holger Jun 25 '19 at 17:22
  • You're allowed (even encouraged) to remove them explicitly, but using a weak collection ensures that they don't leak via this route. – Donal Fellows Aug 15 '23 at 11:04

2 Answers2

204

Collections.newSetFromMap

Set<Object> weakHashSet = 
    Collections.newSetFromMap(
        new WeakHashMap<Object, Boolean>()
    );

As seen in Collections.newSetFromMap documentation, passing a WeakHashMap to get a Set.

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
mart
  • 2,537
  • 1
  • 15
  • 13
  • 4
    actually any Set in java collection contains Map for storing. – mart Oct 31 '10 at 11:49
  • 5
    Yep, but why there is no specific class for such stuff? – Stan Kurilin Oct 31 '10 at 11:52
  • 14
    It's easy to imagine why the maintainers of java.util might have wanted to stop having to provide dual Map and Set versions of everything they do, and opted to just provide newSetFromMap() instead... isn't it? – Kevin Bourrillion Oct 31 '10 at 15:48
  • @Kevin Bourrillion : It is. But there is some strange about their selections. – Stan Kurilin Oct 31 '10 at 20:40
  • I think the javadoc might be wrong in this case. The created set will still store values as a value in the backing map (instead of as a key). WeakHashMap uses references on the keys, but the values are still strongly referenced. This will basically only store a weak reference to the hashcode of all the values added. – Mike Jun 02 '11 at 13:03
  • 24
    It's worth noting that Collections#newSetFromMap is missing from Android before API 9. It's not difficult to find an implementation to compile into your app, though, but it's a compatibility gotcha. – nmr Feb 17 '12 at 23:11
  • 4
    @Mike The JavaDoc is correct. Note the code in this answer returns a Set of Objects not Booleans. `newSetFromMap` creates a set of the type of the keys, not the values. – kabuko May 04 '12 at 19:56
  • @mart: Not true. BitSet doesn't. – Rok Kralj Jul 16 '14 at 18:13
  • 1
    @RokKralj: BitSet is not a Collection. – yamass Oct 12 '14 at 12:19
  • 2
    Tried this newSetFromMap solution; big limitation: can't retrieve items from set without iterating (performance killer). Want to use weak references to deduplicate objects, but can only compare keys and retrieve values, not retrieve previously stored key that matches (unless iterating). Set needs a key-getter to retrieve the actual object stored which matches with `.equals()`. – Christopher Aug 12 '17 at 19:58
  • @Christopher A WeakSet, if it existed in the standard library, would probably not be good in your use case because, as you mention, the Set interface does not support retrieving the actual element that is stored without iterating to find it. You might try [`ReferenceMap`](https://commons.apache.org/proper/commons-collections/apidocs/org/apache/commons/collections4/map/ReferenceMap.html) from Apache Commons Collections, created with `WEAK` key and value reference type, and store each element as the key and value of the `ReferenceMap` entry. – Daniel Trebbien Oct 11 '17 at 17:12
  • 1
    @DanielTrebbien Thanks. Ended up going with Guava: `CacheBuilder.newBuilder().weakValues().build()` which worked for my use case. Just a bit disappointed that this limitation exists in the Set interface. Would be nice to have a method to do `storedKey = getKey(equivalentKeyForLocating)`. Could do so much with that. – Christopher Oct 12 '17 at 23:19
  • 1
    The types can also be inferred, leaving out the Boolean type that is invisible once you've let go of the reference to the Map (as is recommended): `Set weakHashSet = Collections.newSetFromMap(new WeakHashMap<>());` – Joshua Goldberg Jun 12 '20 at 19:27
  • @KevinBourrillion As for your theory that the Collections Framework designers omitted certain `Set` implementations by way of `Collections.newSetFromMap`… apparently not. The Collections Framework arrived in Java 2, but `newSetFromMap` did not arrive until Java 6, eight years later. – Basil Bourque May 07 '21 at 08:23
  • This refutes nothing I said; it remains very conceivable that multiple feature requests that had accumulated over 8 years were closed when that method was added. – Kevin Bourrillion May 08 '21 at 10:36
2

While you can indeed use Collections.newSetFromMap() to get a WeakSet, it's use cases are actually quite limited.

If you want to implement something like String.intern() you might want to have a look at Guava's Interners.newWeakInterner() functionality instead.

Axel Dörfler
  • 379
  • 3
  • 6
  • if someone is looking for optimizing memory use, the performance are quite better than guava weak interners. https://docs.geotools.org/stable/javadocs/org/geotools/util/CanonicalSet.html. – elbo Apr 27 '22 at 10:08