不能将多级泛型类型的返回值分配给扩展类型



我有这个方便的方法(我已经使用了很多年没有问题)。它只是将List转换为Map<SomeKey, List>,按键属性对它们进行分组。

为了避免不必要的强制转换,我将 key 属性作为 String(引用方法名称)传递,并且还指定了该属性的类型。

@SuppressWarnings({"unchecked"})
@Nullable
public static <K, E> Map<K, List<E>> getMultiMapFromList(Collection<E> objectList, String keyAttribute, Class<K> contentClass)
{
// creates a map from a list of objects using reflection
...
}

上述方法在许多应用中已经完美运行多年。但是今天下面的案例提出了一个问题:

List<? extends MyBean> fullBeanList = getFullBeanList();
Map<MyKey, List<? extends MyBean>> multiMap;
// the following line doesn't compile.
multiMap = Utils.getMultiMapFromList(fullBeanList, "key", MyKey.class); 

在开发过程中,我的 IntelliJ IDE 没有任何警告。 但是在编译过程中会出现这种情况:

Error:(...,...) java: incompatible types: java.util.Map<mypackage.MyKey, java.util.List<capture #2 of ? extends mypackage.MyBean>> cannot be converted to java.util.Map<mypackage.MyKey, java.util.List<? extends mypackage.MyBean>>

不过我无法弄清楚这一点。 我猜这与? extends有关.但我没有看到任何违规行为。我也想知道为什么它只出现在编译时?我认为由于类型擦除,一旦编译就无关紧要。

我相信我可以通过添加一些演员来强制这样做,但我想了解这里发生了什么。

编辑:

为方便起见:

Test.java

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test
{
public static void main(String[] args)
{
List<? extends MyBean> input = new ArrayList<>();
Map<MyKey, List<? extends MyBean>> output;
output = test(input, MyKey.class); // doesn't compile
}
public static <K, E> Map<K, List<E>> test(Collection<E> a, Class<K> b)
{
return null;
}
private static class MyKey{}
private static class MyBean{}
}

编辑 2

在疯狂中继续更进一步:

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test
{
public static void main(String[] args)
{
List<? extends Number> input = new ArrayList<>();
// compiles fine
List<? extends Number> output1 = test1(input);
// doesn't compile
Map<String, List<? extends Number>> output2 = test2(input);
}
public static <E> List<E> test1(Collection<E> a) { return null;}
public static <E, K> Map<K, List<E>> test2(Collection<E> a) { return null;}
}

我不知道该怎么想。只要我使用 1 级泛型,它就可以正常工作。但是当我使用 2 级泛型(即泛型中的泛型,例如Map<K,List<V>>),然后它失败了。

这将解决您的问题。

您必须更改方法测试,如下所示。

public static <K, E> Map<K, List<? extends E>> test(
Collection<? extends E> a, Class<K> b) {
return null;
}

问题是你没有告诉传递给方法的,并且在Java中不能保证它们是相同的。使此方法泛型,以便您有一个泛型类型参数可以引用并在整个方法中保持一致。

下面是代码。

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
public class Test {
public static void main(String[] args) {
List<? extends MyBean> input = new ArrayList<>();
Map<MyKey, List<? extends MyBean>> output;
output = test(input, MyKey.class); // doesn't compile
}
public static <K, E> Map<K, List<? extends E>> test(
Collection<? extends E> a, Class<K> b) {
return null;
}
private static class MyKey {
}
private static class MyBean {
}

}

读完迪利普·辛格·卡萨纳的回答后,我仍然不明白。 但后来我看到了这篇文章,它向我解释了它。

我不打算复制整个事情,而只是照搬启发我的部分。

Collection< Pair<String,Long> >        c1 = new ArrayList<Pair<String,Long>>(); 
Collection< Pair<String,Long> >        c2 = c1;   // fine   
Collection< Pair<String,?> >           c3 = c1;   // error   
Collection< ? extends Pair<String,?> > c4 = c1;   // fine    

当然,我们可以将Collection<Pair<String,Long>>分配给Collection<Pair<String,Long>>.这里没有什么令人惊讶的。

但是我们不能将Collection<Pair<String,Long>>分配给Collection<Pair<String,?>>.参数化类型Collection<Pair<String,Long>>是成对的同质集合 一个String和一个Long; 参数化类型Collection<Pair<String,?>>String和-未知类型的东西-。 异质的 例如,Collection<Pair<String,?>>可以包含Pair<String,Date>,这显然不属于Collection<Pair<String,Long>>.

因此,分配不是 允许。

将其应用于问题。

如果我们为方法提供类型List<? extends Number>的输入public static <E> Map<String, List<E>> test(Collection<E> objectList),它实际上会返回一个Map<String, List<? extends Number>

但是此返回值不能分配给Map<String, List<? extends Number>完全相同类型的字段。

这样做的原因是返回的地图可能是Map<String, List<Integer>。如果我把它分配给一个Map<String, List<? extends Number>,那么我以后可以在里面放一个List<Double>。这显然会打破它,但没有什么能阻止我这样做。

考虑一下:

// behind the scenes there's a map containing Integers.
private static Map<String, List<Integer>> myIntegerMap = new HashMap<>;
// both collections return the same thing, but one of them hides the exact type.
public static Map<String, List<? extends Number> getMap() { return myIntegerMap; } 
public static Map<String, List<Integer>> getIntegerMap() { return myIntegerMap; } 
private static void test()
{
// fortunately the following line does not compile
Map<String, List<? extends Number> map = getMap();
// because nothing would stop us from adding other types.
List<Double> myDoubleList = new ArrayList<>();
myDoubleList.add(Double.valueOf(666));
map.put("key", myDoubleList);
// if it would compile, then this list would contain a list with doubles.
Map<String, List<Integer>> brokenMap = getIntegerMap();
}

正如Dilip Singh Kasana指出的那样,如果该方法返回Map<String, List<? extends Number>>,它确实有效。添加扩展会更改一切。

// still the same map.
private static Map<String, List<Integer>> myIntegerMap = new HashMap<>;
// the return value is an extended type now.
public static Map<String, ? extends List<? extends Number> getMap() { return myIntegerMap; } 
public static Map<String, List<Integer>> getIntegerMap() { return myIntegerMap; } 
private static void test()
{
// the following compiles now.
Map<String, ? extends List<? extends Number> map = getMap();
// if we try to add something now ...
List<Double> myDoubleList = new ArrayList<>();
myDoubleList.add(Double.valueOf(666));
// the following won't compile.
map.put("key", myDoubleList);
}

因此,这次分配它有效,但结果类型是"只读"映射。 (PS:为了完整。显而易见:您无法向具有? extends X类型的集合或地图添加任何内容。这些集合是"只读的",这是完全有道理的。

因此,编译时错误可以防止映射可能被破坏的情况。