Related
I have a collection constisting of Map<Pair<DateTime, String>, List<Entity>> which was previously grouped using streams. Entity is a simple class with int property and getValue() method.
Now, I want to aggregate values of Entity with usage of my simple EntityAccumulator modyfing the type of the previous map to Map<Pair<DateTime, String>, EntityAccumulator>. The only way to achieve this as far as I understand is to create my own custom collector, howevere I've stucked at finisher() method which should return Pair.
Or, maybe is there simpler way to achieve the result I want ?
StreamProcessing
Map<Pair<DateTime, String>, EntityAccumulator> collect = entities.stream()
.collect(Collectors.groupingBy(entity-> Pair.of(entity.getTimestamp(), entity.getName())))
.entrySet().stream()
.collect(new EntityCollector()));
EntityAccumulator
private static class EntityAccumulator {
private int result = 0.0;
public EntityAccumulator() { }
public EntityAccumulator(int result) {
this.result = result;
}
public void calculate(Entity entity) {
result += entity.getValue();
}
public EntityAccumulatoradd(EntityAccumulator other) {
return new EntityAccumulator(this.result + other.result);
}
}
Collector
public class EntityCollector implements Collector<Map.Entry<Pair<DateTime, String>, List<Entity>>, EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> {
#Override
public Supplier<EntityAccumulator> supplier() {
return EntityAccumulator::new;
}
#Override
public BiConsumer<EntityAccumulator, Map.Entry<Pair<DateTime, String>, List<Entity>>> accumulator() {
return (result, pairListEntry) -> pairListEntry.getValue().forEach(result::calculate);
}
#Override
public BinaryOperator<EntityAccumulator> combiner() {
return EntityAccumulator::add;
}
#Override
public Function<EntityAccumulator, Map.Entry<Pair<DateTime, String>, EntityAccumulator>> finisher() {
return (k) -> {
return null; // ??? HELP HERE
}
}
#Override
public Set<Characteristics> characteristics() {
return EnumSet.of(Characteristics.UNORDERED);
}
}
Apparently, you actually want to do
Map<Pair<DateTime, String>, Double> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collectors.summingDouble(Entity::getValue)));
or
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collectors.summingInt(Entity::getValue)));
depending on the actual value type. Your declaration int result = 0.0 isn’t quite clear.
First, if you want to perform reduction on the groups, you should provide the Collector for the values as a second argument to the groupingBy collector. Then, it doesn’t have to deal with neither, Map nor Map.Entry.
Since it’s basically folding the entities to a single number (for each group), you can use an existing collector, i.e. summingInt or summingDouble.
When you create your own collector, you can’t reconstitute information in the finisher function that you have dropped in the accumulator function. If your container type EntityAccumulator contains a single number only, there is no way to produce a Map.Entry<Pair<DateTime, String>, EntityAccumulator> from it.
By the way, you rarely need to implemented the Collector interface with a class, even when creating a custom collector. You can simply use Collector.of, specifying the functions and characteristics, to create a Collector.
So using your original EntityAccumulator class (assuming, result should be int and 0.0 is a typo), you could use
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(
entity -> Pair.of(entity.getTimestamp(), entity.getName()),
Collector.of(EntityAccumulator::new,
EntityAccumulator::calculate,
EntityAccumulator::add,
ea -> ea.result,
Collector.Characteristics.UNORDERED)));
to achieve the same as above. It would also be possible to perform the operation in two steps, like in your attempt, using
Map<Pair<DateTime, String>, Integer> collect = entities.stream()
.collect(Collectors.groupingBy(e -> Pair.of(e.getTimestamp(), e.getName())))
.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().stream().collect(
Collector.of(EntityAccumulator::new,
EntityAccumulator::calculate,
EntityAccumulator::add,
ea -> ea.result,
Collector.Characteristics.UNORDERED))));
but, of course, this is only for completeness. The solution shown at the beginning of this answer is simpler and more efficient.
why cannot I access the id property of Device ?
final List<Device> devicesList = jsonFileHandlerDevice.getList();
ConcurrentMap<Integer, Device> map =
devicesList.stream()
.collect(Collectors.toMap(item -> item.id, item -> item));
where
public class Device {
public MobileOs mobileOs;
public Integer id;
public Device() {
}
public Device(MobileOs mobileOs, double osVersion, int allocatedPort, Integer id, String uuid) {
this.mobileOs = mobileOs;
this.id = id;
}
}
see here:
You got a misleading error message. The actual error is using a ConcurrentMap<Integer, Device> type when the type returned by the collector is Map<Integer, Device>.
If you want the returned Map to be a ConcurrentMap, you can use the toMap variant that accepts a supplier (which determines the type of the Map to be returned).
Something like this should work :
ConcurrentMap<Integer, Device> map =
devicesList.stream()
.collect(Collectors.toMap(item -> item.id,
item -> item,
(item1,item2)->item2,
ConcurrentHashMap::new));
or as Alexis commented, just use Collector.toConcurrentMap.
The following method performs ordering.
public List<Comparator<Entity>> sort(Map<String, String> map) {
List<Comparator<Entity>> list = new ArrayList<Comparator<Entity>>();
for (Map.Entry<String, String> entry : map.entrySet()) {
boolean sortOrder = entry.getValue().equalsIgnoreCase("asc");
switch (entry.getKey()) {
case "id":
list.add(sortOrder ? Comparator.comparing(Entity::getId) : Comparator.comparing(Entity::getId, Comparator.reverseOrder()));
break;
case "size":
list.add(sortOrder ? Comparator.comparing(Entity::getSize) : Comparator.comparing(Entity::getSize, Comparator.reverseOrder()));
//break;
}
}
return list;
}
The list being returned by the above method is used in the following way.
// map is initialized somewhere based on client's interactions with sorting.
// Based on client's interactions, map may be empty or it may contain one or more ordering fields.
if (MapUtils.isNotEmpty(map)) { // map = new LinkedHashMap<String, String>();
List<Comparator<Entity>> comparators = sort(map);
Comparator<Entity> comparator = comparators.remove(0);
for (Comparator<Entity> c : comparators) {
comparator = comparator.thenComparing(c);
}
list = list.stream().sorted(comparator).collect(Collectors.toList());
} else {
// This is the default ordering.
list = list.stream().sorted(Comparator.comparing(Entity::getId).reversed()).collect(Collectors.toList());
}
Entity contains two fields named id of type Integer and size of type BigDecimal and list is a type of List<Entity>.
Since there are several other classes having the same fields with the same datatypes, I want this method to be generic so that it has to be defined only once like so,
public <T extends Object> List<Comparator<T>> sort(Map<String, String> map, Class<T> clazz) {
List<Comparator<T>> list = new ArrayList<Comparator<T>>();
// Sorting logic.
return list;
}
But doing so, expressions like T::getId will not compile as obvious, since the generic type parameter T evaluates to Object.
Is there a way to code sorting without knowing the actual class type so that this method can be prevented from being repeated everywhere, when it is needed?
A simple way, without having to rely on reflection magic, is to introduce a common interface for all the types having the same fields with the same datatypes as Entity.
Consider the following IdSize interface with the following Entity.
interface IdSize {
Integer getId();
BigDecimal getSize();
}
class Entity implements IdSize {
private Integer id;
private BigDecimal size;
#Override
public Integer getId() {
return id;
}
#Override
public BigDecimal getSize() {
return size;
}
}
Then you can make your method generic like this:
public <T extends IdSize> List<Comparator<T>> sort(Map<String, String> map) {
List<Comparator<T>> list = new ArrayList<Comparator<T>>();
for (Map.Entry<String, String> entry : map.entrySet()) {
boolean sortOrder = entry.getValue().equalsIgnoreCase("asc");
Comparator<T> comparator = null;
switch (entry.getKey()) {
case "id":
comparator = Comparator.comparing(IdSize::getId);
break;
case "size":
comparator = Comparator.comparing(IdSize::getSize);
break;
default: // do something here, throw an exception?
}
list.add(sortOrder ? comparator : comparator.reversed());
}
return list;
}
(I refactored a little the switch-case statement to remove the duplicated code.). Also, you might want to add a default clause.
Use interfaces:
public interface Sizable {
BigDecimal getSize();
}
public interface Id {
int getId();
}
Have your classes implement those interface and use them in your generic methods:
public <T extends Id & Sizable> List<Comparator<T>> sort(Map<String, String> map) {
// ...
}
You'll probably need something more dynamic. Some annotations may help
class Shoe
#Column("id")
#Sortable
public int getId(){ ... }
#Column("Description")
public String getDescription(){...}
Given any class, you can reflect on columns to display, columns that can be sorted ("id", ...), and values of columns ("getId()", ...).
If you want to create a compound Comparator anyway, there is no point in filling a List first. Just do it in one operation:
public static <T> Comparator<T> getOrdering(
Map<String, String> map, Map<String,Comparator<T>> defined) {
return map.entrySet().stream().map(e -> {
Comparator<T> c=defined.get(e.getKey());
return e.getValue().equalsIgnoreCase("asc")? c: c.reversed();
})
.reduce(Comparator::thenComparing)
.orElseThrow(()->new IllegalArgumentException("empty"));
}
This works for arbitrary types but requires to provide a map of existing comparators for a type. But this map isn’t a restriction, it actually improves the operation as it removes the hardcoded set of existing named property comparators. You can use it with an arbitrary type, Entity being exemplary here, as follows:
Map<String,Comparator<Entity>> map=new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
map.put("id", Comparator.comparing(Entity::getID));
map.put("size", Comparator.comparing(Entity::getSize));
Comparator<Entity> cmp=getOrdering(param, map);
whereas param is the ordered map of your question, mapping from property name to either "asc" or "desc". The map holding the predefined comparators can be created once in initialization code and then be re-used.
The creation code doesn’t look so complicated that it deserves implementing a dynamic solution, however, if you still wish to do it, here is the code to generate such a map for arbitrary classes:
public final class DynamicComparators<T> {
public static <T> Map<String,Comparator<T>> getComparators(Class<T> cl) {
return CACHE.get(cl).cast(cl).comps;
}
private static final ClassValue<DynamicComparators> CACHE
=new ClassValue<DynamicComparators>() {
#Override protected DynamicComparators computeValue(Class<?> type) {
return new DynamicComparators<>(type);
}
};
private final Class<T> theClass;
private final Map<String, Comparator<T>> comps;
private DynamicComparators(Class<T> cl) {
theClass=cl;
Map<String,Comparator<T>> map=new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
try {
BeanInfo bi=Introspector.getBeanInfo(cl);
MethodHandles.Lookup l=MethodHandles.lookup();
MethodType invoked=MethodType.methodType(Function.class);
for(PropertyDescriptor pd: bi.getPropertyDescriptors()) {
Method m=pd.getReadMethod();
if(m==null) continue;
Class<?> t=m.getReturnType();
if(!t.isPrimitive() && !Comparable.class.isAssignableFrom(t))
continue;
MethodHandle mh=l.unreflect(m);
MethodType mt=mh.type();
#SuppressWarnings("unchecked")Comparator<T> cmp
= Comparator.comparing((Function<T,Comparable>)LambdaMetafactory
.metafactory(l, "apply", invoked, mt.generic(), mh, mt)
.getTarget().invokeExact());
map.put(pd.getName(), cmp);
}
} catch(Throwable ex) {
throw new RuntimeException(ex);
}
this.comps=Collections.unmodifiableMap(map);
}
#SuppressWarnings("unchecked") <U> DynamicComparators<U> cast(Class<U> cl) {
if(cl!=theClass) throw new ClassCastException();
return (DynamicComparators<U>)this;
}
}
It may be a bad practice, but I haven't been able to figure out any better solution for my problem. So I have this map
// Map<state, Map<transition, Map<property, value>>>
private Map<String, Map<String, Map<String, String>>> properties;
and I want to initialize it so I don't get NullPointerException with this
properties.get("a").get("b").get("c");
I tried this one but I didn't work (obviously)
properties = new HashMap<String, Map<String, Map<String,String>>>();
Other things I tried didn't compile.
Also if you have any ideas how to avoid this nested maps, I would appreciate it.
It seems to me that you need to create your own Key class:
public class Key {
private final String a;
private final String b;
private final String c;
public Key(String a, String b, String c) {
// initialize all fields here
}
// you need to implement equals and hashcode. Eclipse and IntelliJ can do that for you
}
If you implement your own key class, your map will look like this:
Map<Key, String> map = new HashMap<Key, String>();
And when looking for something in the map you can use:
map.get(new Key("a", "b", "c"));
The method above will not throw a NullPointerException.
Please remember that for this solution to work, you need to override equals and hashcode in the Key class. There is help here. If you don't override equals and hashcode, then a new key with the same elements won't match an existing key in the map.
There are other possible solutions but implementing your own key is a pretty clean one in my opinion. If you don't want to use the constructor you can initialize your key with a static method and use something like:
Key.build(a, b, c)
It is up to you.
You need to put maps in your maps in your map. Literally:
properties = new HashMap<String, Map<String, Map<String,String>>>();
properties.put("a", new HashMap<String, Map<String,String>>());
properites.get("a").put("b", new HashMap<String,String>());
If your target is lazy initialization without NPE you have to create your own map:
private static abstract class MyMap<K, V> extends HashMap<K, V> {
#Override
public V get(Object key) {
V val = super.get(key);
if (val == null && key instanceof K) {
put((K)key, val = create());
}
return val;
}
protected abstract V create();
}
public void initialize() {
properties = new MyMap<String, Map<String, Map<String, String>>>() {
#Override
protected Map<String, Map<String, String>> create() {
return new MyMap<String, Map<String, String>>() {
#Override
protected Map<String, String> create() {
return new HashMap<String, String>();
}
};
}
};
}
You could use a utility method:
public static <T> T get(Map<?, ?> properties, Object... keys) {
Map<?, ?> nestedMap = properties;
for (int i = 0; i < keys.length; i++) {
if (i == keys.length - 1) {
#SuppressWarnings("unchecked")
T value = (T) nestedMap.get(keys[i]);
return value;
} else {
nestedMap = (Map<?, ?>) nestedMap.get(keys[i]);
if(nestedMap == null) {
return null;
}
}
}
return null;
}
This can be invoked like this:
String result = get(properties, "a", "b", "c");
Note that care is required when using this as it is not type-safe.
The only way to do it with this structure is to pre-initialise the 1st and 2nd level maps with ALL possible keys. If this is not possible to do you can't achieve what you are asking with plain Maps.
As an alternative you can build a custom data structure that is more forgiving. For example a common trick is for a failed key lookup to return an "empty" structure rather than null, allowing nested access.
You can't initialize this in one go, since you normally don't know what keys you'll have in advance.
Thus you'd have to check whether the submap for a key is null and if so you might add an empty map for that. Preferably you'd only do that when adding entries to the map and upon retrieving entries you return null if one of the submaps in the path doesn't exist. You could wrap that in your own map implementation for ease of use.
As an alternative, apache commons collections' MultiKeyMap might provide what you want.
It's impossible to use properties.get("a").get("b").get("c"); and be sure to avoid null unless you make your own Map. In fact, you can't predict that your map will contains "b" key.
So try to make your own class to handle nested get.
I think a better solution is using an object as the only key to the map of values. The key will be composed of three fields, state, transition and property.
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
public class Key {
private String state;
private String transition;
private String property;
public Key(String state, String transition, String property) {
this.state = state;
this.transition = transition;
this.property = property;
}
#Override
public boolean equals(Object other) {
return EqualsBuilder.reflectionEquals(this, other);
}
#Override
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
}
When you check for a value, the map will return null for a key that is not associated with a value
Map<Key, String> values = new HashMap<Key, String>();
assert values.get(new Key("a", "b", "c")) == null;
values.put(new Key("a", "b", "c"), "value");
assert values.get(new Key("a", "b", "c")) != null;
assert values.get(new Key("a", "b", "c")).equals("value");
To efficiently and correctly use an object as a key in a Map you should override the methods equals() and hashCode(). I have built thos methods using the reflective functionalities of the Commons Lang library.
I think, following is the easier way:
public static final Map<Integer, Map<Integer, Map<Integer, Double>>> A_Map = new HashMap<Integer, Map<Integer, Map<Integer, Double>>>()
{
{
put(0, new HashMap<Integer, Map<Integer, Double>>()
{
{
put(0, new HashMap<Integer, Double>()
{
{
put(0, 1 / 60.0);
put(1, 1 / 3600.0);
}
});
put(1, new HashMap<Integer, Double>()
{
{
put(0, 1 / 160.0);
put(1, 1 / 13600.0);
}
});
}
});
put(1, new HashMap<Integer, Map<Integer, Double>>()
{
{
put(0, new HashMap<Integer, Double>()
{
{
put(0, 1 / 260.0);
put(1, 1 / 3600.0);
}
});
put(1, new HashMap<Integer, Double>()
{
{
put(0, 1 / 560.0);
put(1, 1 / 1300.0);
}
});
}
});
}
};
Using computeIfAbsent/putIfAbsent makes it simple:
private <T> void addValueToMap(String keyA, String keyB, String keyC, String value) {
map.computeIfAbsent(keyA, k -> new HashMap<>())
.computeIfAbsent(keyB, k -> new HashMap<>())
.putIfAbsent(keyC, value);
}
I would like to apply a function to a Java collection, in this particular case a map. Is there a nice way to do this? I have a map and would like to just run trim() on all the values in the map and have the map reflect the updates.
With Java 8's lambdas, this is a one liner:
map.replaceAll((k, v) -> v.trim());
For the sake of history, here's a version without lambdas:
public void trimValues(Map<?, String> map) {
for (Map.Entry<?, String> e : map.entrySet()) {
String val = e.getValue();
if (val != null)
e.setValue(val.trim());
}
}
Or, more generally:
interface Function<T> {
T operate(T val);
}
public static <T> void replaceValues(Map<?, T> map, Function<T> f)
{
for (Map.Entry<?, T> e : map.entrySet())
e.setValue(f.operate(e.getValue()));
}
Util.replaceValues(myMap, new Function<String>() {
public String operate(String val)
{
return (val == null) ? null : val.trim();
}
});
I don't know a way to do that with the JDK libraries other than your accepted response, however Google Collections lets you do the following thing, with the classes com.google.collect.Maps and com.google.common.base.Function:
Map<?,String> trimmedMap = Maps.transformValues(untrimmedMap, new Function<String, String>() {
public String apply(String from) {
if (from != null)
return from.trim();
return null;
}
}
The biggest difference of that method with the proposed one is that it provides a view to your original map, which means that, while it is always in sync with your original map, the apply method could be invoked many times if you are manipulating said map heavily.
A similar Collections2.transform(Collection<F>,Function<F,T>) method exists for collections.
Whether you can modify your collection in-place or not depends on the class of the objects in the collection.
If those objects are immutable (which Strings are) then you can't just take the items from the collection and modify them - instead you'll need to iterate over the collection, call the relevant function, and then put the resulting value back.
Might be overkill for something like this, but there are a number of really good utilities for these types of problems in the Apache Commons Collections library.
Map<String, String> map = new HashMap<String, String>();
map.put("key1", "a ");
map.put("key2", " b ");
map.put("key3", " c");
TransformedMap.decorateTransform(map,
TransformerUtils.nopTransformer(),
TransformerUtils.invokerTransformer("trim"));
I highly recommend the Jakarta Commons Cookbook from O'Reilly.
I ended up using a mutation of #erickson's answer, mutated to:
return a new Collection, not modify in place
return Collections with elements of type equal to the return type of the Function
support mapping over either the values of a map or the elements of a list
Code:
public static interface Function<L, R> {
L operate(R val);
}
public static <K, L, R> Map<K, L> map(Map<K, R> map, Function<L, R> f) {
Map<K, L> retMap = new HashMap<K, L>();
for (Map.Entry<K, R> e : map.entrySet()) retMap.put(e.getKey(), f.operate(e.getValue()));
return retMap;
}
public static <L, R> List<L> map(List<R> list, Function<L, R> f) {
List<L> retList = new ArrayList<L>();
for (R e : list) retList.add(f.operate(e));
return retList;
}
You'll have to iterate over all the entries and trim each String value. Since String is immutable you'll have to re-put it in the map. A better approach might be to trim the values as they're placed in the map.
I have come up with a "Mapper" class
public static abstract class Mapper<FromClass, ToClass> {
private Collection<FromClass> source;
// Mapping methods
public abstract ToClass map(FromClass source);
// Constructors
public Mapper(Collection<FromClass> source) {
this.source = source;
}
public Mapper(FromClass ... source) {
this.source = Arrays.asList(source);
}
// Apply map on every item
public Collection<ToClass> apply() {
ArrayList<ToClass> result = new ArrayList<ToClass>();
for (FromClass item : this.source) {
result.add(this.map(item));
}
return result;
}
}
That I use like that :
Collection<Loader> loaders = new Mapper<File, Loader>(files) {
#Override public Loader map(File source) {
return new Loader(source);
}
}.apply();
You could also take a look at Google Collections