Що викликає java.lang.IncompatibleClassChangeError?


218

Я пакую бібліотеку Java як JAR, і вона кидає багато java.lang.IncompatibleClassChangeErrorс, коли я намагаюся викликати методи з неї. Ці помилки здаються випадковими. Які проблеми можуть спричинити цю помилку?


У проекті Eclipse, де я тестував Apache FOP 1.0 та Barcode4J, додаткові бібліотеки, що постачаються разом із Barcode4J, були, очевидно, перевершують ті, що поставляються з FOP (деякі мали більший номер версії). Це дуже обережно, що ви ставите на шляху складання / classpath.
Wivani

Відповіді:


170

Це означає, що ви внесли деякі несумісні бінарні зміни в бібліотеку, не перекомпілюючи код клієнта. Спеціалізація мови Java §13 детально описує всі такі зміни, особливо важливо, змінюючи неприватні staticполя / методи, які мають бути, staticабо навпаки.

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

ОНОВЛЕННЯ: Якщо ви публікуєте публічну бібліотеку, вам слід уникати внесення невідповідних бінарних змін, наскільки це можливо, щоб зберегти те, що відомо як "бінарна сумісність назад". Одночасне оновлення банок залежностей в ідеалі не повинно порушувати додаток чи збірку. Якщо вам доведеться порушити сумісність двійкової біржі , рекомендується збільшити основний номер версії (наприклад, з 1.xy до 2.0.0), перш ніж випускати зміни.


2
Чомусь тут у розробників виникає проблема, коли перекомпіляція коду клієнта не точно вирішує проблему. З якоїсь причини, якщо вони редагують файл там, де він відбувається, і перекомпілюють, помилка більше не виникає там, але більше випадково з’явиться в іншому місці проекту, на яке посилається бібліотека. Мені цікаво, що могло би викликати це.
Зомбі

5
Ви намагалися зробити чисту збірку (видалити всі *.classфайли) та перекомпілювати? Редагування файлів має подібний ефект.
notnoop

Немає динамічно генерованого коду .... якщо ви не вважаєте JSP таким. Ми намагалися видалити файли класу, і це, здається, не допомогло. Крихітна річ у тому, що для мене це просто не здається, але це трапляється для іншого розробника.
Зомбі

2
Чи можете ви переконатися, що, коли ви робите чисту збірку, ви збираєтеся з тими ж банками, з якими ви працюєте?
notnoop

Чи є щось, що стосується 32-бітної або 64-бітної платформи, я виявив помилку лише в 64-бітній платформі.
ковбой-пенг

96

Ваша щойно упакована бібліотека не є бінарною сумісністю назад (BC) зі старою версією. З цієї причини виняток можуть кинути деякі клієнти бібліотеки, які не перекомпільовані.

Це повний перелік змін у API бібліотеки Java, які можуть призвести до того, що клієнти, створені зі старою версією бібліотеки, кидають java.lang. IncompatibleClassChangeError, якщо вони працюють на новому (тобто, порушуючи BC):

  1. Нефінальне поле стає статичним,
  2. Нестатеве поле стає нестатичним,
  3. Клас стає інтерфейсом,
  4. Інтерфейс стає класовим,
  5. якщо ви додаєте нове поле до класу / інтерфейсу (або додаєте новий суперкласс / суперінтерфейс), то статичне поле із суперінтерфейсу клієнтського класу C може приховати додане поле (з тим самим іменем), успадковане від суперклас С (дуже рідкісний випадок).

Примітка . Існує багато інших винятків, викликаних іншими несумісними змінами: NoSuchFieldError , NoSuchMethodError , IllegalAccessError , InstantiationError , VerifyError , NoClassDefFoundError та AbstractMethodError .

Кращий документ про BC - це "Еволюціонуючі Java-API 2: Досягнення бінарної сумісності API", написаний Джим де Рів'єром.

Існують також деякі автоматичні інструменти для виявлення таких змін:

Використання перевірки відповідності japi для вашої бібліотеки:

japi-compliance-checker OLD.jar NEW.jar

Використання інструменту clirr:

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

Удачі!


59

