Why doesn’t Map<String, Set<String>> match Map<T, Set<?>>? [duplicate] - java

This question already has answers here:
Can't cast to to unspecific nested type with generics
(5 answers)
Closed 6 years ago.
I have a method with the following signature:
public <T> int numberOfValues(Map<T, Set<?>> map)
However I can’t call it passing in a Map<String, Set<String>>. For instance, the following doesn’t compile:
Map<String, Set<String>> map = new HashMap<>();
numberOfValues(map);
The error message being that:
numberOfValues (java.util.Map<java.lang.String,java.util.Set<?>>) in class cannot be applied to (java.util.Map<java.lang.String,java.util.Set<java.lang.String>>)
However, if I change to the following all is fine:
public <T, V> int numberOfValues(Map<T, Set<V>> map)
However I’m not at all interested in V, as I just want to know the size of each of the sets.
For completeness sake, this is the whole method:
public <T, V> int numberOfValues(Map<T, Set<V>> map) {
int n = 0;
for (T key : map.keySet()) {
n += map.get(key).size();
}
return n;
}
Which I’m aware it can also be accomplished like this, but isn’t the point of the question :)
public <T> int numberOfValues(Map<?, Set<T>> map) {
int n = 0;
for (Set<T> value : map.values()) {
n += value.size();
}
return n;
}
Update: yet another way of achieving the same
public <T> int numberOfValues(Map<?, Set<T>> map) {
int n = 0;
for (Object key : map.keySet()) {
n += map.get(key).size();
}
return n;
}
Final update:
Thanks to Jorn’s answer, this is the final implementation...
public int numberOfValues(Map<?, ? extends Set<?>> map) {
int n = 0;
for (Set<?> value : map.values()) {
n += value.size();
}
return n;
}

You're missing the fact that Set<?> is also used as a generic parameter. And generics are invariant. i.e. when the parameter is Map<String, Set<?>>, the passed argument must be exactly Map<String, Set<?> (or a subtype of). Whereas with Set<V> the type argument is inferred.
You can solve this by using a bounded wildcard:
public <T> int numberOfValues(Map<T, ? extends Set<?>> map) {
...
}

Hint:
In your numberOfValues method, it is completely legal to write something like
Set<Object> set = new HashSet<>();
set.add(1);
set.add("Powned");
map.put(null, set);
You can replace the null with a suitable key already in the map to make a valid mapping, utterly breaking the type safety in the surrounding code.
Either infer or explicitly define the type of the set, or add a wildcard bound preventing mutating map access (like ? extends Set<?>)

Related

Create a String-parsing method building a Map in a generic way?

Preface: This is not an actual problem that I have, it just came to my mind in a "What if... ...how would I do that?" fashion.
When I have Strings consisting of several key-value pairs (like 123=456;321=654;89=90), I can make a Map from that ({123=456, 321=654, 89=90}) pretty easily with a method like this:
public static Map<Integer, Integer> makeMap(String theString) {
String[] chunks = theString.split(";");
Map<Integer, Integer> result = new HashMap<>(chunks.length);
for (String chunk : chunks) {
String[] chunksChunks = chunk.split("=");
int key = Integer.parseInt(chunksChunks[0]);
int value = Integer.parseInt(chunksChunks[1]);
result.put(key, value);
}
return result;
}
Is there any elegant way to "widen" this method to be a generic method, accepting e.g. all (wrappers for) primitive types?
It would be possible to write...
public static <K extends Object, V extends Object> Map<K, V> makeMapGeneric(String theString) {
// ???
}
...but I have no idea how I would do the "castings" to the keys and values.
As far as I know, the primitive types do not have any common makeXYfromString(String ...) method, just explicit Integer.parseInt, Double.parseDouble and so on, and they do not have a common superclass/interface that I could restrict K and V to.
Giving the classes as argument (makeMapGeneric(String theString, Class<K> keyClass, Class<V> valueClass)) and writing something like K key = keyClass.cast(keyString);, isn't possible since you cannot cast a String to eg. an int, just parse it.
Is there any elegant solution possible?
I took a tought on it for a few minutes and i came up with this solution
public static <K, V> Map<K, V> makeMap(String input, Function<String, K> keyFunc, Function<String, V> valFunc) {
return Arrays.stream(input.split(";"))
.map(s -> s.split("="))
.collect(Collectors.toMap(s -> keyFunc.apply(s[0]), s -> valFunc.apply(s[1])));
}
You need to pass a two functions which will transform the string to the right value.
Use it like this:
Map<Integer, Integer> x = makeMap("123=456;321=654;89=90", Integer::parseInt, Integer::parseInt);
You could provide a Function to you method:
<K, V> Map<K, V> makeMapGeneric(String theString, Function<String, K> keyFn, Function<String, V> valueFn) {
String key = "123";
String value = "456";
K parsedKey = keyFn.apply(key);
V parsedValue = valueFn.apply(key);
}
Now you can call it with a Function that converts String to K (and V):
Map<Integer, Double> result =
makeMapGeneric("123=456", Integer::parseInt, Double::parseDouble);

