java - javac 无法编译上限通配符,但 Eclipse 可以
问题描述
到目前为止,我正在使用一个依赖 Eclipse 进行编译的代码库。我的目标是用javac
(via ant
) 编译它以简化构建过程。该项目在 Eclipse(版本 2019-12 (4.14.0))中编译没有问题,但是javac
(OpenJDK,版本 1.8.0_275 和 14.0.2)会产生method ... cannot be applied to given types
涉及上限通配符的错误。
重现步骤
请注意,在撰写本文时,存储库为 64 MB:
git clone git@github.com:jamesdamillington/CRAFTY_Brazil.git
cd CRAFTY_Brazil && git checkout 550e88e
javac -Xdiags:verbose \
-classpath bin:lib/jts-1.13.jar:lib/MORe.jar:lib/ParMa.jar:lib/ModellingUtilities.jar:lib/log4j-1.2.17.jar:lib/repast.simphony.bin_and_src.jar \
src/org/volante/abm/agent/DefaultSocialLandUseAgent.java
错误信息输出:
src/org/volante/abm/agent/DefaultSocialLandUseAgent.java:166: error: method removeNode in interface MoreNetworkModifier<AgentType,EdgeType> cannot be applied to given types;
this.region.getNetworkService().removeNode(this.region.getNetwork(), this);
^
required: MoreNetwork<SocialAgent,CAP#1>,SocialAgent
found: MoreNetwork<SocialAgent,MoreEdge<SocialAgent>>,DefaultSocialLandUseAgent
reason: argument mismatch; MoreNetwork<SocialAgent,MoreEdge<SocialAgent>> cannot be converted to MoreNetwork<SocialAgent,CAP#1>
where AgentType,EdgeType are type-variables:
AgentType extends Object declared in interface MoreNetworkModifier
EdgeType extends MoreEdge<? super AgentType> declared in interface MoreNetworkModifier
where CAP#1 is a fresh type-variable:
CAP#1 extends MoreEdge<SocialAgent> from capture of ? extends MoreEdge<SocialAgent>
1 error
为了完整起见,包含违规行的方法是:
public void die() {
if (this.region.getNetworkService() != null && this.region.getNetwork() != null) {
this.region.getNetworkService().removeNode(this.region.getNetwork(), this);
}
if (this.region.getGeography() != null
&& this.region.getGeography().getGeometry(this) != null) {
this.region.getGeography().move(this, null);
}
}
错误信息分析
- 我们被告知编译器
MoreNetwork<SocialAgent,MoreEdge<SocialAgent>>
在需要的地方找到了类型MoreNetwork<SocialAgent,CAP#1>
。 - 这意味着推断的值
CAP#1
与MoreEdge<SocialAgent>
- 然而我们被告知
CAP#1 extends MoreEdge<SocialAgent> from capture of ? extends MoreEdge<SocialAgent>
关于上界通配符的Java 文档指出
上限通配符 ,
<? extends Foo>
其中Foo
是任何类型,匹配Foo
的任何子类型Foo
。
我看不出为什么MoreEdge<SocialAgent>
不匹配<? extends MoreEdge<SocialAgent>>
,因此无法协调 2 和 3。
为解决问题所做的努力
虽然我的目标是为 Java 8 进行编译,但我知道过去发现了javac
与泛型和通配符相关的错误(请参阅围绕此答案的讨论)。但是,我发现 javac 1.8.0_275 和 javac 14.0.2 存在相同的问题。
我还考虑过某种形式的显式转换是否可以为编译器提供足够的提示。但是,我想不出要更改什么,因为按预期在错误消息中this.region.getNetwork()
报告了类型。MoreNetwork<SocialAgent,MoreEdge<SocialAgent>>
问题的概括(编辑)
@rzwitserloot 正确地指出,我没有在上面的代码中包含足够的关于依赖项的信息来正确调试。复制所有依赖项(包括我无法控制的库中的一些代码)会变得非常混乱,因此我将问题提炼成一个产生类似错误的独立程序。
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
public class UpperBoundNestedGenericsDemo {
public static void main(String[] args) {
MapContainerManagerBroken mapContainerManager = new MapContainerManagerBroken();
MapContainer<A, B<A>> mapContainer = new MapContainer<>();
mapContainerManager.setMapContainer(mapContainer);
Map<A, B<A>> aMap = new HashMap<>();
aMap.put(new A(), new B<A>());
mapContainerManager.getMapContainer().addMap(aMap);
mapContainerManager.getMapContainer().removeMap(aMap);
}
}
/**
* Analogue of Region
*/
class MapContainerManagerBroken {
private MapContainer<A, ? extends B<A>> mapContainer;
void setMapContainer(MapContainer<A, ? extends B<A>> mapContainer) {
this.mapContainer = mapContainer;
}
MapContainer<A, ? extends B<A>> getMapContainer() {
return this.mapContainer;
}
}
/**
* Analogue of MoreNetworkService
*/
class MapContainer<T1, T2> {
List<Map<T1, T2>> listOfMaps = new ArrayList<>();
void addMap(Map<T1, T2> map) {
listOfMaps.add(map);
}
boolean removeMap(Map<T1, T2> map) {
return listOfMaps.remove(map);
}
}
class A {
}
class B<T> {
}
这在 Eclipse 中编译,但在编译时使用
javac -Xdiags:verbose UpperBoundNestedGenericsDemo.java
产生错误信息
UpperBoundNestedGenericsDemo.java:18: error: method addMap in class MapContainer<T1,T2> cannot be applied to given types;
mapContainerManager.getMapContainer().addMap(aMap);
^
required: Map<A,CAP#1>
found: Map<A,B<A>>
reason: argument mismatch; Map<A,B<A>> cannot be converted to Map<A,CAP#1>
where T1,T2 are type-variables:
T1 extends Object declared in class MapContainer
T2 extends Object declared in class MapContainer
where CAP#1 is a fresh type-variable:
CAP#1 extends B<A> from capture of ? extends B<A>
UpperBoundNestedGenericsDemo.java:19: error: method removeMap in class MapContainer<T1,T2> cannot be applied to given types;
mapContainerManager.getMapContainer().removeMap(aMap);
^
required: Map<A,CAP#1>
found: Map<A,B<A>>
reason: argument mismatch; Map<A,B<A>> cannot be converted to Map<A,CAP#1>
where T1,T2 are type-variables:
T1 extends Object declared in class MapContainer
T2 extends Object declared in class MapContainer
where CAP#1 is a fresh type-variable:
CAP#1 extends B<A> from capture of ? extends B<A>
2 errors
部分解决方案
可以修改上一节中的程序,使其在 Eclipse 下编译并javac
替换MapContainerManagerBroken
为
class MapContainerManagerNoWildcards {
private MapContainer<A, B<A>> mapContainer;
void setMapContainer(MapContainer<A, B<A>> mapContainer) {
this.mapContainer = mapContainer;
}
MapContainer<A, B<A>> getMapContainer() {
return this.mapContainer;
}
}
也就是说,通过删除MapContainer
. 这解决了眼前的实际问题,但限制了MapContainerManagerNoWildcards
比较的灵活性MapContainerManagerBroken
。另一种方法是使此类通用,例如
class MapContainerManagerFixedGeneric<T extends B<A>> {
private MapContainer<A, T> mapContainer;
void setMapContainer(MapContainer<A, T> mapContainer) {
this.mapContainer = mapContainer;
}
MapContainer<A, T> getMapContainer() {
return this.mapContainer;
}
}
但是,这并不能解释为什么当is a时该行mapContainerManager.getMapContainer().addMap(aMap)
是编译器错误(例如在示例程序中)。具体来说,为什么前一行是错误的,但下面是编译的?mapContainerManager
MapContainerManagerBroken
MapContainer<A, ? extends B<A>> mapContainer = new MapContainer<A, B<A>>();
解决方案
您误解了关于? extends
类型兼容性的含义的规则。
任何两次出现的? extends Number
彼此都不相容,也不? extends Number
与Number
自身相容。这是一个简单的“证明”,说明为什么会这样:
List<Integer> ints = new ArrayList<Integer>();
List<? extends Number> numbers1 = ints; // legal
List<Number> numbers2 = numbers1; // legal?
numbers2.add(new Double(5.0)); // oh whoopsie
如果上面的编译,那么有一个非 int ints
,那就不好了。幸运的是,它没有编译。具体来说,第三行是编译器错误。
您所说的 JLS 部分是在谈论单向街道。您可以将 a 分配给List<Number>
类型的变量List<? extends Number>
(或List<Number>
在参数属于该类型时将 a 作为参数传递),但不能反过来。
?
与“想象我在这里使用了一个字母,而我只在此处使用该字母而没有在其他任何地方使用”相同。因此,如果您?
涉及两个,它们可能彼此不相等。因此,出于类型兼容性的目的,它们是不兼容的。这是有道理的;想象你有:
void foo(List<? extends Number> a, List<? extends Number b>) {}
那么很明显,重点是我可以调用这个传递一些List<Integer>
a 和List<Double>
b:?
只要它符合界限,每个都可以成为它想要的任何东西。这也意味着不可能在这些列表中的任何一个上调用add
,因为您添加的东西必须是 type ?
,并且您不能做到这一点(除了,琐碎地,通过编写.add(null)
,因为 null 是用于此类目的的每种类型) ,但这不是很有用)。它还解释了为什么你不能写a = b;
,这就是你问题的核心。为什么不能分配a
给b
?毕竟,它们是同一类型!- 不,它们不是,CAP 的东西捕捉到了这一点:a 是类型CAP#1
,b 是类型CAP#2
. 这就是 javac(和 ecj,大概)如何解决这个问题,这就是为什么这个 CAP 的东西出现了。这不是编译器故意密集或缺乏规范的问题。这是泛型复杂性所固有的。
因此,是CAP#1
的: 与 不同? extends Number
,它只是其中的一个捕获(然后任何进一步? extends Number
将被称为CAP#2
,并且 CAP#1 和 CAP#2 不兼容;一个可能是 Integer,一个可能是 Double,毕竟)。错误信息本身是明智的。
通常,如果ecj
和javac
不同意,通常ecj
是正确的,而 javac 不是,根据个人经验(我回忆了大约 10 次我遇到 ecj 和 javac 不同意的情况,10 次中有 9 次,ecj 比 javac 更正确;虽然通常这是我随后报告的 JLS 中的模棱两可并且已得到解决的问题)。尽管如此,鉴于 JDK14 仍然存在问题,并尝试解释这些错误消息(如果没有此处涉及的所有签名,这非常困难,您还没有粘贴代码库的有用部分),它看起来确实javac
是正确的。
通常的解决方法是?
在那里扔更多。特别是,removeEdge
肯定听起来应该接受Object
or? extends T
和 not T
。毕竟,arraylist 的.remove()
方法接受任何对象,而不是T
- 尝试从 int 列表中删除一些 double 根本不会做任何事情,根据规范:要求列表删除不在内部的东西是一个 noop。没有理由限制参数,那么。这解决了很多问题。
编辑,在您以更详细的方式显着更新您的问题之后。
地图容器<A, ? 扩展 B> mapContainer = new MapContainer<A, B>();
因为这就是那个意思。记住,MapContainer<? extends Number>
不代表一个类型。它代表了整个维度的类型。意思是这个mapContainer
引用几乎可以指向任何东西,只要它是一个 MapContainer,任何“标签”(<>
你喜欢的东西,只要介于两者之间的东西是 Number 或其任何子类型。您可以在这个爆炸的“可能是这么多东西”类型上调用的唯一方法是它可能在命令中拥有的所有可能的东西,并且没有addMap
任何条带的方法是交集的一部分。addMap
方法的参数涉及一个A
,如,在这个例子中,同样的事情? extends Number
编译器说:嗯,我不知道。没有适合的类型。我不能去Number
;如果你有一个MapContainer<Integer>
?如果我让你addMap
用 any打电话Number
,你可以在里面放一个 Double ,这是不允许的。日食完全允许它的事实很奇怪。
这是一个边缘微不足道的例子:
Map<? extends Number, String> x = ...;
x.put(A, B);
在上面的例子中,不能在 3 个点上写任何东西,或者代替A
make that ever compile。? extends
是简写:没有添加/放置。时期。
实际上有一件事会起作用:x.put(null, B);
,因为 null '适合'每种类型。但这是一种逃避,对严肃的代码一点用处都没有。
一旦你完全理解了这一点,问题就得到了解释。更一般地说,鉴于你有一个MapContainer<? extends something>
,你不能在那件事上调用 addMap。时期。您不能对extends
样式类型边界调用“写入”操作。
我已经解释了为什么它在这个答案的最上面。
推荐阅读
- javascript - 使用 React-native-picker-select 无头组件
- python - 从选择标签获取数据
- javascript - 如何在另一个 GraphQL Gatsby 中使用一个查询的输出
- hive - Hive 窗口查询
- json - JSONObject 错误解析包含 javascript 的 JSON
- java - 计时器可以显示在上一行,我可以在cmd中输入下一行吗?(java)
- excel - 如果另一列中有单元格匹配,Excel公式将返回另一列的值
- ios - Objective-c 中的 NSDate 日期给出的日期时间与 swift 中的 Date() 不同
- python - Pandas:如何查找范围内的值的行和列?
- node.js - 如何使用硒加载视频?