Хоча ці відповіді правильні, вирішити проблему часто складніше. Це, як правило, результат двох м'яко різних версій однієї і тієї ж залежності від classpath, і майже завжди викликається або іншим надкласом, ніж спочатку був складений проти того, щоб бути на classpath, або якийсь імпорт перехідного закриття відрізнявся, але, як правило, у класі інстанція та виклик конструктора. (Після успішного завантаження класу та виклику ctor ви отримаєте NoSuchMethodExceptionчи що.)

Якщо поведінка виявляється випадковою, ймовірно, це результат багатопотокової програми, що завантажує різні перехідні залежності, залежно від того, який код потрапив першим.

Щоб вирішити ці проблеми, спробуйте запустити VM -verboseв якості аргументу, а потім подивіться класи, які завантажувались, коли відбувається виняток. Ви повинні побачити деяку дивовижну інформацію. Наприклад, маючи кілька копій однієї і тієї ж залежності та версії, яких ви ніколи не очікували або прийняли б, якби знали, що вони включаються.

Розв’язування дублікатів баночок за допомогою Maven найкраще проводити за допомогою комбінації плагіну Maven-залежності та maven-Execucer-плагіну під Maven (або плагіну графіку залежності залежності від SBT , потім додаючи ці банки до розділу вашого POM вищого рівня або як імпортної залежності елементи в SBT (для усунення цих залежностей).

Удачі!


5
Докладний аргумент допоміг мені визначити, які банки створювали проблеми. Спасибі
Вірат Кадару

6

Я також виявив, що, використовуючи 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...
        }
    }
}

1
Дякую за підказку. У мене була така ж проблема при виклику методу Java з поставленим неправильним екземпляром (цей).
sstn

5

У мене був той самий випуск, і пізніше я зрозумів, що я запускаю додаток на Java версії 1.4, тоді як додаток збирається на версії 6.

Власне, причина полягала в тому, що бібліотека-дублікат була одна, одна розташована у класі class, а інша - всередині файлу jar, який знаходиться в межах classpath.


Як ти дійшов до цього? Я впевнений, що у мене є подібна проблема, але не маю поняття, як простежити, яка бібліотека залежностей дублюється.
beterthanlife

@beterthanlife написати сценарій, який шукає все файли jar, які шукають дублювані класи (пошук за їх повною мірою, тобто за назвою пакета) :)
Eng.Fouad

ОК, використовуючи NetBeans і maven-shadow, я думаю, що я бачу всі класи, які дублюються, і файли jar, в яких вони перебувають. Багато з цих файлів jar я не посилався безпосередньо в моєму pom.xml, тому я припускаю, що вони повинні бути включеним моїми залежностями. Чи існує простий спосіб дізнатися, яка залежність включає їх, не проходячи через файл пам’яті кожного із залежностей? (вибачте, будь ласка, мою нобізгість, Im java Virgin!)
beterthanlife

@beterthanlife Краще задайте нове запитання щодо цього :)
Eng.Fouad

