RFC: draft API for JEP 269 Convenience Collection Factories

Stuart Marks stuart.marks at oracle.com
Fri Oct 9 23:11:09 UTC 2015



On 10/9/15 6:11 AM, Stephen Colebourne wrote:
> On 9 October 2015 at 00:39, Stuart Marks <stuart.marks at oracle.com> wrote:
>> 1. Number of fixed arg overloads.
>
> Guava follows this pattern:
>
> of(T)
> of(T, T)
> of(T, T, T)
> of(T, T, T, T... elements)
>
> whereas the proposal has
>
> of(T)
> of(T, T)
> of(T, T, T)
> of(T... elements)
>
> I'd be interested to know why Guava did it that way and what the trade offs are.

I can't answer for the Guava guys, of course, but I can supply some additional 
background about why we chose this approach for our proposal.

Note carefully that the Guava pattern is:

     of()                    // zero args, for completeness
     of(T)
     of(T, T)
     of(T, T, T)
     of(T, T, T, T, T...)    // an extra fixed arg THEN the varargs param

Following this pattern means that overload resolution can be performed entirely 
based on the arity of the call site.

With the JEP 269 proposal, there is a potential overloading ambiguity with these 
two methods:

     of(T)
     of(T...)

The reason we chose to provide a one-arg of(T...) method is that it supports a 
secondary use case, which is how to create one of these collections if the 
values aren't known until runtime. The of(T...) method allows you to pass an 
arbitrary number of actual arguments as well as an array of arbitrary size. It's 
really hard to do this with the Guava pattern of fixed and varargs methods.

Now, Guava handles this use case by providing a family of copying factories that 
can accept an array, a Collection, an Iterator, or an Iterable. These are all 
useful, but for JEP 269, we wanted to focus on the "collection literal like" 
APIs and not expand the proposal to include a bunch of additional factory 
methods. Since we need to have a varargs method anyway, it seemed reasonable to 
arrange it so that it could easily accept an array as well.

Now, the usual problem with this scheme is that calling of(null) is ambiguous. 
Indeed, it actually is ambiguous in the JEP 269 proposal, and call sites will 
generate varargs warnings. It turns out that we've finessed this issue since we 
disallow nulls.

Another issue is that a call site that passes a T[] argument matches the 
of(T...) overload and not of(T), where in the latter case T is an array type. 
This is a problem if you want to create a list or set containing a single array. 
If you need this, you can do

     List<String[]> list = List.<String[]>of(stringArray);

This is messy, but it really seems like an edge case, and there's a simple 
workaround. I've described this in an @apiNote on List.of(T...). (I need to add 
something similar for Set.of(T...).)

>> 2. Other concrete collection factories.
>>
>> I've chosen to provide factories for the concrete collections ArrayList,
>> HashSet, and HashMap, since those seem to be the most commonly used. Is
>> there a need to provide factories for other concrete collections, such as
>> LinkedHashMap?
>
> LinkedHashMap definitely
> LinkedList definitely not (as its very slow and use should not be encouraged).
> TreeSet/TreeMap, maybe, they'd need an extra parameter though.

LinkedHashMap: ok. I assume this should create an insertion-ordered map, with 
the initial entries being inserted in left-to-right order.

Does anybody care about LinkedHashSet?

>> 3. Duplicate handling.
>>
>> My current thinking is for the Set and Map factories to throw
>> IllegalArgumentException if a duplicate element or key is detected.
>
> Definitely.

Well ok then! :-)

> Given that ofEntries() takes a Map.Entry as input, why does
> Map.KeyValueHolder need to be public? This would require
> Map.entry(K,V) return Map.Entry, not Map.KeyValueHolder.

Good question, it might or might not need to be this way. By having Map.entry() 
return KeyValueHolder instead of Map.Entry, call sites are compiled with 
KeyValueHolder as the method return type. If, in the future, KeyValueHolder were 
to become a value type, and in the future, if another overload

     Map.ofEntries(KeyValueHolder... entries)

were added, then recompiling will avoid upcasting to Map.Entry, which might 
prevent boxing of the value type.

I think the way this needs to be set up and how it will interact with some 
future evolution toward value types is mainly in Brian Goetz' head right now, so 
I'll need to go over this again to make sure it's set up correctly.

s'marks



My thinking was, suppose KeyValueHolder eventually turns into a value type



More information about the core-libs-dev mailing list