-1

I am learning some cool stuff about Java StreamAPI and got stuck'd into one problem: I have a use case where I want to return newly create hashmap using stream. I am using the traditional way of defining a HashMap in the function and adding up values to it. I was more interested in knowing some better ways to achieve so

public Map<String,String> constructMap(List<CustomObject> lists){
Map<String,String> newMap = new HashMap<>();
lists.stream().filter(x->x!=null).forEach(map -> newMap.putAll(map.getSomeMapping(studentId));
return newMap;
}

Can I achieve this using reduceAPI or any other way without having to create a custom hashmap (directly return the stream one liner)?

Edit: for Example:

CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = new ArrayList();
lists.add(c1); lists.add(c2);

The getter in class CustomObject is: getSomeMapping(input)
 which return Map<BookID, Book> 

Expected output: 
{"bookId1" : "book1", "bookId2" : "book2"}

Edit2:

One more thing to clarify, the CustomObject class does not have any other getters defined. The only function I have access to is getSomeMapping(input) which returns a mapping

thank you for any help.

gabru101
  • 65
  • 1
  • 5

5 Answers5

2

Assuming CustomObject has the following structure and getter getSomeMapping which returns a map:

class CustomObject {
    private Map<String, String> someMapping;

    public CustomObject(String key, String value) {
        this.someMapping = new HashMap<>();
        someMapping.put(key, value);
    }


    public Map<String, String> getSomeMapping() {
        return someMapping;
    }
}

Then constructMap will use already mentioned Collectors.toMap after flattening the entries in someMapping:

public static Map<String, String> constructMap(List<CustomObject> list) {
    return list.stream()
            .filter(Objects::nonNull)
            .map(CustomObject::getSomeMapping)
            .flatMap(map -> map.entrySet().stream())
            .collect(Collectors.toMap(
                    Map.Entry::getKey,
                    Map.Entry::getValue,
                    (v1, v2) -> v1, // merge function to handle possible duplicates
                    LinkedHashMap::new
            ));
}

Test

CustomObject c1 = new CustomObject("bookId1", "book1");
CustomObject c2 = new CustomObject("bookId2", "book2");
List<CustomObject> lists = Arrays.asList(c1, c2);

Map<String, String> result = constructMap(lists);
System.out.println(result);

Output:

{bookId1=book1, bookId2=book2}
Nowhere Man
  • 19,170
  • 9
  • 17
  • 42
  • getSomeMapping() takes an input -> process -> returns mapping. so I had to do something like: list.stream() .filter(Objects::nonNull).map(x -> x.getSomeMapping(input)) .flatMap(map -> map.entrySet().stream()) .collect(Collectors.toMap( Map.Entry::getKey, Map.Entry::getValue, (v1, v2) -> v1, // merge function to handle possible duplicates LinkedHashMap::new )); how does it checks for duplicates? by keys? or complete key-value pair? @Alex – gabru101 Feb 23 '21 at 04:56
  • Merge function selects a value from values `v1` and `v2` by the same key (to keep existing value `v1` or to replace it with the "new" value `v2`). Btw, what is the `input`? – Nowhere Man Feb 23 '21 at 05:53
1

You can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper, BinaryOperator<U> mergeFunction, Supplier<M> mapSupplier) to create a LinkedHashMap using the bookId as the key, and bookName as the value.

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

class CustomObject {
    private String bookId;
    private String bookName;

    public CustomObject(String bookId, String bookName) {
        this.bookId = bookId;
        this.bookName = bookName;
    }

    public String getBookId() {
        return bookId;
    }

    public String getBookName() {
        return bookName;
    }
    
    // Other stuff e.g. equals, hashCode etc.
}

public class Main {
    public static void main(String[] args) {
        List<CustomObject> list = List.of(new CustomObject("bookId1", "book1"), new CustomObject("bookId2", "book2"));
        System.out.println(constructMap(list));
    }

    public static Map<String, String> constructMap(List<CustomObject> list) {
        return list.stream()
                .filter(Objects::nonNull)
                .collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName, (a, b) -> a, LinkedHashMap::new));
    }
}

Output:

{bookId1=book1, bookId2=book2}

Note: The mergeFunction, (a, b) -> a resolves the collision between values associated with the same key e.g. in this case, we have defined it to select a out of a and b having the same key. If the order of elements does not matter, you can use Collectors#toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper) as shown below:

public static Map<String, String> constructMap(List<CustomObject> list) {
    return list.stream()
            .filter(Objects::nonNull)
            .collect(Collectors.toMap(CustomObject::getBookId, CustomObject::getBookName));
}

A sample output:

{bookId2=book2, bookId1=book1}
Arvind Kumar Avinash
  • 71,965
  • 6
  • 74
  • 110
  • unfortunately, I do not have getters defined for BookId and Book in my class due to some reason. The only function exposed is getSomeMapping which returns a mapping.@Arvind Kumar Avinash – gabru101 Feb 23 '21 at 04:49
  • 1
    @gabru101 - I just saw your updated question. Without the definition (code) of `CustomObject`, one can only guess what you need and therefore I suggest you please post the relevant code of `CustomObject`. – Arvind Kumar Avinash Feb 23 '21 at 07:08
0

Look into [Collectors.toMap()] 1. This can return the items as a new Map.

lists.stream().filter(x->x!=null).collect(Collectors.toMap(CustomObject::getMapKey(), CustomObject::getMapValue()));

getMapKey and getMapValue are here methods returning the key and value of the CustomObject for the map. Instead of using simple getters it might also be necessary to execute some more advanced logic.

lists.stream().filter(x->x!=null).collect(Collectors.toMap(l -> {...; return key;}, l -> { ...; return value;}));
k_o_
  • 5,143
  • 1
  • 34
  • 43
0

To turn a stream into a map you're better off using collect(). For instance:


public Map<String,String> toMap(List<Entry<String,String>> entries) {
  return entries.stream().collect(Collectors.toMap(Entry::getKey, Entry::getValue));
}

Or if your keys are non-unique and you want the values to be combined as a list:

public Map<String,List<CustomObject>> toMap(List<CustomObject> entries) {
  return entries.stream().collect(Collectors.groupingBy(CustomObject::getKey));
}
Jeroen Steenbeeke
  • 3,884
  • 5
  • 17
  • 26
0

Let's assume your CustomObject class has getters to retrieve a school id with a name. You could do it like this. I declared it static as it does not appear to depend on instance fields.

public static Map<String,String> constructMap(List<CustomObject> lists){
    return lists.stream()
           .filter(Objects::nonNull)
           .collect(Collectors.toMap(CustomObject::getName, CustomObject::getID));
}

This presumes that names and Id's are one-to-one, as this does not handle duplicate keys.

WJS
  • 36,363
  • 4
  • 24
  • 39
  • getName takes a input and returns a string – gabru101 Feb 22 '21 at 17:13
  • I don't understand. I am returning a string. No where (even in your example) do I see where you ask for input. I presumed that all the info was in the List of CustomObjects. And remember, this was just an example. You would need to modify the Map types to match what you are returning. – WJS Feb 22 '21 at 17:17
  • sorry, updated the question with an example – gabru101 Feb 22 '21 at 17:27