I'd like to map the Label -> Value of a generic enum instance. This is feasible with this code:
public static <T extends Enum<T>> List<Map<String, String>> mapEnumValues(T[] myEnumValues) {
List<Map<String, String>> list = new ArrayList<>();
for (T myEnumValue : myEnumValues) {
Map<String, String> map = new HashMap<>();
map.put("label", myEnumValue.name());
map.put("value", myEnumValue.toString());
list.add(map);
}
return list;
}
But now I want to use another key for the map, which is for instance a method getLabel() implemented by every one of my enums. For example, given:
public interface Labelizable{
public String getLabel();
}
static public enum Options implements Labelizable {
option01("Option 01", 1), option02("Option 02", 2)
... constructor, getLabel(), ....
So I'd like to replace that row of code with:
map.put("label", myEnumValue.getLabel()); // instead of .name()
In order to do that I should declare something on the signature thst forces the parameter to be an enum that implements the getLabel() method. I tried, for example,
public static <T extends Enum<T implements Labelizable>>
and some other syntax like this with no success. Is there any way to achieve this behaviour, or the fact that enums can't inherit prevent such a method?
The syntax is:
public static <T extends Enum<T> & Labelizable>
List<Map<String, String>> mapEnumValues(T[] myEnumValues) {
You put the superclass that your generic parameter has to inherit first, then the interfaces that it has to implement, all separated by &.
Related
This question already has answers here:
what is the difference between ? and T in class and method signatures?
(4 answers)
Static generic field in Java
(1 answer)
Closed 1 year ago.
I can define it with '?':
private static final Map<Class<? extends SuperClass>, String> map=
new HashMap<Class<? extends SuperClass>, String>() {
private static final long serialVersionUID = 913392463302576279L;
{
put(SubClaas1.class, "sub1");
put(SubClaas2.class, "sub2");
}
};
But define with T, compile error:
private static <T extends SuperClass> Map<Class<T>, String> map = new HashMap<>();
How to define it use 'T' mode.
Java does not support declaring type parameters for individual fields, only for classes or methods.
You either need to move the declaration of T into the surrounding class itself, or keep using Class<? extends SuperClass> and do unchecked casts to whatever the appropriate type is.
Edit: Sorry, I just noticed that the field is static, in which case you can't just move the type parameter to the class. You need to stick to Class<? extends SuperClass>, either directly or by encapsulating the type unsafety in some custom class map.
A generic type is a generic class or interface that is parameterized over types.
It can be used for a class or a method, but not fields.
If you want to define a map with 'T':
a. use a method:
private static <T extends SuperClass> HashMap<T, String> generateMap() {}
b. use a method:
class Foo<T extends SuperClass> {
private HashMap<Class<T>, String> map = new HashMap<Class<T>, String>();
}
You can get a map of a type based on the needs of the method caller, eg:
public static <T extends SuperClass> HashMap<T, String> giveMeMap() {
return new HashMap<T, String>();
}
so then this will complie:
private static HashMap<SuperClass, String> map = giveMeMap();
but this not, because of the type restriction on the method <T extends SuperClass>
private static HashMap<String, String> map2 = giveMeMap();
is this answering your question?
I have a generic Command interface:
public interface Command<T> {
public void execute(T value);
}
And some implementations:
public class ChangeName implements Command<String>{
public void execute(String value) {...}
}
public class SetTimeout implements Command<Integer>{
public void execute(Integer value) {...}
}
What I need is a Map to link command names with a specific Command object:
Map<String, Command> commands = new HashMap<>();
...
commands.put("changeName", new ChangeName());
Obviously, I am getting rawtypes warnings when declaring the Map.
If I use a question mark I end up with a compilation error:
Map<String, Command<?>> commands = new HashMap<>();
...
commands.get("changeName").execute("Foo"); // -> compilation error
The method execute(capture#2-of ?) in the type Command is not applicable for the arguments (String)
I know that you cannot have a typesafe heterogeneous container with a non-reifiable type (Item 29 in Effective Java), but what is the best approach to address this problem?
I think you need to make the Commands aware of their acceptable argument at run-time:
public abstract class Command<T> {
private final Class<T> argumentClass;
protected Command(Class<T> argumentClass) {
this.argumentClass = argumentClass;
}
public abstract <U extends T> void execute(U argument);
#SuppressWarnings("unchecked")
public final <U> Command<? super U> cast(Class<U> argumentClass) {
if (this.argumentClass.isAssignableFrom(argumentClass)) {
return (Command<? super U>) this;
} else {
throw new UnsupportedOperationException("this command cannot handle argument of type " + argumentClass.getName());
}
}
}
Now the using code would be something like this:
private <U> void executeCommand(final String name, final U arg) {
#SuppressWarnings("unchecked")
Class<U> clazz = (Class<U>) arg.getClass();
commands.get(name).cast(clazz).execute(arg);
}
The suppress-warning above is an annoying one as that cast must always be true but is a limitation of the final definition of getClass as returning Class<?>.
The map could be typed as:
Map<String, Command<?>> commands = new HashMap<>();
And each command subtype class would extend of the abstract Command class.
For example an anonymous inner class definition o a print string command to stderr:
final Command<String> printString = new Command<String>(String.class) {
public <U extends String> void execute(U arg) {
System.err.println(arg);
}
};
The standalone version:
public StdErrPrintCommand extends Command<String> {
public StdErrPrintCommand() { super(String.class); }
#Override
public <U extends String> void excecute(U arg) {
System.err.println(arg);
}
}
If you prefer you could extract an Command interface and rename the abstract class as AbstractCommand.
If you think about it logically, what is that highest common interface which all your command template have to satisfy?
Looking at your example of String, Integer, it seems like it can't be anything but Java Object. Try this,
Map<String, Command<? extends Object>> commands = new HashMap<>();
Edit:
Basically, you are adding template information while declaring but would want to completely erase it while using it. There are two options here:
a) You don't use generics because you are not able to use them to their potential. Deal with simple Object class instead and in your specific execute functions just test for the right types.
b) Create different maps for different types. This way you would be able to use templates to their potential.
I need to store one instance of objects for some classes.
I'd like to create a generic map like this:
<T> Map<Class<T>, T> objects;
But it's not possible and
Map<Class<?>, ?> objects;
Is not what I want because I would like to avoid casts:
MyClass c = (MyClass)objects.get(MyClass.class);
Also, I'd like Java to forbid me to do this:
objects.put(MyClass.class, new MyClass2());
One solution is to create a custom map so I would have a generic parameter <T>, but is there a solution without creating a new class?
The standard Map interface is not suitable for such scenario. However you can implement your own class which parameterizes get and put methods delegating them to the private Map field:
public class ClassObjectMap {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> clazz, T value) {
assert clazz.isInstance(value);
map.put(clazz, value);
}
public <T> T get(Class<T> clazz) {
return clazz.cast(map.get(clazz));
}
}
Adding other methods like remove() or size() is not very hard as well.
If you can, instead of using the map directly, put in these methods to access the map always. That way you put the casting into the method and you dont see it anywhere else.
private Map<Class<?>, Object> map = new HashMap<Class<?>, Object>();
private <T> T getMapValue(Class<T> key){
return (T) map.get(key);
}
private <T> void putMapValue(Class<T> key, T value){
map.put(key, value);
}
And also it will forbid you from doing
putMapValue(MyClass.class, new MyClass2());
Here is my code. The compiler refuses to compile it :
private transient List<? extends Map<String, Object>> donnees;
// ...
public <M extends Map<String, Object>> void addDonnee(M nouvelleDonnee) {
getDonnees().add(nouvelleDonnee);
}
public List<? extends Map<String, Object>> getDonnees() {
// ...
return donnees;
}
Why do I get this error ?
The method add(capture#4-of ? extends Map<String,Object>) in the type List<capture#4-of ? extends Map<String,Object>> is not applicable for the arguments (M)
EDIT
Here how I solve my problem :
private transient List<Map<String, Object>> donnees;
// ...
public void addDonnee(Map<String, Object> nouvelleDonnee) {
getDonnees().add(nouvelleDonnee);
}
public List<Map<String, Object>> getDonnees() {
// ...
return donnees;
}
And now the compiler is happy ! :)
Both ? and M are subclasses, and may not be castable into each other. Try declaring donnees:
private transient List<Map<String, Object>> donnees;
The getDonnees method returns a list of "something" that implements Map<String,Object>. It could be a list of TreeMaps, or of HashMaps for example; there's no way to know.
Since you don't know the exact component type of the list, you can't add anything to it. For example, you could be trying to add a HashMap to a list of TreeMap.
The two methods should use the same parameter somehow, for example one declared with the class:
class SomeClass<M extends Map<String, Object>> {
public void addDonnee(M nouvelleDonnee) {
getDonnees().add(nouvelleDonnee);
}
public List<M> getDonnees() {
// ...
return donnees;
}
}
You cannot insert anything into a List defined as List<? extends Foo>.
This is discussed in a decent level of detail in the JDK documentation (see the bottom of the page).
Java is complaining because it doesn't know that the types of donnees, addDonnee and getDonnees are the same. The one could be a List<HashMap<String, Object>>, while the others can be a List<TreeMap<String, Object>>, which are not compatible (you can't add a HashMap to a List<TreeMap> and vice versa).
You can add a generic parameter to the class instead:
public class SomeClass<T extends Map<String, Object>>
{
private transient List<T> donnees;
public void addDonnee(T nouvelleDonnee) {
getDonnees().add(nouvelleDonnee);
}
public List<T> getDonnees() {
return donnees;
}
}
What I would like to have is something like this:
public abstract Class Content {
private Map<Class<? extends Content>, List<? extends Content>> relations;
}
Content has a bunch of subclasses - A,B,C,D etc...
The most frequent use case is to get all A's:
public List<A> getA() {
return (List<A>)relations.get(A.class);
}
Kind of ok - apart from the ugly cast.
But the real problem is there's nothing stopping me from doing something stupid like:
relations.put(A.class, List<B> myListOfBs);
So a call to getA() above would result in a horrible cast exception. Is there any way I can write it so the compiler would warn me in the above example - and also remove the need for the ugly cast.
Thanks
You can create a wrapper around a Map and use a generic method to constrain your put method:
public class HeterogeneousContainer {
private final Map<Class<? extends Content>, List<? extends Content>> map;
public <T extends Content> void put(Class<T> type, List<T> values) {
map.put(type, values);
}
public <T extends Content> List<T> get(Class<T> type) {
//this warning can be safely suppressed after inspection
return (List<T>) map.get(type);
}
}
Now you know that as long as the users of the container aren't using it improperly (i.e. in raw form) then the key and value must correspond...you couldn't call put(B.class, listOfAs);.
Create a custom Map interface that fixes the type to the same type:
interface RelatedMap<T> extends Map<Class<T>, List<T>> {}
then
private RelatedMap<? extends Content> relations;