是什么导致java.lang.IncompatibleClassChangeError?

Zom*_*ies 212 java runtime-error jar binary-compatibility

我将Java库打包为JAR,java.lang.IncompatibleClassChangeError当我尝试从中调用方法时,它会抛出很多s.这些错误似乎是随机出现的.什么类型的问题可能导致此错误?

not*_*oop 167

这意味着您已对库进行了一些不兼容的二进制更改,而无需重新编译客户端代码. Java语言规范§13详细说明了所有此类更改,最突出的是,将static非非私有字段/方法更改为static反之亦然.

针对新库重新编译客户端代码,您应该很高兴.

更新:如果您发布公共库,则应尽可能避免进行不兼容的二进制更改,以保留所谓的"二进制向后兼容性".理想情况下,单独更新依赖关系jar不应该破坏应用程序或构建.如果必须打破二进制向后兼容性,建议在发布更改之前增加主要版本号(例如从1.xy到2.0.0).

  • 你试过做一个干净的构建(删除所有的`*.class`文件)并重新编译吗?编辑文件具有类似的效果. (5认同)
  • 出于某种原因,这里的开发人员遇到了一个问题,即重新编译客户端代码并不能完全解决问题.出于某种原因,如果他们编辑文件发生的位置并重新编译错误,那么不再出现错误,但会在项目的其他地方随机弹出更多文件,其中引用了库.我很好奇什么可能是这样的. (2认同)
  • 你可以确保当你进行干净的构建时,你正在编译你运行的同一个罐子吗? (2认同)

lin*_*ild 96

您新打包的库与旧版本不是后向二进制兼容(BC).因此,一些未重新编译的库客户端可能会抛出异常.

这是Java库API中的更改的完整列表,可能导致使用旧版本库构建的客户端抛出java.lang.IncompatibleClassChangeError如果它们在新的上运行(即破坏BC):

  1. 非最终字段变为静态,
  2. 非常数场变为非静态,
  3. 类成为接口,
  4. 界面变成了类,
  5. 如果向类/接口添加新字段(或添加新的超类/超级接口),则来自客户端类C的超级接口的静态字段可能隐藏从中继承的添加字段(具有相同名称)超级C(非常罕见的情况).

注意:由于其他不兼容的更改而导致许多其他异常:NoSuchFieldError,NoSuchMethodError,IllegalAccessError,InstantiationError,VerifyError,NoClassDefFoundErrorAbstractMethodError.

关于BC的更好的论文是"发展基于Java的API 2:实现API二进制兼容性",由JimdesRivières撰写.

还有一些自动工具可以检测到这些变化:

使用japi-compliance-checker为您的图书馆:

japi-compliance-checker OLD.jar NEW.jar
Run Code Online (Sandbox Code Playgroud)

clirr工具的用法:

java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar
Run Code Online (Sandbox Code Playgroud)

祝好运!


Bri*_*ing 57

虽然这些答案都是正确的,但解决问题往往更加困难.它通常是类路径上相同依赖关系的两个稍微不同版本的结果,并且几乎总是由不同于最初编译的超类导致在类路径上传递闭包的某些导入是不同的,但通常在类实例化和构造函数调用.(成功加载类和ctor之后,你会得到NoSuchMethodException或不知道.)

如果行为看起来是随机的,那么很可能是多线程程序根据首先命中的代码加载不同的传递依赖性的结果.

要解决这些问题,请尝试使用-verbose作为参数启动VM ,然后查看发生异常时正在加载的类.你应该看到一些令人惊讶的信息.例如,拥有相同依赖项和版本的多个副本,如果您知道它们被包含在内,则您从未预料到或将会接受这些副本.

使用Maven解决重复的jar文件最好使用Maven下的maven-dependency-pluginmaven-enforcer-plugin(或SBT的Dependency Graph Plugin,然后将这些jar添加到顶级POM的一部分或作为导入的依赖项) SBT中的元素(删除那些依赖项).

祝好运!

  • 冗长的论点帮助我确定了哪些罐子存在问题.谢谢 (5认同)

Ogr*_*m33 6

我还发现,当使用JNI从C ++调用Java方法时,如果以错误的顺序将参数传递给调用的Java方法,则在尝试使用被调用方法内部的参数时会出现此错误(因为它们不会是正确的类型)。最初让我感到吃惊的是,在您调用该方法时,JNI不会在类签名检查中为您执行此检查,但是我认为它们不会进行这种检查,因为您可能要传递多态参数,并且它们必须假设你知道自己在做什么。

示例C ++ JNI代码:

void invokeFooDoSomething() {
    jobject javaFred = FredFactory::getFred(); // Get a Fred jobject
    jobject javaFoo = FooFactory::getFoo(); // Get a Foo jobject
    jobject javaBar = FooFactory::getBar(); // Get a Bar jobject
    jmethodID methodID = getDoSomethingMethodId() // Get the JNI Method ID


    jniEnv->CallVoidMethod(javaFoo,
                           methodID,
                           javaFred, // Woops!  I switched the Fred and Bar parameters!
                           javaBar);

    // << Insert error handling code here to discover the JNI Exception >>
    //  ... This is where the IncompatibleClassChangeError will show up.
}
Run Code Online (Sandbox Code Playgroud)

示例Java代码:

class Bar { ... }

class Fred {
    public int size() { ... }
} 

class Foo {
    public void doSomething(Fred aFred, Bar anotherObject) {
        if (name.size() > 0) { // Will throw a cryptic java.lang.IncompatibleClassChangeError
            // Do some stuff...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Eng*_*uad 5

我有同样的问题,后来我发现我在Java版本1.4上运行应用程序,而应用程序是在版本6上编译的.

实际上,原因是有一个重复的库,一个位于类路径中,另一个包含在位于类路径中的jar文件中.