Hi Stuart,
On 01/11/2017 04:10 AM, Stuart Marks wrote:
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.
As they are all rather simple and straightforward I've added them for
consistency.
-------
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.
Done.
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.
Done.
-------
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.
Done.
-------
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.
This was intentional: for those implementations where I dropped private
there are inner
classes which access the fields directly. This leads to synthetic
accessors, which shows up
as JIT compilation overhead during module bootstrap profiling.
This is what made me look at this in the first place, and a large part
of the reason why I
believe that it's motivated to fast-track this enhancement as a means to
improve JDK 9
startup metrics. If you have a strong preference against them being
made package-private
there's a similar gain to be had by passing them as arguments to
concrete inner classes,
e.g., for Set2:
static class Itr<E> implements Iterator<E> {
private final E e0;
private final E e1;
private int idx = 0;
Itr(E e0, E e1) {
this.e0 = e0;
this.e1 = e1;
}
@Override
public boolean hasNext() {
return idx < 2;
}
@Override
public E next() {
if (idx == 0) {
idx = 1;
return e0;
} else if (idx == 1) {
idx = 2;
return e1;
} else {
throw new NoSuchElementException();
}
}
}
@Override
public Iterator<E> iterator() {
return new Itr<E>(e0, e1);
}
-------
664 @Override
665 public boolean containsKey(Object o) {
666 return probe(o) >= 0; // probe implicity nullchecks o
667 }
Typo "implicity".
Fixed.
-------
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
All done:
http://cr.openjdk.java.net/~redestad/8166365/webrev.02/
Thanks for the thorough review!
/Claes
-------
Thanks!!
s'marks