59 votes

Quoi d'autre peut lancer une ClassCastException en java ?

C'est une question d'entretien.

L'entretien est terminé, mais cette question est toujours présente dans mon esprit.

Je ne peux pas demander à l'interviewer, car je n'ai pas obtenu le poste.

Scénario :

  • mettre l'objet de classe C1 dans un cache avec la clé "a".

Code ultérieur :

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

Ce code lève une ClassCastException.

Quelles peuvent être les raisons ?

J'ai dit parce que quelqu'un d'autre put un autre objet avec la même clé et l'a donc écrasé. On m'a dit non, pensez à d'autres possibilités.

J'ai dit que le bocal définissant la classe C1 n'était peut-être pas disponible sur ce nœud (je ne sais pas si cela entraînerait un cast de classe ou une ClassNotFoundException, mais j'étais à la recherche d'une piste maintenant. Ensuite, j'ai dit que la version de la classe n'était peut-être pas la bonne ? Ils ont dit que le même jar de la classe C1 est présent dans tous les nœuds).

Modifier/ Ajouter J'ai demandé si le get lançait le ClassCast, mais on m'a répondu que non. Après cela, je lui ai dit que mon action pour résoudre un tel problème serait de déposer un jsp de test qui imiterait les actions et mettrait une meilleure journalisation (stack trace) après l'exception. C'était la deuxième partie de la question (pourquoi et que feriez-vous si cela se produisait en production).

Est-ce que quelqu'un d'autre a une idée sur la raison pour laquelle une cachette obtenir entraînerait un problème de distribution ?

52voto

Keppil Points 28356

Une raison pourrait être que la partie du code qui insère l'objet utilise un classloader différent de celui du code qui le récupère.
Une instance d'une classe ne peut pas être castée vers la même classe chargée par un classloader différent.

Réponse à l'édition :

Que feriez-vous si cela se produisait en production ?

Cela se produit généralement lorsque les modules de lecture et d'insertion comprennent tous deux le même jar contenant C1 .
Étant donné que la plupart des conteneurs essaient d'abord le chargeur de classe parent, puis le chargeur de classe local (l'élément Les parents d'abord ), la solution courante au problème consiste à charger la classe dans le parent commun le plus proche des modules d'insertion et de lecture.
Si vous déplacez le module contenant le C1 au module parent, vous forcez les deux sous-modules à obtenir la classe du parent, en supprimant toute différence de classloader.

31voto

Ed Plese Points 1218

El ClassCastException peut se produire si la même classe a été chargée par plusieurs classloaders différents et que des instances de ces classes sont partagées entre eux.

Considérons l'exemple de hiérarchie suivant.

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

Je pense qu'en général, les points suivants sont vrais, mais il est possible d'écrire des classloaders personnalisés qui s'en écartent.

  • Les instances des classes chargées par SystemClassloader sont accessibles dans n'importe lequel des contextes du classloader.
  • Les instances des classes chargées par l'AppClassloader sont accessibles dans n'importe lequel des contextes du classloader.
  • Les instances des classes chargées par Classloader1 ne sont pas accessibles par Classloader2.
  • Les instances des classes chargées par Classloader2 ne sont pas accessibles par Classloader1.

Comme nous l'avons mentionné, un scénario courant où cela se produit est celui des déploiements d'applications web où, en général, l'AppClassloader ressemble beaucoup au classpath configuré dans l'appserver et où les Classloader1 et Classloader2 représentent les classpaths des applications web déployées individuellement.

Si plusieurs applications web déploient les mêmes JARs/classes, alors l'option ClassCastException peut se produire s'il existe un mécanisme permettant aux applications web de partager des objets tels qu'un cache ou une session partagée.

Un autre scénario similaire peut se produire si les classes sont chargées par l'application Web et que les instances de ces classes sont stockées dans la session ou le cache de l'utilisateur. Si la web app est redéployée, ces classes sont rechargées par un nouveau classloader et toute tentative d'accès aux objets de la session ou du cache entraînera cette exception.

Une méthode pour éviter ce problème en production est de déplacer les JARs plus haut dans la hiérarchie du classloader. Ainsi, au lieu d'inclure le même JAR dans chaque application Web, il est préférable de les inclure dans le classpath de l'appserver. De cette façon, les classes ne sont chargées qu'une seule fois et sont accessibles à toutes les applications Web.

Une autre méthode pour éviter cela est d'opérer uniquement sur les interfaces que les objets partagés. Les interfaces doivent alors être chargées plus haut dans la hiérarchie du chargeur de classes, mais pas les classes elles-mêmes. Votre exemple de récupération de l'objet depuis le cache serait le même, mais l'interface C1 serait remplacée par une interface qui C1 met en œuvre.

Vous trouverez ci-dessous un exemple de code qui peut être exécuté indépendamment pour recréer ce scénario. Ce n'est pas le plus concis et il y a certainement de meilleures façons de l'illustrer, mais il lève l'exception pour les raisons mentionnées ci-dessus.

Sur a.jar package les deux classes suivantes, A et MyRunnable . Ceux-ci sont chargés plusieurs fois par deux classloaders indépendants.

package classloadertest;

public class A {
    private String value;

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

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

Et

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());
    }
}

Indépendamment des classes ci-dessus, le programme suivant est exécuté. Il ne doit pas partager un classpath avec les classes ci-dessus pour s'assurer qu'elles sont chargées à partir du fichier 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);
    }
}

En exécutant cette opération, le résultat suivant s'affiche :

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)

J'ai mis la source sur GitHub également.

3voto

OldCurmudgeon Points 16615

Et enfin, quelqu'un a piraté le String intern pour la chaîne de caractères "a" .

Voir un exemple de la façon dont cela peut être fait ici .

0voto

shiladitya Points 718

Peut-être parce que C1 est une classe abstraite, et que la fonction get renvoie également un objet (d'une sous-classe de C1 bien sûr) qui a été casté en C1 avant de revenir ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X