I need some data structure that I can build from standard collections or using guava. So it should be mutable Map<Enum, V>. Where V is pretty interesting structure.
V requirements:
mutable
sorted by comparator (with allowing elements such as compare(a, b) == 0) - these is need for iterations
set (there is no such a and b, that a.equals(b) == true) - optional
extra optional requirement to map
keys should be iterated by their natural order
now it's HashMap<SomeEnum, LinkedList<Entity>> with different stuff like collections.sort() in the code.
Thanks.
A Sample implementation
Here is a Guava Multimap implementation of the class you need:
First the drawback: it will have to reside in package com.google.common.collect. The makers of guava have in their infinite wisdom made AbstractSortedSetMultimap package scoped.
I will use this enum in all my examples:
public enum Color{
RED, BLUE, GREEN
};
Constructing the Class
There are six constructors:
Empty (Uses a HashMap and natural ordering for values)
SortedSetMultimap<Color,String> simple =
new EnumValueSortMultiMap<Color, String>();
with a Comparator(V) (Uses a HashMap<K,SortedSet<V>> with the supplied comparator for the values)
SortedSetMultimap<Color,String> inreverse =
new EnumValueSortMultiMap<Color, String>(
Ordering.natural().reverse()
);
with a Map<K,SortedSet<V>> (use this if you want to sort keys, pass in a SortedMap implementation)
SortedSetMultimap<Color,String> withSortedKeys =
new EnumValueSortMultiMap<Color, String>(
new TreeMap<Color, Collection<String>>()
);
with a Map<K,SortedSet<V>> and a Comparator<V> (same as above, but values are sorted using custom comparator)
SortedSetMultimap<Color,String> reverseWithSortedKeys =
new EnumValueSortMultiMap<Color, String>(
new TreeMap<Color, Collection<String>>(),
Ordering.natural().reverse()
);
with a Class<K extends Enum<K>> (uses an EnumMap internally for higher efficiency, natural ordering for values)
SortedSetMultimap<Color,String> withEnumMap =
new EnumValueSortMultiMap<Color, String>(
Color.class
);
with a Class<K extends Enum<K>> and a Comparator<V> (same as above, but values are sorted using custom comparator)
SortedSetMultimap<Color,String> reverseWithEnumMap =
new EnumValueSortMultiMap<Color, String>(
Color.class, Ordering.natural().reverse()
);
Source Code
Here's the class:
package com.google.common.collect;
import java.util.Collection;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
public class EnumValueSortMultiMap<K extends Enum<K>,
V extends Comparable<? super V>>
extends AbstractSortedSetMultimap<K, V>{
private static final long serialVersionUID = 5359491222446743952L;
private Comparator<? super V> comparator;
private Class<K> enumType;
public EnumValueSortMultiMap(){
this(new HashMap<K, Collection<V>>());
}
public EnumValueSortMultiMap(final Comparator<? super V> comparator){
this(new HashMap<K, Collection<V>>(), comparator);
}
public EnumValueSortMultiMap(final Map<K, Collection<V>> map){
this(map, Ordering.natural());
}
public EnumValueSortMultiMap(final Map<K, Collection<V>> map,
final Comparator<? super V> comparator){
super(map);
this.comparator = comparator;
}
public EnumValueSortMultiMap(final Class<K> enumClass,
final Comparator<? super V> comparator){
this(new EnumMap<K, Collection<V>>(enumClass), comparator);
}
public EnumValueSortMultiMap(final Class<K> enumClass){
this(new EnumMap<K, Collection<V>>(enumClass));
}
#Override
Map<K, Collection<V>> backingMap(){
return new EnumMap<K, Collection<V>>(enumType);
}
#Override
public Comparator<? super V> valueComparator(){
return comparator;
}
#Override
SortedSet<V> createCollection(){
return new TreeSet<V>(comparator);
}
}
Other ways to do it
UPDATE: I guess the proper Guava way to do it would have been something like this (it uses the SortedArrayList class I wrote in my other answer):
public static <E extends Enum<E>, V> Multimap<E, V> getMap(
final Class<E> clz){
return Multimaps.newListMultimap(
Maps.<E, Collection<V>> newEnumMap(clz),
new Supplier<List<V>>(){
#Override
public List<V> get(){
return new SortedArrayList<V>();
}
}
);
}
If an extra class is too much, you maybe want to use the factory methods of the class Multimaps.
SortedSetMultimap<Color, Entity> set = Multimaps.newSortedSetMultimap(
new HashMap<Enum, Collection<Entity>>(),
new Supplier<TreeSet<Entity>>() {
#Override
public TreeSet<Entity> get() {
return new TreeSet<Entity>(new Comparator<Entity>() {
#Override
public int compare(Entity o1, Entity o2) {
//TODO implement
}
});
}
});
You can use an EnumMap instead of the current HashMap. EnumMaps are more efficient for enum keys. In guava check Multimap [examples].
You need an collection implementation V which is used this way: HashMap<SomeEnum, V>. The above stated requirements affect V (not Entity). Right?
I think a link TreeSet<E> should fullfill your requirents:
TreeSet implements the Set interface
Sorted by natural order or custom Comparator
Elements can be added and removed
Related
I am trying to write my own CollectionUtils helper class that other application will use. Here is my first method
public static <T, K, V>
Map<K, List<V>> listToMap(List<T> inputList,
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper)
{
Collector c = Collectors.toMap(keyMapper, valueMapper);
return inputList.stream()
.collect(c);
}
public void test()
{
// trying to get a Map<String, List<String>> showing student's name and their activities.
listToMap(StudentDatabase.getAllStudents(), Student::getName, Student::getActivites);
}
However, I am getting lots of compilation error that I do not understand how to solve. Can I get some help here?
Is there any third party library that already does that (but it has to be using java stream api) I can use so that I do not need to write my own?
I tried above and having compilation issue.
There's a couple of problems with the current code:
The Collector interface is generic. You should parameterize it like you're doing for all the other generic types in the code. See What is a raw type and why shouldn't we use it? for more information.
You've defined the return type as Map<K, List<V>>. That would seem to indicate you're trying to implement a grouping operation. However, there are three other parts of your code indicating otherwise:
You use Collectors#toMap(Function,Function)
Your valueMapper maps to V, not List<V>
You call listToMap with Student::getActivities as an argument for the valueMapper, and I can only assume that method returns a list of activities (or some other collection).
So, given all that, you should change the return type to Map<K, V>. That gives the caller full control over the value type of the map, rather than forcing them to use a list. But if you are trying to implement a grouping operation, and you always want the value type to be a List<V>, then consider using Collectors#groupingBy(Function,Collector) instead.
Fixing those two things will give you something like:
public static <T, K, V> Map<K, V> listToMap(
List<T> list,
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper) {
Collector<T, ?, Map<K, V>> collector = Collectors.toMap(keyMapper, valueMapper);
return list.stream().collect(collector);
}
And here's a minimal example using the above:
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
public class Main {
public record Student(String name, List<String> activities) {}
public static <T, K, V> Map<K, V> listToMap(
List<T> list,
Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends V> valueMapper) {
Collector<T, ?, Map<K, V>> collector = Collectors.toMap(keyMapper, valueMapper);
return list.stream().collect(collector);
}
public static void main(String[] args) {
List<Student> students = List.of(
new Student("John", List.of("Piano", "Running")),
new Student("Jane", List.of("Soccer", "Video Games")),
new Student("Bob", List.of("Snowboarding"))
);
Map<String, List<String>> map = listToMap(students, Student::name, Student::activities);
System.out.println(map);
}
}
Output:
{Bob=[Snowboarding], John=[Piano, Running], Jane=[Soccer, Video Games]}
The method should return a Map instead of a Collector. Also, the Collectors.toMap is not enough to convert a List to a Map of List. You need to use a groupingBy collector instead of toMap.
return inputList.stream().collect(Collectors.groupingBy(keyMapper, Collectors.mapping(valueMapper, Collectors.toList())));
I am having hard time using streaming API in Java for generics map. I have a map which extends LinkedHashMap in the following way
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private int size;
public LRUCache(int size) {
super(size);
this.size = size;
}
#Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > size;
}
public LRUCache<K, V> collect() {
return entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)
);
}
}
I am experimenting with a dummy method collect which will actually stream on the entrySet, apply some filters on it and then return a new LRUCache, but Collectors.toMap keep throwing an error which says
"Non-static method cannot be referenced from a static context"
I know this is some issue with Collectors.toMap generics definition. But, I am not able to figure out the right generics to get rid of the error and achieve the streaming and collecting functionality
LinkedHashMap includes in its implementation is a no-argument constructor that acts as a supplier to the toMap collect operation. You can introduce the same as well by including:
public LRUCache() {
this(10); // default size
}
Thereafter you can collect the LRUCache implementation using the toMap override with LRUCache::new supplier in the following manner:
public LRUCache<K, V> collect() {
return entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey,
Map.Entry::getValue,
(a, b) -> a, LRUCache::new));
}
Note:- That what matters is providing a supplier to collect to a different implementation than a HashMap which is what you get using the other overloaded implementation of toMap.
You appear simply to be trying to copy your map/cache. There is no need for streams to do this.
Add a (maybe private) constructor, which invokes the map copy constructor:
private LRUCache(Map<K, V> map, int size) {
super(map);
this.size = size;
}
Then just use this in your collect method:
public LRUCache<K, V> collect() {
return new LRUCache<>(this, size);
}
Or, without adding the constructor:
public LRUCache<K, V> collect() {
LRUCache<K, V> copy = new LRUCache<>(size);
copy.putAll(this);
return copy;
}
I have a data model that looks like this:
class CustomField {
String key;
String value;
}
From an API that I can get instances of List<CustomField>. The keys are unique in the list, which means that this collection really should be a Map<String, String>. Operating on this list is a pain, since every operation requires iteration to check for existing keys (CustomField doesn't implement equals either)
How can I create a Map<String, String> "view" backed by this list, so that I can operate on it using the Map interface?
I want a generic method like: <T, K, V> Map<K, V> createMapBackedByList(List<T> list, BiFunction<K, V, T> elementMapper, Function<T, K> keyMapper, Function<T, V> valueMapper) or similar.
It would use the functions to map between the list elements and map keys and values.
The important thing here is that I want changes to the map to be reflected in the underlying list, which is why the Streams API does not work here...
EDIT: I can't modify the API or the CustomField class.
Simple:
Write your own
public class ListBackedMap<K, V> implements Map<K, V> {
which takes some sort of List<Pair<K,V>> on creation; and "defers" to that. Of course, that requires that your CustomField class implements that Pair interface. (which you would probably need to invent, too)
( alternatively: your new class extends AbstractMap<K,V> to safe you most of the work ).
And now your methods simply return an instance of such a Map.
In other words: I am not aware of a built-in wrapper that meets your requirements. But implementing one yourself should be pretty straight forward.
Edit: given the fact that the OP can't change the CustomField class, a simple helper such as
interface <K, V> MapEntryAdapter {
K getKey();
V getValue();
}
would be required; together with a specific implementation that knows how to retrieve key/value from an instance of CustomField. In this case, the map would be backed by a List<MapEntryAdapter<K, V>> instead.
I ended up trying to implement it myself and basing it on an AbstractList. It was actually easier than I first though...
public class ListBackedMap<T, K, V> extends AbstractMap<K, V> {
private final List<T> list;
private final BiFunction<K, V, T> keyValueToElement;
private final Function<T, K> elementToKey;
private final Function<T, V> elementToValue;
public ListBackedMap(List<T> list, BiFunction<K, V, T> keyValueToElement, Function<T, K> elementToKey, Function<T, V> elementToValue) {
this.list = list;
this.keyValueToElement = keyValueToElement;
this.elementToKey = elementToKey;
this.elementToValue = elementToValue;
}
#Override
public Set<Entry<K, V>> entrySet() {
return list.stream()
.collect(toMap(elementToKey, elementToValue))
.entrySet();
}
#Override
public V put(K key, V value) {
V previousValue = remove(key);
list.add(keyValueToElement.apply(key, value));
return previousValue;
}
public List<T> getList() {
return list;
}
}
It's not very performant (or thread safe), but it seems to do the job well enough.
Example:
List<CustomField> list = getList();
ListBackedMap<CustomField, String, String> map = new ListBackedMap<>(
list,
(key, value) -> new CustomField(key, value),
CustomField::getKey,
CustomField::getValue);
I have a task where I need to implement some functionallity to abstract methods. The idea is to use Java 8, but I'm kinda new to programming with Java 8. The following is the abstract class that I need to implement:
public abstract class SortedMap<K extends Comparable<K>, V> implements Iterable<Pair<K, V>>
{
/**
* Returns a map where all values have been translated using the function
* <code>f</code>.
*/
public abstract <C> SortedMap<K, C> map(Function<? super V, ? extends C> f);
/**
* Returns a map containing only the keys that satisfies
* the predicate <code>p</code>.
*/
public abstract SortedMap<K, V> filter(Predicate<? super K> p);
// ...
}
What I've got so far (with Java 8) is:
public final class SortedMapImpl<K extends Comparable<K>, V> extends SortedMap<K,V>
{
private final Map<K, V> map;
private SortedMapImpl(Map<K, V> map)
{
this.map = new HashMap<K, V>(map);
}
#Override
public <C> SortedMap<K, C> map(Function<? super V, ? extends C> f)
{
// TODO Auto-generated method stub
return null;
}
#Override
public SortedMap<K, V> filter(Predicate<? super K> p)
{
final Map<K, V> filteredMap = map.entrySet()
.stream()
.filter(Predicate<? super Entry<K, V>> p)
.collect(Collectors.toMap(p -> p.getKey(), p -> p.getValue()));
return new SortedMapImpl<K, V>(filteredMap);
}
// ...
}
As you can see I've got no clue at the moment how to implement the map() method, and the filter method is at least partly wrong. Any help is greatly appreciated!
I'll help you implement the filter, and hopefully you should get the idea and implement the map by yourself.
You need to filter the entries of the original map, and only keep the ones for which the key satisifes the key predicate:
public SortedMap<K, V> filter(Predicate<? super K> predicate) {
Map<K, V> filteredMap =
map.entrySet()
.stream()
.filter(entry -> predicate.test(entry.getKey()))
.collect(Collectors.toMap(entry -> entry.getKey(),
entry -> entry.getValue()));
return new SortedMapImpl<K, V>(filteredMap);
}
I would not use the name SortedMap, though: your map is not a map, it's not sorted either, and SortedMap is already a standard collection name, which will make your class confusing and cumbersome to use.
I have this convenient method (which I have been using for many years without problems). It just converts a List to a Map<SomeKey, List>, grouping them by a key attribute.
To avoid unnecessary casting, I'm passing the key attribute as a String (which refers to a method name) and I'm also specifying the type of that attribute.
#SuppressWarnings({"unchecked"})
#Nullable
public static <K, E> Map<K, List<E>> getMultiMapFromList(Collection<E> objectList, String keyAttribute, Class<K> contentClass)
{
// creates a map from a list of objects using reflection
...
}
The above method has been working flawlessly for many years in many applications. But today the following case raises a problem:
List<? extends MyBean> fullBeanList = getFullBeanList();
Map<MyKey, List<? extends MyBean>> multiMap;
// the following line doesn't compile.
multiMap = Utils.getMultiMapFromList(fullBeanList, "key", MyKey.class);
During development there are no warnings what so ever from my IntelliJ IDE.
But during compilation this appears:
Error:(...,...) java: incompatible types: java.util.Map<mypackage.MyKey, java.util.List<capture #2 of ? extends mypackage.MyBean>> cannot be converted to java.util.Map<mypackage.MyKey, java.util.List<? extends mypackage.MyBean>>
I can't figure this one out though.
My guess it has something to do with the ? extends. But I don't see any violations. And I'm also wondering a bit about why it only appears at compilation time? I would think that due to type erasure it doesn't even matter once it's compiled anyway.
I'm sure I could force this by adding some casts, but I would like to understand what's happening here.
EDIT:
for convenience:
Test.java
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test
{
public static void main(String[] args)
{
List<? extends MyBean> input = new ArrayList<>();
Map<MyKey, List<? extends MyBean>> output;
output = test(input, MyKey.class); // doesn't compile
}
public static <K, E> Map<K, List<E>> test(Collection<E> a, Class<K> b)
{
return null;
}
private static class MyKey{}
private static class MyBean{}
}
EDIT 2
To continue one step further in the madness:
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test
{
public static void main(String[] args)
{
List<? extends Number> input = new ArrayList<>();
// compiles fine
List<? extends Number> output1 = test1(input);
// doesn't compile
Map<String, List<? extends Number>> output2 = test2(input);
}
public static <E> List<E> test1(Collection<E> a) { return null;}
public static <E, K> Map<K, List<E>> test2(Collection<E> a) { return null;}
}
I'm not sure what to think of this. As long as I use 1 level of generics then it works fine. But when I use 2-level generics (i.e. generics in generics, e.g. Map<K,List<V>>) then it fails.
This will resolve your problem.
You have to change the method test as like below.
public static <K, E> Map<K, List<? extends E>> test(
Collection<? extends E> a, Class<K> b) {
return null;
}
The problem is that you are not telling ?s passed to the method and in Java they aren't guaranteed to be the same. Make this method generic, so that you have a generic type parameter to reference and to be the same throughout the method.
Below is the code.
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args) {
List<? extends MyBean> input = new ArrayList<>();
Map<MyKey, List<? extends MyBean>> output;
output = test(input, MyKey.class); // doesn't compile
}
public static <K, E> Map<K, List<? extends E>> test(
Collection<? extends E> a, Class<K> b) {
return null;
}
private static class MyKey {
}
private static class MyBean {
}
}
After reading Dilip Singh Kasana's answer I still didn't get it.
But then I came accross this article, which explained it to me.
I am not going to copy the whole thing, but just the part that enlighted me.
Collection< Pair<String,Long> > c1 = new ArrayList<Pair<String,Long>>();
Collection< Pair<String,Long> > c2 = c1; // fine
Collection< Pair<String,?> > c3 = c1; // error
Collection< ? extends Pair<String,?> > c4 = c1; // fine
Of course, we can assign a Collection<Pair<String,Long>> to a
Collection<Pair<String,Long>>. There is nothing surprising here.
But we can not assign a Collection<Pair<String,Long>> to a
Collection<Pair<String,?>>. The parameterized type
Collection<Pair<String,Long>> is a homogenous collection of pairs of
a String and a Long ; the parameterized type
Collection<Pair<String,?>> is a heterogenous collection of pairs of a
String and -something of unknown type-. The heterogenous
Collection<Pair<String,?>> could for instance contain a
Pair<String,Date> and that clearly does not belong into a
Collection<Pair<String,Long>>.
For this reason the assignment is not
permitted.
Applying it to the question.
If we supply an input of type List<? extends Number> to a method
public static <E> Map<String, List<E>> test(Collection<E> objectList) then it will actually return a Map<String, List<? extends Number>.
But this return-value cannot be assigned to a field of the exact same type Map<String, List<? extends Number>.
The reason for this, is that the returned map could be a Map<String, List<Integer>. If I were to assign it to a Map<String, List<? extends Number>, then I could later on put a List<Double> in it. That would clearly break it, but nothing would stop me from doing it.
Consider this:
// behind the scenes there's a map containing Integers.
private static Map<String, List<Integer>> myIntegerMap = new HashMap<>;
// both collections return the same thing, but one of them hides the exact type.
public static Map<String, List<? extends Number> getMap() { return myIntegerMap; }
public static Map<String, List<Integer>> getIntegerMap() { return myIntegerMap; }
private static void test()
{
// fortunately the following line does not compile
Map<String, List<? extends Number> map = getMap();
// because nothing would stop us from adding other types.
List<Double> myDoubleList = new ArrayList<>();
myDoubleList.add(Double.valueOf(666));
map.put("key", myDoubleList);
// if it would compile, then this list would contain a list with doubles.
Map<String, List<Integer>> brokenMap = getIntegerMap();
}
As Dilip Singh Kasana pointed out, it does work if the method would return a Map<String, List<? extends Number>>. Adding the extends changes everything.
// still the same map.
private static Map<String, List<Integer>> myIntegerMap = new HashMap<>;
// the return value is an extended type now.
public static Map<String, ? extends List<? extends Number> getMap() { return myIntegerMap; }
public static Map<String, List<Integer>> getIntegerMap() { return myIntegerMap; }
private static void test()
{
// the following compiles now.
Map<String, ? extends List<? extends Number> map = getMap();
// if we try to add something now ...
List<Double> myDoubleList = new ArrayList<>();
myDoubleList.add(Double.valueOf(666));
// the following won't compile.
map.put("key", myDoubleList);
}
So, this time assigning it works, but the resulting type is a "read-only" map. (PS: For the sake of being complete. Stating the obvious: You can't add anything to a collection or map with an ? extends X type. Those collections are "read-only", that makes perfect sense.)
So the compile time error prevents this situation where the map could be broken.