Let's say I have the following Map which is created using Guava's library: (List<Integer> is also immutable)
Map<String, List<Integer>> map = ImmutableMap.builder()...
I pass this map to a class where I want to create a mutable copy of it and modify it. It is of course possible to do it manually, but is there a way to convert a nested immutable collection back to a mutable one?
As pointed out, I'd use an ImmutableListMultimap<String, Integer> instead of a ImmutableMap<String, ImmutableList<Integer>>.
Then if you want a mutable copy, you can just pass the immutable multimap to the create static factory method on one of the mutable ListMultimap implementations (ArrayListMultimap or LinkedListMultimap).
Here is my solution. There'e quite a lot of code required to set it up, but once it's done it's really easy to use.
public class Main {
// UnaryOperator and identity are in Java 8.
// I include them here in case you are using an earlier version.
static interface UnaryOperator<T> {
T apply(T t);
}
static <T> UnaryOperator<T> identity() {
return new UnaryOperator<T>() {
#Override
public T apply(T t) {
return t;
}
};
}
// This unary operator turns any List into an ArrayList.
static <E> UnaryOperator<List<E>> arrayList(final UnaryOperator<E> op) {
return new UnaryOperator<List<E>>() {
#Override
public List<E> apply(List<E> list) {
List<E> temp = new ArrayList<E>();
for (E e : list)
temp.add(op.apply(e));
return temp;
}
};
}
// This unary operator turns any Set into a HashSet.
static <E> UnaryOperator<Set<E>> hashSet(final UnaryOperator<E> op) {
return new UnaryOperator<Set<E>>() {
#Override
public Set<E> apply(Set<E> set) {
Set<E> temp = new HashSet<E>();
for (E e : set)
temp.add(op.apply(e));
return temp;
}
};
}
// This unary operator turns any Map into a HashMap.
static <K, V> UnaryOperator<Map<K, V>> hashMap(final UnaryOperator<K> op1, final UnaryOperator<V> op2) {
return new UnaryOperator<Map<K, V>>() {
#Override
public Map<K, V> apply(Map<K, V> map) {
Map<K, V> temp = new HashMap<K, V>();
for (Map.Entry<K, V> entry : map.entrySet())
temp.put(op1.apply(entry.getKey()), op2.apply(entry.getValue()));
return temp;
}
};
}
public static void main(String[] args) {
// In this example I will first create an unmodifiable collection of unmodifiable collections.
Map<String, List<Set<Integer>>> map = new HashMap<String, List<Set<Integer>>>();
map.put("Example", Collections.unmodifiableList(Arrays.asList(Collections.unmodifiableSet(new HashSet<Integer>(Arrays.asList(1, 2, 3))))));
map = Collections.unmodifiableMap(map);
// Now I will make it mutable in one line!
map = hashMap(Main.<String>identity(), arrayList(hashSet(Main.<Integer>identity()))).apply(map);
}
}
Related
I want to make a HashMap which contains HashSets as values and returns an empty HashSet when the key is not found.
public class IsbnHashMap<K,V> extends HashMap<K,V> {
protected V defaultValue;
public IsbnHashMap(V defaultValue) {
this.defaultValue = defaultValue;
}
#Override
public V get(Object k) {
return containsKey(k) ? super.get(k) : defaultValue;
}
}
However my implementation does not work.
private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>();
This returns "HashSet cannot be applied". If I try to change K,V in IsbnHashMap to <String, HashSet<String>> I get some funky errors as well. How can I implement this?
First it should be noted that in Java-8 you can use instead:
isbnToId.computeIfAbsent(isbn, k -> new HashSet<>()).add(_id);
Second, if you really want to do something like this in previous Java versions, you'd better to create separate method for this purpose (for example, getOrDefault()) in order not to violate the contract. Third, you need to create new HashSet<>() for every new key. If you return the same instance, it will be shared between given keys. If you don't expect users to modify it, it's better to use unmodifiable Collections.emptySet() as default value. This way users may safely do isbnToId.getOrDefault(isbn).contains(_id), but trying isbnToId.getOrDefault(isbn).add(_id) will result in exception. If you want to support the modification (prior to Java-8), you can, for example, pass the element class to the constructor instead:
public static class MyMap<K, V> extends HashMap<K, V> {
private Class<?> clazz;
public MyMap(Class<?> clazz) {
this.clazz = clazz;
}
public V getOrCompute(K key) {
V v = get(key);
if(v == null) {
try {
v = (V) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
put(key, v);
}
return v;
}
}
Usage example:
MyMap<String, Set<String>> map = new MyMap<>(HashSet.class);
map.getOrCompute("a").add("b");
map.getOrCompute("a").add("c");
map.getOrCompute("d").add("e");
System.out.println(map); // {a=[b, c], d=[e]}
Here we assume that instantiating the passed class with default constructor is ok. An alternative would be to pass the factory interface which is capable to produce the default values.
As Jon Skeet said ...
private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>(new HashSet<String>());
... however, that would return the same default object as Dunni pointed out.
So this will do:
private static HashMap<String, HashSet<String>> isbnToId = new HashMap<String, HashSet<String>>();
public static void coupleIsbnToId(String isbn, String _id) {
if (!isbnToId.containsKey(isbn)) {
isbnToId.put(isbn, new HashSet<String>());
}
isbnToId.get(isbn).add(_id);
}
Folks,
Is there any easy way to add generic class in non generic class.
Basically the cache manager will have map of Cache class which is implemented with proper generics.
But in below class we return (getCache method) Cache via get method it requires explicit cast at callers place how to avoid it.
e.g.
public class CacheManager {
private Map<String, Cache<?,?>> cacheMap = new HashMap<String, Cache<?,?>>();
public Cache<?,?> getCache(String cacheName) {
return cacheMap.get(cacheName);
}
public void addCache(String cacheName,Cache<?,?> cache) {
cacheMap.put(cacheName, cache);
}
}
Short answer: No (as far as I know).
The problem here is that what you are doing is not type-safe in Java at all. Have a look at this example:
import java.util.*;
class ClassCast {
public static void main(String[] args) {
HashMap<String, Pair<?, ?>> map = new HashMap<>();
map.put("test", new Pair<String, Integer>("Hello", 5));
Pair<Double, Double> pair = (Pair<Double, Double>) map.get("test");
}
}
class Pair<T,V> {
T a;
V b;
Pair(T a, V b) {
this.a = a;
this.b = b;
}
}
You would expect a ClassCastException here, but it compiles and runs perfectly fine. The reason for this is that the actual class of Pair<String, Integer> and Pair<Double, Double> is in fact just Pair (after type erasure).
To get type safety you have to implement the "Typesafe heterogeneous container pattern" (explained in detail in Effective Java by Josh Bloch). In short, you have to involve the type parameter in the key of your map. Depending on your needs, you might be able to use a class as key directly, otherwise you might have to make a key object.
Example implementation:
public class CacheManager {
private Map<MultiKey, Cache<?,?>> cacheMap = new HashMap<>();
#SuppressWarnings("unchecked")
public <T,V> Cache<T,V> get(String name, Class<T> t, Class<V> v) {
// Type-safe since types are encoded in key(i.e. map will not
// return something with the wrong type), and key is type-checked
// on insertion.
return (Cache<T,V>) cacheMap.get(new MultiKey(name, t, v));
}
public <T,V> void put(String name, Class<T> t, Class<V> v, Cache<T,V> cache) {
cacheMap.put(new MultiKey(name, t, v), cache);
}
class MultiKey {
Object[] keys;
Integer hash = null;
MultiKey(Object... keys) {
this.keys = keys;
}
#Override
public int hashCode() {
if (hash == null) hash = Arrays.hashCode(keys);
return hash;
}
#Override
public boolean equals(Object o) {
if (o == null || !(o instanceof MultiKey)) return false;
return Arrays.equals(keys, ((MultiKey) o).keys);
}
}
}
Example usage:
CacheManager mng = new CacheManager();
mng.addCache("SI", String.class, Integer.class, new Cache<String, Integer>());
Cache<String, Integer> cache = mng.getCache("SI", String.class, Integer.class);
System.out.println(cache);
It's not pretty, but it is actually type-safe. It can be improved depending on the actual situation though, so you should not use this code as is. For example, if you can get the types from the Cache object you don't need the Class arguments in addCache.
For the purpose of learning I try to make a MultiMap implementation. (and for avoiding relying on other libraries for a library I make).
It doesn't have to be perfect.
At the moment I have this:
class MultiMap<k, v> implements Map {
HashMap<k, List<v>> hMap = new HashMap<k, List<v>>();
public MultiMap () {
}
Followed by all #Override methods from Map.
One is like this:
#Override
public Object get(Object o) {
return hMap.get(o);
}
I have problems with this one:
#Override
public Object put(Object o, Object o2) {
// will return a list
Object toReturn = get(o);
if(hMap.containsValue(o)) {
// is this even possible?
(List<v>)(List<?>)get(o); // <<< problem: "Syntax error on token(s), misplaced construct(s)"
// ^ next .add(o2);
}
// etc.
return toReturn;
}
Is it possible to get a List out of the get method?
You define class like this class MultiMap<k, v> implements Map<k, List<v>>
and then add new method public List<v> put(k key, v value) like that.
public List<v> put(k key, v value) {
List<v> list = get(key);
if(list == null)
list = new ArrayList<>();
list.add(value);
return list;
}
In Java, I want to add a getOrAdd method to a regular map, just like putIfAbsent on a ConcurrentHashMap.
Furthermore, for a certain key I want to store a list of items. Here's my attempt:
public class ListMap<K, V> extends HashMap<K, V> {
private HashMap<K, List<V>> map;
public ListMap() {
map = new HashMap<K, List<V>>();
}
public List<V> getOrAdd(K key) {
if (map.containsKey(key)) {
return map.get(key);
} else {
List<V> l = new ArrayList<V>();
map.put(key, l);
return l;
}
}
}
However, if someone wanted to iterate over a ListMap, he would need to cast the values explictly.
ListMap<Integer, MyClass> listMap = new ListMap<Integer, MyClass>();
for (Map.Entry<Integer, MyClass> entry : listMap.entrySet()) {
List<MyClass> val = (List<MyClass>) entry.getValue();
}
Is there a way of extending the HashMap class by some methods without creating a subclass? ( I've seen this in C#)
How can the ListMap class be modified such that one can get a ListMaps's value (List) without casting?
Instance of your class will be also HashMap so you don't need to, or even shouldn't add another field just to support getOrAdd method because other inherited and not overridden methods will not be referring to map field but to this instance.
So instead of adding separate field
private HashMap<K, List<V>> map;
change extending type of your ListMap to
public class ListMap<K, V> extends HashMap<K, List<V>>
^^^^^^^
and change your getOrAdd method to not use map field but this
public List<V> getOrAdd(K key) {
if (containsKey(key)) {
return get(key);
} else {
List<V> l = new ArrayList<V>();
put(key, l);
return l;
}
}
This change will let you use your map like
ListMap<Integer, String> listMap = new ListMap<Integer, String>();
for (Map.Entry<Integer, List<String>> entry : listMap.entrySet()) {
List<String> val = entry.getValue();//NO CASTING NEEDED
}
You can just extend HashMap like this:
public class ListMap<K, V> extends HashMap<K, List<V>> {
...
}
let's imagine this scenario - I would like to use TreeMap in java. It's part of the Colletions framework and the only implementation of the SortedMap interface.
public class MyDictionary extends TreeMap<String, String> {
// some code
}
In order to walk through the entries stored in my Dictionary class I will need a type of Map.Entry. Somewhere in the code (could be a method of the MyDictionary class or even more likely a method in the wrapper class containing a variable of MyDictionary class holding my data) there will be something like:
public void showEntries() {
for (Map.Entry<String, String> e : dictionary) {
System.out.println(e.getKey(), e.getValue()); // do something
}
}
And now the question: is there a way to bind the generic types of Map.Entry to the generic types declared for the TreeMap?
The goal is to have the generic types defined in one place only.
In case I decide to change the type of data held in the TreeMap later I won't have to search all places where I used those types.
The example above is a Proof-Of-Concept. Pls help.
You can make the MyDictionary class generic, with type parameters to match TreeMap.
public class MyDictionary<K, V> extends TreeMap<K, V>
Then you can refer to those type parameters throughout your class. Specifically:
for (Map.Entry<K, V> e : dictionary) {
Or if you know that the key and the value will always be the same type:
public class MyDictionary<E> extends TreeMap<E, E>
and
for (Map.Entry<E, E> e : dictionary) {
This can be achieved by using two adapters - one for Entry and one for Iterator.
First, inside Dictionary, you create your Entry adapter, e.g.:
public static class Entry implements Map.Entry<String, String> {
private Map.Entry<String, String> entry;
Entry(Map.Entry<String, String> entry) {
this.entry = entry;
}
#Override
public String getKey() {
return entry.getKey();
}
#Override
public String getValue() {
return entry.getValue();
}
#Override
public String setValue(String value) {
return entry.setValue(value);
}
}
To be able to use your Dictionary class in the foreach loop, you have to implement Iterable interface. TreeMap does not implement it.
public class Dictionary extends TreeMap<String, String> implements Iterable<Dictionary.Entry>
You can write your iterator() method like this:
#Override
public Iterator<Entry> iterator() {
return new Iterator<Entry>() {
Iterator<Map.Entry<String, String>> wrapped = entrySet().iterator();
#Override
public boolean hasNext() {
return wrapped.hasNext();
}
#Override
public Entry next() {
return new Entry(wrapped.next());
}
#Override
public void remove() {
wrapped.remove();
}
};
}
Now, you can enjoy using foreach loops without generic types:
for (Dictionary.Entry e : dictionary) {
System.out.println(e.getKey() + " " + e.getValue());
}