我有这个方便的方法(我已经使用了很多年没有问题)。它只是将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
类型的集合或地图添加任何内容。这些集合是"只读的",这是完全有道理的。
因此,编译时错误可以防止映射可能被破坏的情况。