Что еще может бросить ClassCastException в java?

Это вопрос интервью.

Я не могу спросить интервьюера, так как я не получил работу.

сценарий:

  • поместите объект класса C1 в кэш с ключом "a"

последующий код:

C1 c1FromCache = (C1) cache.get("a");

этот код создает исключение ClassCastException.

что может причины быть?

Я сказал, потому что кто-то другой поставить другой объект с тем же ключом и поэтому переписал его. Мне сказали нет, подумайте о других возможностях.

Я сказал, что, возможно, jar, определяющий класс C1, не был доступен на этом узле (не уверен, приведет ли это к классу cast или ClassNotFoundException, но я хватался за любой свинец сейчас. Тогда я сказал, Может быть, неправильный вариант класса? Сказали же баночку класса С1 есть во всех узлы.)

Изменить/ Добавить спросил, бросает ли get ClassCast, но ему сказали нет. после этого я сказал ему, что мое действие для решения такой проблемы будет заключаться в том, чтобы бросить тест jsp, который будет имитировать действия и поместить лучшее ведение журнала (трассировку стека) после исключения. это была 2-я часть вопроса (почему и что бы вы сделали, если бы это произошло в производстве)

у кого-нибудь есть любые идеи о том, почему кэш get приведет к броску проблема?

4 ответов


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

ответ на редактирование:

что бы вы сделали, если бы это произошло в производстве?

обычно это происходит, когда модули чтения и вставки включают в себя одну и ту же банку, содержащую C1.
Поскольку большинство контейнеров сначала пытаются Родительский загрузчик классов, а затем локальный загрузчик классов (родитель "первый"!--16--> strategy), общим решением проблемы является загрузка класса в ближайший общий родитель для модулей вставки и чтения.
Если вы переместите модуль, содержащий C1 class в родительский модуль, вы заставляете оба подмодуля получать класс от родителя, удаляя любые различия classloader.


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

рассмотрим следующий пример иерархии.

SystemClassloader <--- AppClassloader <--+--- Classloader1
                                         |
                                         +--- Classloader2

я думаю, что в целом верно следующее, Но пользовательские загрузчики классов могут быть написаны, которые отклоняются от этого.

  • экземпляры классов, загруженных SystemClassloader, доступны в любом из загрузчиков классов контексты.
  • экземпляры классов, загруженных AppClassloader, доступны в любом из контекстов classloader.
  • экземпляры классов, загруженных Classloader1, недоступны Classloader2.
  • экземпляры классов, загруженных Classloader2, недоступны Classloader1.

как уже упоминалось, общим сценарием, где это происходит, является развертывание веб-приложений, где вообще говоря AppClassloader близко напоминает путь к классам настроенные в appserver, а затем Classloader1 и Classloader2 представляют пути к классам индивидуально развернутых веб-приложений.

если несколько веб-приложений развертывают одни и те же банки/классы, то ClassCastException может произойти, если есть какой-либо механизм для веб-приложений для совместного использования объектов, таких как кэш или общий сеанс.

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

один из способов избежать этой проблемы в производстве-переместить банки выше в иерархии загрузчика классов. Поэтому вместо того, чтобы включать один и тот же JAR в каждое веб-приложение, лучше включить их в путь к классам appserver. При этом классы загружаются только один раз и доступны для всех веб-приложений.

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

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

на a.jar упакуйте следующие два класса,A и MyRunnable. Они загружаются несколько раз двумя независимыми загрузчиками классов.

package classloadertest;

public class A {
    private String value;

    public A(String value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "<A value=\"" + value + "\">";
    }
}

и

package classloadertest;

import java.util.concurrent.ConcurrentHashMap;

public class MyRunnable implements Runnable {
    private ConcurrentHashMap<String, Object> cache;
    private String name;

    public MyRunnable(String name, ConcurrentHashMap<String, Object> cache) {
        this.name = name;
        this.cache = cache;
    }

    @Override
    public void run() {
        System.out.println("Run " + name + ": running");

        // Set the object in the cache
        A a = new A(name);
        cache.putIfAbsent("key", a);

        // Read the object from the cache which may be differed from above if it had already been set.
        A cached = (A) cache.get("key");
        System.out.println("Run " + name + ": cache[\"key\"] = " + cached.toString());
    }
}

независимо от классов выше запустите следующую программу. Он не должен делиться classpath с вышеуказанными классами, чтобы убедитесь, что они загружены из файла JAR.

package classloadertest;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.ConcurrentHashMap;

public class Main {
    public static void run(String name, ConcurrentHashMap<String, Object> cache) throws Exception {
        // Create a classloader using a.jar as the classpath.
        URLClassLoader classloader = URLClassLoader.newInstance(new URL[] { new File("a.jar").toURI().toURL() });

        // Instantiate MyRunnable from within a.jar and call its run() method.
        Class<?> c = classloader.loadClass("classloadertest.MyRunnable");
        Runnable r = (Runnable)c.getConstructor(String.class, ConcurrentHashMap.class).newInstance(name, cache);
        r.run();
    }

    public static void main(String[] args) throws Exception {
        // Create a shared cache.
        ConcurrentHashMap<String, Object> cache = new ConcurrentHashMap<String, Object>();

        run("1", cache);
        run("2", cache);
    }
}

при запуске этого отображается следующий вывод:

Run 1: running
Run 1: cache["key"] = <A value="1">
Run 2: running
Exception in thread "main" java.lang.ClassCastException: classloadertest.A cannot be cast to classloadertest.A
        at classloadertest.MyRunnable.run(MyRunnable.java:23)
        at classloadertest.Main.run(Main.java:16)
        at classloadertest.Main.main(Main.java:24)

я поставил источник на GitHub как хорошо.


и, наконец, кто-то взломал String intern таблица для строки "a".

см. пример того, как это можно сделать здесь.


ну, может быть, потому, что C1 является абстрактным классом, и функция get также возвращает объект(подкласса C1, конечно), который был приведен к C1 перед возвращением?