为什么我不能在预期使用List <X>的地方使用实现List <X>的类?

时间:2019-10-24 17:06:25

标签: java generics

首先是简单情况(A):

public class PsList implements List<Ps> { ... }

其他地方

private void doSomething(List<Ps> list) { ... }

// compiles
List<Ps> arrayList = new ArrayList<Ps>();
doSomething(arrayList);

// does not compile
PsList psList = new PsList();
doSomething(psList);

好的。我知道我可以通过添加将其更改为“工作”。扩展如下:

private void doSomething(? extends List<Ps> list) { ... }

// compiles
List<Ps> arrayList = new ArrayList<Ps>();
doSomething(arrayList);

// compiles
PsList psList = new PsList();
doSomething(psList);

我的问题是为什么我 需要这样做?对我来说完全是无稽之谈。我正在实现预期的确切接口。我可以传递除ArrayList以外的其他List类型的原因,为什么不可以?

生活总是比这复杂,所以我真正的编码问题是(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>());

...
}

因此,无论哪种情况,Java都在抱怨map在期待什么?将List扩展为值。

即使我将其更改为:

List<Ps> list = new ArrayList<>();
List<Ps> psList = new PsList();
map.put("string", custom ? psList : list);

当然不会编译:

? extends List<Ps> list = new ArrayList<>();
? extends List<Ps> psList = new PsList();
map.put("string", custom ? psList : list);

那我应该怎么做才能使类似的东西起作用?

编辑1: 好吧,复制最少:

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);
    }
}
  

第一个参数类型错误。找到:“ java.lang.String,com.foo.PsList>”,必需:“ java.util.Map>”

2 个答案:

答案 0 :(得分:1)

您似乎已经意识到问题的中途,您的问题并不是List<Ps>PsList可以互换。

问题是您无法添加到Map<String, ? extends List<Ps>>

让我们考虑一个更简单的示例:

void doSomething(Map<String, ? extends Number> map) {
    map.put(String, Integer.valueOf(0));    // Not allowed.
}

问题在于Map<String, ? extends Number>并不意味着“值可以是Number或Number的任何子类。”

每个通用类型的对象都有特定的非通配符类型。这意味着不存在类型为Map<String, ? extends Number>的Map。但是,可以存在以下

  • Map<String, Integer>(仅允许使用整数值)
  • Map<String, Double>(仅允许使用Double值)
  • Map<String, Number>(允许使用任何Number子类的值)

Map<String, ? extends Number>是指可能是以上任意一个(或者,当然,任何其他特定Number子类)的Map。编译器不知道Map的值是哪种特定类型,但是Map仍具有其值的特定类型,因此无论如何都不会使用?

因此,再次查看示例方法:

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);
}

实际上,您无法向类型为上限通配符的Map或Collection添加任何内容,因为无法知道这样做是否正确和安全。

在您的情况下,最简单的解决方案是删除通配符:

private void doSomething(Map<String, List<Ps>> map, boolean custom) {
    // ...
    map.put("stringValue", custom ? new PsList() : new ArrayList<Ps>());
}

如果您的地图确实具有不同的值类型,则需要告诉方法所使用的特定类型:

private <L extends List<Ps>> void doSomething(Map<String, L> map,
                                              Supplier<L> listCreator) {
    // ...

    map.put("stringValue", listCreator.get());
}

然后您可以调用如下方法:

if (custom) {
    doSomething(psMap, PsList::new);
} else {
    doSomething(listMap, ArrayList::new);
}

答案 1 :(得分:0)

您绝对可以做到。这是一个示例:

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!");
    }
}

请参见演示here