Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

List<Object[]> to Map<K, V> in java 8

Often there is the need to transform results for a query like:

select category, count(*)
from table
group by category

to a map in which keys are categories and values are count of records belonging to the same category.

Many persistence frameworks return the results of such a query as List<Object[]>, where object arrays contain two elements (category and the count for each returned result set row).

I am trying to find the most readable way to convert this list to the corresponding map.

Of course, traditional approach would involve creating the map and putting the entries manually:

Map<String, Integer> map = new HashMap<>();
list.stream().forEach(e -> map.put((String) e[0], (Integer) e[1]));

The first one-liner that came to my mind was to utilize the out of the box available Collectors.toMap collector:

Map<String, Integer> map = list.stream().collect(toMap(e -> (String) e[0], e -> (Integer) e[1]));

However, I find this e -> (T) e[i] syntax a bit less readable than traditional approach. To overcome this, I could create a util method which I can reuse in all such situations:

public static <K, V> Collector<Object[], ?, Map<K, V>> toMap() {
  return Collectors.toMap(e -> (K) e[0], e -> (V) e[1]);
}

Then I've got a perfect one-liner:

Map<String, Integer> map = list.stream().collect(Utils.toMap());

There is even no need to cast key and value because of type inference. However, this is a bit more difficult to grasp for other readers of the code (Collector<Object[], ?, Map<K, V>> in the util method signature, etc).

I am wondering, is there anything else in the java 8 toolbox that could help this to be achieved in a more readable/elegant way?

like image 274
Dragan Bozanovic Avatar asked Feb 28 '16 22:02

Dragan Bozanovic


People also ask

How do you convert a list to map in Java 8?

With Java 8, you can convert a List to Map in one line using the stream() and Collectors. toMap() utility methods. The Collectors. toMap() method collects a stream as a Map and uses its arguments to decide what key/value to use.

Can we convert object to map in Java?

In Java 8, we have the ability to convert an object to another type using a map() method of Stream object with a lambda expression. The map() method is an intermediate operation in a stream object, so we need a terminal method to complete the stream.


1 Answers

I think your current 'one-liner' is fine as is. But if you don't particularly like the magic indices built into the command then you could encapsulate in an enum:

enum Column {
    CATEGORY(0), 
    COUNT(1);

    private final int index;

    Column(int index) {
        this.index = index;
    }

    public int getIntValue(Object[] row) {
        return (int)row[index]);
    }

    public String getStringValue(Object[] row) {
        return (String)row[index];
    }
}

Then you're extraction code gets a bit clearer:

list.stream().collect(Collectors.toMap(CATEGORY::getStringValue, COUNT::getIntValue));

Ideally you'd add a type field to the column and check the correct method is called.

While outside the scope of your question, ideally you would create a class representing the rows which encapsulates the query. Something like the following (skipped the getters for clarity):

class CategoryCount {
    private static final String QUERY = "
        select category, count(*) 
        from table 
        group by category";

    private final String category;
    private final int count;

    public static Stream<CategoryCount> getAllCategoryCounts() {
        list<Object[]> results = runQuery(QUERY);
        return Arrays.stream(results).map(CategoryCount::new);
    }

    private CategoryCount(Object[] row) {
        category = (String)row[0];
        count = (int)row[1];
    }
}

That puts the dependency between the query and the decoding of the rows into the same class and hides all the unnecessary details from the user.

Then creating your map becomes:

Map<String,Integer> categoryCountMap = CategoryCount.getAllCategoryCounts()
    .collect(Collectors.toMap(CategoryCount::getCategory, CategoryCount::getCount));
like image 95
sprinter Avatar answered Oct 09 '22 23:10

sprinter