Java - How to pass a List type to a HashMap?

I'm trying to make a method public static Object getMostOccurringObject(List<?> list) which should return the most occurring object in the given list, or null if there are multiple objects with the highest occurrence.
In order to achieve this, I need to use a HashMap with the objects in list as key and the frequency of that object as value: HashMap<[type of list], Integer>.
This is what I've come up with:
public static <T> T getMostOccuringObject(List<T> list) {
HashMap<T, Integer> map = new HashMap<>();
for (T item : list) {
if (map.containsKey(item)) {
map.put(item, map.get(item) + 1);
} else {
map.put(item, 1);
}
}
int max = Collections.max(map.values());
if (Collections.frequency(map.values(), max) == 1) {
for (T object : map.keySet()) {
if (map.get(object).equals(max)) {
return object;
}
}
}
return null;
}
But I'm not sure if this will do what I want. The code does compile. Could anyone confirm for me if this works, and if not, how I can make it work?
By using a wildcard ? as the key to your Map, no elements will be able to be added; the Map#put method expects a capture type, and will not compile when you attempt to add any Object to it. Instead, you can use a generic:
public static <T> T getMostOccuringObject(List<T> c) {
Map<T, Integer> frequency = new HashMap<>();
// rest of the code
}
the following works for you. and also make sure parameters should have proper hashcode&equals method
public static <T> Object getMostOccuringObject(List<T> c) {
HashMap<T, Integer> frequency = new HashMap<>();
// rest of the code
}
refer:
https://docs.oracle.com/javase/tutorial/java/generics/boundedTypeParams.html

Generics with optional multiple bounds, e.g. List<? extends Integer OR String>