Я знайшов дублюючу банку, переглянувши графік залежностей від gradle (./gradlew: <ім'я>: залежності). Тож я знайшов винуватця, який залучив до проекту стару версію lib.
nyx

1

Інша ситуація, коли ця помилка може з’явитися, - це з покриттям коду Emma.

Це відбувається під час присвоєння Об'єкту інтерфейсу. Я думаю, це має щось спільне з тим, що Об'єкт інструментується і вже не є бінарним сумісним.

http://sourceforge.net/tracker/?func=detail&aid=3178921&group_id=177969&atid=883351

На щастя, цієї проблеми не виникає з Cobertura, тому я додав плагін cobertura-maven у свої плагіни для звітування мого pom.xml


1

Я стикався з цим питанням під час нерозгортання та перерозподілу війни зі скляними рибками. Моя структура класу була такою,

public interface A{
}

public class AImpl implements A{
}

і це було змінено на

public abstract class A{
}

public class AImpl extends A{
}

Після зупинки та перезавантаження домену він вийшов чудово. Я використовував рибку 3.1.43


1

У мене є веб-додаток, який прекрасно розгортається на tomcat моєї локальної машини (8.0.20). Однак, коли я помістив його в середовище qa (tomcat - 8.0.20), він продовжував надавати мені IncompatibleClassChangeError, і він скаржився, що я поширююсь на інтерфейс. Цей інтерфейс було змінено на абстрактний клас. І я склав уроки для батьків та дітей, і все ще продовжував отримувати те саме питання. Нарешті, я хотів налагоджувати помилку, тому я змінив версію на батьківській програмі на x.0.1-SNAPSHOT, а потім скомпілював усе і зараз це працює. Якщо хтось все-таки відчуває проблему після виконання відповідей, наведених тут, переконайтесь, що версії вашого pom.xml також є правильними. Змініть версії, щоб побачити, чи це працює. Якщо так, то виправте проблему з версією.


1

Я вважаю, що моя відповідь буде конкретна Intellij.

Я був відновлений в чистоті, навіть забираючи вручну видалення "назовні" та "цільових" брудів. Intellij має "недійсні кеші та перезавантаження", які іноді видаляє непарні помилки. Цього разу це не вийшло. Усі версії залежності виглядали правильно в меню налаштувань проекту-> модулі.

Остаточна відповідь полягала в тому, щоб вручну видалити свою залежність проблеми з мого місцевого репорта Maven. Винна винуватця старої версії Bouncycastle (я знав, що щойно змінив версії, і це буде проблема), і хоча стара версія не з'явилася там, де будується, вона вирішила мою проблему. Я використовував intellij версії 14, а потім оновив до 15 під час цього процесу.


1

У моєму випадку я зіткнувся з цією помилкою таким чином. pom.xmlмого проекту визначили дві залежності Aта B. І обидві, Aі Bвизначені залежності від одного артефакту (називайте його C), але різних його версій ( C.1і C.2). Коли це відбувається, для кожного класу в Cmaven можна вибрати лише одну версію класу з двох версій (під час створення uber-jar ). Він вибере "найближчу" версію на основі правил посередництва залежностей і видасть попередження "У нас дублікат класу ..." Якщо метод / підпис класу змінюється між версіями, це може спричинити java.lang.IncompatibleClassChangeErrorвиняток, якщо неправильна версія використовується під час виконання.

Розширений: Якщо Aтреба використовувати v1 of Cі Bmust use v2 of C, тоді ми повинні переїхати C в Aі B'poms, щоб уникнути конфлікту класів (у нас є дублювання попередження про клас) при складанні остаточного проекту, який залежить і від, Aі від B.


1

У моєму випадку помилка з’явилася, коли я додав com.nimbusdsбібліотеку у свою програму, розгорнуту в Websphere 8.5.
Виник нижче:

Викликано: java.lang.IncompatibleClassChangeError: org.objectweb.asm.AnnotationVisitor

Рішення полягало в тому, щоб виключити банку з посиланням з бібліотеки:

<dependency>
    <groupId>com.nimbusds</groupId>
    <artifactId>nimbus-jose-jwt</artifactId>
    <version>5.1</version>
    <exclusions>
        <exclusion>
            <artifactId>asm</artifactId>
            <groupId>org.ow2.asm</groupId>
        </exclusion>
    </exclusions>
</dependency>

0

Перевірте, чи ваш код не складається з двох проектів модулів, які мають однакові назви класів та визначення пакетів. Наприклад, це може статися, якщо хтось використовує copy-paste для створення нової реалізації інтерфейсу на основі попередньої реалізації.


0

Якщо це запис можливих випадків цієї помилки, тоді:

Я щойно отримав цю помилку на WAS (8.5.0.1), під час завантаження пружини (3.1.1_release) CXF (2.6.0), де BeanInstantiationException згорнув CXF ExtensionException, згортаючи IncompatibleClassChangeError. Наступний фрагмент показує суть сліду стека:

Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [org.apache.cxf.bus.spring.SpringBus]: Constructor threw exception; nested exception is org.apache.cxf.bus.extension.ExtensionException
            at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:162)
            at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:76)
            at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:990)
            ... 116 more
