Inserting values in a typed map - java

Why would the code below result in the following error when trying to add to the Map?
Wrong 1st argument type. Found: 'com.test.Test.SomeEnums', required 'T'
public class Test {
public enum SomeEnums implements SomeType {
A;
public <T extends Enum<T> & SomeType> Map<T, Object> buildMap() {
Map<T, Object> map = new HashMap<>();
map.put(SomeEnums.A, new Object());
return map;
}
}
}
public interface SomeType {
}
Any ideas?

The problem is that map.put(SomeEnums.A, new Object()) is not always safe for Map<T, Object>. Although SomeEnums is a valid substitute for extends Enum<T> & SomeType, it is not always the concrete type parameter.
For example, consider this second enum:
enum OtherEnum implements SomeType {
B;
}
If the same method is to be called:
Map<OtherEnum, Object> otherMap = Test.SomeEnums.A.buildMap();
This is a valid call given the signature of buildMap(). However, the problem is that the method is adding an incorrect map key:
map.put(SomeEnums.A, new Object());
//SomeEnums.A is not always of type <T>, so this is not allowed.
The code will compile with a type cast (map.put((T) SomeEnums.A, new Object())) - with a warning, but that's unsafe and likely not the point of the generic method.

Related

Method with ? extends Collections<String> is not applicable for Set<String>

Short, self contained, non-compilable example:
public void test1() throws Exception {
Map<String, Map<String, Set<String>>> a = Collections.singletonMap("One",
Collections.singletonMap("Two", new HashSet<String>()));
foo(a);
}
void foo(Map<String, Map<String, ? extends Collection<String>>> x) {
}
Yields (javac 1.8.0_102):
error: incompatible types: Map<String,Map<String,Set<String>>> cannot be converted to Map<String,Map<String,? extends Collection<String>>>
foo(a);
^
I expect foo to take any subtype of Collection like Set or List. What is wrong with code above?
The way you declared foo, the compiler demands a type parameter of the exact type Map<String, ? extends Collection<String>>. You could make foo generic:
<C extends Collection<String>> void foo(Map<String, Map<String, C>> x) {
}
Your code doesn't compile for the same reason the following (simpler) code doesn't compile:
List<Integer> l = Arrays.asList(1,2,3);
foo(l); //The method v(List<Object>) in the type Test is not applicable for the arguments (List<Integer>)
public void foo (List<Object> a){
...
}
The fact that Integer extends Object doesn't mean that List<Integer> extends List<Object>. They're two different, unrelated, types.
We can fix the compilation error in the simple case by changing foo's signature to:
public void foo (List<? extends Object> a){
...
}
This tells the compiler that foo will be accepting a List of something that extends Object, which is what List<Integer> is.
In your case, Map<String, Map<String, Set<String>>> doesn't extend Map<String, Map<String, ? extends Collection<String>>>.
Even if you changed your foo to void foo(Map<String, Object> x) it wouldn't compile since Map<String, Map<String, Set<String>>> does not extend Map<String, Object>. They're two different types.
To get it to work, you can change foo's signature to void foo(Map<String, ? extends Map<String, ? extends Collection<String>>> x)
This compiles
void foo(Map<String, ? extends Map<String, ? extends Collection<String>>> x)

Composing function with identity function yields type mismatch

I have an enum and a method that makes custom string-conversion functions for enum values:
public enum MyEnum {
DUMMY;
}
public <E extends Enum<E>> Function<E, String> stringify(String suffix) {
return enumValue -> enumValue.name() + suffix;
}
I want to use the method to make a function for my specific enumeration type:
public void test() {
Function<MyEnum, String> f = stringify("");
}
That works, but I also need my function to do some follow-on processing to the string. For the sake of example, let's say the follow-on processing is just the identity function:
public void test() {
Function<MyEnum, String> f = stringify("").andThen(Function.identity());
}
Now I get a compile error. Eclipse (Neon) says:
Type mismatch: cannot convert from Function<Enum<Enum<E>>,String> to Function<Test.MyEnum,String>
and javac says:
error: incompatible types: no instance(s) of type variable(s) V,T#2 exist so that Function<E,V> conforms to Function<MyEnum,String>
Function<MyEnum, String> f = stringify("").andThen(Function.identity());
^
where V,R,T#1,T#2,E are type-variables:
V extends Object declared in method <V>andThen(Function<? super R,? extends V>)
R extends Object declared in interface Function
T#1 extends Object declared in interface Function
T#2 extends Object declared in method <T#2>identity()
E extends Enum<E>
The return type of Function.identity() is the same as its argument type, so I don't see how it changes the overall result to something other than Function<MyEnum, String>. I'm especially confused by the Enum<Enum<E>> in the Eclipse error message.
I've noticed that I can avoid the problem by assigning the intermediate result to a variable:
public void test() {
Function<MyEnum, String> f1 = stringify("");
Function<MyEnum, String> f2 = f1.andThen(Function.identity());
}
but I'd rather avoid that if possible.
Why does this type mismatch occur? What's the best way to resolve it?
This is a generics bounding issue.
In this statement:
Function<MyEnum, String> f = stringify("").andThen(Function.identity());
The compiler does not know the bound for stringify("") and thus cannot infer the bound for Function.identity() as well.
To fix this, you need to add a bound to stringify(""):
Function<MyEnum, String> f = this.<MyEnum>stringify("").andThen(Function.identity());
notice the this keyword is added as well, since you cannot simply write <MyEnum>stringify("").
If the stringify("") method is from some static util class, it will look like the follow instead:
Function<MyEnum, String> f = MyUtils.<MyEnum>stringify("").andThen(Function.identity());

