# HG changeset patch # User redestad # Date 1512569256 -3600 # Wed Dec 06 15:07:36 2017 +0100 # Node ID 7f069353a949858ac3f5f460f15da625a053bd0e # Parent 794cbfa7a309fedafc6c2891e2bc5089302c43c7 8193128: Reduce number of implementation classes returned by List/Set/Map.of() Reviewed-by: smarks diff --git a/src/java.base/share/classes/java/lang/Module.java b/src/java.base/share/classes/java/lang/Module.java --- a/src/java.base/share/classes/java/lang/Module.java +++ b/src/java.base/share/classes/java/lang/Module.java @@ -247,13 +247,16 @@ // -- - // special Module to mean "all unnamed modules" - private static final Module ALL_UNNAMED_MODULE = new Module(null); - private static final Set ALL_UNNAMED_MODULE_SET = Set.of(ALL_UNNAMED_MODULE); + private static class Special { + // special Module to mean "all unnamed modules" + static final Module ALL_UNNAMED_MODULE = new Module(null); + static final Set ALL_UNNAMED_MODULE_SET = Set.of(ALL_UNNAMED_MODULE); - // special Module to mean "everyone" - private static final Module EVERYONE_MODULE = new Module(null); - private static final Set EVERYONE_SET = Set.of(EVERYONE_MODULE); + // special Module to mean "everyone" + static final Module EVERYONE_MODULE = new Module(null); + static final Set EVERYONE_SET = Set.of(EVERYONE_MODULE); + } + /** * The holder of data structures to support readability, exports, and @@ -325,7 +328,7 @@ // if other is an unnamed module then check if this module reads // all unnamed modules if (!other.isNamed() - && ReflectionData.reads.containsKeyPair(this, ALL_UNNAMED_MODULE)) + && ReflectionData.reads.containsKeyPair(this, Special.ALL_UNNAMED_MODULE)) return true; return false; @@ -382,7 +385,7 @@ * @apiNote Used by the --add-reads command line option. */ void implAddReadsAllUnnamed() { - implAddReads(Module.ALL_UNNAMED_MODULE, true); + implAddReads(Special.ALL_UNNAMED_MODULE, true); } /** @@ -404,7 +407,7 @@ if (!canRead(other)) { // update VM first, just in case it fails if (syncVM) { - if (other == ALL_UNNAMED_MODULE) { + if (other == Special.ALL_UNNAMED_MODULE) { addReads0(this, null); } else { addReads0(this, other); @@ -507,7 +510,7 @@ */ public boolean isExported(String pn) { Objects.requireNonNull(pn); - return implIsExportedOrOpen(pn, EVERYONE_MODULE, /*open*/false); + return implIsExportedOrOpen(pn, Special.EVERYONE_MODULE, /*open*/false); } /** @@ -531,7 +534,7 @@ */ public boolean isOpen(String pn) { Objects.requireNonNull(pn); - return implIsExportedOrOpen(pn, EVERYONE_MODULE, /*open*/true); + return implIsExportedOrOpen(pn, Special.EVERYONE_MODULE, /*open*/true); } @@ -594,12 +597,12 @@ */ private boolean allows(Set targets, Module module) { if (targets != null) { - if (targets.contains(EVERYONE_MODULE)) + if (targets.contains(Special.EVERYONE_MODULE)) return true; - if (module != EVERYONE_MODULE) { + if (module != Special.EVERYONE_MODULE) { if (targets.contains(module)) return true; - if (!module.isNamed() && targets.contains(ALL_UNNAMED_MODULE)) + if (!module.isNamed() && targets.contains(Special.ALL_UNNAMED_MODULE)) return true; } } @@ -612,7 +615,7 @@ */ private boolean isReflectivelyExportedOrOpen(String pn, Module other, boolean open) { // exported or open to all modules - Map exports = ReflectionData.exports.get(this, EVERYONE_MODULE); + Map exports = ReflectionData.exports.get(this, Special.EVERYONE_MODULE); if (exports != null) { Boolean b = exports.get(pn); if (b != null) { @@ -621,7 +624,7 @@ } } - if (other != EVERYONE_MODULE) { + if (other != Special.EVERYONE_MODULE) { // exported or open to other exports = ReflectionData.exports.get(this, other); @@ -635,7 +638,7 @@ // other is an unnamed module && exported or open to all unnamed if (!other.isNamed()) { - exports = ReflectionData.exports.get(this, ALL_UNNAMED_MODULE); + exports = ReflectionData.exports.get(this, Special.ALL_UNNAMED_MODULE); if (exports != null) { Boolean b = exports.get(pn); if (b != null) { @@ -774,7 +777,7 @@ * @apiNote This method is for JDK tests only. */ void implAddExports(String pn) { - implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, false, true); + implAddExportsOrOpens(pn, Special.EVERYONE_MODULE, false, true); } /** @@ -792,7 +795,7 @@ * @apiNote Used by the --add-exports command line option. */ void implAddExportsToAllUnnamed(String pn) { - implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, false, true); + implAddExportsOrOpens(pn, Special.ALL_UNNAMED_MODULE, false, true); } /** @@ -802,7 +805,7 @@ * @apiNote This method is for VM white-box testing. */ void implAddExportsNoSync(String pn) { - implAddExportsOrOpens(pn.replace('/', '.'), Module.EVERYONE_MODULE, false, false); + implAddExportsOrOpens(pn.replace('/', '.'), Special.EVERYONE_MODULE, false, false); } /** @@ -821,7 +824,7 @@ * @apiNote This method is for JDK tests only. */ void implAddOpens(String pn) { - implAddExportsOrOpens(pn, Module.EVERYONE_MODULE, true, true); + implAddExportsOrOpens(pn, Special.EVERYONE_MODULE, true, true); } /** @@ -839,7 +842,7 @@ * @apiNote Used by the --add-opens command line option. */ void implAddOpensToAllUnnamed(String pn) { - implAddExportsOrOpens(pn, Module.ALL_UNNAMED_MODULE, true, true); + implAddExportsOrOpens(pn, Special.ALL_UNNAMED_MODULE, true, true); } /** @@ -889,9 +892,9 @@ // update VM first, just in case it fails if (syncVM) { - if (other == EVERYONE_MODULE) { + if (other == Special.EVERYONE_MODULE) { addExportsToAll0(this, pn); - } else if (other == ALL_UNNAMED_MODULE) { + } else if (other == Special.ALL_UNNAMED_MODULE) { addExportsToAllUnnamed0(this, pn); } else { addExports0(this, pn, other); @@ -930,9 +933,9 @@ } while (iterator.hasNext()) { String pn = iterator.next(); - Set prev = openPackages.putIfAbsent(pn, ALL_UNNAMED_MODULE_SET); + Set prev = openPackages.putIfAbsent(pn, Special.ALL_UNNAMED_MODULE_SET); if (prev != null) { - prev.add(ALL_UNNAMED_MODULE); + prev.add(Special.ALL_UNNAMED_MODULE); } // update VM to export the package @@ -1152,7 +1155,7 @@ // automatic modules read all unnamed modules if (descriptor.isAutomatic()) { - m.implAddReads(ALL_UNNAMED_MODULE, true); + m.implAddReads(Special.ALL_UNNAMED_MODULE, true); } // exports and opens, skipped for open and automatic @@ -1244,7 +1247,7 @@ } else { // unqualified exports addExportsToAll0(m, source); - exportedPackages.put(source, EVERYONE_SET); + exportedPackages.put(source, Special.EVERYONE_SET); } } @@ -1289,7 +1292,7 @@ } else { // unqualified opens addExportsToAll0(m, source); - openPackages.put(source, EVERYONE_SET); + openPackages.put(source, Special.EVERYONE_SET); } } @@ -1299,7 +1302,7 @@ // skip export if package is already open to everyone Set openToTargets = openPackages.get(source); - if (openToTargets != null && openToTargets.contains(EVERYONE_MODULE)) + if (openToTargets != null && openToTargets.contains(Special.EVERYONE_MODULE)) continue; if (exports.isQualified()) { @@ -1321,7 +1324,7 @@ } else { // unqualified exports addExportsToAll0(m, source); - exportedPackages.put(source, EVERYONE_SET); + exportedPackages.put(source, Special.EVERYONE_SET); } } diff --git a/src/java.base/share/classes/java/util/ImmutableCollections.java b/src/java.base/share/classes/java/util/ImmutableCollections.java --- a/src/java.base/share/classes/java/util/ImmutableCollections.java +++ b/src/java.base/share/classes/java/util/ImmutableCollections.java @@ -37,6 +37,7 @@ import java.util.function.UnaryOperator; import jdk.internal.misc.SharedSecrets; import jdk.internal.vm.annotation.Stable; +import sun.security.action.GetPropertyAction; /** * Container class for immutable collections. Not part of the public API. @@ -72,159 +73,477 @@ // ---------- List Implementations ---------- - abstract static class AbstractImmutableList extends AbstractList - implements RandomAccess, Serializable { + static abstract class AbstractImmutableList extends AbstractCollection + implements List, RandomAccess, Serializable { + + /** + * The number of collection specializations to use; numbers 0, 1 and 2 are supported. + * + * 0: Only a single, generic class, e.g., ListN, will be used. This removes footprint + * benefits of having smaller specializations, but may help make call-sites + * monomorphic, which may improve performance. + * 1: Single element collections will be specialized, gaining back some of the + * footprint gains, while pushing call-sites towards being either mono- or + * bimorphic that would otherwise be megamorphic. + * 2: Both single and two element lists will be specialized. Best setting for + * footprint. + * + * Defined here rather at top-level to get around a bootstrapping issue + * due to ImmutableCollections (but not any of the List classes) being + * initialized before System properties are initialized. + */ + static final int SPECIALIZATIONS; + static { + int specializations = Integer.parseInt( + GetPropertyAction.privilegedGetProperty( + "jdk.ImmutableCollections.specializations", "2")); + SPECIALIZATIONS = Math.min(2, Math.max(0, specializations)); + } + static final List EMPTY_LIST = new ListN<>(); + + @SuppressWarnings("unchecked") + static List emptyList() { + return (List) EMPTY_LIST; + } + + static List of(E[] elements) { + switch (elements.length) { // implicit null check of elements + case 0: + return emptyList(); + case 1: + return of(elements[0]); + case 2: + return of(elements[0], elements[1]); + default: + return new ListN<>(elements); + } + } + + static List of(E e1) { + switch (SPECIALIZATIONS) { + case 1: + return new List1<>(e1); + case 2: + return new List12<>(e1); + default: + return new ListN<>(e1); + } + } + + static List of(E e0, E e1) { + switch (SPECIALIZATIONS) { + case 2: + return new List12<>(e0, e1); + default: + return new ListN<>(e0, e1); + } + } + + // all mutating methods throw UnsupportedOperationException @Override public boolean add(E e) { throw uoe(); } + @Override public void add(int index, E element) { throw uoe(); } @Override public boolean addAll(Collection c) { throw uoe(); } @Override public boolean addAll(int index, Collection c) { throw uoe(); } @Override public void clear() { throw uoe(); } @Override public boolean remove(Object o) { throw uoe(); } + @Override public E remove(int index) { throw uoe(); } @Override public boolean removeAll(Collection c) { throw uoe(); } @Override public boolean removeIf(Predicate filter) { throw uoe(); } @Override public void replaceAll(UnaryOperator operator) { throw uoe(); } @Override public boolean retainAll(Collection c) { throw uoe(); } + @Override public E set(int index, E element) { throw uoe(); } @Override public void sort(Comparator c) { throw uoe(); } - } - - static final class List0 extends AbstractImmutableList { - private static final List0 INSTANCE = new List0<>(); - - @SuppressWarnings("unchecked") - static List0 instance() { - return (List0) INSTANCE; - } - - private List0() { } @Override - public int size() { - return 0; + public int indexOf(Object o) { + // Should be checked for null, needs a CSR. See + // Objects.requireNonNull(o); + ListIterator it = listIterator(); + while (it.hasNext()) { + if (o.equals(it.next())) { + return it.previousIndex(); + } + } + return -1; } @Override - public E get(int index) { - Objects.checkIndex(index, 0); // always throws IndexOutOfBoundsException - return null; // but the compiler doesn't know this + public int lastIndexOf(Object o) { + Objects.requireNonNull(o); + ListIterator it = listIterator(); + while (it.hasNext()) { + if (o.equals(it.next())) { + return it.previousIndex(); + } + } + return -1; + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public List subList(int fromIndex, int toIndex) { + int size = size(); + Objects.checkFromToIndex(fromIndex, toIndex, size); + if (size == 0 || fromIndex == toIndex) { + return ListN.emptyList(); + } else if (size == 1) { + // checks above deal with corner cases subList(0,0) and subList(1,1) + // that would return the empty list + assert(fromIndex == 0 && toIndex == 1); + return this; + } else { + return new SubList(this, fromIndex, toIndex); + } + } + + private static class SubList extends AbstractList implements RandomAccess { + private final List root; + private final int offset; + int size; + + // all mutating methods throw UnsupportedOperationException + @Override public boolean add(E e) { throw uoe(); } + @Override public void add(int index, E element) { throw uoe(); } + @Override public boolean addAll(Collection c) { throw uoe(); } + @Override public boolean addAll(int index, Collection c) { throw uoe(); } + @Override public void clear() { throw uoe(); } + @Override public boolean remove(Object o) { throw uoe(); } + @Override public E remove(int index) { throw uoe(); } + @Override public boolean removeAll(Collection c) { throw uoe(); } + @Override public boolean removeIf(Predicate filter) { throw uoe(); } + @Override public void replaceAll(UnaryOperator operator) { throw uoe(); } + @Override public boolean retainAll(Collection c) { throw uoe(); } + @Override public E set(int index, E element) { throw uoe(); } + @Override public void sort(Comparator c) { throw uoe(); } + + /** + * Constructs a sublist of an arbitrary AbstractList, which is + * not a SubList itself. + */ + public SubList(List root, int fromIndex, int toIndex) { + this.root = root; + this.offset = fromIndex; + this.size = toIndex - fromIndex; + } + + /** + * Constructs a sublist of another SubList. + */ + protected SubList(SubList parent, int fromIndex, int toIndex) { + this.root = parent.root; + this.offset = parent.offset + fromIndex; + this.size = toIndex - fromIndex; + } + + public E get(int index) { + Objects.checkIndex(index, size); + return root.get(offset + index); + } + + public int size() { + return size; + } + + protected void removeRange(int fromIndex, int toIndex) { + throw uoe(); + } + + public Iterator iterator() { + return listIterator(); + } + + public ListIterator listIterator(int index) { + rangeCheck(index); + + return new ListIterator() { + private final ListIterator i = + root.listIterator(offset + index); + + public boolean hasNext() { + return nextIndex() < size; + } + + public E next() { + if (hasNext()) + return i.next(); + else + throw new NoSuchElementException(); + } + + public boolean hasPrevious() { + return previousIndex() >= 0; + } + + public E previous() { + if (hasPrevious()) + return i.previous(); + else + throw new NoSuchElementException(); + } + + public int nextIndex() { + return i.nextIndex() - offset; + } + + public int previousIndex() { + return i.previousIndex() - offset; + } + + public void remove() { throw uoe(); } + public void set(E e) { throw uoe(); } + public void add(E e) { throw uoe(); } + }; + } + + public List subList(int fromIndex, int toIndex) { + subListRangeCheck(fromIndex, toIndex, size); + return new SubList<>(this, fromIndex, toIndex); + } + + private void rangeCheck(int index) { + if (index < 0 || index > size) + throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); + } + + private String outOfBoundsMsg(int index) { + return "Index: "+index+", Size: "+size; + } } @Override public Iterator iterator() { - return Collections.emptyIterator(); + return new Itr(); + } + + @Override + public ListIterator listIterator() { + return listIterator(0); + } + + @Override + public ListIterator listIterator(final int index) { + Objects.checkIndex(index, size()); + return new ListItr(index); + } + + private class Itr implements Iterator { + Itr() { + size = size(); + } + + int cursor = 0; + + private final int size; + + public boolean hasNext() { + return cursor != size; + } + + public E next() { + try { + int i = cursor; + E next = get(i); + cursor = i + 1; + return next; + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } + } + + public void remove() { + throw uoe(); + } + } + + private class ListItr extends Itr implements ListIterator { + ListItr(int index) { + cursor = index; + } + + public boolean hasPrevious() { + return cursor != 0; + } + + public E previous() { + try { + int i = cursor - 1; + E previous = get(i); + cursor = i; + return previous; + } catch (IndexOutOfBoundsException e) { + throw new NoSuchElementException(); + } + } + + public int nextIndex() { + return cursor; + } + + public int previousIndex() { + return cursor - 1; + } + + public void set(E e) { + throw uoe(); + } + + public void add(E e) { + throw uoe(); + } + } + + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + + if (!(o instanceof List)) { + return false; + } + + Iterator e1 = iterator(); + Iterator e2 = ((List) o).iterator(); + while (e1.hasNext() && e2.hasNext()) { + E o1 = e1.next(); + Object o2 = e2.next(); + if (!(o1==null ? o2==null : o1.equals(o2))) + return false; + } + return !(e1.hasNext() || e2.hasNext()); + } + + IndexOutOfBoundsException outOfBounds(int index) { + return new IndexOutOfBoundsException("Index: " + index + " Size: " + size()); + } + + } + + static final class List1 extends AbstractImmutableList { + + @Stable + private final E e0; + + List1(E input) { + e0 = Objects.requireNonNull(input); + } + + @Override + @SuppressWarnings("unchecked") + public int size() { + return 1; + } + + @Override + @SuppressWarnings("unchecked") + public E get(int index) { + Objects.checkIndex(index, 1); + return e0; + } + + @Override + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + return o.equals(e0); // implicit null check of o + } + + @Override + @SuppressWarnings("unchecked") + public int hashCode() { + return 31 + e0.hashCode(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } + @SuppressWarnings("unchecked") private Object writeReplace() { - return new CollSer(CollSer.IMM_LIST); + return new CollSer(CollSer.IMM_LIST, e0); + } + } + + static final class List12 extends AbstractImmutableList { + + @Stable + private final E e0; + + @Stable + private final E e1; + + List12(E e0) { + this.e0 = Objects.requireNonNull(e0); + this.e1 = null; + } + + List12(E e0, E e1) { + this.e0 = Objects.requireNonNull(e0); + this.e1 = Objects.requireNonNull(e1); } @Override - public boolean contains(Object o) { - Objects.requireNonNull(o); - return false; + @SuppressWarnings("unchecked") + public int size() { + return e1 != null ? 2 : 1; } @Override - public boolean containsAll(Collection o) { - return o.isEmpty(); // implicit nullcheck of o + @SuppressWarnings("unchecked") + public E get(int index) { + if (index == 0) { + return e0; + } else if (e1 != null && index == 1) { + return e1; + } + throw outOfBounds(index); } @Override - public int hashCode() { - return 1; - } - } - - static final class List1 extends AbstractImmutableList { - @Stable - private final E e0; - - List1(E e0) { - this.e0 = Objects.requireNonNull(e0); + @SuppressWarnings("unchecked") + public boolean contains(Object o) { + return o.equals(e0) || o.equals(e1); // implicit null check of o } @Override - public int size() { - return 1; - } - - @Override - public E get(int index) { - Objects.checkIndex(index, 1); - return e0; + @SuppressWarnings("unchecked") + public int hashCode() { + int hash = 31 + e0.hashCode(); + if (e1 != null) { + hash = 31 * hash + e1.hashCode(); + } + return hash; } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { throw new InvalidObjectException("not serial proxy"); } - private Object writeReplace() { - return new CollSer(CollSer.IMM_LIST, e0); - } - - @Override - public boolean contains(Object o) { - return o.equals(e0); // implicit nullcheck of o - } - - @Override - public int hashCode() { - return 31 + e0.hashCode(); - } - } - - static final class List2 extends AbstractImmutableList { - @Stable - private final E e0; - @Stable - private final E e1; - - List2(E e0, E e1) { - this.e0 = Objects.requireNonNull(e0); - this.e1 = Objects.requireNonNull(e1); - } - - @Override - public int size() { - return 2; - } - - @Override - public E get(int index) { - Objects.checkIndex(index, 2); - if (index == 0) { - return e0; - } else { // index == 1 - return e1; - } - } - - @Override - public boolean contains(Object o) { - return o.equals(e0) || o.equals(e1); // implicit nullcheck of o - } - - @Override - public int hashCode() { - int hash = 31 + e0.hashCode(); - return 31 * hash + e1.hashCode(); - } - - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - throw new InvalidObjectException("not serial proxy"); - } - + @SuppressWarnings("unchecked") private Object writeReplace() { return new CollSer(CollSer.IMM_LIST, e0, e1); } } static final class ListN extends AbstractImmutableList { + @Stable private final E[] elements; + @SuppressWarnings("unchecked") + ListN(E e0) { + elements = (E[])new Object[] { e0 }; + } + + @SuppressWarnings("unchecked") + ListN(E e0, E e1) { + elements = (E[])new Object[] { e0, e1 }; + } + @SafeVarargs ListN(E... input) { // copy and check manually to avoid TOCTOU @@ -233,24 +552,32 @@ for (int i = 0; i < input.length; i++) { tmp[i] = Objects.requireNonNull(input[i]); } - this.elements = tmp; + elements = tmp; } @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + @SuppressWarnings("unchecked") public int size() { return elements.length; } @Override + @SuppressWarnings("unchecked") public E get(int index) { - Objects.checkIndex(index, elements.length); return elements[index]; } @Override + @SuppressWarnings("unchecked") public boolean contains(Object o) { + Objects.requireNonNull(o); for (E e : elements) { - if (o.equals(e)) { // implicit nullcheck of o + if (o.equals(e)) { return true; } } @@ -258,6 +585,7 @@ } @Override + @SuppressWarnings("unchecked") public int hashCode() { int hash = 1; for (E e : elements) { @@ -270,6 +598,7 @@ throw new InvalidObjectException("not serial proxy"); } + @SuppressWarnings("unchecked") private Object writeReplace() { return new CollSer(CollSer.IMM_LIST, elements); } @@ -277,6 +606,13 @@ // ---------- Set Implementations ---------- + static final Set EMPTY_SET = new SetN<>(); + + @SuppressWarnings("unchecked") + static Set emptySet() { + return (Set) EMPTY_SET; + } + abstract static class AbstractImmutableSet extends AbstractSet implements Serializable { @Override public boolean add(E e) { throw uoe(); } @Override public boolean addAll(Collection c) { throw uoe(); } @@ -285,50 +621,39 @@ @Override public boolean removeAll(Collection c) { throw uoe(); } @Override public boolean removeIf(Predicate filter) { throw uoe(); } @Override public boolean retainAll(Collection c) { throw uoe(); } - } - - static final class Set0 extends AbstractImmutableSet { - private static final Set0 INSTANCE = new Set0<>(); @SuppressWarnings("unchecked") - static Set0 instance() { - return (Set0) INSTANCE; + static Set of(E[] elements) { + switch (elements.length) { // implicit null check of elements + case 0: + return emptySet(); + case 1: + return of(elements[0]); + case 2: + return of(elements[0], elements[1]); + default: + return new SetN<>(elements); + } } - private Set0() { } - - @Override - public int size() { - return 0; + static Set of(E e1) { + switch (AbstractImmutableList.SPECIALIZATIONS) { + case 1: + return new Set1<>(e1); + case 2: + return new Set12<>(e1); + default: + return new SetN<>(e1); + } } - @Override - public boolean contains(Object o) { - Objects.requireNonNull(o); - return false; - } - - @Override - public boolean containsAll(Collection o) { - return o.isEmpty(); // implicit nullcheck of o - } - - @Override - public Iterator iterator() { - return Collections.emptyIterator(); - } - - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - throw new InvalidObjectException("not serial proxy"); - } - - private Object writeReplace() { - return new CollSer(CollSer.IMM_SET); - } - - @Override - public int hashCode() { - return 0; + static Set of(E e0, E e1) { + switch (AbstractImmutableList.SPECIALIZATIONS) { + case 2: + return new Set12<>(e0, e1); + default: + return new SetN<>(e0, e1); + } } } @@ -369,13 +694,18 @@ } } - static final class Set2 extends AbstractImmutableSet { + static final class Set12 extends AbstractImmutableSet { @Stable final E e0; @Stable final E e1; - Set2(E e0, E e1) { + Set12(E e0) { + this.e0 = Objects.requireNonNull(e0); + this.e1 = null; + } + + Set12(E e0, E e1) { if (e0.equals(Objects.requireNonNull(e1))) { // implicit nullcheck of e0 throw new IllegalArgumentException("duplicate element: " + e0); } @@ -391,7 +721,7 @@ @Override public int size() { - return 2; + return (e1 == null) ? 1 : 2; } @Override @@ -401,26 +731,26 @@ @Override public int hashCode() { - return e0.hashCode() + e1.hashCode(); + return e0.hashCode() + (e1 == null ? 0 : e1.hashCode()); } @Override public Iterator iterator() { return new Iterator() { - private int idx = 0; + private int idx = size(); @Override public boolean hasNext() { - return idx < 2; + return idx > 0; } @Override public E next() { - if (idx == 0) { + if (idx == 1) { + idx = 0; + return e0; + } else if (idx == 2) { idx = 1; - return e0; - } else if (idx == 1) { - idx = 2; return e1; } else { throw new NoSuchElementException(); @@ -474,7 +804,8 @@ @Override public boolean contains(Object o) { - return probe(o) >= 0; // implicit nullcheck of o + Objects.requireNonNull(0); + return size > 0 && probe(o) >= 0; // implicit nullcheck of o } @Override @@ -549,6 +880,13 @@ // ---------- Map Implementations ---------- + static final Map EMPTY_MAP = new MapN<>(); + + @SuppressWarnings("unchecked") + static Map emptyMap() { + return (Map) EMPTY_MAP; + } + abstract static class AbstractImmutableMap extends AbstractMap implements Serializable { @Override public void clear() { throw uoe(); } @Override public V compute(K key, BiFunction rf) { throw uoe(); } @@ -563,46 +901,34 @@ @Override public V replace(K key, V value) { throw uoe(); } @Override public boolean replace(K key, V oldValue, V newValue) { throw uoe(); } @Override public void replaceAll(BiFunction f) { throw uoe(); } - } - static final class Map0 extends AbstractImmutableMap { - private static final Map0 INSTANCE = new Map0<>(); - - @SuppressWarnings("unchecked") - static Map0 instance() { - return (Map0) INSTANCE; + static Map of(K k1, V v1) { + if (AbstractImmutableList.SPECIALIZATIONS > 0) { + return new ImmutableCollections.Map1<>(k1, v1); + } else { + return new ImmutableCollections.MapN<>(k1, v1); + } } - private Map0() { } - - @Override - public Set> entrySet() { - return Set.of(); - } - - @Override - public boolean containsKey(Object o) { - Objects.requireNonNull(o); - return false; - } - - @Override - public boolean containsValue(Object o) { - Objects.requireNonNull(o); - return false; - } - - private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - throw new InvalidObjectException("not serial proxy"); - } - - private Object writeReplace() { - return new CollSer(CollSer.IMM_MAP); - } - - @Override - public int hashCode() { - return 0; + @SafeVarargs + @SuppressWarnings("varargs") + static Map ofEntries(Entry... entries) { + if (entries.length == 0) { // implicit null check of entries array + return ImmutableCollections.emptyMap(); + } else if (AbstractImmutableList.SPECIALIZATIONS > 0 && entries.length == 1) { + // implicit null check of the array slot + return new ImmutableCollections.Map1<>(entries[0].getKey(), + entries[0].getValue()); + } else { + Object[] kva = new Object[entries.length << 1]; + int a = 0; + for (Entry entry : entries) { + // implicit null checks of each array slot + kva[a++] = entry.getKey(); + kva[a++] = entry.getValue(); + } + return new ImmutableCollections.MapN<>(kva); + } } } @@ -658,9 +984,15 @@ static final class MapN extends AbstractImmutableMap { @Stable final Object[] table; // pairs of key, value - @Stable final int size; // number of pairs + MapN(K key, V value) { + table = new Object[2]; + table[0] = key; + table[1] = value; + size = 1; + } + MapN(Object... input) { if ((input.length & 1) != 0) { // implicit nullcheck of input throw new InternalError("length is odd"); @@ -689,7 +1021,8 @@ @Override public boolean containsKey(Object o) { - return probe(o) >= 0; // implicit nullcheck of o + Objects.requireNonNull(0); + return size > 0 && probe(o) >= 0; } @Override @@ -718,6 +1051,9 @@ @Override @SuppressWarnings("unchecked") public V get(Object o) { + if (size == 0) { + return null; + } int i = probe(o); if (i >= 0) { return (V)table[i+1]; @@ -948,7 +1284,7 @@ return Set.of(array); case IMM_MAP: if (array.length == 0) { - return ImmutableCollections.Map0.instance(); + return ImmutableCollections.emptyMap(); } else if (array.length == 2) { return new ImmutableCollections.Map1<>(array[0], array[1]); } else { diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -788,7 +788,7 @@ * @since 9 */ static List of() { - return ImmutableCollections.List0.instance(); + return ImmutableCollections.AbstractImmutableList.emptyList(); } /** @@ -804,7 +804,7 @@ * @since 9 */ static List of(E e1) { - return new ImmutableCollections.List1<>(e1); + return ImmutableCollections.AbstractImmutableList.of(e1); } /** @@ -821,7 +821,7 @@ * @since 9 */ static List of(E e1, E e2) { - return new ImmutableCollections.List2<>(e1, e2); + return ImmutableCollections.AbstractImmutableList.of(e1, e2); } /** @@ -1029,16 +1029,7 @@ @SafeVarargs @SuppressWarnings("varargs") static List of(E... elements) { - switch (elements.length) { // implicit null check of elements - case 0: - return ImmutableCollections.List0.instance(); - case 1: - return new ImmutableCollections.List1<>(elements[0]); - case 2: - return new ImmutableCollections.List2<>(elements[0], elements[1]); - default: - return new ImmutableCollections.ListN<>(elements); - } + return ImmutableCollections.AbstractImmutableList.of(elements); } /** diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -1287,7 +1287,7 @@ * @since 9 */ static Map of() { - return ImmutableCollections.Map0.instance(); + return ImmutableCollections.emptyMap(); } /** @@ -1304,7 +1304,7 @@ * @since 9 */ static Map of(K k1, V v1) { - return new ImmutableCollections.Map1<>(k1, v1); + return ImmutableCollections.AbstractImmutableMap.of(k1, v1); } /** @@ -1603,22 +1603,7 @@ @SafeVarargs @SuppressWarnings("varargs") static Map ofEntries(Entry... entries) { - if (entries.length == 0) { // implicit null check of entries array - return ImmutableCollections.Map0.instance(); - } else if (entries.length == 1) { - // implicit null check of the array slot - return new ImmutableCollections.Map1<>(entries[0].getKey(), - entries[0].getValue()); - } else { - Object[] kva = new Object[entries.length << 1]; - int a = 0; - for (Entry entry : entries) { - // implicit null checks of each array slot - kva[a++] = entry.getKey(); - kva[a++] = entry.getValue(); - } - return new ImmutableCollections.MapN<>(kva); - } + return ImmutableCollections.AbstractImmutableMap.ofEntries(entries); } /** diff --git a/src/java.base/share/classes/java/util/Set.java b/src/java.base/share/classes/java/util/Set.java --- a/src/java.base/share/classes/java/util/Set.java +++ b/src/java.base/share/classes/java/util/Set.java @@ -449,7 +449,7 @@ * @since 9 */ static Set of() { - return ImmutableCollections.Set0.instance(); + return ImmutableCollections.emptySet(); } /** @@ -464,7 +464,7 @@ * @since 9 */ static Set of(E e1) { - return new ImmutableCollections.Set1<>(e1); + return ImmutableCollections.AbstractImmutableSet.of(e1); } /** @@ -481,7 +481,7 @@ * @since 9 */ static Set of(E e1, E e2) { - return new ImmutableCollections.Set2<>(e1, e2); + return ImmutableCollections.AbstractImmutableSet.of(e1, e2); } /** @@ -690,16 +690,7 @@ @SafeVarargs @SuppressWarnings("varargs") static Set of(E... elements) { - switch (elements.length) { // implicit null check of elements - case 0: - return ImmutableCollections.Set0.instance(); - case 1: - return new ImmutableCollections.Set1<>(elements[0]); - case 2: - return new ImmutableCollections.Set2<>(elements[0], elements[1]); - default: - return new ImmutableCollections.SetN<>(elements); - } + return ImmutableCollections.AbstractImmutableSet.of(elements); } /**