Java 二级参数化类型推断
Java two-level parameterized type inference
我有一些代码 return 是通配符参数化类型。我试图将其传递给参数化方法,但出现编译器错误。有人可以向我解释为什么类型不匹配,解决这个问题的最佳方法是什么?
static <L extends List<T>,T extends Number>
void useList(List<L> list) {}
public static void main(String[] args) {
List<List<? extends Number>> list = null;
useList(list);
}
编译错误:
demo/GenericsHell.java:75: error: method useList in class GenericsHell cannot be applied to given types;
useList(list);
^
required: List<L>
found: List<List<? extends Number>>
reason: inference variable L has incompatible bounds
equality constraints: List<? extends Number>
upper bounds: List<CAP#1>
where L,T are type-variables:
L extends List<T> declared in method <L,T>useList(List<L>)
T extends Number declared in method <L,T>useList(List<L>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
1 error
在我的真实代码中,list
是由一个复杂的方法生成的,该方法可以 return 多种类型的 "List"(实际上是自定义泛型 class)。是否有另一种方法来参数化 useList 函数,使其以类型安全的方式接受它?
编辑 1
我正在尝试将一大段代码缩减为一个简洁、连贯的问题。从答案中我可以看出,上面的代码过度简化了我遇到的确切问题。我将尝试用一个更复杂的例子来重申,同时更好地指定我的约束。
首先,我实际上并没有使用 List,而是更复杂的 class 和双重参数化。我想不出有任何标准 class 可以做到这一点,所以我将定义一个用于示例:
static class NumberLists<L extends List<T>,T extends Number> extends ArrayList<L> {}
我会避免重复使用一个类型不止一次,这样更容易保持层次清晰。 useList() 方法确实需要双重参数化,因为它在内部使用了两种类型:
static <L extends List<T>,T extends Number>
Set<NumberLists<L,T>> useList(L list) {
NumberLists<L,T> nl = new NumberLists<L, T>();
nl.add(list);
return Collections.singleton(nl);
}
只要你有一个具体的框架,这个框架就很好用 class:
// Everything works with a concrete class
List<Integer> intList = new ArrayList<Integer>();
// Use with parametric functions
Set<NumberLists<List<Integer>,Integer>> concreteSet = useList(intList);
// Access & use elements
NumberLists<List<Integer>,Integer> concreteNL = concreteSet.iterator().next();
concreteNL.add(intList);
问题是我有一个可以是几种不同类型的输入列表:
static List<? extends Number> makeList() {
if(Math.random()<.5) {
return new ArrayList<Integer>();
} else {
return new ArrayList<Double>();
}
}
List<? extends Number> numList = makeList();
这打破了上面可能存在的许多模式。
// What is the type here? This is obviously an error
Set<NumberLists<? extends List<? extends Number>>,? extends Number> paramSet = useList(numList);
// Type should ideally be compatible with numList still
NumberLists<?,?> paraNL1 = paramSet.iterator().next();
// Captures don't match
paraNL1.add(numList);
所以问题是我无法在编译时知道numList的具体class。而且感觉我没有做任何实际上类型不安全的事情,因为我知道 numList 的类型必须与 return 由 useList 等编辑的类型相匹配。
我确实可以控制大部分代码的类型签名。但是,我更喜欢以下内容:
- 它应该以类型安全的方式与具体的 classes(例如 intList)
一起很好地工作
- 它还将接收其类型直到运行时才能具体知道的输入
- 如果执行转换或其他未经检查的操作,它们应该发生在输入的构造附近。应检查以下操作。
有关完整(但未编译)的 java 文件,请参阅 https://gist.github.com/sbliven/f7babb729e0b1bee8d2dabe5ee979431. For the reason I'm interested in this question, see https://github.com/biojava/biojava/issues/354。
问题在于编译器想要将具体类型分配给 T
。在你的声明中,T
需要根据 list
的声明变成 ? extends Number
,这不是具体的。
您可以将 useList
更改为以下声明之一,编译器会很高兴:
static <L extends List<? extends Number>> void useList(List<L> list) {
}
(没有T
,没问题)或
static <L extends List<? extends T>, T extends Number> void useList(List<L> list) {
}
(这里的T
会变成Number
)。
如果您可以在变量声明中替换 ? extends Number
,一切都会好起来的:
public static <T extends Number> void main(String[] args) {
List<List<T>> list = null;
useList(list);
}
对于第一个编译问题,您的问题来自于您尝试将其分配给某个类型不匹配的变量。
最简单的方法是从 useList()
调用开始,以确保此简单代码可以编译:
List<? extends Number> numList = makeList();
useList(numList);
如果你检查这段代码,它已经编译了。
现在请 IDE 将结果分配给局部变量。 IDE 将为您计算表达式的类型以声明变量,并且 voilà:
Set<? extends NumberLists<? extends List<? extends Number>, ? extends Number>> paramSet =
useList(numList);
(如果可能,去掉 NumbersList
上的 L
类型,这会简化很多事情)
对于 paraNL1.add(numList);
调用,由于通配符,它无法工作:编译器无法检查 paraNL1
是否接受您的未知类型的列表。
即使您按照相同的过程修复声明:
NumberLists<? extends List<? extends Number>, ? extends Number> paraNL1 =
paramSet.iterator().next();
paraNL1.add(numList); // will still not compile
你看到你正在尝试使用paraNL1
作为消费者(它消耗numList
),并且PECS告诉你必须用super
声明它让它工作。
的确,编译器知道你的 NumberLists
一定存在某种类型 L extends List<? extends Number>
,但它不知道,也没有办法检查它是否与 [=21] 的类型匹配=].例如 L
可以是 LinkedList<Float>
而 numList
可以是 ArrayList<Integer>
.
编辑:要使其正常工作,您可以像这样使用 :
private static <T extends Number, L extends List<T>> void helper(L numList) {
Set<NumberLists<L, T>> paramSet = useList(numList);
NumberLists<L, T> paraNL1 = paramSet.iterator().next();
paraNL1.add(numList);
}
(这甚至允许您去掉 Set
声明中的 ? extends NumerLists
)
并用
调用它
List<? extends Number> numList = makeList();
helper(numList);
我有一些代码 return 是通配符参数化类型。我试图将其传递给参数化方法,但出现编译器错误。有人可以向我解释为什么类型不匹配,解决这个问题的最佳方法是什么?
static <L extends List<T>,T extends Number>
void useList(List<L> list) {}
public static void main(String[] args) {
List<List<? extends Number>> list = null;
useList(list);
}
编译错误:
demo/GenericsHell.java:75: error: method useList in class GenericsHell cannot be applied to given types;
useList(list);
^
required: List<L>
found: List<List<? extends Number>>
reason: inference variable L has incompatible bounds
equality constraints: List<? extends Number>
upper bounds: List<CAP#1>
where L,T are type-variables:
L extends List<T> declared in method <L,T>useList(List<L>)
T extends Number declared in method <L,T>useList(List<L>)
where CAP#1 is a fresh type-variable:
CAP#1 extends Number from capture of ? extends Number
1 error
在我的真实代码中,list
是由一个复杂的方法生成的,该方法可以 return 多种类型的 "List"(实际上是自定义泛型 class)。是否有另一种方法来参数化 useList 函数,使其以类型安全的方式接受它?
编辑 1
我正在尝试将一大段代码缩减为一个简洁、连贯的问题。从答案中我可以看出,上面的代码过度简化了我遇到的确切问题。我将尝试用一个更复杂的例子来重申,同时更好地指定我的约束。
首先,我实际上并没有使用 List,而是更复杂的 class 和双重参数化。我想不出有任何标准 class 可以做到这一点,所以我将定义一个用于示例:
static class NumberLists<L extends List<T>,T extends Number> extends ArrayList<L> {}
我会避免重复使用一个类型不止一次,这样更容易保持层次清晰。 useList() 方法确实需要双重参数化,因为它在内部使用了两种类型:
static <L extends List<T>,T extends Number>
Set<NumberLists<L,T>> useList(L list) {
NumberLists<L,T> nl = new NumberLists<L, T>();
nl.add(list);
return Collections.singleton(nl);
}
只要你有一个具体的框架,这个框架就很好用 class:
// Everything works with a concrete class
List<Integer> intList = new ArrayList<Integer>();
// Use with parametric functions
Set<NumberLists<List<Integer>,Integer>> concreteSet = useList(intList);
// Access & use elements
NumberLists<List<Integer>,Integer> concreteNL = concreteSet.iterator().next();
concreteNL.add(intList);
问题是我有一个可以是几种不同类型的输入列表:
static List<? extends Number> makeList() {
if(Math.random()<.5) {
return new ArrayList<Integer>();
} else {
return new ArrayList<Double>();
}
}
List<? extends Number> numList = makeList();
这打破了上面可能存在的许多模式。
// What is the type here? This is obviously an error
Set<NumberLists<? extends List<? extends Number>>,? extends Number> paramSet = useList(numList);
// Type should ideally be compatible with numList still
NumberLists<?,?> paraNL1 = paramSet.iterator().next();
// Captures don't match
paraNL1.add(numList);
所以问题是我无法在编译时知道numList的具体class。而且感觉我没有做任何实际上类型不安全的事情,因为我知道 numList 的类型必须与 return 由 useList 等编辑的类型相匹配。
我确实可以控制大部分代码的类型签名。但是,我更喜欢以下内容:
- 它应该以类型安全的方式与具体的 classes(例如 intList) 一起很好地工作
- 它还将接收其类型直到运行时才能具体知道的输入
- 如果执行转换或其他未经检查的操作,它们应该发生在输入的构造附近。应检查以下操作。
有关完整(但未编译)的 java 文件,请参阅 https://gist.github.com/sbliven/f7babb729e0b1bee8d2dabe5ee979431. For the reason I'm interested in this question, see https://github.com/biojava/biojava/issues/354。
问题在于编译器想要将具体类型分配给 T
。在你的声明中,T
需要根据 list
的声明变成 ? extends Number
,这不是具体的。
您可以将 useList
更改为以下声明之一,编译器会很高兴:
static <L extends List<? extends Number>> void useList(List<L> list) {
}
(没有T
,没问题)或
static <L extends List<? extends T>, T extends Number> void useList(List<L> list) {
}
(这里的T
会变成Number
)。
如果您可以在变量声明中替换 ? extends Number
,一切都会好起来的:
public static <T extends Number> void main(String[] args) {
List<List<T>> list = null;
useList(list);
}
对于第一个编译问题,您的问题来自于您尝试将其分配给某个类型不匹配的变量。
最简单的方法是从 useList()
调用开始,以确保此简单代码可以编译:
List<? extends Number> numList = makeList();
useList(numList);
如果你检查这段代码,它已经编译了。
现在请 IDE 将结果分配给局部变量。 IDE 将为您计算表达式的类型以声明变量,并且 voilà:
Set<? extends NumberLists<? extends List<? extends Number>, ? extends Number>> paramSet =
useList(numList);
(如果可能,去掉 NumbersList
上的 L
类型,这会简化很多事情)
对于 paraNL1.add(numList);
调用,由于通配符,它无法工作:编译器无法检查 paraNL1
是否接受您的未知类型的列表。
即使您按照相同的过程修复声明:
NumberLists<? extends List<? extends Number>, ? extends Number> paraNL1 =
paramSet.iterator().next();
paraNL1.add(numList); // will still not compile
你看到你正在尝试使用paraNL1
作为消费者(它消耗numList
),并且PECS告诉你必须用super
声明它让它工作。
的确,编译器知道你的 NumberLists
一定存在某种类型 L extends List<? extends Number>
,但它不知道,也没有办法检查它是否与 [=21] 的类型匹配=].例如 L
可以是 LinkedList<Float>
而 numList
可以是 ArrayList<Integer>
.
编辑:要使其正常工作,您可以像这样使用
private static <T extends Number, L extends List<T>> void helper(L numList) {
Set<NumberLists<L, T>> paramSet = useList(numList);
NumberLists<L, T> paraNL1 = paramSet.iterator().next();
paraNL1.add(numList);
}
(这甚至允许您去掉 Set
声明中的 ? extends NumerLists
)
并用
调用它List<? extends Number> numList = makeList();
helper(numList);