我将一个Java库打包为JAR,当我试图从它调用方法时,它抛出许多Java .lang. incompatibleclasschangeerror。这些错误似乎是随机出现的。什么样的问题会导致这个错误?


当前回答

如果你使用scala和sbt和scala-logging作为依赖项,那么这可能会发生,因为scala-logging的早期版本的名称是scala-logging-api.因此,本质上依赖项解析不会发生,因为不同的名称会导致启动scala应用程序时的运行时错误。

其他回答

所有以上-无论出于什么原因,我做了一些大的重构,开始得到这个。我重命名了我的接口所在的包,这就清除了它。希望这能有所帮助。

I have also discovered that, when using JNI, invoking a Java method from C++, if you pass parameters to the invoked Java method in the wrong order, you will get this error when you attempt to use the parameters inside the called method (because they won't be the right type). I was initially taken aback that JNI does not do this checking for you as part of the class signature checking when you invoke the method, but I assume they don't do this kind of checking because you may be passing polymorphic parameters and they have to assume you know what you are doing.

示例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.
}

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...
        }
    }
}

如果你使用scala和sbt和scala-logging作为依赖项,那么这可能会发生,因为scala-logging的早期版本的名称是scala-logging-api.因此,本质上依赖项解析不会发生,因为不同的名称会导致启动scala应用程序时的运行时错误。

新打包的库与旧版本不向后二进制兼容(BC)。由于这个原因,一些没有重新编译的库客户端可能会抛出异常。

这是一个完整的Java库API更改列表,这些更改可能会导致使用旧版本库构建的客户端在运行新版本库时抛出Java .lang. incompatibleclasschangeerror(即破坏BC):

非终场变为静态, 非常数场变成非静态场, 类变成接口, 接口变成类, 如果你添加了一个新的字段到类/接口(或者添加了一个新的超类/超接口),那么一个来自客户端类C的超接口的静态字段可能会隐藏一个从C的超类继承的添加的字段(具有相同的名称)(非常罕见的情况)。

注意:还有许多其他由其他不兼容的更改引起的异常:NoSuchFieldError, NoSuchMethodError, IllegalAccessError, InstantiationError, VerifyError, NoClassDefFoundError和AbstractMethodError。

关于BC更好的论文是由Jim des Rivières撰写的“进化基于java的API 2:实现API二进制兼容性”。

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

japi-compliance-checker clirr japitools sigtest japi-checker

在你的库中使用日语遵从检查器:

japi-compliance-checker OLD.jar NEW.jar

clir工具的使用:

java -jar clirr-core-0.6-uber.jar -o OLD.jar -n NEW.jar

好运!

While these answers are all correct, resolving the problem is often more difficult. It's generally the result of two mildly different versions of the same dependency on the classpath, and is almost always caused by either a different superclass than was originally compiled against being on the classpath or some import of the transitive closure being different, but generally at class instantiation and constructor invocation. (After successful class loading and ctor invocation, you'll get NoSuchMethodException or whatnot.)

如果这种行为看起来是随机的,那么它很可能是多线程程序类根据最先遇到的代码加载不同的传递依赖项的结果。

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

用Maven解决重复的jar最好是结合Maven下的Maven - Dependency - Plugin和Maven -enforcer- Plugin(或SBT的Dependency Graph Plugin),然后将这些jar添加到顶级POM的一部分或作为SBT中的导入依赖元素(以删除这些依赖)。

好运!