我使用的代码库到目前为止一直依赖eclipse进行编译。我的目标是用 javac
(通过 ant
)以简化构建过程。该项目在eclipse(版本2019-12(4.14.0))中编译,没有任何问题,但是 javac
(openjdk,1.8.0和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。
为解决问题所作的努力
虽然我的目标是为Java8进行编译,但我知道在 javac
过去与泛型和通配符相关(参见围绕此答案的讨论)。但是,我发现javac1.8.0ē和javac14.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;
}
}
然而,这并不能解释为什么 mapContainerManager.getMapContainer().addMap(aMap)
是编译器错误 mapContainerManager
是一个 MapContainerManagerBroken
(例如在示例程序中)。具体来说,为什么前一行是错误的,而下面的编译是错误的?
MapContainer<A, ? extends B<A>> mapContainer = new MapContainer<A, B<A>>();
1条答案
按热度按时间swvgeqrz1#
你误解了什么的规则
? extends
就类型兼容性而言。任何两次
? extends Number
彼此不兼容,也不是? extends Number
兼容Number
它自己。下面是一个微不足道的“证据”来说明原因:如果以上编译,那么在
ints
,这不好。幸运的是,它没有编译。具体来说,第三行是编译器错误。你所说的jls部分是指单行道。您可以指定
List<Number>
类型的变量List<? extends Number>
(或通过List<Number>
作为参数(当参数是该类型时),但不是相反。?
就像“想象我在这里用了一封信,而我只在这里用这封信,其他地方都不用”。因此,如果你有两个?
他们可能并不平等。因此,出于类型兼容性的目的,它们是不兼容的。这是有道理的;想象一下你有:因此,我可以通过一些
List<Integer>
一段时间后List<Double>
b:每个?
只要符合界限就可以成为它想要的。这也意味着add
在这两个列表中的任何一个上,因为您添加的内容必须是?
,而你不能做到这一点(除了,琐碎的,通过写作).add(null)
,因为null是用于此类目的的所有类型),但这不是很有用)。这也解释了为什么你不能写作a = b;
这就是你问题的核心所在。为什么不能分配a
至b
? 毕竟他们是同一类型的不,他们不是,帽子的东西抓住了这一点:a的类型CAP#1
b型CAP#2
. 这就是javac(和ecj,大概)如何解决这个问题的,这就是为什么cap的东西出现了。这不是编译器故意密集或不规范的问题。这是泛型的复杂性所固有的。因此,是的:
CAP#1
不等于? extends Number
,这仅仅是其中的一个捕获(以及任何进一步的捕获)? extends Number
将被称为CAP#2
,且cap#1和cap#2不兼容;一个可能是整数,一个可能是双精度)。错误消息本身是合理的。正常情况下
ecj
以及javac
通常不同意ecj
是正确的,javac不是,根据个人经验(我讲述了10次我遇到ecj和javac不一致的情况,10次中有9次ecj比javac更正确;尽管我随后报告的jls中经常存在歧义,并且已经解决了)。尽管如此,考虑到jdk14仍然存在这个问题,并且试图解释这些错误消息(如果没有这里涉及的所有签名,这是相当困难的,您还没有粘贴代码库的有用部分),它看起来确实像javac
是正确的。通常的解决办法是扔更多
?
在那里。特别是removeEdge
当然,听起来它应该接受Object
或者? extends T
而不是T
. 毕竟,arraylist的.remove()
方法接受任何对象,而不是T
-按照规范,试图从int列表中删除一些double并没有任何作用:要求列表删除不在内部的东西是一个noop。那么,没有理由限制参数。解决了很多问题。编辑,在你用更详细的方式显著更新你的问题之后。
Map容器<a?扩展b>mapcontainer=新mapcontainer<a,b>();
因为这就是它的意思。记得,
MapContainer<? extends Number>
不表示类型。它代表了一个完整的维度值的类型。是这么说的mapContainer
是一个可以指向几乎任何东西的引用,只要它是mapcontainer,就可以指向任何“tag”(文件中的内容)<>
你可以,只要中间的东西不是数字就是它的任何子类型。在这个分解的“可能有这么多东西”类型上,您可以调用的唯一方法是它可以在命令中拥有的所有可能的东西,而不是addMap
任何条纹的方法都是交叉点的一部分。这个addMap
方法的参数涉及A
,在本例中,与? extends Number
编译器说:嗯,我不知道。没有合适的类型。我不能和你一起去Number
; 如果你有一个MapContainer<Integer>
? 如果我让你打电话addMap
使用任何Number
,你可以在里面放一个双人床,这是不允许的。eclipse完全允许这样做的事实很奇怪。下面是一个微不足道的例子:
在上面的例子中,任何东西都不能写在3个点上,也不能代替
A
使它永远编译。? extends
是:no add/put的简写。句号。实际上有一件事是可行的:
x.put(null, B);
,因为null“适合”每种类型。但这是一个copout,对于严肃的代码来说一点用处都没有。一旦你完全理解了这一点,问题就解释清楚了。一般来说,考虑到你有
MapContainer<? extends something>
,你不能在那东西上叫addmap。句号。不能在上调用“写入”操作extends
样式类型边界。我已经解释了为什么这是最重要的答案。