Java 8 flatmap , stream,collect - java

I have a situation where I have
class A {
private B b;
public B getB() {
return b;
}
}
and another class B
class B {
private List<C> c;
public List<C> getListC() {
return c;
}
}
Now class C contains two instance variables
class C {
private int id;
private String name;
public int getId() {
return id;
}
public String getName() {
return name;
}
}
Now I want to achieve the below using java 8
List<C> newListC = a.getB().getListC();
Map<Integer, String> map = new HashMap<>();
for(C c : newListC) {
map.put(c.getId,c.getName());
}
I have tried many time but every time I face different problems.
My code:
Optional<A> a=Optional.of(new A());
Map<Integer, String> map= a.map(A::getB)
.flatMap(b ->
b.getListC()
.stream()
.collect(Collectors.toMap(
C::getId,
C::getName
)
)
);
Error message :
Error:(164, 33) java: incompatible types: no instance(s) of type
variable(s) U,R,A,capture#1 of ?,T,K,U exist so that
java.util.Optional<U> conforms to
java.util.Map<java.lang.Integer,java.lang.String>
Thanks in advance

Though I hit couple of compilation errors as I can see few typos, but
Try:
List<C> newListC= new A().getB().getListC();
Map<Integer, String> stringMap = newListC.stream()
.collect(Collectors
.toMap(C::getId, C::getName));
Provided that you fixed your compilation issues, it should be emitting result equivalent to non-stream version of map.

You can’t flatMap an Optional to a Map; the function has to return an Optional. On the other hand, since the function doesn’t return an Optional, a flatMap is unnecessary and an ordinary map will do:
Map<Integer, String> map = Optional.of(new A())
.map(A::getB)
.map(b -> b.getListC().stream().collect(Collectors.toMap(C::getId, C::getName)))
.orElse(Collections.emptyMap());
But since the result of new A() can’t be null (and using of instead of ofNullable acknowledges this), the indirect processing at the beginning of the chain is unnecessary:
Map<Integer, String> map = Optional.ofNullable(new A().getB())
.map(b -> b.getListC().stream().collect(Collectors.toMap(C::getId, C::getName)))
.orElse(Collections.emptyMap());
But note that only the nullability of the result of getB is handled, as the function passed to the next mapping step unconditionally invokes stream() on the list returned by getListC. But returning null where a List is expected is bad coding style anyway; you can always return an empty list to represent the absence of values.
Maybe your confusion stems from a Stream based alternative solution:
Map<Integer, String> map = Stream.of(new A().getB())
.filter(Objects::nonNull)
.flatMap(b -> b.getListC().stream())
.collect(Collectors.toMap(C::getId, C::getName));
Here, a stream consisting of at most one element is created, followed by flatMaping it to the items of the list returned by getListC…

Related

How could I make a stream pipeline more variable in Java?