I have a method that should only accept a Map whose key is of type String and value of type Integer or String, but not, say, Boolean.
For example,
map.put("prop1", 1); // allowed
map.put("prop2", "value"); // allowed
map.put("prop3", true); // compile time error
It is not possible to declare a Map as below (to enforce compile time check).
void setProperties(Map<String, ? extends Integer || String> properties)
What is the best alternative other than declaring the value type as an unbounded wildcard and validating for Integer or String at runtime?
void setProperties(Map<String, ?> properties)
This method accepts a set of properties to configure an underlying service entity. The entity supports property values of type String and Integer alone. For example, a property maxLength=2 is valid, defaultTimezone=UTC is also valid, but allowDuplicate=false is invalid.
Another solution would be a custom Map implementation and overrides of the put and putAll methods to validate the data:
public class ValidatedMap extends HashMap<String, Object> {
#Override
public Object put(final String key, final Object value) {
validate(value);
return super.put(key, value);
}
#Override
public void putAll(final Map<? extends String, ?> m) {
m.values().forEach(v -> validate(v));
super.putAll(m);
}
private void validate(final Object value) {
if (value instanceof String || value instanceof Integer) {
// OK
} else {
// TODO: use some custom exception
throw new RuntimeException("Illegal value type");
}
}
}
NB: use the Map implementation that fits your needs as base class
Since Integer and String closest common ancestor in the class hierarchy is Object you cannot achieve what you are trying to do - you can help compiler to narrow the type to Object only.
You can either
wrap your value into a class which can contain either Integer or String, or
extend Map as in the #RC's answer, or
wrap 2 Maps in a class
You can’t declare a type variable to be either of two types. But you can create a helper class to encapsulate values not having a public constructor but factory methods for dedicated types:
public static final class Value {
private final Object value;
private Value(Object o) { value=o; }
}
public static Value value(int i) {
// you could verify the range here
return new Value(i);
}
public static Value value(String s) {
// could reject null or invalid string contents here
return new Value(s);
}
// these helper methods may be superseded by Java 9’s Map.of(...) methods
public static <K,V> Map<K,V> map(K k, V v) { return Collections.singletonMap(k, v); }
public static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2) {
final HashMap<K, V> m = new HashMap<>();
m.put(k1, v1);
m.put(k2, v2);
return m;
}
public static <K,V> Map<K,V> map(K k1, V v1, K k2, V v2, K k3, V v3) {
final Map<K, V> m = map(k1, v1, k2, v2);
m.put(k3, v3);
return m;
}
public void setProperties(Map<String, Value> properties) {
Map<String,Object> actual;
if(properties.isEmpty()) actual = Collections.emptyMap();
else {
actual = new HashMap<>(properties.size());
for(Map.Entry<String, Value> e: properties.entrySet())
actual.put(e.getKey(), e.getValue().value);
}
// proceed with actual map
}
If you are using 3rd party libraries with map builders, you don’t need the map methods, they’re convenient for short maps only. With this pattern, you may call the method like
setProperties(map("mapLength", value(2), "timezone", value("UTC")));
Since there are only the two Value factory methods for int and String, no other type can be passed to the map. Note that this also allows using int as parameter type, so widening of byte, short etc. to int is possible here.
Define two overloads:
void setIntegerProperties(Map<String, Integer> properties)
void setStringProperties(Map<String, String> properties)
They have to be called different things, because you can't have two methods with the same erasure.
I'm fairly certain if any language was going to disallow multiple accepted types for a value, it would be Java. If you really need this kind of capability, I'd suggest looking into other languages. Python can definitely do it.
What's the use case for having both Integers and Strings as the values to your map? If we are really dealing with just Integers and Strings, you're going to have to either:
Define a wrapper object that can hold either a String or an Integer. I would advise against this though, because it will look a lot like the other solution below.
Pick either String or Integer to be the value (String seems like the easier choice), and then just do extra work outside of the map to work with both data types.
Map<String, String> map;
Integer myValue = 5;
if (myValue instanceof Integer) {
String temp = myValue.toString();
map.put(key, temp);
}
// taking things out of the map requires more delicate care.
try { // parseInt() can throw a NumberFormatException
Integer result = Integer.parseInt(map.get(key));
}
catch (NumberFormatException e) {} // do something here
This is a very ugly solution, but it's probably one of the only reasonable solutions that can be provided using Java to maintain some sense of strong typing to your values.

Map: Defining a Method for Type Integer and Double but not String