Caused by: org.apache.cxf.bus.extension.ExtensionException
            at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:167)
            at org.apache.cxf.bus.extension.Extension.getClassObject(Extension.java:179)
            at org.apache.cxf.bus.extension.ExtensionManagerImpl.activateAllByType(ExtensionManagerImpl.java:138)
            at org.apache.cxf.bus.extension.ExtensionManagerBus.<init>(ExtensionManagerBus.java:131)
            [etc...]
            at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:147)
            ... 118 more

Caused by: java.lang.IncompatibleClassChangeError: 
org.apache.neethi.AssertionBuilderFactory
            at java.lang.ClassLoader.defineClassImpl(Native Method)
            at java.lang.ClassLoader.defineClass(ClassLoader.java:284)
            [etc...]
            at com.ibm.ws.classloader.CompoundClassLoader.loadClass(CompoundClassLoader.java:586)
            at java.lang.ClassLoader.loadClass(ClassLoader.java:658)
            at org.apache.cxf.bus.extension.Extension.tryClass(Extension.java:163)
            ... 128 more

У цьому випадку рішенням було змінити порядок курсу модуля в моєму файлі війни. Тобто, відкрийте програму війни на консолі WAS під та виберіть клієнтський модуль. У конфігурації модуля встановіть завантаження класів "батьківським останнім".

Це знайдено в консолі WAS:

  • Applicationsatoins -> Типи додатків -> WebSphere Enterprise Applications
  • Клацніть посилання, що представляє вашу заявку (війна)
  • Клацніть "Керувати модулями" у розділі "Модулі"
  • Клацніть посилання для базових модулів
  • Змініть "Порядок завантажувача класу" на "(батьківський останній)".

0

Документування іншого сценарію після спалювання занадто багато часу.

Переконайтеся, що у вас немає банку залежностей, на якому є клас із приміткою EJB.

У нас був спільний файл з баночкою, який мав @localпримітку. Пізніше цей клас був перенесений із цього спільного проекту та до нашого основного проекту банку jar. Наша банка ejb і наша звичайна банка обидва є в вусі. Не було оновлено версію нашої загальної залежності від банок. Таким чином, 2 класи намагаються бути чимось із несумісними змінами.


0

Все вищесказане - з будь-якої причини я робив якийсь великий рефактор і починав отримувати це. Я перейменував пакет, в якому знаходився інтерфейс, і це очистило його. Сподіваюся, що це допомагає.


0

Чомусь той самий виняток також кидається при використанні JNI та передачі аргументу jclass замість завдання для виклику a Call*Method().

Це схоже на відповідь Огрського псалму33.

void example(JNIEnv *env, jobject inJavaList) {
    jclass class_List = env->FindClass("java/util/List");

    jmethodID method_size = env->GetMethodID(class_List, "size", "()I");
    long size = env->CallIntMethod(class_List, method_size); // should be passing 'inJavaList' instead of 'class_List'

    std::cout << "LIST SIZE " << size << std::endl;
}

Я знаю, що відповісти на це питання трохи пізно через 5 років після запитання, але це один із найкращих звернень під час пошуку, java.lang.IncompatibleClassChangeErrorтому я хотів задокументувати цей особливий випадок.


0

Додавання моїх 2 копійок. Якщо ви використовуєте scala та sbt та scala-logging як залежність, то це може статися тому, що попередня версія scala-logging мала назву scala-logging-api.So; по суті, роздільна здатність залежності не відбувається через різні імена, що призводять до помилок виконання під час запуску програми scala.


0

Додаткова причина цієї проблеми - якщо ви Instant Runввімкнули Android Studio.

Виправлення

Якщо ви виявите, що починаєте отримувати цю помилку, вимкніть її Instant Run.

  1. Основні налаштування Android Studio
  2. Побудова, виконання, розгортання
  3. Миттєвий запуск
  4. Зніміть прапорець "Увімкнути миттєвий запуск ..."

Чому?

Instant Runпід час розробки модифікує велику кількість речей, щоб швидше надавати оновлення вашому запущеному додатку. Звідси миттєвий запуск. Коли це працює, це дійсно корисно. Однак, коли виникла така проблема, як це, найкраще зробити, це вимкнути її Instant Runдо появи нової версії Android Studio.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.