In a library project, I have :
public interface InterfaceA {
}
public interface InterfaceB {
}
public void myMethod(Map<? extends InterfaceA, List<? extends InterfaceB>> map) {
//do something
}
Then I have another project (having this library as a dependency) that contains two object implementing these interfaces :
public class ObjectA implements InterfaceA {
}
public class ObjectB implements InterfaceB {
}
When I try to call the library method myMethod like this :
HashMap<ObjectA, List<ObjectB>> hashMap = new HashMap<>();
//populate hashmap
myMethod(hashMap);
I get a compilation warning saying there is an argument mismatch.
What am I missing here ? Does it have something to do with the map ?
EDIT :
The exact error (it's not a warning actually) is :
incompatible types: HashMap<ObjectA,List<ObjectB>> cannot be converted to Map<? extends InterfaceA,List<? extends InterfaceB>>
Generics are invariant.
If your method declares:
Map<? extends InterfaceA, List<? extends InterfaceB>>
Then the second type parameter has to be exactly List<? extends InterfaceB>.
You can fix it by using:
Map<? extends InterfaceA, ? extends List<? extends InterfaceB>>
Instead.
You either modify your Hashmap creation for this:
Map<? extends InterfaceA, List<? extends InterfaceB>> hashMap = new HashMap<>();
or modify your method definition for this:
public <A extends InterfaceA, B extends InterfaceB> void myMethod(Map<A, List<B>> map) {
//do something
}
Declare your map as
HashMap<ObjectA, List<? extends InterfaceB>> hashMap = new HashMap<ObjectA, List<? extends InterfaceB>>();
Related
Consider the following code:
public interface A {};
public class AImpl implements A {};
public interface B {};
public class BImpl implements B {};
public interface Service{
Map<? extends A, List<? extends B>> get();
}
Why does the following implementation of Service not compile?
public class ServiceImpl implements Service {
public Map<AImpl, List<BImpl>> get() {
return null;
}
}
Compiler error:
The return type is incompatible with Service.get()
But the following code compile:
public interface Service{
List<? extents B> get();
}
public class ServiceImpl implements Service{
public List<BImpl> get(){
return null;
}
}
Because <? extends BaseType> means "some unspecified sub-type BaseType", and class Sub extends BaseType, while sub-type of BaseType, is not it. Read Java Generics FAQ, in particular starting from Wildcard Capture section, for more details.
You should generify your code properly:
public interface Service<K extends A, V extends B> {
Map<K, List<V>> get();
}
public class ServiceImpl implements Service<AImpl, BImpl> {
#Override
public Map<AImpl, List<BImpl>> get() {
return null;
}
}
The return type of the Service#get() method is specified as
Map<? extends A, List<? extends B>> get();
And you are trying to return a
Map<AImpl, List<BImpl>>
You can use covariant return types. And it seems like you thought this would be the case here. But the problem is that this covariance does not apply to the generic type parameters. Although List<BImpl> is a subtype of List<? extends B>, this does not mean that Map<AImpl, List<BImpl>> is a subtype of Map<? extends A, List<? extends B>>.
A structurally similar but simpler case is that List<Integer> is not a subtype of List<Number>.
You could change the return type in the Service interface to
Map<? extends A, ? extends List<? extends B>> get();
to make it work.
ServiceImpl cannot be an interface because your Implementation cannot be in an interface; Change it to a base base class and try it.
What do you write?
Map<? extents A, List<? extents B> get();
java doesn't know anything about extents
At least use extends
I'm struggling to capture a wildcard when it is "nested in another wildcard".
Is it possible?
The code:
public class ConvolutedGenerics {
// listClass is a class implementing a List of some Serializable type
public void doSomethingWithListOfSerializables(
Class<? extends List<? extends Serializable>> listClass) {
// Capture '? extends Serializable' as 'T extends Serializable'
// The line does not compile with javac 7
captureTheWildcard(listClass); // <------ zonk here
}
// listClass is a class implementing a List of some Serializable type
private <T extends Serializable>
void captureTheWildcard(
Class<? extends List</* ? extends */T>> listClass) {
// Do something
}
}
compiled with javac 7 produces:
ConvolutedGenerics.java:18: error: method captureTheWildcard in class ConvolutedGenerics cannot be applied to given types;
captureTheWildcard(listClass);
^
required: Class<? extends List<T>>
found: Class<CAP#1>
reason: no instance(s) of type variable(s) T exist so that argument type Class<CAP#1> conforms to formal parameter type Class<? extends List<T>>
where T is a type-variable:
T extends Serializable declared in method <T>captureTheWildcard(Class<? extends List<T>>)
where CAP#1 is a fresh type-variable:
CAP#1 extends List<? extends Serializable> from capture of ? extends List<? extends Serializable>
1 error
Besides many more simpler cases I've found
Incompatible generic wildcard captures
Using Java wildcards
but I could not infer an answer for my problem from those.
It is not possible, as you probably already know.
Let me illustrate with a counter-example:
List<Integer> foo = Arrays.asList(1,2,3);
List<String> bar = Arrays.asList("hi","mom");
List<List<? extends Serializable>> baz = Arrays.asList(foo, bar);
doSomethingWithListOfSerializables(baz);
public void doSomethingWithListOfSerializables(
List<? extends List<? extends Serializable>> listList) {
captureTheWildcard(listList);
}
private <T extends Serializable>
void captureTheWildcard(
List<? extends List<T>> listList) {
// Do something
}
What should T be?
The problem with your code is that you're trying to call captureTheWildcard passing different typed parameter then defined here:
private <T extends Serializable> void captureTheWildcard(Class<? extends List<T>> listClass)
You should explicitly say in your method definition that parameter passed is actually of type of Class<? extends List<? extends Serializable>>or modify the type of listClass like this:
import java.util.List;
import java.io.Serializable;
public class ConvolutedGenerics {
// listClass is a class implementing a List of some Serializable type
public <T extends Serializable> void doSomethingWithListOfSerializables(
Class<? extends List<T>> listClass) {
// Capture '? extends Serializable' as 'T extends Serializable'
// The line does not compile with javac 7
captureTheWildcard(listClass); // <------ zonk here
}
// listClass is a class implementing a List of some Serializable type
private <T extends Serializable> void captureTheWildcard(Class<? extends List<T>> listClass) {
// Do something
}
}
Compiles well with javac 1.7.0_25
I have a variable that has several upper bounded wildcards, like so:
private Map< Class< ? extends MyClass1 >, List< ? extends MyClass2< ? extends MyClass3, ? extends MyClass1> > > _variableName;
Is there any way to enforce that the bound value ? extends MyClass1 be the same for the Type of the Class class and the 2nd param of the MyClass2 class?
Yes:
public class Foo<T extends MyClass1> {
private Map< Class<T>, List<? extends MyClass2<? extends MyClass3, T>>> bar;
...
}
You could also do this with a generic method. It depends on where you want to make the guarantee (and by extension, where the Map is instantiated), when defining the class or when calling a method that instantiates this Map.
UPDATE:
In response to the (correct) observation that this map will only contain one element, perhaps the questioner had this in mind instead:
public class Foo {
private Map<Class<? extends MyClass1>, List<? extends MyClass2<? extends MyClass3, ? extends MyClass1>>> bar;
public <T extends MyClass1> void add(Class<T> myClass, List<? extends MyClass2<? extends MyClass3, T>>> myList) {
bar.put(myClass, myList);
}
...
}
Assuming this is the only way to add elements to your private map, then that ensures the types always match.
Here is my problem:
my function in class A:
public void setData(Map<String,? extends ArrayList<? extends SomeInterface>>){...}
my call:
Map<String, ArrayList<ImplementsSomeInterface>> a=...;
instanceOfA.setData(a); //does not compile
instanceOfA.setData((Map<String,? extends ArrayList<? extends SomeInterface>>) a); // works thanks to the Casting.
I don't think this is clean. Is there a way to avoid the casting without droping the wildcard use in the function?
First your setData method should read:
public void setData(Map<String,? extends List<? extends SomeInterface>>)
Your map declaration should read:
Map<String, List<ImplementsSomeInterface>> a = ...;
Which is pretty much what you've got following your edits to the original question. The change I've made from ArrayList to List doesn't effect the behaviour of the code.
Following comments:
public static void main()
{
Map<String, List<Double>> map = new HashMap<String, List<Double>>();
map.put("prices", new ArrayList<Double>(Arrays.asList(1.1, 2.2, 3.3)));
setData(map);
}
public static void setData(Map<String,? extends List<? extends Serializable>> map)
{
}
The problem is that setData takes as an argument a
Map<String,? extends ArrayList<? extends SomeClass>>Map>
whereas in your first call(the one that doesn't compile) you are trying to pass it a Map<String,? extends SomeClass>
Java cannot automatically cast a subclass of
SomeClass
to a subclass of
ArrayList<? extends SomeClass>
because it is not neccesarily an instance of ArrayList.
I am trying to determine if it is possible to use Generics in the following situation. It can best be described by my code. (This code is just an example; I took out most of the code not relevant to the problem I'm having.)
public class FooBar {
public <T extends MyModel> Map<Class<T>, List<T>> convertToModelList(
Map<String, Class<T>> infoMap) {
// do stuff...
}
}
public class MyClient {
public void doSomething() {
Map<String, Class<? extends MyModel>> oldMap = new HashMap<String, Class<? extends MyModel>>();
oldMap.put ("car", Car.class);
oldMap.put("truck", Truck.class);
FooBar f = new FooBar();
Map<Class<? extends MyModel>, List<? extends MyModel>> newMap = f
.convertToModelList(oldMap);
}
}
public class Car extends MyModel {
}
public class Truck extends MyModel {
}
public class MyModel {
}
The compiler is saying that I can't call convertToModelList (in MyClient) because a Map<String, Class<? extends MyModel>> is not equivalent to Map<String, Class<T>>. I somewhat understand why this is occurring, but is there a way around this?
EDIT:
To be more specific, the problem I'd like to solve is using Generics in the convertToModelList() method above. If I can't use generics here, then whatever I return from that method has to be cast on the client. For example, if I change FooBar to this:
public class FooBar {
public Map<Class<? extends MyModel>, List<? extends MyModel>> convertToModelList(
Map<String, Class<? extends MyModel>> infoMap) {
// do stuff...
}
}
If I pass in a
Map<String, Class<Truck>>
to convertToModelList, it will return a
Map<Class<Truck>, List<Truck>>
, however the client won't know it's a Truck - all it will know is that it's of type MyModel - using generics lets me avoid the cast to Truck in the MyClient code:
public class MyClient {
public void doSomething() {
Map<String, Class<? extends MyModel>> oldMap = new HashMap<String, Class<? extends MyModel>>();
oldMap.put ("car", Car.class);
oldMap.put("truck", Truck.class);
FooBar f = new FooBar();
Map<Class<? extends MyModel>, List<? extends MyModel>> newMap = f
.convertToModelList(oldMap);
// I'm trying to avoid this cast
List<Truck> trucks = (List<Truck>)newMap.get(Truck.class);
}
}
Although the common superclass of Car and Truck is MyModel, the common superclass of List<Car> and List<Truck> is Object. The map should be of type Map<Class<? extends MyModel>, Object>>. You better wrap the map with some invariant checks and casting instead of getting info directly from the map.
This may be helpful too: Heterogeneous container to store genericly typed objects in Java
Since it extends MyModel, you are saying you only want that type.
It is valid to make it just MyModel and not ? extends MyModel.
Class<MyModel>
Then, any objects that extends this (which will be all the Car and Truck, will have to call the super method of MyModel. This will mean they are all of this type.