I'm attempting to define a method putIfGreaterThan() for my new Map class (given a key it replaces the old value with the new value only if the new value is greater than the old value).
I understand I could accomplish this either via composition (by having a private final Map<String, Double> map; in new class and then passing a Map to constructor) or by implementing the Map interface in my new class and passing a Map to the constructor (although I'm not sure which approach is superior).
My main problem is that I need to be able to call the method putIfGreaterThan() on <String, Double> and <String, Integer> but not on <String, String> (bec it doesn't make sense to call it on <String, String> ). If I use generics ( <K, V>) the client can pass a <String, String> which is not allowed. On the other hand if I allow a Double I will not be able to pass an Integer or vice versa. How can I define a method to allow either Integer or Double but not String?
Note: I'm unable to define two constructors (one for Double and one for Integer) bec I get the error: Erasure of method XX is the same as another method in type XX .
You can use the decorator pattern and limit the generics to subtypes of Number, which you can than compare without casting with a little trick taken from this answer. It takes the string representation of the number instances and creates an instance of BigDecimal - thus circumventing casting of any kind.
Below you find the relevant implementation details of the decorator, of course you'll need to override the remaining methods of the Map interface.
public class GreaterThanDecorator<K, V extends Number> implements Map<K, V> {
private final Map<K, V> delegate;
public GreaterThanDecorator(Map<K, V> delegate) {
this.delegate = delegate;
}
public V putIfGreaterThan(K key, V value) {
V old = delegate.get(key);
if (old == null) {
delegate.put(key, value);
return null;
}
BigDecimal newValue = new BigDecimal(value.toString());
BigDecimal oldValue = new BigDecimal(old.toString());
if (newValue.compareTo(oldValue) >= 1)
old = delegate.put(key, value);
return old;
}
}
Feel free to forbid the other subtypes of Number, i.e. by throwing an exception, as you see fit.
Generics and numbers don't go together well, unfortunately. But you can do something like this:
public class MyMap {
private final Map<String, Number> map = new HashMap<>();
public void putInt(String key, int value) {
map.put(key, value);
}
public void putDouble(String key, int value) {
map.put(key, value);
}
public void putIfGreaterThan(String key, Number value) {
if (value instanceof Double) {
double doubleValue = (Double) value;
map.compute(key, (k, v) -> {
if (!(v instanceof Double) || v.doubleValue() > doubleValue) {
return v;
} else {
return value
}
});
} else if (value instanceof Integer) {
int intValue = (Integer) value;
map.compute(key, (k, v) -> {
if (!(v instanceof Integer) || v.intValue() > intValue) {
return v;
} else {
return value
}
});
} else {
throw new IllegalArgumentException("Expected Double or Integer, but got " + value);
}
}
}
Ideally you'd want to declare a method introducing a type parameter which extends V & Comparable<? super V>, but that is not valid Java.
However, you can define a static method without using the &.
public static <K, V extends Comparable<? super V>> void putIfGreater(
Map<K,V> map, K key, V value
) {
V old = map.get(key);
if (old != null && value.compareTo(old) > 0) {
map.put(key, value);
}
// Or:
//map.computeIfPresent(key, (k, old) -> value.compareTo(old) > 0 ? value : old);
}
If you were going down the route of different construction, then the type would need to be different for the case where V is Comparable<? super V>. If two constructors have the same erasure, it's probably time to have meaningfully named static creation methods.

Why is Map<String, int> list = new HashMap<String, int> not permissible?

So as you can see be title I am confused on:
Map<String, int> list = new HashMap<String, int>
I am a bit lost in class on this specific topic and would appreciate if anybody could explain why and how it actually works.
The type int is not a class, it's a primitive type. Generic type parameters must be assigned classes, not primitive types. You can use
Map<String, Integer> list = new HashMap<String, Integer>();
instead. All Java primitive types have class wrappers, and as of Java 1.5, autoboxing allows expressions such as map.put("dummy", 1);, where 1 is autoboxed as an Integer.
Incidentally, it can be confusing to call a Map list. You could remove the confusion by calling it map.
IN java Some thing like following happens
public interface Map<K, V> {
public K getKey();
public V getValue();
}
public class HashMap<K, V> implements Map<K, V> {
private K key; //1
private V value; //2
public K getKey() { return key; }
public V getValue() { return value; }
//other getter setter methods
}
As In
Here in place of<K,V> in
<String,int> int is a primitive type And we can't make object of primitive type.
see //1 and //2 above in code
But <String,Integer> is possible as they are wrapper type and Objects can be made of them

Categories

Resources