I wrote a stream pipeline:
private void calcMin(Clazz clazz) {
OptionalInt min = listOfObjects.stream().filter(y -> (y.getName()
.matches(clazz.getFilter())))
.map(y -> (y.getUserNumber()))
.mapToInt(Integer::intValue)
.min();
list.add(min.getAsInt());
}
This pipeline gives me the lowest UserNumber.
So far, so good.
But I also need the greatest UserNumber.
And I also need the lowest GroupNumber.
And also the greatest GroupNumber.
I could write:
private void calcMax(Clazz clazz) {
OptionalInt max = listOfObjects.stream().filter(y -> (y.getName()
.matches(clazz.getFilter())))
.map(y -> (y.getUserNumber()))
.mapToInt(Integer::intValue)
.max();
list.add(max.getAsInt());
}
And I could also write the same for .map(y -> (y.getGroupNumber())).
This will work, but it is very redudant.
Is there a way to do it more variable?
There are two differences in the examples: the map() operation, and the terminal operation (min() and max()). So, to reuse the rest of the pipeline, you'll want to parameterize these.
I will warn you up front, however, that if you call this parameterized method directly from many places, your code will be harder to read. Comprehension of the caller's code will be easier if you keep a helper function—with a meaningful name—that delegates to the generic method. Obviously, there is a balance here. If you wanted to add additional functional parameters, the number of helper methods would grow rapidly and become cumbersome. And if you only call each helper from one place, maybe using the underlying function directly won't add too much clutter.
You don't show the type of elements in the stream. I'm using the name MyClass in this example as a placeholder.
private static OptionalInt extremum(
Collection<? extends MyClass> input,
Clazz clazz,
ToIntFunction<? super MyClass> valExtractor,
Function<IntStream, OptionalInt> terminalOp) {
IntStream matches = input.stream()
.filter(y -> y.getName().matches(clazz.getFilter()))
.mapToInt(valExtractor);
return terminalOp.apply(matches);
}
private OptionalInt calcMinUserNumber(Clazz clazz) {
return extremum(listOfObjects, clazz, MyClass::getUserNumber, IntStream::min);
}
private OptionalInt calcMaxUserNumber(Clazz clazz) {
return extremum(listOfObjects, clazz, MyClass::getUserNumber, IntStream::max);
}
private OptionalInt calcMinGroupNumber(Clazz clazz) {
return extremum(listOfObjects, clazz, MyClass::getGroupNumber, IntStream::min);
}
private OptionalInt calcMaxGroupNumber(Clazz clazz) {
return extremum(listOfObjects, clazz, MyClass::getGroupNumber, IntStream::max);
}
...
And here's a usage example:
calcMaxGroupNumber(clazz).ifPresent(list::add);
The solution may reduce redundancy but it removes readability from the code.
IntStream maxi = listOfObjects.stream().filter(y -> (y.getName()
.matches(clazz.getFilter())))
.map(y -> (y.getUserNumber()))
.mapToInt(Integer::intValue);
System.out.println(applier(() -> maxi, IntStream::max));
//System.out.println(applier(() -> maxi, IntStream::min));
...
public static OptionalInt applier(Supplier<IntStream> supplier, Function<IntStream, OptionalInt> predicate) {
return predicate.apply(supplier.get());
}
For the sake of variety, I want to add the following approach which uses a nested Collectors.teeing (Java 12 or higher) which enables to get all values by just streaming over the collection only once.
For the set up, I am using the below simple class :
#AllArgsConstructor
#ToString
#Getter
static class MyObject {
int userNumber;
int groupNumber;
}
and a list of MyObjects:
List<MyObject> myObjectList = List.of(
new MyObject(1, 2),
new MyObject(2, 3),
new MyObject(3, 4),
new MyObject(5, 3),
new MyObject(6, 2),
new MyObject(7, 6),
new MyObject(1, 12));
If the task was to get the max and min userNumber one could do a simple teeing like below and add for example the values to map:
Map<String , Integer> maxMinUserNum =
myObjectList.stream()
.collect(
Collectors.teeing(
Collectors.reducing(Integer.MAX_VALUE, MyObject::getUserNumber, Integer::min),
Collectors.reducing(Integer.MIN_VALUE, MyObject::getUserNumber, Integer::max),
(min,max) -> {
Map<String,Integer> map = new HashMap<>();
map.put("minUser",min);
map.put("maxUser",max);
return map;
}));
System.out.println(maxMinUserNum);
//output: {minUser=1, maxUser=7}
Since the task also includes to get the max and min group numbers, we could use the same approach as above and only need to nest the teeing collector :
Map<String , Integer> result =
myObjectList.stream()
.collect(
Collectors.teeing(
Collectors.teeing(
Collectors.reducing(Integer.MAX_VALUE, MyObject::getUserNumber, Integer::min),
Collectors.reducing(Integer.MIN_VALUE, MyObject::getUserNumber, Integer::max),
(min,max) -> {
Map<String,Integer> map = new LinkedHashMap<>();
map.put("minUser",min);
map.put("maxUser",max);
return map;
}),
Collectors.teeing(
Collectors.reducing(Integer.MAX_VALUE, MyObject::getGroupNumber, Integer::min),
Collectors.reducing(Integer.MIN_VALUE, MyObject::getGroupNumber, Integer::max),
(min,max) -> {
Map<String,Integer> map = new LinkedHashMap<>();
map.put("minGroup",min);
map.put("maxGroup",max);
return map;
}),
(map1,map2) -> {
map1.putAll(map2);
return map1;
}));
System.out.println(result);
output
{minUser=1, maxUser=7, minGroup=2, maxGroup=12}

Lambda function with four lists of strings and if-else condition

