Java嵌套泛型类型 [英] Java nested generic type
问题描述
如何使用泛型类型 Map<?, ? extends List<>>
,而不是简单的 Map <?, List<>>
为以下 test()
方法?
public static void main(String [] args){
Map< Integer,List< String>> mappy =
new HashMap< Integer,List< String>>();
test(mappy);
public static void test(Map<?,?extends List<>> m){}
//不会编译
// public static void test(Map<?,List<>> m){}
$ b $注意下面的例子,以及三种方法都有相同的擦除类型。
public static< e基void test(Map <?,List< E>> m){}
List< List<>>
和 List<? extends List<>> ;
具有不同的类型参数。 实际上,一个是另一个的子类型,但首先让我们更多地了解它们的含义
了解语义差异
一般而言,通配符?
代表一些缺失的信息。这意味着这里有一个类型参数,但我们不知道它是什么。因为我们不知道它是什么,所以我们会限制如何使用任何引用特定类型参数的东西。
现在,让我们简化例如通过使用 List
而不是 Map
。
-
A
List< List<>>
拥有任何种类的具有任何类型参数的List 。即:
List< List<>> theAnyList = new ArrayList< List<>>();
//我们可以这样做
theAnyList.add(new ArrayList< String>());
theAnyList.add(new LinkedList< Integer>());
列表<?> typeInfoLost = theAnyList.get(0);
//但我们无法执行此
typeInfoLost.add(new Integer(1));
我们可以将
List
放入theAnyList
,但是通过这样做,我们已经失去了元素的知识。 -
当我们使用
? extends
时,List
包含List的某个特定子类型,不知道它是什么了。也就是说:
列表< ;?扩展List< Float>> theNotSureList =
new ArrayList< ArrayList< Float>>();
//我们仍然可以使用它的元素
//因为我们知道他们存储了浮点数
List< Float> aFloatList = theNotSureList.get(0);
aFloatList.add(new Float(1.0f));
//但我们无法执行此操作
theNotSureList.add(new LinkedList< Float>());
将任何内容添加到
theNotSureList $ c $因为我们不知道它的元素的实际类型。 ( 最初是
List< LinkedList< Float>>
?或aList< Vector< Float>>
&NBSP;延伸&NBSP;列表与LT;?>> 。我们不知道它有什么类型的
List
它已经存在了,我们不知道那些元素类型 列出
s。也就是说: 列表< ;?扩展List<>>> theReallyNotSureList;
//这些都很好
theReallyNotSureList = theAnyList;
theReallyNotSureList = theNotSureList;
//但我们无法执行此操作
theReallyNotSureList.add(new Vector< Float>);
//以及这个
theReallyNotSureList.get(0).add(a String);
我们已经失去了关于 theReallyNotSureList的
,以及里面的 List
s的元素类型。
(但您可能会注意到,我们可以将任何种类的列表保存到列表中......)
所以要分解它:
// ┌适用于外部列表
//▼
列表< ;?扩展List<>>>
//▲
//└适用于内部列表
Map
的工作方式相同,只是有更多类型参数:
//┌映射K参数
//│┌映射V参数
//▼▼
映射<?,?扩展List<>>>
//▲
//└列表E参数
为什么? extends
是必须的
您可能知道concrete泛型类型具有 invariance ,即 List< Dog>
不是即使 class Dog扩展 Animal
,子类型列表< Animal>
。相反,通配符是我们的协变,也就是 List< Dog>
是<$ em的子类型c $ c> List<? extends Animal> 。
// Dog is a动物的子类型
class Animal {}
class Dog extends Animal {}
// List< Dog>是List的子类型?延伸动物>
列表< ;?延伸动物> a = new ArrayList< Dog>();
//所有参数化列表是List<>的子类型。
列表<?> b = a;
因此,将这些想法应用于嵌套的 List
:
$ b
-
List< String>
是列表< / code>但
。如前所示,这样可以防止我们通过向List< List< String>>
不是<$ c $的子类型C>列表与LT;列表与LT;?>>List
添加错误元素来危害类型安全性。 -
List< ; List< String>>
是List的子类型<? extends List<>>
,因为有界通配符允许协变。也就是说,? extends
允许List< String>
是列表<?>
来考虑。 -
List<? extends List<>>> ;
实际上是一个共享的超类型:List< ;?扩展List<>>>
/╲
List< List<>>列表与LT;列表与LT;字符串>>
回顾
-
Map< Integer, List< String>>
仅接受List< String>
作为一个值。
-
Map<?, List<>>
-
接受任何
和List
> Map< Integer, List< String>>Map <?, List<>>它们具有单独的语义。
是一个共享的超类型,它强加安全限制:
- 一个不能转换为另一个,以防止我们以不安全的方式进行修改。 code> Map< ?, extends List<>>
地图<?,?扩展List<>>>
╱╲
Map<?,List<>> Map< Integer,List< String>>
< h3>泛型方法如何工作
通过在方法中使用类型参数,我们可以断言 List
has一些具体的类型。
static< E> void test(Map<?,List< E>> m){}
Map
中的 all List
s具有相同的元素类型。我们不知道这个类型实际上是 ,但是我们可以以抽象的方式使用它。这允许我们执行盲目操作。
static< E>列表与LT E - 代替; test(Map <?,List< E>> m){
List< E> result = new ArrayList< E>(); (列表< E>值:m.values()){
result.addAll(value);
。
}
返回结果;
我们不能调用 put
在 m
上,因为我们不知道它的键类型是什么了。然而,我们可以操作它的值,因为我们知道它们都是 List
具有相同的元素类型。
仅用于踢腿
另一个问题没有讨论的选项是同时使用有界通配符和泛型类型 List :
static< E> void test(Map<?,?extends List< E>> m){}
可以用类似于 Map< Integer,ArrayList< String>>
的方式调用它。这是最宽松的声明,如果我们只关心 E
的类型。
我们也可以使用边界嵌套类型参数:
static< K,E,L extends List< E>> void(Map
$m){
for(K key:m.keySet()){
L list = m.get(key); (E元素:列表){
// ...
}
}
}
p $ p>
这既是对我们可以传递给它的宽容,也是对我们如何操纵
m
以及其中的一切。
另见
- Java泛型:什么是PECS? 之间的区别
? extends
和? super
。 - JLS 4.10.2。在类和接口类型中进行子类型化 和 JLS 4.5.1。输入参数化类型的参数 以获取此答案的技术细节的入口点。
How come one must use the generic type Map<?, ? extends List<?>>
instead of a simpler Map<?, List<?>>
for the following test()
method?
public static void main(String[] args) {
Map<Integer, List<String>> mappy =
new HashMap<Integer, List<String>>();
test(mappy);
}
public static void test(Map<?, ? extends List<?>> m) {}
// Doesn't compile
// public static void test(Map<?, List<?>> m) {}
Noting that the following works, and that the three methods have the same erased type anyways.
public static <E> void test(Map<?, List<E>> m) {}
Fundamentally, List<List<?>>
and List<? extends List<?>>
have distinct type arguments.
It's actually the case that one is a subtype of the other, but first let's learn more about what they mean individually.
Understanding semantic differences
Generally speaking, the wildcard ?
represents some "missing information". It means "there was a type argument here once, but we don't know what it is anymore". And because we don't know what it is, restrictions are imposed on how we can use anything that refers to that particular type argument.
For the moment, let's simplify the example by using List
instead of Map
.
A
List<List<?>>
holds any kind of List with any type argument. So i.e.:List<List<?>> theAnyList = new ArrayList<List<?>>(); // we can do this theAnyList.add( new ArrayList<String>() ); theAnyList.add( new LinkedList<Integer>() ); List<?> typeInfoLost = theAnyList.get(0); // but we are prevented from doing this typeInfoLost.add( new Integer(1) );
We can put any
List
intheAnyList
, but by doing so we have lost knowledge of their elements.When we use
? extends
, theList
holds some specific subtype of List, but we don't know what it is anymore. So i.e.:List<? extends List<Float>> theNotSureList = new ArrayList<ArrayList<Float>>(); // we can still use its elements // because we know they store Float List<Float> aFloatList = theNotSureList.get(0); aFloatList.add( new Float(1.0f) ); // but we are prevented from doing this theNotSureList.add( new LinkedList<Float>() );
It's no longer safe to add anything to the
theNotSureList
, because we don't know the actual type of its elements. (Was it originally aList<LinkedList<Float>>
? Or aList<Vector<Float>>
? We don't know.)We can put these together and have a
List<? extends List<?>>
. We don't know what type ofList
it has in it anymore, and we don't know the element type of thoseList
s either. So i.e.:List<? extends List<?>> theReallyNotSureList; // these are fine theReallyNotSureList = theAnyList; theReallyNotSureList = theNotSureList; // but we are prevented from doing this theReallyNotSureList.add( new Vector<Float>() ); // as well as this theReallyNotSureList.get(0).add( "a String" );
We've lost information both about
theReallyNotSureList
, as well as the element type of theList
s inside it.(But you may note that we can assign any kind of List holding Lists to it...)
So to break it down:
// ┌ applies to the "outer" List
// ▼
List<? extends List<?>>
// ▲
// └ applies to the "inner" List
The Map
works the same way, it just has more type parameters:
// ┌ Map K argument
// │ ┌ Map V argument
// ▼ ▼
Map<?, ? extends List<?>>
// ▲
// └ List E argument
Why ? extends
is necessary
You may know that "concrete" generic types have invariance, that is, List<Dog>
is not a subtype of List<Animal>
even if class Dog extends Animal
. Instead, the wildcard is how we have covariance, that is, List<Dog>
is a subtype of List<? extends Animal>
.
// Dog is a subtype of Animal
class Animal {}
class Dog extends Animal {}
// List<Dog> is a subtype of List<? extends Animal>
List<? extends Animal> a = new ArrayList<Dog>();
// all parameterized Lists are subtypes of List<?>
List<?> b = a;
So applying these ideas to a nested List
:
List<String>
is a subtype ofList<?>
butList<List<String>>
is not a subtype ofList<List<?>>
. As shown before, this prevents us from compromising type safety by adding wrong elements to theList
.List<List<String>>
is a subtype ofList<? extends List<?>>
, because the bounded wildcard allows covariance. That is,? extends
allows the fact thatList<String>
is a subtype ofList<?>
to be considered.List<? extends List<?>>
is in fact a shared supertype:List<? extends List<?>> ╱ ╲ List<List<?>> List<List<String>>
In review
Map<Integer, List<String>>
accepts onlyList<String>
as a value.Map<?, List<?>>
accepts anyList
as a value.Map<Integer, List<String>>
andMap<?, List<?>>
are distinct types which have separate semantics.- One cannot be converted to the other, to prevent us from doing modifications in an unsafe way.
Map<?, ? extends List<?>>
is a shared supertype which imposes safe restrictions:Map<?, ? extends List<?>> ╱ ╲ Map<?, List<?>> Map<Integer, List<String>>
How the generic method works
By using a type parameter on the method, we can assert that List
has some concrete type.
static <E> void test(Map<?, List<E>> m) {}
This particular declaration requires that all List
s in the Map
have the same element type. We don't know what that type actually is, but we can use it in an abstract manner. This allows us to perform "blind" operations.
For example, this kind of declaration might be useful for some kind of accumulation:
static <E> List<E> test(Map<?, List<E>> m) {
List<E> result = new ArrayList<E>();
for(List<E> value : m.values()) {
result.addAll(value);
}
return result;
}
We can't call put
on m
because we don't know what its key type is anymore. However, we can manipulate its values because we understand they are all List
with the same element type.
Just for kicks
Another option which the question does not discuss is to have both a bounded wildcard and a generic type for the List
:
static <E> void test(Map<?, ? extends List<E>> m) {}
We would be able to call it with something like a Map<Integer, ArrayList<String>>
. This is the most permissive declaration, if we only cared about the type of E
.
We can also use bounds to nest type parameters:
static <K, E, L extends List<E>> void(Map<K, L> m) {
for(K key : m.keySet()) {
L list = m.get(key);
for(E element : list) {
// ...
}
}
}
This is both permissive about what we can pass to it, as well as permissive about how we can manipulate m
and everything in it.
See also
- "Java Generics: What is PECS?" for the difference between
? extends
and? super
. - JLS 4.10.2. Subtyping among Class and Interface Types and JLS 4.5.1. Type Arguments of Parameterized Types for entry points to the technical details of this answer.
这篇关于Java嵌套泛型类型的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!