Proposal: Collection Literals (original) (raw)
Joshua Bloch jjb at google.com
Mon Mar 30 16:30:14 PDT 2009
- Previous message: Multiple return values
- Next message: Proposal: Collection Literals
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]
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>.) 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:
void foo(E... args) { }
A set literal behaves 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}. * *
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 List 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.
- Previous message: Multiple return values
- Next message: Proposal: Collection Literals
- Messages sorted by: [ date ] [ thread ] [ subject ] [ author ]