I have a request from tech lead to rewrite this code and replace for-loop with generic lambda. I doubt that this will lead to more simpler, more readable and maintainable code.
Is there a really a good way to do that, please?
The question is about how to transform current for-loop into the lambda function. Change of item's data structure is completely out of scope. See the loop - it is a devision of the states list while simultaneously checking value in addressType at the same index.
How to do that with lambda and will it actually simplify the code?
List<String> states = Arrays.asList(item.getState().split(","));
List<String> addressType = Arrays.asList(item.getAddressType().split(","));
List<String> mailingStates = new ArrayList<>();
List<String> physicalStates = new ArrayList<>();
for(int i = 0; i<states.size(); i++){
if(Constants.MAILING.equals(addressType.get(i))){
mailingStates.add(states.get(i));
} else {
physicalStates.add(states.get(i));
}
}
Need to say - Java 8 only
The code will be the same, so I don't know what the point would be, but it will use a lambda expression block.
List<String> states = Arrays.asList(item.getState().split(","));
List<String> addressType = Arrays.asList(item.getAddressType().split(","));
List<String> mailingStates = new ArrayList<>(), physicalStates = new ArrayList<>();
IntStream.range(0, states.size()).forEach(i -> {
if (Constants.MAILING.equals(addressType.get(i))) {
mailingStates.add(states.get(i));
} else {
physicalStates.add(states.get(i));
}
});
What you want to do is go through two lists at the same name. The generic name for this operation is "zip" - when you go through two (or sometimes more) arrays/lists/streams/etc and do something with each element.
You can pick an implementation for streams from here: Zipping streams using JDK8 with lambda (java.util.stream.Streams.zip) there are many that are already implemented in existing libraries, as well. If you already have such a library in your project, you need but an import to use it.
For illustrative purposes, I'll assume there is an implementation available with this signature:
<A, B, C> Stream<C> zip(Stream<? extends A> a,
Stream<? extends B> b,
BiFunction<? super A, ? super B, ? extends C> zipper)
Also, a good simple generic utility would be a Pair class that has two values. There are many existing implementations. I'll an implementation with this this signature is available:
class Pair<LEFT, RIGHT> {
Pair(LEFT left, RIGHT right);
LEFT getLeft();
RIGHT getRight();
}
This will hold the related state and address type. But you can also consider creating a specific object that encapsulates a given state and address type.
With these generic helpers, your code can look like this:
Stream<String> states = Arrays.stream(item.getState().split(","));
Stream<String> addressType = Arrays.stream(item.getAddressType().split(","));
Map<Boolean, List<String>> splitStates = zip(states, addressTypes,
(state, addressType) -> new Pair<String, String>(state, addressType))
.collect(
Collectors.partitioningBy(pair -> Constants.MAILING.equals(pair.getRight()),
collectors.mapping(pair -> pair.getLeft())
)
);
List<String> mailingStates = split.get(true);
List<String> physicalStates = split.get(false);
If lambdas are replaced with method references and some minor rearrangement when possible, then you get:
private static final Predicate<Pair<String, String> IS_Mailing =
pair -> Constants.MAILING.equals(pair.getRight());
/* ... */
Stream<String> states = Arrays.stream(item.getState().split(","));
Stream<String> addressType = Arrays.stream(item.getAddressType().split(","));
Map<Boolean, List<String>> splitStates = zip(states, addressTypes, Pair::new)
.collect(Collectors.partitioningBy(IS_MAILING),
collectors.mapping(Pair::getLeft()));
List<String> mailingStates = split.get(true);
List<String> physicalStates = split.get(false);
And if instead of generic Pair class you implement a class like:
class StateData {
private String state;
private String addressType;
public StateData(String state, String addressType) {
this.state = state;
this.addressType = addressType;
}
public String getState() { return this.state; }
public String getAddressType() { return this.addressType; }
public boolean isMailing() {
return Constants.MAILING.equals(this.getAddressType());
}
}
The code becomes more semantic:
Stream<String> states = Arrays.stream(item.getState().split(","));
Stream<String> addressType = Arrays.stream(item.getAddressType().split(","));
Map<Boolean, List<String>> splitStates = zip(states, addressTypes, StateData::new)
.collect(Collectors.partitioningBy(StateData::isMailing),
collectors.mapping(StateData::getState()));
List<String> mailingStates = split.get(true);
List<String> physicalStates = split.get(false);
One final consideration would be to create an enum for addressType instead of comparing to a constant.
You may use the partitioningBy to separate out items based on the Constants.MAILING.equals(addressType.get(i)) condition.
Map<Boolean, List<Integer>> map
= IntStream.range(0, states.size())
.boxed()
.collect(Collectors.partitioningBy(i -> Constants.MAILING.equals(addressType.get(i))));
List<String> mailingStates = map.get(true);
List<String> physicalStates = map.get(false);

How do I convert List to Map where the key is one of the properties and value is the object using Collector [duplicate]

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 need to iterate and compute Nested Map<key, someObjectArray[]>

I need to iterate a Map with Key and List to provide another type of object. I have tried to explain in the code level.
I have tried with for loop which works fine. But I like to have in Java8 streaming
public Map<String, List<TestClassResult>> getComputed(
Map<String, SourceClass[]> sourceMapObject) {
Map<String, List<TestClassResult>> response = new HashMap<>();
// Here goes the functionality
List<TestClassResult> result;
for (Map.Entry<String, SourceClass[]> entry : sourceMapObject.entrySet()) {
result = new ArrayList<>();
String key = entry.getKey();
for (SourceClass value : entry.getValue()) {
result.add(someMethod(value.id, value.empCode));
}
response.put(key, result);
}
return response;
}
public class SourceClass{
private String id;
private String empCode;
}
public class TestClassResult{
private String empName;
private String empMartial;
private int empAge;
}
I need this to be implemented with Java 8 streams and lambdas
sourceMapObject.entrySet()
.stream()
.collect(Collectors.toMap(
Entry::getKey,
entry -> Arrays.stream(entry.getValue())
.map(value -> someMethod(value.id, value.empCode))
.collect(Collectors.toList()),
(left, right) -> right
))
If you know for sure you will not have duplicates you can omit the (left, right) -> right part. But since in your existing code, you had response.put(key, result); I'd kept it to conform to that.
The point here is that Map::put will override the previous value that you already had in the Map, while a Collectors::toMap without a merger will throw an Exception. On the other hand with (left, right) -> right, it will behave just like the put.

Java 8: efficiently put values from List to the Map [duplicate]

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}

Categories

Resources