This is an automated email from the ASF dual-hosted git repository.
paulk-asert pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/groovy.git
The following commit(s) were added to refs/heads/master by this push:
new f6e2248d12 GROOVY-12036/GROOVY-12037: GDK: cache Collectors instances
in StreamGroovyMethods and ParallelCollectionExtensions
f6e2248d12 is described below
commit f6e2248d1262e2d0cb0f9f835f1aee40f0d162b2
Author: Paul King <[email protected]>
AuthorDate: Sun May 24 10:57:15 2026 +1000
GROOVY-12036/GROOVY-12037: GDK: cache Collectors instances in
StreamGroovyMethods and ParallelCollectionExtensions
---
.../groovy/runtime/ArrayGroovyMethods.java | 295 +++++++++++++++++++++
.../groovy/runtime/DefaultGroovyMethods.java | 86 ++++++
.../runtime/ParallelCollectionExtensions.java | 23 +-
.../groovy/runtime/StreamGroovyMethods.java | 207 ++++++++++++++-
4 files changed, 600 insertions(+), 11 deletions(-)
diff --git a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
index efa2aeb979..7b891b980a 100644
--- a/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/ArrayGroovyMethods.java
@@ -9961,6 +9961,138 @@ public class ArrayGroovyMethods extends
DefaultGroovyMethodsSupport {
return new ArrayList<>(Arrays.asList(self));
}
+
//--------------------------------------------------------------------------
+ // toImmutableList
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a boolean array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Boolean> toImmutableList(boolean[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a byte array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Byte> toImmutableList(byte[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a char array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Character> toImmutableList(char[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a short array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Short> toImmutableList(short[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self an int array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Integer> toImmutableList(int[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a long array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Long> toImmutableList(long[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a float array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Float> toImmutableList(float[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list.
+ *
+ * @param self a double array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static List<Double> toImmutableList(double[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(toList(self));
+ }
+
+ /**
+ * Converts this array to an immutable List of the same size, with each
+ * element added to the list. Null elements are preserved (unlike
+ * {@link List#of(Object[])}, which rejects nulls). The returned list is
+ * unmodifiable; mutation attempts throw {@link
UnsupportedOperationException}.
+ * Returns the canonical empty list ({@link Collections#emptyList()}) when
+ * the array is empty.
+ * <pre class="language-groovy groovyTestCase">
+ * import static groovy.test.GroovyAssert.shouldFail
+ * String[] arr = ['a', null, 'b']
+ * def list = arr.toImmutableList()
+ * assert list == ['a', null, 'b']
+ * shouldFail(UnsupportedOperationException) { list << 'c' }
+ * assert (new String[0]).toImmutableList() === Collections.emptyList()
+ * </pre>
+ *
+ * @param self an object array
+ * @return An immutable list containing the contents of this array.
+ * @since 6.0.0
+ */
+ public static <T> List<T> toImmutableList(T[] self) {
+ if (self.length == 0) return Collections.emptyList();
+ return Collections.unmodifiableList(new
ArrayList<>(Arrays.asList(self)));
+ }
+
//--------------------------------------------------------------------------
// toSet
@@ -10111,6 +10243,169 @@ public class ArrayGroovyMethods extends
DefaultGroovyMethodsSupport {
return DefaultGroovyMethods.toSet(Arrays.asList(self));
}
+
//--------------------------------------------------------------------------
+ // toImmutableSet
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * boolean[] array = [true, false, true]
+ * Set expected = [true, false]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a boolean array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Boolean> toImmutableSet(boolean[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * byte[] array = [1, 2, 3, 2, 1]
+ * Set expected = [1, 2, 3]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a byte array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Byte> toImmutableSet(byte[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * char[] array = 'xyzzy'.chars
+ * Set expected = ['x', 'y', 'z']
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a char array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Character> toImmutableSet(char[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * short[] array = [1, 2, 3, 2, 1]
+ * Set expected = [1, 2, 3]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a short array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Short> toImmutableSet(short[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * int[] array = [1, 2, 3, 2, 1]
+ * Set expected = [1, 2, 3]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self an int array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Integer> toImmutableSet(int[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * long[] array = [1, 2, 3, 2, 1]
+ * Set expected = [1, 2, 3]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a long array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Long> toImmutableSet(long[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * float[] array = [1.0f, 2.0f, 3.0f, 2.0f, 1.0f]
+ * Set expected = [1.0f, 2.0f, 3.0f]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a float array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Float> toImmutableSet(float[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
to the set.
+ * <pre class="language-groovy groovyTestCase">
+ * double[] array = [1.0d, 2.0d, 3.0d, 2.0d, 1.0d]
+ * Set expected = [1.0d, 2.0d, 3.0d]
+ * assert array.toImmutableSet() == expected
+ * </pre>
+ *
+ * @param self a double array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static Set<Double> toImmutableSet(double[] self) {
+ if (self.length == 0) return Collections.emptySet();
+ return Collections.unmodifiableSet(toSet(self));
+ }
+
+ /**
+ * Converts this array to an immutable Set, with each unique element added
+ * to the set. Null elements are preserved (unlike {@link
Set#copyOf(java.util.Collection)},
+ * which rejects nulls). The returned set is unmodifiable; mutation
attempts
+ * throw {@link UnsupportedOperationException}. Returns the canonical empty
+ * set ({@link Collections#emptySet()}) when the array is empty.
+ * <pre class="language-groovy groovyTestCase">
+ * import static groovy.test.GroovyAssert.shouldFail
+ * String[] arr = ['a', null, 'b', 'a']
+ * def set = arr.toImmutableSet()
+ * assert set == ['a', null, 'b'] as Set
+ * shouldFail(UnsupportedOperationException) { set << 'c' }
+ * assert (new String[0]).toImmutableSet() === Collections.emptySet()
+ * </pre>
+ *
+ * @param self an object array
+ * @return An immutable set containing the unique contents of this array.
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toImmutableSet(T[] self) {
+ Set<T> answer = toSet(self);
+ return answer.isEmpty() ? Collections.emptySet() :
Collections.unmodifiableSet(answer);
+ }
+
//--------------------------------------------------------------------------
// toSorted
diff --git
a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
index f5a6093490..dfecf3286f 100644
--- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java
@@ -15689,6 +15689,37 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
return answer;
}
+ /**
+ * Convert an iterator to an immutable List. The iterator will become
+ * exhausted of elements after making this conversion. Returns the
+ * canonical empty list ({@link Collections#emptyList()}) when the iterator
+ * is empty.
+ * <p>
+ * Null elements are preserved (unlike {@link
List#copyOf(java.util.Collection)},
+ * which rejects nulls). The returned list is unmodifiable; mutation
+ * attempts throw {@link UnsupportedOperationException}.
+ * <p>
+ * <pre class="language-groovy groovyTestCase">
+ * import static groovy.test.GroovyAssert.shouldFail
+ * def list = [1, 2, null, 3].iterator().toImmutableList()
+ * assert list == [1, 2, null, 3]
+ * shouldFail(UnsupportedOperationException) { list << 4 }
+ * assert [].iterator().toImmutableList() === Collections.emptyList()
+ * </pre>
+ *
+ * @param self an iterator
+ * @return an immutable List of the elements from the iterator
+ * @since 6.0.0
+ */
+ public static <T> List<T> toImmutableList(Iterator<T> self) {
+ if (!self.hasNext()) return Collections.emptyList();
+ List<T> answer = new ArrayList<>();
+ while (self.hasNext()) {
+ answer.add(self.next());
+ }
+ return Collections.unmodifiableList(answer);
+ }
+
/**
* Convert an Iterable to a List. The Iterable's iterator will
* become exhausted of elements after making this conversion.
@@ -15706,6 +15737,18 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
return toList(self.iterator());
}
+ /**
+ * Convert an Iterable to an immutable List. The Iterable's iterator will
+ * become exhausted of elements after making this conversion.
+ *
+ * @param self an Iterable
+ * @return an immutable List of the elements from the Iterable
+ * @since 6.0.0
+ */
+ public static <T> List<T> toImmutableList(Iterable<T> self) {
+ return toImmutableList(self.iterator());
+ }
+
/**
* Convert an enumeration to a List.
*
@@ -15854,6 +15897,18 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
return toSet(self.iterator());
}
+ /**
+ * Convert an Iterable to an immutable Set. The Iterable's iterator will
+ * become exhausted of elements after making this conversion.
+ *
+ * @param self an Iterable
+ * @return an immutable Set of the elements from the Iterable
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toImmutableSet(Iterable<T> self) {
+ return toImmutableSet(self.iterator());
+ }
+
/**
* Convert an iterator to a Set. The iterator will become
* exhausted of elements after making this conversion.
@@ -15870,6 +15925,37 @@ public class DefaultGroovyMethods extends
DefaultGroovyMethodsSupport {
return answer;
}
+ /**
+ * Convert an iterator to an immutable Set. The iterator will become
+ * exhausted of elements after making this conversion. Returns the
+ * canonical empty set ({@link Collections#emptySet()}) when the iterator
+ * is empty.
+ * <p>
+ * Null elements are preserved (unlike {@link
Set#copyOf(java.util.Collection)},
+ * which rejects nulls). The returned set is unmodifiable; mutation
+ * attempts throw {@link UnsupportedOperationException}.
+ * <p>
+ * <pre class="language-groovy groovyTestCase">
+ * import static groovy.test.GroovyAssert.shouldFail
+ * def set = [1, 2, null, 3].iterator().toImmutableSet()
+ * assert set == [1, 2, null, 3] as Set
+ * shouldFail(UnsupportedOperationException) { set << 4 }
+ * assert [].iterator().toImmutableSet() === Collections.emptySet()
+ * </pre>
+ *
+ * @param self an iterator
+ * @return an immutable Set of the elements from the iterator
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toImmutableSet(Iterator<T> self) {
+ if (!self.hasNext()) return Collections.emptySet();
+ Set<T> answer = new HashSet<>();
+ while (self.hasNext()) {
+ answer.add(self.next());
+ }
+ return Collections.unmodifiableSet(answer);
+ }
+
/**
* Convert an enumeration to a Set.
*
diff --git
a/src/main/java/org/codehaus/groovy/runtime/ParallelCollectionExtensions.java
b/src/main/java/org/codehaus/groovy/runtime/ParallelCollectionExtensions.java
index fc11727a20..3ffd7d8752 100644
---
a/src/main/java/org/codehaus/groovy/runtime/ParallelCollectionExtensions.java
+++
b/src/main/java/org/codehaus/groovy/runtime/ParallelCollectionExtensions.java
@@ -31,6 +31,7 @@ import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
+import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
@@ -52,6 +53,20 @@ import java.util.stream.IntStream;
*/
public class ParallelCollectionExtensions {
+ // Cached Collector: stateless config object; the per-call mutable
ArrayList
+ // is produced fresh by the Supplier inside collect(), so sharing this
+ // instance across threads is safe and saves a per-call allocation.
+ // toCollection(ArrayList::new) is used (rather than toList()) because the
+ // JDK contract on Collectors.toList() doesn't guarantee a mutable result
or
+ // a concrete ArrayList type, while these GDK methods promise both.
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static final Collector TO_LIST =
Collectors.toCollection(ArrayList::new);
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static <T> Collector<T, ?, List<T>> toListCollector() {
+ return (Collector) TO_LIST;
+ }
+
// ---- Iteration ------------------------------------------------------
/**
@@ -71,7 +86,7 @@ public class ParallelCollectionExtensions {
*/
public static <T, R> List<R> collectParallel(Collection<T> self,
Function<T, R> transform) {
return withCurrentFJP(fjp ->
- fjp.submit(() ->
self.parallelStream().map(transform).collect(Collectors.toList())).join()
+ fjp.submit(() ->
self.parallelStream().map(transform).collect(toListCollector())).join()
);
}
@@ -82,7 +97,7 @@ public class ParallelCollectionExtensions {
*/
public static <T> List<T> findAllParallel(Collection<T> self, Predicate<T>
filter) {
return withCurrentFJP(fjp ->
- fjp.submit(() ->
self.parallelStream().filter(filter).collect(Collectors.toList())).join()
+ fjp.submit(() ->
self.parallelStream().filter(filter).collect(toListCollector())).join()
);
}
@@ -203,7 +218,7 @@ public class ParallelCollectionExtensions {
return withCurrentFJP(fjp ->
fjp.submit(() -> self.parallelStream()
.flatMap(e -> transform.apply(e).stream())
- .collect(Collectors.toList())).join()
+ .collect(toListCollector())).join()
);
}
@@ -249,7 +264,7 @@ public class ParallelCollectionExtensions {
return withCurrentFJP(fjp ->
fjp.submit(() -> self.parallelStream()
.filter(e -> InvokerHelper.invokeMethod(filter,
"isCase", e) != Boolean.FALSE)
- .collect(Collectors.toList())).join()
+ .collect(toListCollector())).join()
);
}
diff --git a/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java
b/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java
index 3ee3ca8bed..d2c20ee9c4 100644
--- a/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java
+++ b/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java
@@ -25,7 +25,9 @@ import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
+import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
@@ -37,6 +39,7 @@ import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.BaseStream;
+import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.DoubleStream;
import java.util.stream.IntStream;
@@ -49,6 +52,42 @@ public class StreamGroovyMethods {
private StreamGroovyMethods() {
}
+ // ---- Cached Collectors -------------------------------------------------
+ // Collector instances are stateless configuration objects; the per-call
+ // accumulator is freshly produced by the Supplier on each collect()
+ // invocation. So sharing the Collector across calls and threads is safe —
+ // and saves a per-call allocation that the JDK never elides. Same trick
+ // Eclipse Collections' Collectors2 uses.
+ //
+ // toCollection(ArrayList::new) / toCollection(HashSet::new) (rather than
+ // toList()/toSet()) is deliberate: the JDK contract on Collectors.toList()
+ // and toSet() doesn't guarantee mutability or concrete type, and several
+ // public GDK methods promise a "new mutable List/Set". toCollection pins
+ // the supplier so the guarantee is held by the implementation, not by
+ // current JDK behaviour.
+ //
+ // For the immutable Set case (toImmutableSet on Stream / BaseStream), we
+ // collect via the cached TO_SET and wrap with Collections.unmodifiableSet
+ // at the call site rather than using Collectors.toUnmodifiableSet() — the
+ // latter's finisher calls Set.copyOf which rejects null elements, which
+ // would diverge from Groovy's null-tolerant idiom and from the matching
+ // toImmutableSet methods on Iterator/Iterable/T[].
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static final Collector TO_LIST =
Collectors.toCollection(ArrayList::new);
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static final Collector TO_SET =
Collectors.toCollection(HashSet::new);
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static <T> Collector<T, ?, List<T>> toListCollector() {
+ return (Collector) TO_LIST;
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ private static <T> Collector<T, ?, Set<T>> toSetCollector() {
+ return (Collector) TO_SET;
+ }
+
/**
* Returns element at {@code index} or {@code null}.
* <p>
@@ -122,7 +161,7 @@ public class StreamGroovyMethods {
*/
public static <T> List<T> getAt(final Stream<T> self, final IntRange
range) {
if (range.isReverse()) throw new IllegalArgumentException("reverse
range");
- return
self.skip(range.getFromInt()).limit(range.size()).collect(Collectors.toList());
+ return
self.skip(range.getFromInt()).limit(range.size()).collect(toListCollector());
}
/**
@@ -692,16 +731,32 @@ public class StreamGroovyMethods {
}
/**
- * Accumulates the elements of stream into a new List.
+ * Accumulates the elements of stream into a new mutable List.
+ * <p>
+ * <strong>Note:</strong> since JDK 16, {@link Stream} has a native
+ * {@code toList()} method that returns an <em>unmodifiable</em> list.
+ * Java instance methods take precedence over GDK extensions in both
+ * {@code @CompileStatic} and dynamic dispatch, so {@code stream.toList()}
+ * now resolves to the native call and yields an immutable result —
+ * <em>not</em> a fresh {@code ArrayList} as it did pre-JDK 16.
+ * Direct callers of this extension method are pointed at the explicit
+ * replacements; see the deprecation note.
*
* @param self the stream
* @param <T> the type of element
- * @return a new {@code java.util.List} instance
+ * @return a new mutable {@code java.util.List} instance
*
* @since 2.5.0
+ *
+ * @deprecated since 6.0.0; the native {@link Stream#toList()}
(JDK 16+)
+ * shadows this and returns an <em>unmodifiable</em> list. If
+ * you need a mutable list, call {@link
#toMutableList(Stream)};
+ * otherwise use {@code stream.toList()} directly (faster than
+ * this extension because it skips the {@code Collectors}
path).
*/
+ @Deprecated(since = "6.0.0")
public static <T> List<T> toList(final Stream<T> self) {
- return self.collect(Collectors.toList());
+ return self.collect(toListCollector());
}
/**
@@ -714,7 +769,7 @@ public class StreamGroovyMethods {
* @since 2.5.0
*/
public static <T> List<T> toList(final BaseStream<T, ? extends BaseStream>
self) {
- return stream(self.iterator()).collect(Collectors.toList());
+ return stream(self.iterator()).collect(toListCollector());
}
/**
@@ -727,7 +782,7 @@ public class StreamGroovyMethods {
* @since 2.5.0
*/
public static <T> Set<T> toSet(final Stream<T> self) {
- return self.collect(Collectors.toSet());
+ return self.collect(toSetCollector());
}
/**
@@ -740,6 +795,144 @@ public class StreamGroovyMethods {
* @since 2.5.0
*/
public static <T> Set<T> toSet(final BaseStream<T, ? extends BaseStream>
self) {
- return stream(self.iterator()).collect(Collectors.toSet());
+ return stream(self.iterator()).collect(toSetCollector());
+ }
+
+ /**
+ * Accumulates the elements of stream into a new mutable List.
+ * <p>
+ * Explicit alternative to the native {@link Stream#toList()} (which
+ * returns an <em>unmodifiable</em> list since JDK 16). Use this when
+ * you need to add to, remove from, or sort the returned list. The returned
+ * list is a concrete {@link ArrayList} — the cached collector pins the
+ * supplier so this is contract, not happenstance.
+ * <pre class="language-groovy groovyTestCase">
+ * import java.util.stream.Stream
+ * import org.codehaus.groovy.runtime.StreamGroovyMethods
+ * def list = StreamGroovyMethods.toMutableList(Stream.of('a', 'b'))
+ * assert list == ['a', 'b']
+ * list << 'c' // mutable — no UnsupportedOperationException
+ * assert list == ['a', 'b', 'c']
+ * </pre>
+ *
+ * @param self the stream
+ * @param <T> the type of element
+ * @return a new mutable {@code java.util.List} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> List<T> toMutableList(final Stream<T> self) {
+ return self.collect(toListCollector());
+ }
+
+ /**
+ * Accumulates the elements of stream into a new mutable List.
+ * <p>
+ * Explicit-mutability alias for {@link #toList(BaseStream)}, symmetric
with
+ * {@link #toMutableList(Stream)}.
+ *
+ * @param self the {@code java.util.stream.BaseStream}
+ * @param <T> the type of element
+ * @return a new mutable {@code java.util.List} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> List<T> toMutableList(final BaseStream<T, ? extends
BaseStream> self) {
+ return stream(self.iterator()).collect(toListCollector());
+ }
+
+ /**
+ * Accumulates the elements of stream into a new mutable Set.
+ * <p>
+ * Explicit-mutability alias for {@link #toSet(Stream)}. Provided
defensively
+ * so user intent stays unambiguous if a future JDK release adds a native
+ * {@code Stream.toSet()} (which would shadow the GDK extension the way
+ * native {@link Stream#toList()} did in JDK 16).
+ *
+ * @param self the stream
+ * @param <T> the type of element
+ * @return a new mutable {@code java.util.Set} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toMutableSet(final Stream<T> self) {
+ return self.collect(toSetCollector());
+ }
+
+ /**
+ * Accumulates the elements of stream into a new mutable Set.
+ * <p>
+ * Explicit-mutability alias for {@link #toSet(BaseStream)}, symmetric with
+ * {@link #toMutableSet(Stream)}.
+ *
+ * @param self the {@code java.util.stream.BaseStream}
+ * @param <T> the type of element
+ * @return a new mutable {@code java.util.Set} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toMutableSet(final BaseStream<T, ? extends
BaseStream> self) {
+ return stream(self.iterator()).collect(toSetCollector());
+ }
+
+ /**
+ * Accumulates the elements of stream into an immutable List.
+ *
+ * @param self the {@code java.util.stream.BaseStream}
+ * @param <T> the type of element
+ * @return an immutable {@code java.util.List} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> List<T> toImmutableList(final BaseStream<T, ? extends
BaseStream> self) {
+ return stream(self.iterator()).toList();
+ }
+
+ /**
+ * Accumulates the elements of stream into an immutable Set.
+ * <p>
+ * No native {@code Stream.toSet()} exists, so this provides the immutable
+ * counterpart to {@link #toSet(Stream)}. Null elements are preserved
+ * (unlike {@link java.util.stream.Collectors#toUnmodifiableSet()} which
+ * rejects nulls); the returned set is unmodifiable and mutation attempts
+ * throw {@link UnsupportedOperationException}. Returns the canonical
+ * empty set ({@link Collections#emptySet()}) when the stream is empty.
+ * <pre class="language-groovy groovyTestCase">
+ * import java.util.stream.Stream
+ * import static groovy.test.GroovyAssert.shouldFail
+ * import static
org.codehaus.groovy.runtime.StreamGroovyMethods.toImmutableSet
+ * def set = toImmutableSet(Stream.of('a', null, 'b', 'a'))
+ * assert set == ['a', null, 'b'] as Set
+ * shouldFail(UnsupportedOperationException) { set << 'c' }
+ * assert toImmutableSet(Stream.<String>empty()) ===
Collections.emptySet()
+ * </pre>
+ *
+ * @param self the stream
+ * @param <T> the type of element
+ * @return an immutable {@code java.util.Set} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toImmutableSet(final Stream<T> self) {
+ Set<T> answer = self.collect(toSetCollector());
+ return answer.isEmpty() ? Collections.emptySet() :
Collections.unmodifiableSet(answer);
+ }
+
+ /**
+ * Accumulates the elements of stream into an immutable Set.
+ * <p>
+ * See {@link #toImmutableSet(Stream)} for the null-tolerance and empty-set
+ * semantics — this overload is the {@link BaseStream} counterpart with the
+ * same contract.
+ *
+ * @param self the {@code java.util.stream.BaseStream}
+ * @param <T> the type of element
+ * @return an immutable {@code java.util.Set} instance
+ *
+ * @since 6.0.0
+ */
+ public static <T> Set<T> toImmutableSet(final BaseStream<T, ? extends
BaseStream> self) {
+ Set<T> answer = stream(self.iterator()).collect(toSetCollector());
+ return answer.isEmpty() ? Collections.emptySet() :
Collections.unmodifiableSet(answer);
}
}