I want to translate a List of objects into a Map using Java 8's streams and lambdas.
This is how I would write it in Java 7 and below.
private Map<String, Choice> nameMap(List<Choice> choices) {
final Map<String, Choice> hashMap = new HashMap<>();
for (final Choice choice : choices) {
hashMap.put(choice.getName(), choice);
}
return hashMap;
}
I can accomplish this easily using Java 8 and Guava but I would like to know how to do this without Guava.
In Guava:
private Map<String, Choice> nameMap(List<Choice> choices) {
return Maps.uniqueIndex(choices, new Function<Choice, String>() {
#Override
public String apply(final Choice input) {
return input.getName();
}
});
}
And Guava with Java 8 lambdas.
private Map<String, Choice> nameMap(List<Choice> choices) {
return Maps.uniqueIndex(choices, Choice::getName);
}
Based on Collectors documentation it's as simple as:
Map<String, Choice> result =
choices.stream().collect(Collectors.toMap(Choice::getName,
Function.identity()));
If your key is NOT guaranteed to be unique for all elements in the list, you should convert it to a Map<String, List<Choice>> instead of a Map<String, Choice>
Map<String, List<Choice>> result =
choices.stream().collect(Collectors.groupingBy(Choice::getName));
Use getName() as the key and Choice itself as the value of the map:
Map<String, Choice> result =
choices.stream().collect(Collectors.toMap(Choice::getName, c -> c));
Most of the answers listed, miss a case when the list has duplicate items. In that case there answer will throw IllegalStateException. Refer the below code to handle list duplicates as well:
public Map<String, Choice> convertListToMap(List<Choice> choices) {
return choices.stream()
.collect(Collectors.toMap(Choice::getName, choice -> choice,
(oldValue, newValue) -> newValue));
}
Here's another one in case you don't want to use Collectors.toMap()
Map<String, Choice> result =
choices.stream().collect(HashMap<String, Choice>::new,
(m, c) -> m.put(c.getName(), c),
(m, u) -> {});
One more option in simple way
Map<String,Choice> map = new HashMap<>();
choices.forEach(e->map.put(e.getName(),e));
For example, if you want convert object fields to map:
Example object:
class Item{
private String code;
private String name;
public Item(String code, String name) {
this.code = code;
this.name = name;
}
//getters and setters
}
And operation convert List To Map:
List<Item> list = new ArrayList<>();
list.add(new Item("code1", "name1"));
list.add(new Item("code2", "name2"));
Map<String,String> map = list.stream()
.collect(Collectors.toMap(Item::getCode, Item::getName));
If you don't mind using 3rd party libraries, AOL's cyclops-react lib (disclosure I am a contributor) has extensions for all JDK Collection types, including List and Map.
ListX<Choices> choices;
Map<String, Choice> map = choices.toMap(c-> c.getName(),c->c);
You can create a Stream of the indices using an IntStream and then convert them to a Map :
Map<Integer,Item> map =
IntStream.range(0,items.size())
.boxed()
.collect(Collectors.toMap (i -> i, i -> items.get(i)));
I was trying to do this and found that, using the answers above, when using Functions.identity() for the key to the Map, then I had issues with using a local method like this::localMethodName to actually work because of typing issues.
Functions.identity() actually does something to the typing in this case so the method would only work by returning Object and accepting a param of Object
To solve this, I ended up ditching Functions.identity() and using s->s instead.
So my code, in my case to list all directories inside a directory, and for each one use the name of the directory as the key to the map and then call a method with the directory name and return a collection of items, looks like:
Map<String, Collection<ItemType>> items = Arrays.stream(itemFilesDir.listFiles(File::isDirectory))
.map(File::getName)
.collect(Collectors.toMap(s->s, this::retrieveBrandItems));
I will write how to convert list to map using generics and inversion of control. Just universal method!
Maybe we have list of Integers or list of objects. So the question is the following: what should be key of the map?
create interface
public interface KeyFinder<K, E> {
K getKey(E e);
}
now using inversion of control:
static <K, E> Map<K, E> listToMap(List<E> list, KeyFinder<K, E> finder) {
return list.stream().collect(Collectors.toMap(e -> finder.getKey(e) , e -> e));
}
For example, if we have objects of book , this class is to choose key for the map
public class BookKeyFinder implements KeyFinder<Long, Book> {
#Override
public Long getKey(Book e) {
return e.getPrice()
}
}
I use this syntax
Map<Integer, List<Choice>> choiceMap =
choices.stream().collect(Collectors.groupingBy(choice -> choice.getName()));
It's possible to use streams to do this. To remove the need to explicitly use Collectors, it's possible to import toMap statically (as recommended by Effective Java, third edition).
import static java.util.stream.Collectors.toMap;
private static Map<String, Choice> nameMap(List<Choice> choices) {
return choices.stream().collect(toMap(Choice::getName, it -> it));
}
Another possibility only present in comments yet:
Map<String, Choice> result =
choices.stream().collect(Collectors.toMap(c -> c.getName(), c -> c)));
Useful if you want to use a parameter of a sub-object as Key:
Map<String, Choice> result =
choices.stream().collect(Collectors.toMap(c -> c.getUser().getName(), c -> c)));
Map<String, Set<String>> collect = Arrays.asList(Locale.getAvailableLocales()).stream().collect(Collectors
.toMap(l -> l.getDisplayCountry(), l -> Collections.singleton(l.getDisplayLanguage())));
This can be done in 2 ways. Let person be the class we are going to use to demonstrate it.
public class Person {
private String name;
private int age;
public String getAge() {
return age;
}
}
Let persons be the list of Persons to be converted to the map
1.Using Simple foreach and a Lambda Expression on the List
Map<Integer,List<Person>> mapPersons = new HashMap<>();
persons.forEach(p->mapPersons.put(p.getAge(),p));
2.Using Collectors on Stream defined on the given List.
Map<Integer,List<Person>> mapPersons =
persons.stream().collect(Collectors.groupingBy(Person::getAge));
Here is solution by StreamEx
StreamEx.of(choices).toMap(Choice::getName, c -> c);
Map<String,Choice> map=list.stream().collect(Collectors.toMap(Choice::getName, s->s));
Even serves this purpose for me,
Map<String,Choice> map= list1.stream().collect(()-> new HashMap<String,Choice>(),
(r,s) -> r.put(s.getString(),s),(r,s) -> r.putAll(s));
If every new value for the same key name has to be overridden:
public Map < String, Choice > convertListToMap(List < Choice > choices) {
return choices.stream()
.collect(Collectors.toMap(Choice::getName,
Function.identity(),
(oldValue, newValue) - > newValue));
}
If all choices have to be grouped in a list for a name:
public Map < String, Choice > convertListToMap(List < Choice > choices) {
return choices.stream().collect(Collectors.groupingBy(Choice::getName));
}
List<V> choices; // your list
Map<K,V> result = choices.stream().collect(Collectors.toMap(choice::getKey(),choice));
//assuming class "V" has a method to get the key, this method must handle case of duplicates too and provide a unique key.
As an alternative to guava one can use kotlin-stdlib
private Map<String, Choice> nameMap(List<Choice> choices) {
return CollectionsKt.associateBy(choices, Choice::getName);
}
List<Integer> listA = new ArrayList<>();
listA.add(1);
listA.add(5);
listA.add(3);
listA.add(4);
System.out.println(listA.stream().collect(Collectors.toMap(x ->x, x->x)));
String array[] = {"ASDFASDFASDF","AA", "BBB", "CCCC", "DD", "EEDDDAD"};
List<String> list = Arrays.asList(array);
Map<Integer, String> map = list.stream()
.collect(Collectors.toMap(s -> s.length(), s -> s, (x, y) -> {
System.out.println("Dublicate key" + x);
return x;
},()-> new TreeMap<>((s1,s2)->s2.compareTo(s1))));
System.out.println(map);
Dublicate key AA
{12=ASDFASDFASDF, 7=EEDDDAD, 4=CCCC, 3=BBB, 2=AA}
I have the following code that executes as I intend:
import java.util.*;
import java.util.stream.Collectors;
public class HelloWorld{
public static void main(String []args){
HelloWorld.TreeNode rootNode = new HelloWorld().new TreeNode<Integer>(4);
List<Integer> traversal = rootNode.inorderTraversal();
// Prints 4
System.out.println(
String.join(",",
traversal
.stream()
.map(Object::toString)
.collect(Collectors.toList())
)
);
}
class TreeNode<K extends Comparable<K>> {
TreeNode<K> left;
TreeNode<K> right;
K val;
TreeNode(K val, TreeNode<K> left, TreeNode<K> right) {
this.val = val;
this.left = left;
this.right = right;
}
TreeNode(K val) {
this(val, null, null);
}
List<K> inorderTraversal() {
List<K> list = new ArrayList<>();
list.add(this.val);
return list;
}
}
}
However, if I replace the commented line with
System.out.println(
String.join(",",
rootNode.inorderTraversal()
.stream()
.map(Object::toString)
.collect(Collectors.toList())
)
);
I get the following error:
HelloWorld.java:14: error: no suitable method found for join(String,Object)
String.join(",",
^
method String.join(CharSequence,CharSequence...) is not applicable
(varargs mismatch; Object cannot be converted to CharSequence)
method String.join(CharSequence,Iterable<? extends CharSequence>) is not
applicable
(argument mismatch; Object cannot be converted to Iterable<? extends
CharSequence>)
Note: HelloWorld.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
1 error
I saw this very similar issue (Why does this java 8 stream operation evaluate to Object instead of List<Object> or just List?), but I don't see how my solution doesn't circumvent the problem that user had because rootNode.inorderTraversal() return a List<Integer> instead of a List.
Thanks in advance for any assistance!
This is because you are using raw types. Parameterize it with the generic types like so.
HelloWorld.TreeNode<Integer> rootNode = new HelloWorld().new TreeNode<>(4);
This will fix the issue. If you don't supply a generic type parameter on the left-hand side, the List is declared as a raw type.
Currently to do something simple with Collections of CompletionStage requires jumping through several ugly hoops:
public static CompletionStage<String> translate(String foo) {
// just example code to reproduce
return CompletableFuture.completedFuture("translated " + foo);
}
public static CompletionStage<List<String>> translateAllAsync(List<String> input) {
List<CompletableFuture<String>> tFutures = input.stream()
.map(s -> translate(s)
.toCompletableFuture())
.collect(Collectors.toList()); // cannot use toArray because of generics Arrays creation :-(
return CompletableFuture.allOf(tFutures.toArray(new CompletableFuture<?>[0])) // not using size() on purpose, see comments
.thenApply(nil -> tFutures.stream()
.map(f -> f.join())
.map(s -> s.toUpperCase())
.collect(Collectors.toList()));
}
What I want to write is:
public CompletionStage<List<String>> translateAllAsync(List<String> input) {
// allOf takes a collection< futures<X>>,
// and returns a future<collection<x>> for thenApply()
return XXXUtil.allOf(input.stream()
.map(s -> translate(s))
.collect(Collectors.toList()))
.thenApply(translations -> translations.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList()));
}
The whole ceremony about toCompletableFuture and converting to an Array and join is boilerplate distracting from the actual code semantics.
Possibly having a version of allOf() returning a Future<Collection<Future<X>>> instead of Future<Collection<X>> may also be useful in some cases.
I could try implementing XXXUtil myself, but I wonder if there already is a mature 3rdparty library for this and similar issues (Such as Spotify's CompletableFutures). If so, I'd like to see the equivalent code for such a library as an answer.
Or maybe the original code posted above can somehow be written more compactly in a different way?
JUnit test code:
#Test
public void testTranslate() throws Exception {
List<String> list = translateAllAsync(Arrays.asList("foo", "bar")).toCompletableFuture().get();
Collections.sort(list);
assertEquals(list,
Arrays.asList("TRANSLATED BAR", "TRANSLATED FOO"));
}
I just looked into the source code of CompletableFuture.allOf, to find that it basically creates a binary tree of nodes handling two stages at a time. We can easily implement a similar logic without using toCompletableFuture() explicitly and handling the result list generation in one go:
public static <T> CompletionStage<List<T>> allOf(
Stream<? extends CompletionStage<? extends T>> source) {
return allOf(source.collect(Collectors.toList()));
}
public static <T> CompletionStage<List<T>> allOf(
List<? extends CompletionStage<? extends T>> source) {
int size = source.size();
if(size == 0) return CompletableFuture.completedFuture(Collections.emptyList());
List<T> result = new ArrayList<>(Collections.nCopies(size, null));
return allOf(source, result, 0, size-1).thenApply(x -> result);
}
private static <T> CompletionStage<Void> allOf(
List<? extends CompletionStage<? extends T>> source,
List<T> result, int from, int to) {
if(from < to) {
int mid = (from+to)>>>1;
return allOf(source, result, from, mid)
.thenCombine(allOf(source, result, mid+1, to), (x,y)->x);
}
return source.get(from).thenAccept(t -> result.set(from, t));
}
That’s it.
You can use this solution to implement the logic of your question’s code as
public static CompletionStage<List<String>> translateAllAsync(List<String> input) {
return allOf(input.stream().map(s -> translate(s)))
.thenApply(list -> list.stream()
.map(s -> s.toUpperCase())
.collect(Collectors.toList()));
}
though it would be more natural to use
public static CompletionStage<List<String>> translateAllAsync(List<String> input) {
return allOf(input.stream().map(s -> translate(s).thenApply(String::toUpperCase)));
}
Note that this solution maintains the order, so there is no need for sorting the result in the test case:
#Test
public void testTranslate() throws Exception {
List<String> list = translateAllAsync(Arrays.asList("foo", "bar")).toCompletableFuture().get();
assertEquals(list, Arrays.asList("TRANSLATED FOO", "TRANSLATED BAR"));
}
Compile Error :
The method updateStateByKey(Function2<List<Integer>,Optional<S>,Optional<S>>) in the type JavaPairDStream<String,Integer> is not applicable for the arguments (Function2<List<Integer>,Optional<Integer>,Optional<Integer>>)
In a simple word count example , mapping the words with 1
JavaPairDStream<String, Integer> wordCounts = words.mapToPair(s -> new Tuple2<>(s,1));
And then applying updateStateByKey on wordCounts
JavaPairDStream<String, Integer> finalcount = wordCounts.updateStateByKey(updateFunction);
The updateFunction is defined as follows:
final Function2<List<Integer>, Optional<Integer>, Optional<Integer>> updateFunction =
new Function2<List<Integer>, Optional<Integer>, Optional<Integer>>() {
#Override
public Optional<Integer> call(List<Integer> values, Optional<Integer> state) {
Integer newSum = state.orElse(0);
for (Integer value : values) {
newSum += value;
}
return Optional.of(newSum);
}
};
The updateStateByKey has following recommended signatures available:
Please check which package you import for using Optional. Spark use com.google.common.base.Optional not jdk default package java.util.Optional.
I'm using Eclipse and I have mulitmodule Maven project in which I use Lombok(1.16.4) Java=jdk1.7.0_71.
In Eclipse all my code compiles and JUnit testes pass. However in Maven (v. 3.2.3 and 3.3.3) code does not compile.
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] DetermineMarketDirection.java:[86,42] cannot find symbol
symbol: method sumValuesDouble(java.lang.String)
location: variable listToWorkOn of type java.util.List<T>
[INFO] 1 error
This sumValuesDouble is my Lombok's extension method for type List<> which is defined in module A. Error above occures in module C which depends on module A.
This is part of ExtensionList.java:
public static <T> Double sumValuesDouble(List<T> list, final String methodName) {
return sumValuesOf(list, methodName, Double.class);
}
private static <T, N extends Number> N sumValuesOf(List<T> list, final String methodName, Class<N> type) {
Function<T, N> transform = new Function<T, N>() {
#SuppressWarnings("unchecked")
#Override
#SneakyThrows
public N apply(T from) {
Method method = from.getClass().getMethod(methodName);
return (N) method.invoke(from);
}
};
List<N> projectedList = Lists.transform(list, transform);
return sumAll(projectedList);
}
It's very strange because I have tests in Module A for this extension method and they compile OK and pass.
Part of test from Module A:
#ExtensionMethod({ ExtensionList.class })
public class ExtensionListSumAllTest {
#Test
public void testDouble() {
List<ValueDouble> listDouble = new ArrayList<>();
listDouble.add(new ValueDouble(1, 1d));
listDouble.add(new ValueDouble(2, 2d));
listDouble.add(new ValueDouble(3, 3d));
Double actual = listDouble.sumValuesDouble("getValue");
final Double expected = 6d;
assertEquals(expected, actual);
}
#AllArgsConstructor
#Getter
public class ValueDouble {
private final Integer id;
private final Double value;
}
}
What can I do to make Maven "see" this method?
BTW: I use a lot of extension methods in module c and Maven has problem with only that one. Also in this project/eclipse I use Lombok for a long time and I didn't had such problem before.
UPDATE:
I played a little bit more and I found out, that when I add 'dummy' code before execution of Extension method maven compile code wihtout problems.
So, this does not compile
#Slf4j
#ExtensionMethod({ ExtensionList.class })
public class SmaCalculator {
static <C extends Candle> double calculateSMA(List<C> candles, int sma) {
List<C> listToWorkOn = prepareListForSmaCalculation(candles, sma);
Double sum = listToWorkOn.sumValuesOf("getClosePrice");
Double smaValue = sum / sma;
return smaValue;
}
...other methods...
}
But this compiles SUCCESSFULLY by Maven, and dupa object does not even referes to listToWorkOn object:
static <C extends Candle> double calculateSMA(List<C> candles, int sma) {
List<C> listToWorkOn = prepareListForSmaCalculation(candles, sma);
List<C> dupa = new ArrayList<C>();
dupa.add(listToWorkOn.first());
dupa.sumValuesOf("getClosePrice");
Double sum = listToWorkOn.sumValuesOf("getClosePrice");
Double smaValue = sum / sma;
return smaValue;
}
I had the same problem, after switching back to 1.16.2 and it seems to work.