New convenience methods on Stream

Tagir Valeev amaembo at gmail.com
Sat May 1 01:25:18 UTC 2021


Hello!

1. toCollection looks too specific to be added to JDK. Essentially,
it's a shortcut for toCollection constructor and unlike toList, it
cannot add many optimizations there. So we basically save several
characters and nothing more. And toCollection collector is orders of
magnitude less used than toList. I think that it's even less used than
joining or groupingBy, so why not providing these shortcuts first?
It's always about where to draw a line.

2. to() sounds quite specific, as it expects that the target object is
ready to accept an array. But why? Probably its API more suitable to
accept a collection as an input? It's essentially 'toArrayAndThen'
(similar to 'collectingAndThen' collector). There's a suggestion [1]
to add transform() method to stream which is much more general, and
can be used like `stream.transform(s -> Arrays.asList(s.toArray()))`.
Note that collection libraries may provide ready methods that supply
the lambdas specific to this library, and this allows to encapsulate
the exact implementation detail, like whether we need an array or
something else as an intermediate step, like:

class MyCustomCollection<T> {
...
  static <T> Function<Stream<T>, MyCustomCollection<T>> toMyCustomCollection() {
    return s -> createMyCustomCollectionFromStream((T[])s.toArray());
    // in the next version we may change to for-each version if we
find that it's more performant
    // or s -> {var c = new MyCustomCollection<T>();
s.forEach(c::add); return c;}
  }
}

And use `stream.transform(MyCustomCollection.toMyCustomCollection())`
without caring whether it's array or something else in-between.

Finally, adding a `(T[])` cast to the standard library sounds a bad
idea. Imagine if the custom collection has a fixed element type:

class MyStringCollection implements Collection<String> {
  public MyStringCollection(String[] array) {...}
}

It looks like stream.to(MyStringCollection::new) should work but in
fact, it will throw a ClassCastException

3. into() sounds more interesting as it's indeed useful to dump the
stream into an existing collection. It's mostly useful if the
collection is non-empty, as you can append into single collection from
existing sources. Essentially, into(c) == forEach(c::add), but it's
also possible to add optimizations for specific collections (like `if
(isSizedStream() && c instanceof ArrayList<?> al) {
al.ensureCapacity(...); }`), so it could be faster.

With best regards,
Tagir Valeev

[1] https://bugs.openjdk.java.net/browse/JDK-8140283

On Thu, Apr 29, 2021 at 5:58 AM Donald Raab <donraab at gmail.com> wrote:
>
> I looked through a few libraries and found some methods where the option #2 proposal for Steam might be useful. If the JDK had constructors for ArrayList, HashSet and other collection types that take arrays this method would work there as well.
>
> > default <R extends Iterable<T>> R to(Function<T[], R> function)
> > {
> >    return function.apply((T[]) this.toArray());
> > }
>
>
> // JDK
> Set<String> set = stream.to(Set::of);
> List<String> list = stream.to(List::of);
> List<String> arraysAsList = stream.to(Arrays::asList);
>
> // Guava
> ArrayList<String> arrayList = stream.to(Lists::newArrayList);
> HashSet<String> hashSet = stream.to(Sets::newHashSet);
> Multiset<String> multiset = stream.to(ImmutableMultiset::copyOf);
> List<String> guavaList = stream.to(ImmutableList::copyOf);
> Set<String> guavaSet = stream.to(ImmutableSet::copyOf);
>
> // Apache Commons Collections
> FluentIterable<String> fluentIterable = stream.to(FluentIterable::of);
>
> // Eclipse Collections
> MutableList<String> adapter = stream.to(ArrayAdapter::adapt);
>
> MutableList<String> mutableList = stream.to(Lists.mutable::with);
> MutableSet<String> mutableSet = stream.to(Sets.mutable::with);
> MutableBag<String> mutableBag = stream.to(Bags.mutable::with);
>
> // Eclipse Collections - ListIterable, SetIterable and Bag all extend Iterable, not Collection
> ListIterable<String> listIterable = stream.to(Lists.mutable::with);
> SetIterable<String> setIterable = stream.to(Sets.mutable::with);
> Bag<String> bag = stream.to(Bags.mutable::with);
>
> // Eclipse Collections - Immutable Collections do not extend Collection
> ImmutableList<String> immutableList = stream.to(Lists.immutable::with);
> ImmutableSet<String> immutableSet = stream.to(Sets.immutable::with);
> ImmutableBag<String> immutableBag = stream.to(Bags.immutable::with);
>
> // Eclipse Collections - Stack does not extend Collection
> StackIterable<String> stackIterable = stream.to(Stacks.mutable::with);
> MutableStack<String> mutableStack = stream.to(Stacks.mutable::with);
> ImmutableStack<String> immutableStack = stream.to(Stacks.immutable::with);
>
> // Eclipse Collections - Mutable Map and MutableBiMap are both Iterable<V> so they are valid returns
> MutableMap<String, String> map =
>         stream.to(array -> ArrayAdapter.adapt(array)
>                 .toMap(String::toLowerCase, String::toUpperCase));
>
> MutableBiMap<String, String> biMap =
>         stream.to(array -> ArrayAdapter.adapt(array)
>                 .toBiMap(String::toLowerCase, String::toUpperCase));
>
> Thanks,
> Don
>
> > On Apr 27, 2021, at 1:35 AM, Donald Raab <donraab at gmail.com> wrote:
> >
> > I realized after sending that option 2 can be made more abstract:
> >
> > default <R extends Iterable<T>> R to(Function<T[], R> function)
> > {
> >    return function.apply((T[]) this.toArray());
> > }
> >
> >>
> >> 2. Pass the result of toArray directly into a function that can then return a Collection. This should work with Set.of, List.of and any 3rd party collections which take arrays.
> >>
> >> default <R extends Collection<T>> R to(Function<T[], R> function)
> >> {
> >>   return function.apply((T[]) this.toArray());
> >> }
> >>
> >> Usage Examples:
> >>
> >> Set<String> set = stream.to(Set::of);
> >> List<String> list = stream.to(List::of);
> >> List<String> arrayList = stream.to(Arrays::asList);
> >>
> >
>


More information about the core-libs-dev mailing list