How to get a type with multiple generic types to match the type parameters?

For example, say I want a Map<Class<?>, List<?>>, so I can put in a class and get out a list of that type - is there something I can replace the question marks with to make that happen?
You can do the trick if you delegate type check to the method:
private class TypedMap {
private Map<Class<?>, List<?>> map = new HashMap<>();
public <T> void put(Class<T> key, List<T> value) {
map.put(key, value);
}
#SupressWarnings("unchecked")
public <T> List<T> get(Class<T> clazz) {
return (List<T>) map.get(clazz);
}
}
Wildcard ? in map declaration does not ensure that key Class<?> and value List<?> would be of the same type. Method put() ensures that. You can not declare map as Map<Class<T>, List<T>> if your class is not generic - that's why you have to use a method.
Get method is unchecked. The cast is safe if entries are added with put() method. (There's still a problem with raw types - but this is unavoidable)
You can add more methods to TypedMap class, but remember about this restrictions.
public static void main(String[] args) {
TypedMap map = new TypedMap();
List<Cat> cats = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
adder.put(Cat.class, cats);
adder.put(Dog.class, dogs);
adder.put(Cat.class, dogs); // compilation error
}
Java doesn't completely enforce this, but one way to at least get a warning about it, is by using encapsulation:
public class MyClass {
// private, private, private
private Map<Class<?>, List<?>> myMap;
public <T> void put(Class<T> clazz, List<T> list) { // both must have the same T.
myMap.put(clazz, list);
}
...
}
You can still break this by doing something like:
MyClass mc = new MyClass();
Class c = Main.class;
List<String> l = new ArrayList<String>();
mc.put(c, l);
But you'll at least get a warning about unchecked conversion of c to Class<String>. And the unchecked invocation of MyClass::put
Not sure what you're trying to accomplish here, but Map<Class<T>, List<T>> would be the closest thing. The T is one single class type, though, so you can't put multiple classes into one Map.
You'll get a ClassCastException if you try to put objects of different classes into the same Map.

Inferring a generic type from a generic type in Java (compile time error)

I have a static function with the following signature for a generic type T
public static<T> List<T> sortMap(Map<T, Comparable> map)
which should return a list of map keys with some property.
Now I want to pass a generic HashMap of type S
Map<S,Double> map
in calling the static function within a generic class, which has the map as a member variable.
I have listed a minimal code example below.
However, I get an error message (S and T are both T's but in different scopes of my code, i.e. T#1 = T, T#2= S):
required: Map<T#1,Comparable>
found: Map<T#2,Double>
reason: cannot infer type-variable(s) T#1
(argument mismatch; Map<T#2,Double> cannot be converted to Map<T#1,Comparable>)
How can resolve this issue? I am surprised that Java does not allow inferring a generic type from a generic type. What structure in Java can one use to work with that kind of more abstract code reasoning?
Code:
public class ExampleClass<T> {
Map<T, Double> map;
public ExampleClass () {
this.map = new HashMap();
}
//the following line produces the mentioned error
List<T> sortedMapKeys = UtilityMethods.sortMap(map);
}
public class UtilityMethods {
public static<T> List<T> sortMap(Map<T, Comparable> map) {
// sort the map in some way and return the list
}
}
It's not the problem with the T and S, but with the Comparable and Double.
The reason for the error is that a Map<T, Double> is not a Map<T, Comparable>.
You'll have to widen a bit the scope of the second type-parameter. Something like:
public static <T, S extends Comparable<S>> List<T> function(Map<T, S> map) {
//implementation
}
Then, you'll be able to invoke the method with:
Map<S, Double> map = new HashMap<S, Double>();
function(map);

Java -- passing generic parameters

I am trying to write a method that takes in any subclass of MyInterface as a parameter, but getting syntax errors.
public Map<String, List<T extends MyInterface>> makeMap(List<T extends MyInterface>) {
Map<String, List<T extends MyInterface>> myMap = ...
return myMap;
}
This syntax is not valid. The signature gives the error "misplaced construct". But, the idea is that I can pass any subclass of MyInterface inn place of T. Can this be done in Java? how?
You are mixing up the concepts of declaring a generic type and referring to that generic type. Assuming that you want the method to be generic, declare the generic type parameter before the return type, then refer to it plainly as T elsewhere:
// Declaration ref ref
public <T extends MyInterface> Map<String, List<T>> makeMap(List<T>) {
// ref
Map<String, List<T>> myMap = ...
return myMap;
}
public <T extends MyInterface> Map<String, List<T>> makeMap(List<T> myList) {
More on generic methods here
I also noticed that in your original method declaration, you didn't have a variable defined for your method parameter.

Categories

Resources