diff --git a/spring-core/src/main/java/org/springframework/util/MultiValueMapCollector.java b/spring-core/src/main/java/org/springframework/util/MultiValueMapCollector.java
index 83e0f46a62c..c02edbbb5db 100644
--- a/spring-core/src/main/java/org/springframework/util/MultiValueMapCollector.java
+++ b/spring-core/src/main/java/org/springframework/util/MultiValueMapCollector.java
@@ -17,7 +17,7 @@
package org.springframework.util;
import java.util.EnumSet;
-import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
@@ -28,32 +28,74 @@ import java.util.function.Supplier;
import java.util.stream.Collector;
/**
- * A {@link Collector} for building a {@link MultiValueMap} from a {@link java.util.stream.Stream}.
- *
- * Moved from {@code org.springframework.data.util.MultiValueMapCollector}.
+ * A {@link Collector} for building a {@link MultiValueMap} from a
+ * {@link java.util.stream.Stream Stream}.
*
- * @author Jens Schauder
+ *
Copied from the Spring Data Commons project.
*
- * @param – the type of input elements to the reduction operation
- * @param – the type of the key elements
- * @param – the type of the value elements
+ * @author Jens Schauder
+ * @author Florian Hof
+ * @author Sam Brannen
+ * @since 7.0.2
+ * @param the type of input elements to the reduction operation
+ * @param the key type
+ * @param the value element type
*/
-public class MultiValueMapCollector implements Collector, MultiValueMap> {
+public final class MultiValueMapCollector implements Collector, MultiValueMap> {
+
private final Function keyFunction;
+
private final Function valueFunction;
- public MultiValueMapCollector(Function keyFunction, Function valueFunction) {
+
+ private MultiValueMapCollector(Function keyFunction, Function valueFunction) {
this.keyFunction = keyFunction;
this.valueFunction = valueFunction;
}
+
+ /**
+ * Create a new {@code MultiValueMapCollector} from the given key and value
+ * functions.
+ * @param the type of input elements to the reduction operation
+ * @param the key type
+ * @param the value element type
+ * @param keyFunction a {@code Function} which converts an element of type
+ * {@code T} to a key of type {@code K}
+ * @param valueFunction a {@code Function} which converts an element of type
+ * {@code T} to an element of type {@code V}; supply {@link Function#identity()}
+ * if no conversion should be performed
+ * @return a new {@code MultiValueMapCollector}
+ * @see #indexingBy(Function)
+ */
+ public static MultiValueMapCollector of(Function keyFunction, Function valueFunction) {
+ return new MultiValueMapCollector<>(keyFunction, valueFunction);
+ }
+
+ /**
+ * Create a new {@code MultiValueMapCollector} using the given {@code indexer}.
+ * Delegates to {@link #of(Function, Function)}, supplying the given
+ * {@code indexer} as the key function and {@link Function#identity()}
+ * as the value function.
+ *
For example, if you would like to collect the elements of a {@code Stream}
+ * of strings into a {@link MultiValueMap} keyed by the lengths of the strings,
+ * you could create such a {@link Collector} via
+ * {@code MultiValueMapCollector.indexingBy(String::length)}.
+ * @param the key type
+ * @param the value element type
+ * @param indexer a {@code Function} which converts a value of type {@code V}
+ * to a key of type {@code K}
+ * @return a new {@code MultiValueMapCollector} based on an {@code indexer}
+ * @see #of(Function, Function)
+ */
public static MultiValueMapCollector indexingBy(Function indexer) {
return new MultiValueMapCollector<>(indexer, Function.identity());
}
+
@Override
public Supplier> supplier() {
- return () -> CollectionUtils.toMultiValueMap(new HashMap>());
+ return () -> CollectionUtils.toMultiValueMap(new LinkedHashMap>());
}
@Override
@@ -80,4 +122,5 @@ public class MultiValueMapCollector implements Collector characteristics() {
return EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.UNORDERED);
}
+
}
diff --git a/spring-core/src/test/java/org/springframework/util/MultiValueMapCollectorTests.java b/spring-core/src/test/java/org/springframework/util/MultiValueMapCollectorTests.java
index de9c080fce8..589acc07186 100644
--- a/spring-core/src/test/java/org/springframework/util/MultiValueMapCollectorTests.java
+++ b/spring-core/src/test/java/org/springframework/util/MultiValueMapCollectorTests.java
@@ -16,9 +16,10 @@
package org.springframework.util;
+import java.util.function.Function;
import java.util.stream.Stream;
-import org.junit.Test;
+import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -26,16 +27,33 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link MultiValueMapCollector}.
*
* @author Florian Hof
+ * @author Sam Brannen
+ * @since 7.0.2
*/
class MultiValueMapCollectorTests {
@Test
- void indexingBy() {
- MultiValueMapCollector collector = MultiValueMapCollector.indexingBy(String::length);
- MultiValueMap content = Stream.of("abc", "ABC", "123", "1234", "abcdef", "ABCDEF").collect(collector);
- assertThat(content.get(3)).containsOnly("abc", "ABC", "123");
- assertThat(content.get(4)).containsOnly("abcdef", "ABCDEF");
- assertThat(content.get(6)).containsOnly("1234");
- assertThat(content.get(1)).isNull();
+ void ofFactoryMethod() {
+ Function keyFunction = i -> (i % 2 == 0 ? "even" :"odd");
+ Function valueFunction = i -> -i;
+
+ var collector = MultiValueMapCollector.of(keyFunction, valueFunction);
+ var multiValueMap = Stream.of(1, 2, 3, 4, 5).collect(collector);
+
+ assertThat(multiValueMap).containsOnlyKeys("even", "odd");
+ assertThat(multiValueMap.get("odd")).containsOnly(-1, -3, -5);
+ assertThat(multiValueMap.get("even")).containsOnly(-2, -4);
}
+
+ @Test
+ void indexingByFactoryMethod() {
+ var collector = MultiValueMapCollector.indexingBy(String::length);
+ var multiValueMap = Stream.of("abc", "ABC", "123", "1234", "cat", "abcdef", "ABCDEF").collect(collector);
+
+ assertThat(multiValueMap).containsOnlyKeys(3, 4, 6);
+ assertThat(multiValueMap.get(3)).containsOnly("abc", "ABC", "123", "cat");
+ assertThat(multiValueMap.get(4)).containsOnly("1234");
+ assertThat(multiValueMap.get(6)).containsOnly("abcdef", "ABCDEF");
+ }
+
}