RFR(m): 8139233 add initial compact immutable collection implementations

Stuart Marks stuart.marks at oracle.com
Thu May 5 06:32:08 UTC 2016


On 5/4/16 1:38 AM, Remi Forax wrote:
> nice work !

Spoken like a true university professor: "nice work" followed by 100 lines of 
criticism. :-)

> first, all classes should be final otherwise, they are not truly immutable and all arrays should be marked as @Stable.

OK, I'll make the classes final.

On @Stable, I had a chat with Paul Sandoz about this, and he suggested holding 
back from adding this. Its semantics are quite particular and are subject to 
change, and widespread use could easily turn into misuse.

> List0, List1,
>   why not using Collections.emptyList(), Collections.singletonList() here ?

Serialization compatibility with future versions. I want all of these to 
serialize to the same serial proxy in JDK 9, so that in JDK 9+ they can be 
deserialized to whatever equivalent implementation classes exist in that version 
of the JDK. For example, there might be a single field-based implementation that 
covers a small range of sizes, or an implementation using value types.

> ListN:
>   - i don't think that extending AbstractList is a good idea here,
>     AbstractList provide the failfast/modCount mecanism not needed here.
>     Moreover, i think you should have some overriden methods on par with Arrays.asList()
>     (i.e. a custom isEmpty, toArray, indexOf, contains, iterator, spliterator and forEach)
>     otherwise people will use Arrays.asList just because it's faster :(

The implementations all extend AbstractList/Set/Map mainly for implementation 
convenience. This way it makes it easy to add and remove implementations 
tailored for specific sizes, since so much code is shared with the abstract 
superclasses. But they do come with some baggage, as you point out. As the set 
of implementation classes settles down, it would make more sense to override 
more methods, and refactor to handle sharing, at which point maybe the 
dependence on the abstract superclasses can be severed.

>   - in the constructor, you should use a local variable here,
>         @SafeVarargs
>         ListN(E... input) {
>             // copy and check manually to avoid TOCTOU
>             @SuppressWarnings("unchecked")
>             E[] elements = (E[])new Object[input.length]; // implicit nullcheck of input
>             for (int i = 0; i < input.length; i++) {
>                 elements[i] = Objects.requireNonNull(input[i]);
>             }
>             this.elements = elements;
>         }

Nice. Will fix.

> List2:
>   - same as for ListN, should not inherits from AbstractList.
>   - i wonder why iterator() is not overriden like with Set2.

Bringing up a List implementation with AbstractList requires only overriding 
get() and size(), but not iterator(). On the other hand, bringing up a Set 
implementation using AbstractSet requires overriding only iterator() and size().

> Set2:
>   - again, here, i don't think that inheriting from AbstractSet is a good idea.
>   - in the iterator, pos (position ?) should be private and next() can be written without the '+=',
>                 public E next() {
>                     if (pos == 0) {
>                         pos = 1;
>                         return e0;
>                     } else if (pos == 1) {
>                         pos = 2;
>                         return e1;
>                     } else {
>                         throw new NoSuchElementException();
>                     }
>                 }
>   - the iterator should not be defined as inner class (with a reference to Set2) but
>     be defined as a static class that contains a copy of e1 and e2,
>     this will save an indirection and avoid to keep a link on the Set if it is transient.

Good fixes, thanks. Some of this is stuff left over from when there were 
field-based implementations with larger sizes.

> SetN:
>   - in the constructor, use a local variable like for ListN
>   - the @SuppressWarnings for contains is wrong, probe should take an Object, not an E.

Ah, nice cleanup.

>   - the iterator should not be an inner class like Set2 and take the array as parameter.
>     idx should be private. Instead of doing the loop in hasNext and next, the loop should
>     be done once in the constructor and in next. So the code of hasNext will be simple
>     and the JIT will be able to fold a call to hasNext() followed by a call to next().

OK, I'll look at this.

>    - i don't understand how the serialization can work given that the SALT may be different between two VMs

The salt is XORed with the element's hash to determine the position in the 
collection's array. Serializing it writes all the objects in the array from left 
to right, but we don't actually care what the order is. Upon deserialization, 
the objects' hash values are XORed with the deserializing JVM's salt and 
(probably) end up in different positions in the array of the newly deserialized 
collection. But again we don't care where they end up.

Same goes for MapN's keys and values.

> MapN:
>   - see SetN :)

:-)

> SerialProxy:
>   - I believe the class SerialProxy should be public, no ?

No, the proxy object can and usually should be private. What's missing is a doc 
comment specifying the serial format. This should have an "@serial include" tag, 
which will cause the doc comment to end up in the serialized-form.html file. 
This works even if the proxy class itself is private.

This work is tracked by JDK-8133977.

>   - The constructor should take a Object... to simplify all calls in the different writeReplace.

Good idea.

>   - fields flags and array should be private

OK.

Thanks for all the comments! I'll work on an updated webrev and post it soon.

s'marks




More information about the core-libs-dev mailing list