Return 无法将多级泛型类型的值分配给扩展类型
Return value of multi-level generic type cannot be assigned to an extended type
我有这个方便的方法(我已经使用多年没有问题)。它只是将 List
转换为 Map<SomeKey, List>
,按关键属性对它们进行分组。
为了避免不必要的转换,我将 key 属性作为字符串(指的是方法名称)传递,并且我还指定了该属性的类型。
@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;
}
问题是您没有告诉传递给方法的 ?s,并且在 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 {
}
}
看完Dilip Singh Kasana的回答我还是没明白。
但后来我看到了 this 文章,它向我解释了这一点。
我不会复制全部内容,只复制启发我的部分。
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
Of course, we can assign a Collection<Pair<String,Long>>
to a
Collection<Pair<String,Long>>
. There is nothing surprising here.
But we can not assign a Collection<Pair<String,Long>>
to a
Collection<Pair<String,?>>
. The parameterized type
Collection<Pair<String,Long>>
is a homogenous collection of pairs of
a String
and a Long
; the parameterized type
Collection<Pair<String,?>>
is a heterogenous collection of pairs of a
String
and -something of unknown type-. The heterogenous
Collection<Pair<String,?>>
could for instance contain a
Pair<String,Date>
and that clearly does not belong into a
Collection<Pair<String,Long>>
.
For this reason the assignment is not
permitted.
将其应用于问题。
如果我们向方法提供 List<? extends Number>
类型的输入
public static <E> Map<String, List<E>> test(Collection<E> objectList)
那么它实际上会return一个Map<String, List<? extends Number>
。
但是这个 return 值不能分配给完全相同类型的字段 Map<String, List<? extends Number>
。
这样做的原因是 returned 地图可能是 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 指出的那样,如果该方法 return a 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);
}
因此,这次赋值有效,但生成的类型是 "read-only" 映射。(PS:为了完整起见。说明一个显而易见的事实:您不能向具有 ? extends X
类型的集合或地图添加任何内容。这些集合是 "read-only",这很有意义。)
所以编译时错误防止了这种地图可能被破坏的情况。
我有这个方便的方法(我已经使用多年没有问题)。它只是将 List
转换为 Map<SomeKey, List>
,按关键属性对它们进行分组。
为了避免不必要的转换,我将 key 属性作为字符串(指的是方法名称)传递,并且我还指定了该属性的类型。
@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;
}
问题是您没有告诉传递给方法的 ?s,并且在 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 {
}
}
看完Dilip Singh Kasana的回答我还是没明白。 但后来我看到了 this 文章,它向我解释了这一点。
我不会复制全部内容,只复制启发我的部分。
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
Of course, we can assign a
Collection<Pair<String,Long>>
to aCollection<Pair<String,Long>>
. There is nothing surprising here.But we can not assign a
Collection<Pair<String,Long>>
to aCollection<Pair<String,?>>
. The parameterized typeCollection<Pair<String,Long>>
is a homogenous collection of pairs of aString
and aLong
; the parameterized typeCollection<Pair<String,?>>
is a heterogenous collection of pairs of aString
and -something of unknown type-. The heterogenousCollection<Pair<String,?>>
could for instance contain aPair<String,Date>
and that clearly does not belong into aCollection<Pair<String,Long>>
.For this reason the assignment is not permitted.
将其应用于问题。
如果我们向方法提供 List<? extends Number>
类型的输入
public static <E> Map<String, List<E>> test(Collection<E> objectList)
那么它实际上会return一个Map<String, List<? extends Number>
。
但是这个 return 值不能分配给完全相同类型的字段 Map<String, List<? extends Number>
。
这样做的原因是 returned 地图可能是 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 指出的那样,如果该方法 return a 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);
}
因此,这次赋值有效,但生成的类型是 "read-only" 映射。(PS:为了完整起见。说明一个显而易见的事实:您不能向具有 ? extends X
类型的集合或地图添加任何内容。这些集合是 "read-only",这很有意义。)
所以编译时错误防止了这种地图可能被破坏的情况。