RFR: 8166365: Small immutable collections should provide optimized implementations when possible
Stuart Marks
stuart.marks at oracle.com
Wed Jan 11 03:10:26 UTC 2017
On 1/10/17 3:50 AM, Claes Redestad wrote:
> please review this change to improve startup/warmup characteristics
> and performance of the immutable collection factories:
>
> Bug: https://bugs.openjdk.java.net/browse/JDK-8166365
> Webrev: http://cr.openjdk.java.net/~redestad/8166365/webrev.01/
>
> While filed as an RFE, and a more thorough examination of the hierarchy
> of the ImmutableCollection classes has been discussed[1], this more
> limited patch resolves some immediate issues related to module system
> bootstrap and is therefore motivated to push to 9.
>
> Thanks!
>
> /Claes
>
> [1] we should likely not inherit Iterator classes that check for
> co-modification etc,
Hi Claes,
Thanks for taking this on.
Interesting that some -- and only some -- of the existing empty and singleton
collections in Collections.java hadn't overridden hashCode(). Thanks for filling
these in.
The following comments all apply to ImmutableCollections.java.
-------
Regarding Paul's questions about whether you should override equals() and
various additional hashCode() methods:
The general rule from "Effective Java" is that equals() and hashCode() both be
overridden. However, this is mainly directed at classes that are chidren of
Object and that are providing semantics that differ from Object's equals() and
hashCode() methods. The more precise rule is that the semantics of equals() and
hashCode() must match.
It's oddly asymmetric to override hashCode() but not equals(), but that's OK, as
these implementations all inherit equals() from their superclasses, which
provide the right semantics. In addition, equals() is a bit complicated and is
kind of expensive, and it should be relatively infrequent.
The point of hashCode() is to avoid unnecessary calls to equals(), so overriding
hashCode() to make it go fast is more important than overriding equals(). In
that light it's a bit odd that hashCode() is overridden in most places here, but
List2.hashCode()
ListN.hashCode()
MapN.hashCode()
aren't overridden.
I'd like to see them added at some point, but if your benchmarks don't justify
them, maybe they're not necessary right now. If not, we should make a note to
add them later.
-------
All of List{0,1,2,N}.contains(null) will currently return false. With this
change, List{0,1,2}.contains(null) will change to throw NPE, but
ListN.contains(null) will continue to return false.
I actually like the change of throwing NPE on contains(null). Note that all the
Set and Map implementations here throw NPE when null is passed to
contains/containsKey/containsValue, so returning false instead of throwing NPE
from the List.contains() methods might have been an oversight on my part. But
all the List implementations should be made consistent.
Thus, ListN.contains() should be overridden to provide consistent NPE behavior.
It could simply be
return super.contains(Objects.requireNonNull(o));
or maybe just write out the loop in order to avoid Iterator allocation.
Also note that (as Peter Levart mentioned some time ago, in the initial review
of these implementations) that although it isn't guaranteed by specification,
the convention in the collections implementations is to compare an argument o to
a contained element e by calling o.equals(e). Thus List1.contains(o) should
change to
return o.equals(e0); // implicit nullcheck of o
and the call to Objects.requireNonNull(o) can be dropped.
-------
287 @Override
288 public boolean containsAll(Collection<?> o) {
289 return o.isEmpty(); // implicit nullcheck
290 }
324 @Override
325 public boolean contains(Object o) {
326 return o.equals(e0); // implicit nullcheck of o
327 }
373 @Override
374 public boolean contains(Object o) {
375 return o.equals(e0) || o.equals(e1); // implicit nullcheck of o
376 }
While it does add a bit of clutter, I like the idea of commenting every location
where an implicit nullcheck is done, as is done here, and in several other
places you had removed the Objects.requireNonNull() call. Note that some
implicit nullchecks in the List code aren't commented; comments should be added
there. Please also add a comment at this code in Set2:
355 if (e0.equals(Objects.requireNonNull(e1))) {
something like "// implicit nullcheck of e0".
Also please add comments for SetN.probe() and MapN.probe() to indicate that
callers are relying on them to do implicit nullchecks of their args.
-------
In Set2, SetN, Map1, and MapN, the addition of @Stable also dropped the
"private" access modifier. Other implementations still have corresponding fields
as private. Was this intentional? I think they should all remain private.
-------
664 @Override
665 public boolean containsKey(Object o) {
666 return probe(o) >= 0; // probe implicity nullchecks o
667 }
Typo "implicity".
-------
669 @Override
670 public boolean containsValue(Object o) {
671 for (int i = 1; i < table.length; i += 2) {
672 Object v = table[i];
673 if (v != null && o.equals(v)) {
674 return true;
675 }
676 }
677 return false;
678 }
At 673, // implicit nullcheck of o
-------
Thanks!!
s'marks
More information about the core-libs-dev
mailing list