javac不能编译上界通配符,但eclipse可以

niknxzdl  于 2021-06-27  发布在  Java
关注(0)|答案(1)|浏览(275)

我使用的代码库到目前为止一直依赖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>>();
swvgeqrz

swvgeqrz1#

你误解了什么的规则 ? 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

如果以上编译,那么在 ints ,这不好。幸运的是,它没有编译。具体来说,第三行是编译器错误。
你所说的jls部分是指单行道。您可以指定 List<Number> 类型的变量 List<? extends Number> (或通过 List<Number> 作为参数(当参数是该类型时),但不是相反。 ? 就像“想象我在这里用了一封信,而我只在这里用这封信,其他地方都不用”。因此,如果你有两个 ? 他们可能并不平等。因此,出于类型兼容性的目的,它们是不兼容的。这是有道理的;想象一下你有:

void foo(List<? extends Number> a, List<? extends Number b>) {}

因此,我可以通过一些 List<Integer> 一段时间后 List<Double> b:每个 ? 只要符合界限就可以成为它想要的。这也意味着 add 在这两个列表中的任何一个上,因为您添加的内容必须是 ? ,而你不能做到这一点(除了,琐碎的,通过写作) .add(null) ,因为null是用于此类目的的所有类型),但这不是很有用)。这也解释了为什么你不能写作 a = b; 这就是你问题的核心所在。为什么不能分配 ab ? 毕竟他们是同一类型的不,他们不是,帽子的东西抓住了这一点: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完全允许这样做的事实很奇怪。
下面是一个微不足道的例子:

Map<? extends Number, String> x = ...;
x.put(A, B);

在上面的例子中,任何东西都不能写在3个点上,也不能代替 A 使它永远编译。 ? extends 是:no add/put的简写。句号。
实际上有一件事是可行的: x.put(null, B); ,因为null“适合”每种类型。但这是一个copout,对于严肃的代码来说一点用处都没有。
一旦你完全理解了这一点,问题就解释清楚了。一般来说,考虑到你有 MapContainer<? extends something> ,你不能在那东西上叫addmap。句号。不能在上调用“写入”操作 extends 样式类型边界。
我已经解释了为什么这是最重要的答案。

相关问题