Java 异常:IncompatibleClassChangeError

1、概览

本文将带你了解 Java 中的 IncompatibleClassChangeError 异常,这是一种运行时异常,当 JVM 检测到类的更改与之前加载的类不兼容时就会发生。

本文将带你了解出现这个异常的原因以及解决办法。

2、IncompatibleClassChangeError 类

IncompatibleClassChangeError 是 Java 中的一种 LinkageError。该异常通常表示一个或多个依赖类出现了问题。

IncompatibleClassChangeError(不兼容类变更错误)是 LinkageError 的子类,当一个或多个从属类的类定义发生不兼容变更时会导致该异常。

注意,这是 Error 的子类,因此不应该试图 catch 这些异常,因为这意味着应用程序或运行时出现异常需要进行处理。

3、异常的产生

接下来,让我们模拟一种会导致 IncompatibleClassChangeError 的情况。

3.1、预定义一个第三方库

首先创建一个简单的库(项目),其中有一个父类 Dinosaur 和一个子类 Coelophysis,后者继承自 Dinosaur

public class Dinosaur {
    public void species(String sp) {
        if(sp == null) {
            System.out.println("I am a generic Dinosaur");
        } else {
            System.out.println(sp);
        }
    }
}

public class Coelophysis extends Dinosaur {
    public void mySpecies() {
        species("My species is Coelophysis of the Triassic Period");
    }

    public static void main(String[] args) {
        Coelophysis coelophysis = new Coelophysis();
        coelophysis.mySpecies();
    }
}

注意,父类中的 species() 方法是非 static 的。

3.2、生成 JAR

就绪后,运行 mvn package ,并从该项目生成一个 jar 文件。

如果我们创建一个 Coelophysis 类的实例并调用 species() 方法,它就能正常运行并输出符合预期的结果:

➜ javac Coelophysis.java
➜ java Coelophysis
My species is Coelophysis of the Triassic Period

3.3、创建第三方库的第二个版本

接下来,创建另一个库,它与父类 Dinosaur 类似,但版本略有不同,包括一个静态 species() 方法:

public class Dinosaur {
    public Dinosaur() {
    }

    public static void species(String sp) {
        if (sp == null) {
            System.out.println("I am a generic Dinosaur");
        } else {
            System.out.println(sp);
        }
    }
}

为这个项目也创建一个 jar,然后使用 Maven 将它们都导入到我们的客户端项目中:

<dependency>
    <groupId>org.dinosaur</groupId>
    <artifactId>dinosaur</artifactId>
    <version>2</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/src/main/java/com/baeldung/incompatibleclasschange/dinosaur-1.jar</systemPath>
</dependency>

3.4、产生异常

现在,当我们通过将修改后的版本作为 classpath 依赖调用 Coelophysis 类时,会出现异常:

➜  java -cp dinosaur-2:dinosaur-1 Coelophysis
Exception in thread "main" java.lang.IncompatibleClassChangeError: Expecting non-static method 'void Dinosaur.species(java.lang.String)'
    at Coelophysis.mySpecies(Coelophysis.java:3)
    at Coelophysis.main(Coelophysis.java:8)

4、导致 IncompatibleClassChangeError 出现的常见原因

Java 中的 IncompatibleClassChangeError 在类与类之间出现二进制不兼容时发生,通常是由于依赖类的定义发生了更改。

可能导致该异常的一些常见情况如下。

4.1、对依赖类或二进制文件的类定义进行更改

考虑子类/父类的情况,对从属子类的某些字段进行更改。这种更改可以是将非 staticprivate 字段或方法更改为 static 的情况。在这种情况下,父类会在运行时生成一个 IncompatibleClassChangeError 异常。

出现这种情况的原因是,JVM 在运行时对预期的一致性引入了干扰。

我们可以在依赖的文件中观察到类似的变化:

  • final 字段变为 static
  • 类变成了接口,反之亦然。
  • 非常量字段变为非 static
  • 依赖类中的方法签名发生了变化。

4.2、继承模式的变化

当子类的继承模式发生禁止的变化时,JVM 也可能抛出异常。这包括在实现接口时未添加所需的抽象方法的重载实现,或错误地实现一个类等情况。

4.3、同一依赖项在类路径中的不同版本

假设我们使用 Maven 进行项目依赖

管理,并在 pom.xml 中定义了两个库 AB,将其包含在 classpath 中。但是,这两个库可能都依赖于第三个库 C 的不同版本。

因此,这两个库尝试将库 C 的不同版本引入 classpath,这些版本在结构上略有不同。

5、处理 IncompatibleClassChangeError 异常

了解了异常的原因后,来看看如何修复和避免它。

每当依赖库或二进制文件发生变化时,都应重新编译客户端代码,以了解其兼容性。必须确保编译时的类定义与运行时的类定义相匹配。因此,保持二进制的向后兼容性对于确保依赖的客户端不会中断是非常重要的

现代 IDE(如 IntelliJ)已经可以检查 classpath 中的依赖关系变化,并对不兼容的变化发出警告。

Maven 等工具还会生成所有依赖项的完整依赖关系图,并在 pom.xml 中突出显示不兼容或破坏性更改。此外,clean build 会自动重新生成所有依赖项的源码,这有助于避免出现这种异常情况。

我们还可以使用 Maven 等构建工具来确保 classpath 中不会出现相同依赖的重复或冲突版本。从 target 文件夹中不断删除过时的类文件也是一种很好的做法,这样可以确保执行时始终有最新的类文件。

6、总结

本文介绍了 Java 中出现 IncompatibleClassChangeError(不兼容类变更错误)异常的原因和解决办法,以及在编译时和运行时保持类结构一致的重要性。


Ref:https://www.baeldung.com/java-incompatibleclasschangeerror