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.
Related
class SuperCl {}
class A extends SuperCl {}
class B extends SuperCl {}
static void method(Map<Integer, List<? extends SuperCl>> map) {}
public static void main(String[] args) {
method(new HashMap<Integer, List<A>>()); //ERROR
}
The compile time error is that the types are incompatible:
Map<Integer, List<A>> cannot be converted to Map<Integer, List<? extends SuperCl>>
How can I fix it and where does the error come from?
I assume it comes from the "method" being static.
EDIT: I changed the map implementation to HashMap (copy error) - this should not change anything
Change your method to
static <T extends SuperCl> void method(Map<Integer, List<T>> map) {
}
Edit: The error mainly comes from the use of a nested generic. If you would have something like
static void method (List<? extends SuperC1> list) {
}
public static void main (String[] args) {
List<A> list = new ArrayList<>();
method(list);
}
you would not get a compile time error because A satisfies ? extends SuperCl.
A HashMap<Integer, List<A>> isn't a Map<Integer, List<? extends SuperCl>>, because you can add any type of List<? extends SuperCl> to the latter.
For example:
Map<Integer, List<A>> original = new HashMap<Integer, List<A>>();
// Raw types to intentionally break the type system.
Map<Integer, List<? extends SuperCl>> map = (Map) original;
List<B> listOfB = new ArrayList<>();
listOfB.add(new B());
map.put(0, listOfB);
List<A> listOfA = original.values().iterator().next();
A item = listOfA.get(0); // ClassCastException.
If you could do that, you'd have been able to add a value that's not a List<A> to it. Hence it's not allowed.
You could change the type in the method signature to this, for example:
Map<Integer, ? extends List<? extends SuperCl>>
and that would be fine, because you can't put any value into that (other than literal null).
This works:
class SuperCl {}
class A extends SuperCl {}
class B extends SuperCl {}
static <T extends SuperCl> void method(Map<Integer, List<T>> map) {}
public static void main(String[] args) {
method(new HashMap<Integer, List<A>>()); //NO MORE ERROR
}
I simply moved the generics:
<T extends SuperCl>
to the static method declaration. This makes it verifiable at compile time. On the other hand, having that generic at the method argument is not compile time verifiable.
Generics are invariant. For parameterized types to be compatible, their type arguments must match exactly, unless one of them is a wildcard at the top level. Map<Integer, List<A>> is not a subtype of Map<Integer, List<? extends SuperCl>> because List<A> is not identical to List<? extends SuperCl>. Yes, List<A> is a subtype of List<? extends SuperCl>, but they are not identical, which is what is needed.
As you may know, List<Dog> is not a subtype of List<Animal>, even though Dog is a subtype of Animal. It's the same situation here. A subtype relationship of the type arguments does not lead to a subtype relationship of the parameterized types (that would be called "covariant"; Java array types are covariant, but generics are not).
One solution to this is to use a wildcard at the top level. For example, List<Dog> is a subtype of List<? extends Animal>. Similarly in your case, Map<Integer, List<A>> is a subtype of Map<Integer, ? extends List<? extends SuperCl>>. So you can declare your method as:
static void method(Map<Integer, ? extends List<? extends SuperCl>> map) {}
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.
What's wrong with this method definition?
public static List<T extends MyObject> T find() {
}
Compiler says:
Syntax error, insert ";" to complete MethodDeclaration
You have two return types there.
If you wanted to introduce a generic type T that would be
public static <T extends MyObject> List<T> find() {}
The proper method declaration would be:
public static <T extends MyObject> List<T> find() { ... }
When creating (static) generic methods, the generic parameter(s) has to be defined before the return-type, because they may be used in the return-type.
i want to add element extending Type to ArrayList.
ArrayList<? extends Type>
i defined a method as below
public <T extends Type> void addItem(T itemToAdd, Class<?> classType) {
ArrayList<? extends Type> allItems = getAllItems(classType);
allItems.add(itemToAdd);
}
T itemToAdd is error. because i can't add someother type. i thought but i don't know to mention it and its error!
How to add the item via a method call?
Think about your definition: ArrayList<? extends Type>. It means list of elements each of them extends Type. It does not mean that each of them is of type T as itemToAdd. To make this code to compile you have to ensure this fact, i.e. use T in all relevant places:
public <T extends Type> void addItem(T itemToAdd, Class<T> classType) {
List<T> allItems = getAllItems(classType);
allItems.add(itemToAdd);
}
It probably means that you should change definition of getAllItem() to
public <T extends Type> List<T> getAllItems(Class<T> classType)
BTW please pay attention that I changed your ArrayList to List. Although it is out of the topic but avoid using concrete classes at the left part of assignment operator, as a return value of method and as a method parameter.
If you define for your List all object must have the same type
If you want to insert multiple class defined simple List and you can insert objects extends Type
Example :
List<Type> list = gellAllItems(class);
list.add(T);
I'm not sure about your declaration of the return value of getAllItems, but you can't put itemToAdd with type T extends Type into ArrayList<? extends Type>. The type of element may be unmatch (different type although both are the derived class of Type), so the ArrayList should be declared as
ArrayList<? super Type> allItems;
which can guarantee itmeToAdd can be put into the ArrayList, or just:
ArrayList<Type> allItems;
I am new to generics.
Having a Map like
private static Map<String, Object> map;
and a method like
public <T> T getObject(final Class<T> myClass) {
return (T)map.get(myClass);
}
How to change the map declaration in order to not have to do the cast when returning from the method ?
You would need to make a generic class, not a generic method:
public class MyClass<T> {
private Map<String, T> map;
public T getObject(final String key) {
return map.get(key);
}
}
Also, I changed the parameter from a Class to a String. It doesn't make sense to pass a Class if map.get() expects a String.
Edit: I didn't notice that map was static. If you can change it to non-static without it breaking other parts of your program, this could work. If you can't, then you cannot avoid a cast.
You can't avoid the cast operation as the get() method returns Object
see here for more info
If you're willing to drop the static modifier of your map, than you can do like so:
public class MyClass<T> {
private Map<String, T> map;
public T getObject(final Class<T> myClass) {
return map.get(myClass);
}
}
Otherwise:
It is a compile-time error to refer to a type parameter of a generic
class C anywhere in:
the declaration of a static member of C
(excerpt from the JLS), which prevents you from using parameterized class to achieve the above.
What you were trying to do, however, is to refer a parameterized method's type-parameter from another member (which happen to also be static), which also unreachable.