Вопрос-ответ

What causes java.lang.IncompatibleClassChangeError?

Что вызывает java.lang.IncompatibleClassChangeError?

Я упаковываю библиотеку Java как JAR, и она выдает много java.lang.IncompatibleClassChangeErrorошибок, когда я пытаюсь вызвать методы из нее. Кажется, что эти ошибки появляются случайным образом. Какие проблемы могут быть причиной этой ошибки?

Переведено автоматически
Ответ 1

Это означает, что вы внесли некоторые несовместимые двоичные изменения в библиотеку без перекомпиляции клиентского кода. Спецификация языка Java §13 подробно описывает все такие изменения, в первую очередь, изменение не static закрытых полей / методов на static или наоборот.

Перекомпилируйте клиентский код для новой библиотеки, и все будет готово.

ОБНОВЛЕНИЕ: Если вы публикуете общедоступную библиотеку, вам следует избегать внесения несовместимых двоичных изменений, насколько это возможно, чтобы сохранить то, что известно как "обратная совместимость двоичных файлов". В идеале обновление только jars зависимостей не должно нарушать работу приложения или сборки. Если вам действительно нужно нарушить обратную совместимость двоичных файлов, рекомендуется увеличить основной номер версии (например, с 1.x.y до 2.0.0) перед выпуском изменения.

Ответ 2

Ваша недавно упакованная библиотека не имеет обратной двоичной совместимости (BC) со старой версией. По этой причине некоторые из библиотечных клиентов, которые не были перекомпилированы, могут выдавать исключение.

Это полный список изменений в Java library API, которые могут привести к тому, что клиенты, созданные со старой версией библиотеки, будут выбрасывать java.lang.IncompatibleClassChangeError если они запускаются на новом (т. е. Нарушают BC):


  1. Поле, не являющееся конечным, становится статическим,

  2. Непостоянное поле становится нестатическим,

  3. Класс становится интерфейсом,

  4. Интерфейс становится классом,

  5. если вы добавляете новое поле в класс / интерфейс (или добавляете новый суперкласс / суперинтерфейс), то статическое поле из суперинтерфейса клиентского класса C может скрывать добавленное поле (с тем же именем), унаследованное от суперкласса C (очень редкий случай).

Примечание: Существует множество других исключений, вызванных другими несовместимыми изменениями: NoSuchFieldError, nosuchmetoderror, IllegalAccessError, InstantiationError, VerifyError, NoClassDefFoundError и abstractmetoderror.

Лучшая статья о BC - это "Эволюция Java-based API 2: достижение бинарной совместимости API", написанная Джимом де Ривьером.

Также существуют некоторые автоматические инструменты для обнаружения таких изменений:

Использование japi-compliance-checker для вашей библиотеки:

japi-compliance-checker OLD.jar NEW.jar

Использование инструмента clirr:

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

Удачи!

Ответ 3

Хотя все эти ответы правильные, решить проблему часто бывает сложнее. Обычно это результат двух слегка отличающихся версий одной и той же зависимости от пути к классу и почти всегда вызвано либо тем, что суперкласс отличается от того, который был изначально скомпилирован для нахождения в пути к классу, либо отличается какой-либо импорт транзитивного замыкания, но обычно при создании экземпляра класса и вызове конструктора. (После успешной загрузки класса и вызова ctor вы получите NoSuchMethodException или еще что-то.)

Если поведение кажется случайным, это, вероятно, результат того, что класс многопоточной программы загружает различные транзитивные зависимости в зависимости от того, какой код был обнаружен первым.

Чтобы устранить это, попробуйте запустить виртуальную машину с -verbose в качестве аргумента, затем посмотрите на классы, которые загружались при возникновении исключения. Вы должны увидеть неожиданную информацию. Например, наличие нескольких копий одной и той же зависимости и версий, которые вы никогда не ожидали или приняли бы, если бы знали, что они будут включены.

Разрешение дублирующихся jar с помощью Maven лучше всего выполнять с помощью комбинации maven-dependency-plugin и maven-enforcer-plugin в Maven (или плагина для графа зависимостей от SBT), затем добавляя эти jar в раздел вашего POM верхнего уровня или в виде импортированных элементов зависимостей в SBT (для удаления этих зависимостей).

Удачи!

Ответ 4

Я также обнаружил, что при использовании JNI, вызывающем метод Java из C ++, если вы передаете параметры вызываемому методу 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.
}

Пример кода 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...
}
}
}
java jar