Proposal: Collection Literals

Joshua Bloch jjb at google.com
Mon Mar 30 16:30:14 PDT 2009


Folks,

Hi.  I'd like to throw one more proposal into the Coin purse.  This proposal
is intended to coexist with indexing access syntax for lists and maps.  It
is similar to Shams Mahmood Imam's Concise Collection Initialization Syntax
proposal, but represents a different point in the design space. It is safer
(in that it only produces immutable collections) and more concise (in that
you need not specify the implementation type). It is likely to be higher in
space and time performance, in that the compiler can choose efficient
implementations for small collections (such as empty collections and
singletons).  On the other hand, it is less flexible, in that you
cannot specify
the implementation type.
The proposal can be found here:
http://docs.google.com/Doc?id=ddv8ts74_4cbnn5mhj , and is reproduced below
as required by the submission guidelines.

          Regards,

          Josh

Collection Literals

*AUTHOR: *Joshua Bloch


*OVERVIEW*


FEATURE SUMMARY: Add support for immutable List, Set, and Map literals, with
a syntax similar to that of array initializers.


MAJOR ADVANTAGE: Reduces verbosity and adds clarity when creating small
lists, sets, and maps and whose contents are known at compile time. This
feature is well-liked in the many languages that support it (e.g., Perl,
Python).


MAJOR DISADVANTAGE: The facility can be abused to simulate named parameters,
resulting in loss of type safety. Hopefully people won't do this.


ALTERNATIVES: Typically programmers create an empty collection and insert
the initial elements or mappings.  Sometimes they use Arrays.asList. It is
also possible to abuse anonymous classes to reduce verbosity ("crazybob's
contraption").  Arguably the current best practice for creating immutable
collections is to use the builder pattern, as demonstrated by the immutable
collections in the Google
Collections<http://code.google.com/p/google-collections/>
 project.


*EXAMPLES*


SIMPLE EXAMPLES: Here is a code fragment to create an immutable list as it
would be done today:


    final List<Integer> piDigits =
Collections.unmodifiableList(Arrays.asList(

        3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9 ));


The method invocations (Collections.unmodifiableList(Arrays.asList) are just
noise. With list literals, it would look like this:

    final List<Integer> piDigits = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 9];


By substituting curly braces for the square brackets, you get a set literal
in place of a list:


    final Set<Integer> primes = { 2, 7, 31, 127, 8191, 131071, 524287 };


Here is the empty set literal:


    Set<Senator> honestSenators = {};


Here is a code fragment to create an immutable map as it would be done
today:


    final Map<Integer, String> platonicSolids;

    static {

        solids = new LinkedHashMap<Map<Integer, String>;

        solids.put(4,  "tetrahedron");

        solids.put(6,  "cube");

        solids.put(8,  "octahedron");

        solids.put(12, "dodecahedron");

        solids.put(20, "icosahedron");

        platonicSolids = Collections.immutableMap(solids);

    }


Here’s how it would look with map literals:


    final Map<Integer, String> platonicSolids = { 4 : "tetrahedron",

      6 : "cube", 8 : "octahedron", 12 : "dodecahedron", 20 : "icosahedron"
};


Here is the empty map literal, which has a slightly irregular syntax to make
it differ from the empty set:


    Map<String, Integer> noJokeHere = { : };


In all cases, the iteration order of collection literal corresponds to the
order in which the elements or keys appear in the code.  Collections
literals are serializable (assuming their elements, keys or values are
serializable). List literals implement java.util.RandomAccess  (dynamically,
but not statically).


COMPLEX EXAMPLES: Here is a nested list literal:


    List<List<Integer>> pascalsTriangle =

        [[1],

         [1, 1],

         [1, 2, 1],

         [1, 3, 3, 1],

         [1, 4, 6, 4, 1]];


Note that the element type of the list literal (and the key and value types
of the map literal) are inferred by the compiler.  As is the case when
invoking parameterized methods, the compiler cannot always infer the desired
type.  For example this list literal will not compile, as the compiler will
infer the wrong element type:


    final List<Number> numbers = [ 2, 2.718281828 ];



(Instead of Number, the compiler will infer Number & Comparable<? extends
Number & Comparable<?>>.) Therefore, we provide a similar "escape hatch"
that lets you specify the element (or key and value) types manually.  Like
its method invocation analogue, it isn't pretty, and programmers will rarely
have to use it in practice:


    final List<Number> numbers = *<Number>* [ 2, 2.718281828 ];


*DETAILS*

* *

SPECIFICATION: What follows is not intended to be a formal JLS-quality
specification. It emphasizes brevity and clarity over thoroughness.



SYNTAX: Two new expression types would be defined in JLS 15, *List Literals*
, *Set Literals*, and *Map Literals*:



*ListLiteral*:

    *NonWildTypeArgument**opt** [* *List**ElementInitializers**opt ] *

*ListElementInitializers*:

    *Expression*

    *List**ElementInitializers ,* *Expression*

*NonWildTypeArgument:*

*    *< *ReferenceType *>


*SetLiteral*:

    *NonWildTypeArgument**opt** *{ *SetElementInitializers**opt } *

*SetElementInitializers*:

    *AugmentedConstant**Expression*

    *SetE**lementInitializers ,* *AugmentedConstant**Expression*

*NonWildTypeArgument:*

*    *< *ReferenceType *>



*MapLiteral*:

    *NonWildTypeArgumentPair**opt** *{ *Entry**Initializers** } *

      * NonWildTypeArgumentPairopt EmptyMapLiteral*

*EntryInitializers*:

    *AugmentedConstant**Expression* : *Expression*

    Entry*Initializers ,* *EntryInitializer*

*NonWildTypeArgumentPair:*

*    *<* ReferenceType , ReferenceType *>

*EmptyMapLiteral*

*    *{ : }


The following expression type would be added to (or immediately following)
JLS  §15.28, which currently defines the term constant expression, and the
corresponding nonterminal symbol *ConstantExpression*:


*AugmentedConstant**Expression:*


An *augmented constant expression* is a generalization of a constant
expression that allows several additional kinds of expressions:



   - enum constants (JLS §8.9)
   - class literals (JLS §8.9)
   - set literals (JLS 15.8.2)
   - lists literals all of whose element initializers are themselves
   augmented constant expressions
   - map literals all of whose value initializers are themselves augmented
   constant expressions



SEMANTICS and COMPILATION: A list literal behaves roughly as if replaced by
the following "desugaring":



    Arrays. *NonWildTypeArguments**opt* asUnmodifiableList( *
ElementInitializers* )



This desugaring assumes the existence of a new library method,
asUnmodifiableList, described below under Library Support. Note that type
inference of the element type for the list literal is exactly as if the
following method were invoked with *ElementInitializers *as as actual
parameters:


   <E> void foo(E... args) { }




A set literal b*ehaves roughly as if replaced by the following desugaring:*


    Collections.unmodifiableSet(Arrays. *NonWildTypeArguments**opt* asList(
*ElementInitializers* ))


*It is a compile time error for a set literal to contain multiple equal
element values.*  Therefore, the size of the set (as reported by the
size method)
is the number of element initializers.


In both desugarings above, the wiggle word "roughly" is used in that there
is no guarantee that the indicated methods (Arrays.asUnmodifiableList,
Collections.unmodifiableSet, Arrays.asList) methods are actually called.


Moreover,* there **are no guarantees as to the implementation types of the
collections produced by collection literals.* The compiler can call any
public Java SE APIs to construct any List, Set, or Map implementations with
the correct semantics (immutability, iteration order, serializability and,
in the case of lists, RandomAccess).  So, for example, if a list literal has
no elements, the compiler can emit a call to Collections.emptyList. Similarly,
if a list literal has a single element, the compiler can emit a call to
Collections.singletonList.  Many other such optimizations are possible, and
new ones may become available in subsequent releases.

A map literal behaves as if replaced by the following desugaring:



    Arrays. *NonWildTypeArguments**opt* #unmodifiableMap(

*        #gather( k0, k1, ... kn-1 ),*

*        #gather( v0, v1, ... vn-1 ))*


where ki and vi are the keys and value expressions in the ith
EntryInitializer in EntryInitializers, and #gather and #unmodifiableMap are the
following hypothetical methods:


    private static <T> T[] #gather(T... args) {

        return args;

    }


    private static <K, V> Map<K, V> #unmodifiableMap(K[] keys, V[] vals) {
        Map <K, V> m = new LinkedHashMap<K, V>(2 * keys.length);
        for (int i = 0; i < keys.length; i++)
            m.put(keys[i], vals[i]);
        return Collections.unmodifiableMap(m);
    }

