Reputation: 367
First the simple case (A):
public class PsList implements List<Ps> { ... }
elsewhere
private void doSomething(List<Ps> list) { ... }
// compiles
List<Ps> arrayList = new ArrayList<Ps>();
doSomething(arrayList);
// does not compile
PsList psList = new PsList();
doSomething(psList);
Ok. I know that I can change this to "work" by adding ? extends as such:
private void doSomething(? extends List<Ps> list) { ... }
// compiles
List<Ps> arrayList = new ArrayList<Ps>();
doSomething(arrayList);
// compiles
PsList psList = new PsList();
doSomething(psList);
My question is why do I need to do that? It makes no sense to me. I am implementing the exact interface that is is expecting. I can pass other List types other than ArrayList why not mine?
Life is always more complicated than this, so my real coding issue is (B):
public class PsList implements List<Ps> { ... }
private void doSomething(Map<String, ? extends List<Ps>> map, Boolean custom) {
...
// need to create a new List<Ps> of either an ArrayList<Ps> or PsList
map.put("stringValue", custom ? new PsList() : new ArrayList<Ps>());
...
}
So, in either case Java is complaining that map is expecting ? extends List as the value.
even if I change this to be:
List<Ps> list = new ArrayList<>();
List<Ps> psList = new PsList();
map.put("string", custom ? psList : list);
and of course this doesn't compile:
? extends List<Ps> list = new ArrayList<>();
? extends List<Ps> psList = new PsList();
map.put("string", custom ? psList : list);
So what am I supposed to do to get something like this to work?
Edit 1: Ok, a minimal reproduction:
Ps.java
package com.foo;
public class Ps
{
}
PsList.java
package com.foo;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
public class PsList implements List<Ps>
{
@Override
public int size()
{
return 0;
}
@Override
public boolean isEmpty()
{
return false;
}
@Override
public boolean contains(Object o)
{
return false;
}
@Override
public Iterator<Ps> iterator()
{
return null;
}
@Override
public Object[] toArray()
{
return new Object[0];
}
@Override
public <T> T[] toArray(T[] a)
{
return null;
}
@Override
public boolean add(Ps ps)
{
return false;
}
@Override
public boolean remove(Object o)
{
return false;
}
@Override
public boolean containsAll(Collection<?> c)
{
return false;
}
@Override
public boolean addAll(Collection<? extends Ps> c)
{
return false;
}
@Override
public boolean addAll(int index, Collection<? extends Ps> c)
{
return false;
}
@Override
public boolean removeAll(Collection<?> c)
{
return false;
}
@Override
public boolean retainAll(Collection<?> c)
{
return false;
}
@Override
public void clear()
{
}
@Override
public Ps get(int index)
{
return null;
}
@Override
public Ps set(int index, Ps element)
{
return null;
}
@Override
public void add(int index, Ps element)
{
}
@Override
public Ps remove(int index)
{
return null;
}
@Override
public int indexOf(Object o)
{
return 0;
}
@Override
public int lastIndexOf(Object o)
{
return 0;
}
@Override
public ListIterator<Ps> listIterator()
{
return null;
}
@Override
public ListIterator<Ps> listIterator(int index)
{
return null;
}
@Override
public List<Ps> subList(int fromIndex, int toIndex)
{
return null;
}
}
OtherService.java
package com.foo;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class OtherService
{
private void doSomething(Map<String, List<Ps>> map, Boolean custom)
{
if (custom)
{
map.put("someValue", new PsList());
} else {
map.put("someValue", new ArrayList<>());
}
}
private void callDoSomethingNotCustom()
{
Map<String, List<Ps>> map = new HashMap<>();
doSomething(map, false);
}
private void callDoSomethingCustom()
{
Map<String, PsList> map = new HashMap<String, PsList>();
// map is not the right format
doSomething(map, true);
}
}
Wrong 1st argument type. Found: 'java.lang.String,com.foo.PsList>', required: 'java.util.Map>'
Upvotes: 0
Views: 90
Reputation: 44308
As you seemed to realize halfway through your question, your problem is not about List<Ps>
being interchangeable with PsList
.
The problem is that you can’t add to a Map<String, ? extends List<Ps>>
.
Let’s consider a simpler example:
void doSomething(Map<String, ? extends Number> map) {
map.put(String, Integer.valueOf(0)); // Not allowed.
}
The problem is that Map<String, ? extends Number>
does not mean “values can be Number or any subclass of Number.”
Every generically typed object has a specific, non-wildcard type. Meaning, there does not exist a Map whose type is Map<String, ? extends Number>
. However, the following can exist:
Map<String, Integer>
(allows Integer values only)Map<String, Double>
(allows Double values only)Map<String, Number>
(allows values of any Number subclass)Map<String, ? extends Number>
refers to a Map that might be any one of the above (or, of course, any other specific Number subclass). The compiler doesn’t know which specific type the Map’s values are, but the Map still has a specific type for its values which does not make use of ?
in any way.
So, looking at the example method again:
void doSomething(Map<String, ? extends Number> map) {
// Not allowed. The caller might have passed a Map<String, Double>.
map.put(String, Integer.valueOf(0));
// Not allowed. The caller might have passed a Map<String, Integer>.
map.put(String, Double.valueOf(0));
// Not allowed. The caller might have passed a Map<String, Integer>
// or Map<String, Double>. This method has no way of knowing what the
// actual restriction is.
Number someNumber = generateNewNumber();
map.put(String, someNumber);
}
Indeed, you cannot add anything to a Map or Collection whose type is an upper bound wildcard, because there is no way to know whether it’s correct and safe to do so.
In your case, the simplest solution is to remove the wildcard:
private void doSomething(Map<String, List<Ps>> map, boolean custom) {
// ...
map.put("stringValue", custom ? new PsList() : new ArrayList<Ps>());
}
If you really have Maps with different value types, you would need to tell the method the specific type being used:
private <L extends List<Ps>> void doSomething(Map<String, L> map,
Supplier<L> listCreator) {
// ...
map.put("stringValue", listCreator.get());
}
And then you can call the method like this:
if (custom) {
doSomething(psMap, PsList::new);
} else {
doSomething(listMap, ArrayList::new);
}
Upvotes: 1
Reputation: 20249
You can absolutely do that. Here is an example:
public class MyClass {
static interface FooList<T> {}
static class Ps {}
static void doSomething(FooList<Ps> list) { }
static class PsList implements FooList<Ps> { }
public static void main(String[] args)
{
FooList psList = new PsList();
doSomething(psList);
System.out.println("HelloWorld!");
}
}
See demo here.
Upvotes: 0