当前位置: 首页 > 知识库问答 >
问题:

还有什么可以在java中引发ClassCastException?

陶成济
2023-03-14

这是一个面试问题。

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

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

脚本:

  • 将类C1的对象放入具有键“a”的缓存中

后代码:

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

此代码引发ClassCastException。

原因是什么?

我说是因为别人用同样的键放了另一个对象,所以覆盖了它。我被告知不,想想其他可能性。

我说定义类C1的jar可能在此节点上不可用(不确定这是否会导致类强制转换或类NotFoundException,但我现在正在寻找线索。然后我说可能是类的版本错误?他们说所有节点中都有相同的类C1 jar)。

编辑/添加被问及get是否正在抛出ClassCast,但被告知否。之后,我告诉他我解决此类问题的行动将是放入一个测试jsp,该测试jsp将模仿操作并在异常后放置更好的日志记录(堆栈跟踪)。这是问题的第二部分(如果这发生在生产中,为什么以及你会怎么做)

还有人知道为什么缓存获取会导致强制转换问题吗?

共有3个答案

饶谦
2023-03-14

最后,有人破解了字符串"a"String实习生表。

请在此处查看如何完成的示例。

宗政德宇
2023-03-14

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

考虑以下示例层次结构。

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

我认为以下是正确的,但是可以编写偏离这一点的自定义类加载器。

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

如前所述,发生这种情况的一个常见场景是Web应用程序部署,通常来说AppClassloader与appserver中配置的类路径非常相似,然后Classloader1和Classloader2代表单独部署的Web应用程序的类路径。

如果多个web应用程序部署相同的JAR/类,那么如果web应用程序有任何机制来共享对象,例如缓存或共享会话,则可能会出现类异常。

另一种类似的情况是,类由web应用加载,并且这些类的实例存储在用户会话或缓存中。如果重新部署web应用程序,则这些类将由新的类加载器重新加载,并且尝试从会话或缓存访问对象将引发此异常。

在生产中避免此问题的一种方法是将JAR在类加载器层次结构中向上移动。因此,与其在每个Web应用程序中包含相同的JAR,不如将它们包含在应用服务器的类路径中。通过这样做,类只加载一次,并且所有Web应用程序都可以访问。

另一种避免这种情况的方法是只在共享对象的接口上操作。然后需要在类加载器层次结构中向上加载接口,但类本身不需要。从缓存中获取对象的示例与此相同,但C1类将被实现的接口所取代。

下面是一些可以独立运行以重新创建此场景的示例代码。它不是最简洁的,当然可能有更好的方法来说明它,但出于上述原因,它确实会引发异常。

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

独立于上述类运行以下程序。它不能与上述类共享类路径,以确保它们是从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上发布了源代码。

姚培
2023-03-14

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

对编辑的响应:

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

当读取和插入模块都包含相同的包含C1的jar时,通常会发生这种情况<由于大多数容器首先尝试父类加载器,然后尝试本地类加载器(父类优先策略),因此该问题的常见解决方案是将类加载到距离插入和读取模块最近的公共父类中<如果将包含C1类的模块移动到父模块,则会强制两个子模块从父模块获取类,从而消除任何类加载器差异。

 类似资料:
  • Mybatis 和 Hibernate 各有各的优点吧,感觉用久了有点腻了。 查了一下,还有一种基于 JOOQ 的查询,像写 C# 的 LINQ 一样用 Java 来写 SQL,站内有一个介绍看了感觉还行 https://segmentfault.com/a/1190000045244062 所以 JOOQ、Hibernate、Mybatis Java 应该就是这三种 Dao Layer 的框架了

  • 材料设计非常强调“纸张”的隐喻。要做到这一点,阴影是必不可少的。由于材料设计是一种理念,而不是API(尽管它内置在L中),因此应该在任何地方(Windows窗体、HTML/CSS等)进行设计。如何在Android API 14到20中做到这一点? 请注意,对于圆形和其他非方形形状,预制PNG实际上并不实用。

  • 问题内容: 为什么这段代码没有抛出?看一看: 我不知道! 问题答案: 您为什么不能自己检查一下并抛出异常(如果您要的话)。

  • 问题内容: 用Google搜索它,发现了大量代码。但是他们中的任何一个都给了我我想要的。我想使一个普通数组不可变。我尝试了这个: 但这不起作用,我可以将5分配给array [0] …有什么方法可以使该数组不可变? 问题答案: 如果要将 其用作数组 ,则不能。 您必须为其创建包装器,以便在上引发一个异常,但是没有足够的环绕将允许您在以下情况上引发异常: 当然,元素的不变性完全是另一回事! 注意: 为

  • 问题内容: 运行此命令时: 响应为: 这对我来说真是太棒了。我本以为这会带来编译时错误。 为什么我可以在Java中抛出null,为什么将其转换为NullPointerException? (实际上,鉴于我抛出的是null,因此我不知道这是否是“ upcast”) 除了一个非常愚蠢的面试问题(请没人在面试中问这个问题)之外,我看不到任何理由。也许您想被解雇,但这就是…我的意思是,为什么还会有人呢?

  • 已弃用。这种方法本质上是不安全的。使用thread.Stop停止线程会导致它解锁它锁定的所有监视器(作为未检查的ThreadDeath异常向堆栈上传播的自然结果)。如果以前由这些监视器保护的任何对象处于不一致的状态,则损坏的对象将对其他线程可见,从而可能导致任意行为。stop的许多用法应该被简单修改某个变量以指示目标线程应该停止运行的代码所取代。目标线程应该定期检查这个变量,如果变量指示它要停止运