The compiler does not actually emit calls to the methods #unmodifiableMap
 and #gather (which don't exist), but emulates their behavior.  These methods
are merely an artifice to describe the semantics of the construct, including
type inference.


*It is a compile time error for a map literal to contain multiple equal key
values.*  Therefore, the size of the map (as reported by the size method) is
the number of entry initializers.


TYPE SYSTEM: The proposal has no effect on the type system.



TESTING: The proposed construct can be tested by writing list and map
literals to generate lists and maps of various lengths (say 0 through 100)
and types, and ensuring that these collections have the correct behavior by
using preexisting tests for immutable collections, which are part of the Google
Collections <http://code.google.com/p/google-collections/> project.  (These
tests will have to be modified slightly, but it will be straightforward.)



LIBRARY SUPPORT: The following method (which is trivial to implement) will
be added to java.util.arrays:


    /**
     * Returns a fixed-size, unmodifiable list backed by the specified
array.
     * (Any changes to the array "write through" to the list.)  Attempting
to
     * modify the list will result in an {@link
UnsupportedOperationException}.
     *
     * <p>The returned list is serializable and implements {@link
RandomAccess}.
     *
     * @param a the array by which the list will be backed
     * @return an unmodifiable list view of the specified array
     */
    public static <T> List<T> asUnmodifiableList(T... a);


The compiler may (but is not required to) emit calls to this method in the
implementation of list literals.  It may also be desirable to add some
library support for immutable sets or lists (akin to that provided by the
Google Collections project), but this isn't necessary for the proposal to go
forward, and the compiler can take advantage of such support in whatever
release it happens to be added.

REFLECTIVE APIS: This proposal has no effect on core reflective APIs. The tree
API inside javac<http://java.sun.com/javase/6/docs/jdk/api/javac/tree/index.html>
 would require a minor extension.



OTHER CHANGES: No other parts of the platform need be to be updated.



MIGRATION: Existing manual list, set, and map construction code could be
replaced by more concise list and map literals, but this is not necessary.



*COMPATIBILITY*

* *

BREAKING CHANGES: All previously valid programs remain valid, and their
semantics is unaffected.

* *

EXISTING PROGRAMS: Source and class files of earlier versions are unaffected
by the feature.  No new overloadings or overridings can occur.


*REFERENCES*

* *

EXISTING BUGS: 4632701 <http://bugs.sun.com/view_bug.do?bug_id=4632701>,
6237556 <http://bugs.sun.com/view_bug.do?bug_id=6237556>.


*FUTURE DIRECTIONS*


Assuming we make sure that no syntactic ambiguity would result, a future
release could allow certain collection literals as annotation values.  This
is *not *proposed for Project Coin, but should inform the work.



More information about the coin-dev mailing list