I am currently working with Generics and I'm not that experienced. I got this problem:
I made this class to compare 2 values implementing Comparable and do some other stuff, let's say incrementing the compareTo() value:
public final class Type<T extends Comparable<T>> {
public int compare(T val1, T val2) {
int compTemp = val1.compareTo(val2);
compTemp++;
// do stuff
return compTemp;
}
}
Now I create some instances of Type:
Type<Integer> t1 = new Type<Integer>();
Type<String> t2 = new Type<String>();
And now I want to put them into a Map. As I cannot predict the generic type of Type I use the wilcard ?:
Map<String, Type<?>> map = Maps.newHashMap();
map.put("t1", t1);
map.put("t2", t2);
If I want to invocate Type.compare() it's working for t1, but not for map.get(t1):
t1.compare(1,1); // compiles fine
map.get("t1").compare(1, 1); // does not compile
The latter throws a compilation error:
The method compare(capture#3-of ?, capture#3-of ?) in the type
Type is not applicable for the arguments (int, int)
I know it's because of my wildcard parameter, but I don't exactly know why and how to fix this.
The only "fix" I see is to use raw types for the Map, however this will show several warnings.
I suppose right now I have a huge misunderstanding of the wilcard parameter.
I appreciate every answer!
You are using a the wildcard <?> in your Map means your Map does not know the exact type. For the Map they are all stored as Type<Object>.
One thing you can do here is some type unsafe casting
((Type<Integer>)map.get("t1")).compare(1, 1); // does compile but is type unsafe
Another solution would be to store the generic Class when creating your Type Objects in constructor. Then you can do some type safe casting in your compare method using another method generic Type. See this Type class
public final class Type<T extends Comparable<T>> {
Class<T> type;
Type(Class<T> _type){
type = _type;
}
public <E extends Comparable<E>> int compare(E val1, E val2) {
T v1 = type.cast(val1);
T v2 = type.cast(val2);
int compTemp = v1.compareTo(v2);
compTemp++;
// do stuff
return compTemp;
}
}
Now you can do this:
Type<Integer> t1 = new Type<Integer>(Integer.class);
Type<String> t2 = new Type<String>(String.class);
Map<String, Type<?>> map = new HashMap<>();
map.put("t1", t1);
map.put("t2", t2);
map.get("t1").compare(1, 1); // compiles fine
map.get("t2").compare("one", "one"); // compiles fine
I assume you want to do something like this at runtime:
String key = getKeyFromParams(p1, p2);
map.get( key ).compare(p1, p2);
Since the compiler doesn't know the generic type of the value it returns for the key you can't just call compare() that way.
You could, however, restructure your code a bit to make it work (still with warnings and the need to take care not to break anything):
Make your compare() method accept Comparable parameters
Pass the class of the generic type to the constructor and use it to check the parameters in compare()
if the parameter types are ok compare them
if they don't match the type class throw an exception
use the type class as the map key and use it for the lookup
Example:
public final class Type<T extends Comparable<T>> {
private final Class<T> typeClass;
public Type( Class<T> typeClass) {
this.typeClass = typeClass;
}
public int compare(Comparable val1, Comparable val2) {
if( !(typeClass.isInstance( val1 ) && typeClass.isInstance( val2 ) ) ) {
throw new IllegalArgumentException("message");
}
int compTemp = val1.compareTo(val2);
compTemp++;
// do stuff
return compTemp;
}
//getter for the typeClass
}
A wrapper for the map:
class TypeComparator {
Map<Class<?>, Type<?>> map = new HashMap<Class<?>, Test.Type<?>>();
public void addType(Type<?> type) {
map.put( type.getTypeClass(), type );
}
public <T extends Comparable<T>> void compare(T p1, T p2) {
map.get( p1.getClass() ).compare( p1, p2 );
}
}
And finally call it like this:
//add some types
TypeComparator comp = new TypeComparator();
comp.addType( new Type<Integer>( Integer.class ));
comp.addType( new Type<String>( String.class ));
//compare ints
comp.compare( 1, 1 );
//compare Strings
comp.compare( "1", "1" );
//won't compile
comp.compare( "1", 1 );
Some final thoughts:
You have to handle cases where there is no type in the map (e.g. if one passed Double parameters in the example)
You'll still get warnings but if you hide them well in the implementation and take care not to apply casts on the wrong types you should be fine.
Related
I want to write a utility for general memoization in Java, I want the code be able to look like this:
Util.memoize(() -> longCalculation(1));
where
private Integer longCalculation(Integer x) {
try {
Thread.sleep(1000);
} catch (InterruptedException ignored) {}
return x * 2;
}
To do this, I was thinking I could do something like this:
public class Util{
private static final Map<Object, Object> cache = new ConcurrentHashMap<>();
public interface Operator<T> {
T op();
}
public static<T> T memoize(Operator<T> o) {
ConcurrentHashMap<Object, T> memo = cache.containsKey(o.getClass()) ? (ConcurrentHashMap<Object, T>) cache.get(o.getClass()) : new ConcurrentHashMap<>();
if (memo.containsKey(o)) {
return memo.get(o);
} else {
T val = o.op();
memo.put(o, val);
return val;
}
}
}
I was expecting this to work, but I see no memoization being done. I have tracked it down to the o.getClass() being different for each invocation.
I was thinking that I could try to get the run-time type of T but I cannot figure out a way of doing that.
The answer by Lino points out a couple of flaws in the code, but doesn't work if not reusing the same lambda.
This is because o.getClass() does not return the class of what is returned by the lambda, but the class of the lambda itself. As such, the below code returns two different classes:
Util.memoize(() -> longCalculation(1));
Util.memoize(() -> longCalculation(1));
I don't think there is a good way to find out the class of the returned type without actually executing the potentially long running code, which of course is what you want to avoid.
With this in mind I would suggest passing the class as a second parameter to memoize(). This would give you:
#SuppressWarnings("unchecked")
public static <T> T memoize(Operator<T> o, Class<T> clazz) {
return (T) cache.computeIfAbsent(clazz, k -> o.op());
}
This is based on that you change the type of cache to:
private static final Map<Class<?>, Object> cache = new ConcurrentHashMap<>();
Unfortunately, you have to downcast the Object to a T, but you can guarantee that it is safe with the #SuppressWarnings("unchecked") annotation. After all, you are in control of the code and know that the class of the value will be the same as the key in the map.
An alternative would be to use Guavas ClassToInstanceMap:
private static final ClassToInstanceMap<Object> cache = MutableClassToInstanceMap.create(new ConcurrentHashMap<>());
This, however, doesn't allow you to use computeIfAbsent() without casting, since it returns an Object, so the code would become a bit more verbose:
public static <T> T memoize(Operator<T> o, Class<T> clazz) {
T cachedCalculation = cache.getInstance(clazz);
if (cachedCalculation != null) {
return cachedCalculation;
}
T calculation = o.op();
cache.put(clazz, calculation);
return calculation;
}
As a final side note, you don't need to specify your own functional interface, but you can use the Supplier interface:
#SuppressWarnings("unchecked")
public static <T> T memoize(Supplier<T> o, Class<T> clazz) {
return (T) cache.computeIfAbsent(clazz, k -> o.get());
}
The problem you have is in the line:
ConcurrentHashMap<Object, T> memo = cache.containsKey(o.getClass()) ? (ConcurrentHashMap<Object, T>) cache.get(o.getClass()) : new ConcurrentHashMap<>();
You check whether an entry with the key o.getClass() exists. If yes, you get() it else you use a newly initialized ConcurrentHashMap. The problem now with that is, you don't save this newly created map, back in the cache.
So either:
Place cache.put(o.getClass(), memo); after the line above
Or even better use the computeIfAbsent() method:
ConcurrentHashMap<Object, T> memo = cache.computeIfAbsent(o.getClass(),
k -> new ConcurrentHashMap<>());
Also because you know the structure of your cache you can make it more typesafe, so that you don't have to cast everywhere:
private static final Map<Object, Map<Operator<?>, Object>> cache = new ConcurrentHashMap<>();
Also you can shorten your method even more by using the earlier mentioned computeIfAbsent():
public static <T> T memoize(Operator<T> o) {
return (T) cache
.computeIfAbsent(o.getClass(), k -> new ConcurrentHashMap<>())
.computeIfAbsent(o, k -> o.op());
}
(T): simply casts the unknown return type of Object to the required output type T
.computeIfAbsent(o.getClass(), k -> new ConcurrentHashMap<>()): invokes the provided lambda k -> new ConcurrentHashMap<>() when there is no mapping for the key o.getClass() in cache
.computeIfAbsent(o, k -> o.op());: this is invoked on the returned value from the computeIfAbsent call of 2.. If o doesn't exist in the nested map then execute the lambda k -> o.op() the return value is then stored in the map and returned.
I am setting my my variable like
Map<String, Function<CLASS_NAME, Comparable>> map = new HashMap<>();
where Comparable is giving the warning message as
Comparable is a raw type. References to generic type Comparable<T> should be parameterized
I am using it like
map.put(VARIABLE_NAME1, s -> s.getStringProperty());
map.put(VARIABLE_NAME2, s -> s.getIntProperty());
..
I am using for compare like
Comparator<CLASS_TYPE> v = Comparator.comparing(map.get(VARIABLE_NAME), Comparator.nullsFirst(Comparator.naturalOrder()));
What type of Generic should be used to avoid the warning?
There are several things wrong with your current scheme.
Comparator and Comparable are two different approaches to comparing objects. You are confusing the two.
You are trying to store a function that does comparisons into the map. Later, you fetch the value from the map and try to compare it (the function) to a Comparator. This won't work, as you can't compare a function to anything except another function.
You don't actually store a value anywhere; you are only storing a function.
In your example, you store two different values to the same VARIABLE_NAME. Is that intentional?
If you want to create a property map, then you need to create a storable object that can be stored into the map, and can compare its value to a provided value. For instance,
class Storable<T extends Comparable<T>> {
private final T value;
Storable(T value) {
this.value = value;
}
int compareTo(Object other) {
if ( value.getClass().equals(other.getClass()) ) {
return value.compareTo( (T) other );
}
return -1;
}
}
Now create appropriate sub-classes:
class StorableInt extends Storable<Integer> {
StorableInt(Integer t) {
super(t);
}
}
class StorableString extends Storable<String> {
StorableString(String s) {
super(s);
}
}
Your property map now looks like:
Map<String, Storable<?>>map = new HashMap<>();
map.put(variableName1, new StorableInt(13));
map.put(variableName2, new StorableString("string2"));
<T extends Comparable<T>> int compare( String key, T val ) {
return map.get( key ).compareTo( val );
}
You can now store properties into your map and compare values against those properties.
Comparable is obviously a generic type.
So all you need is just:
Map<String, Function<CLASS_NAME, Comparable<CLASSNAME>>> map = new HashMap<>();
instead of
Map<String, Function<CLASS_NAME, Comparable>> map = new HashMap<>();
or you want to compare another type..?
Map<String, Function<CLASS_NAME, Comparable<SomeOtherClass>>> map = new HashMap<>();
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.
I'm coming from C# world and I'm having troubles using generics in Java. For some reason, Java complains about not being able to convert my types. Here's my code
public <T1, T2> ArrayList<T2> convert(List<T1> list, Function<T1, T2> function) {
ArrayList<T2> result = new ArrayList<T2>();
for (T1 source : list) {
T2 output = function.apply(source);
result.add(output);
}
return result;
}
public SomeType convertSomeType(SourceType input){
.....
return ....
}
and call it something like:
List<SourceType> list...
SomeType result = convert(list, this::convertSomeType)
I get Bad return type in method reference. Cannot convert SomeType to T2.
I also tried specifying generic parameters like so:
List list...
SomeType result = convert(list, this::convertSomeType)
but it didn't help. What am I doing wrong here?
Your method returns an ArrayList<T2> (I'm assuming Tm is a typo), not T2:
// Your version, doesn't work:
SomeType result = convert(list, this::convertSomeType)
// This works:
List<SomeType> result = convert(list, this::convertSomeType)
Also, you should make that convert() method follow PECS:
public <T1, T2> ArrayList<T2> convert(
List<? extends T1> list,
Function<? super T1, ? extends T2> function
);
I have two maps in my class (I am new to generics)
private Map<Integer, Integer> aMap = new ConcurrentHashMap<Integer, Integer>();
private Map<Integer, Short> bMap = new HashMap<Integer, Short>();
If key does not exist in map I want to get a zero value. So I have made this wrapper method to minimize typing containsKey(key)
#SuppressWarnings("unchecked")
private <T extends Number> T getValue (Map<Integer, T> map, Integer key) {
return (T) ((map.containsKey(key)) ? map.get(key) : 0);
}
I call it like
Integer a = getValue(aMap, 15); //okay in any case
Short b = getValue(bMap, 15); //15 key does not exist
For second case it gives me:
ClassCastException: java.lang.Integer cannot be cast to java.lang.Short
So probably I would need to do something like : new Number(0), but Number is abstract.
How can I fix it?
EDIT:
My idea is to do arithmetic operations without additional ifs:
Integer a = getValue(aMap, 15);
a = a + 10;
One way is to supply the default value as an argument to your function:
private <T extends Number> T getValue (Map<Integer, T> map, Integer key, T dflt) {
return (T) ((map.containsKey(key)) ? map.get(key) : dflt);
}
public static void main(String[] args) {
Integer a = getValue(aMap, 15, 0); //okay in any case
Short b = getValue(bMap, 15, (short)0); //15 key does not exist
}
Well, you can't do much about that without also providing T in a way that code can look at.
The simplest approach at that point would probably be to keep a map of 0 values:
private static Map<Class<?>, Number> ZERO_VALUES = createZeroValues();
private static Map<Class<?>, Number> createZeroValues() {
Map<Class<?>, Number> ret = new HashMap<Class<?>, Number>();
ret.put(Integer.class, (int) 0);
ret.put(Short.class, (short) 0);
ret.put(Long.class, (long) 0);
// etc
}
Then:
private <T extends Number> T getValue (Map<Integer, T> map, Integer key, Class<T> clazz) {
return clazz.cast(map.containsKey(key) ? map.get(key) : ZERO_VALUES.get(clazz));
}
Then you'd unfortunately have to call it as:
Short b = getValue(bMap, 15, Short.class);
Basically this is a limitation of Java generics :(
In situations like this, I just override the get() method:
private Map<Integer, Short> bMap = new HashMap<Integer, Short>() {
#Override
public Short get(Object key) {
return containsKey(key) ? super.get(key) : new Short(0);
}
};
Then you can just use it anywhere and it will behave as you specified.
Cast 0 to short explicitly as it will be int by default. And then convert to Short wrapper. In java u cannot directly convert from primitive to a Wrapper of a different type.