还有什么可以在Java中抛出ClassCastException?

这是一个面试问题。

面试结束了,但这个问题还在我脑海里。

我不能问面试官,因为我没有得到这份工作。

场景:

  • 把类C1的对象放到一个caching中,键为“a”

后面的代码:

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

此代码抛出一个ClassCastException。

原因是什么?

我之所以说是因为别人用另一个对象用同样的密钥来覆盖它。 我被告知没有,想到其他的可能性。

我说可能定义类C1的jar在这个节点上是不可用的(不知道这是否会导致类抛出或ClassNotFoundException,但是我现在正在抓取任何一个线索,然后我说可能是错误版本的类?同样的C1类jar子在所有节点都有)。

编辑/添加询问get是抛出ClassCast,但被告知不。 之后,我告诉他我的行动来解决这个问题将放在一个testingjsp,将模仿的行动,并把更好的日志logging(堆栈跟踪)后的例外。 那是问题的第二部分(为什么如果在生产中发生这种情况,你会怎么做)

有没有人有任何想法,为什么一个caching获得将导致铸造问题?

一个原因可能是插入对象的代码部分使用与检索它的代码不同的类加载器。
一个类的实例不能转换成由不同的类加载器加载的同一个类。

对编辑的回应:

如果在生产中发生这种情况,你会怎么做?

这通常发生在读取和插入模块各自包含相同的包含C1jar子时。
由于大多数容器首先尝试父类加载器,然后尝试使用本地类加载器( 父第一个策略),但问题的常见解决scheme是将插入和读取模块中最接近的父类加载到该类中。
如果将包含C1类的模块移动到父模块,则强制这两个子模块从父类中获取类,从而消除所有类加载器的差异。

如果同一个类由多个不同的类加载器加载,并且这些类的实例在它们之间共享, ClassCastException可能发生ClassCastException

考虑下面的示例层次结构。

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

我认为总的来说,下面是真实的,但是可以写出自定义的类加载器。

  • SystemClassloader加载的类的实例可以在任何类加载器上下文中访问。
  • 可以在任何类加载器上下文中访问由AppClassloader加载的类的实例。
  • Classloader2加载的类的实例不能被Classloader2访问。
  • Classloader2加载的类的实例不能被Classloader1访问。

如上所述,发生这种情况的一种常见情况是Web应用程序部署,一般来说,AppClassloader与应用程序服务器中configuration的类path非常相似,然后Classloader1和Classloader2表示单独部署的Web应用程序的类path。

如果多个Web应用程序部署相同的JAR /类,那么如果有任何Web应用程序共享对象(如caching或共享会话)的机制, ClassCastException可能会发生ClassCastException

另一个类似的情况是,如果类是由Web应用程序加载的,并且这些类的实例存储在用户会话或caching中。 如果Web应用程序被重新部署,那么这些类将被新的类加载器重新加载,并尝试从会话中访问对象,否则caching将抛出此exception。

在生产中避免此问题的一种方法是将JAR更高一些地移到类加载器层次结构中。 因此,不要在每个Web应用程序中包含相同的JAR,而是将其包含在应用程序服务器的类path中可能会更好。 通过这样做,这些类只能加载一次,并且可以被所有的Web应用程序访问。

避免这种情况的另一种方法是仅在共享对象的接口上进行操作。 接口需要在类加载器层次结构中更高的地方加载,但是类本身不需要。 你从caching中获取对象的例子将是相同的,但C1类将被C1实现的接口替代。

下面是一些示例代码,可以独立运行以重新创build这种情况。 这不是最简洁的,当然可能有更好的方法来说明它,但它确实抛出了上述原因的例外。

a.jar包中包含以下两个类AMyRunnable 。 这些由两个独立的类加载器多次加载。

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

独立于上面的类运行以下程序。 它不能与上面的类共享类path,以确保它们是从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"的string"a"

看一个如何在这里完成的例子。

那么可能是因为C1是一个抽象类,并且get函数还返回在被返回之前被转移到C1的对象(当然是C1的子类的对象)?