【问题标题】:Type safety with generics in JavaJava中泛型的类型安全
【发布时间】:2013-05-17 08:52:21
【问题描述】:

我在 Java 中遇到了一个我完全无法理解的泛型行为(以我的 .NET 背景)。

public class TestGeneric<T>
{
    public void get (Object arg)
    {
        T temp = (T) arg;

        System.out.println(temp.toString());

        return;
    }
}

TestGeneric<Integer> tg = new TestGeneric<Integer>();
tg.get("Crack!!!");

请告诉我为什么我在 get 中没有得到 ClassCastException,此外,在 Idea 中,我在分配后看到 temp 为 String 并且具有 "Crack!!!" 的值。另外,我怎么能抛出 ClassCastException 呢?我在 Windows 7 x64 上使用 JDK 1.7.0_07。

【问题讨论】:

    标签: java generics


    【解决方案1】:

    您没有收到类转换异常的原因是 Java 泛型是通过类型擦除实现的。与需要对 CLS 进行重大更改的 .NET 泛型不同,Java 泛型完全在编译时处理。在运行时,对T 的强制转换被忽略。为了在运行时检查类型,你需要存储Class&lt;T&gt;,并使用它的方法来检查传入的参数的类型:

    public class TestGeneric<T>
    {
        private Class<T> genClass;
        public TestGeneric(Class<T> t) {genClass = t;}
        public void get (Object arg)
        {
            if (!genClass.isInstance(arg)) {
                throw new ClassCastException();
            }
            T temp = (T) arg;
    
            System.out.println(temp.toString());
    
            return;
        }
    }
    
    TestGeneric<Integer> tg = new TestGeneric<Integer>(Integer.class);
    tg.get("Bang!!!"); // Now that's a real Bang!!!
    

    【讨论】:

    • 您真的“需要”还是有更好的“最佳实践”?我喜欢 nitegazer2003 提到的方法,,因为它明确声明/可见/简洁并且不会弄乱代码体。它还与 C# 中如何限制泛型非常相似。
    • 虽然您已经正确回答了他的问题,但是存在如何正确使用泛型的问题,为什么您需要一个“返回”结束?并且看起来比需要的方式更复杂。我已经添加了关于如何使用泛型的答案。
    【解决方案2】:

    这是因为泛型类型 T 没有定义的边界,所以它被视为一个对象。在这种情况下,将某些内容投射到 T 不会导致 ClassCastException

    但是,如果您的类定义是 public class TestGeneric&lt;T extends Number&gt;,那么如果您将 String 传递给 get(),您将得到 ClassCastException。

    【讨论】:

    • +1 用于回答 OP 关于如何实际获得 ClassCastException 的问题。
    【解决方案3】:

    思考通用代码被“擦除到”的非通用代码是什么样子的,这是一个很有启发性的练习:

    public class TestGeneric
    {
        public void get (Object arg)
        {
            Object temp = (Object) arg;
    
            System.out.println(temp.toString());
    
            return;
        }
    }
    
    TestGeneric tg = new TestGeneric();
    tg.get("Crack!!!"); // should there be any problem?
    

    【讨论】:

    • 很棒的答案!擦除实际作用的具体示例。
    【解决方案4】:

    永远记住 Java 中的泛型是编译时实体。它与运行时无关。让我给你演示一下你的oqn代码。

    public class TestGeneric<T>
    {
        public void get (Object arg)
        {
            T temp = (T) arg;
    
            System.out.println(temp.toString());
            System.out.println(temp.getClass());
    
            return;
        }
    
        public static void main(String args[])
        {
            TestGeneric<Integer> tg = new TestGeneric<Integer>();
            tg.get("Crack!!!");
        }
    }
    

    输出是

    Crack!!!
    class java.lang.String
    

    现在有意义吗? Object 是至高无上的超类。所以由于多态性,它可以得到一个String对象。尽管您正在进行类型转换,或者我会说创建一个指向字符串对象的整数引用点 Java 在内部知道它在运行时是一个 String 对象。如果 toString() 没有在 Integer 类中定义,就会出现问题。您调用的函数必须在引用中定义,但实现将在运行时引用的对象中适当地提取。

    【讨论】:

      【解决方案5】:

      如果你这样做,你甚至不需要检查 ClassCastException,它甚至无法编译。

      public class TestGeneric<T> {
          public void get (T arg){
              System.out.println(arg.toString());
          } 
      }
      
      TestGeneric<Integer> tg = new TestGeneric<Integer>();
      tg.get("Crack!!!");
      

      【讨论】:

        【解决方案6】:

        这是由于type-erasure。这意味着在 Java 中,所有泛型在运行时都减少到 Object。因此,您已将 String 转换为 Object,这完全没问题。而且由于toString 是在Object 上实现的,所以也不例外。

        这是type-erasure上的链接

        真正获得ClassCastException 的唯一方法是将Class&lt;T&gt; 的实例传递给泛型类型,然后执行myClass.isInstance(arg) 并在false 时抛出异常

        【讨论】:

        • 真的是“唯一的方法”吗? nitegazer2003 提到了让我印象深刻的一种更简洁的方法,,因为它已明确声明/可见/简洁,并且不会使代码体混乱。但是 +1 表示“在 Java 中所有泛型都简化为 Object”。
        • 我所说的“唯一方法”是泛型在运行时没有定义的类型。因此,如果期望的行为是抛出 ClassCastException,则必须通过传入 Class&lt;T&gt; 的实例将类型显式提供给泛型,然后可以使用该实例检查类型。您对Number 的评论仅在符合条件的情况下才有效。通用实现中没有任何内容表明它只适用于 Number 的实例。
        【解决方案7】:

        类型Integer 有一个方法toString。其实每个Object都有这个方法,所以ClassCastException不会出现。

        您没有在您的对象上调用任何String 特定的方法,因此没有发生异常。

        原因是在运行时你不会看到类型参数因为type erasure

        关键是在你的代码编译之后你将无法再看到泛型类型参数,因为它们被删除了。

        这里还有一个问题解释了类转换异常:explanation

        在该代码中,您可以看到用户尝试显式转换为 String 而不是泛型参数。

        所以你可以说这是java与C#相比的缺点。

        【讨论】:

        • -1: toString 在你施放某些东西时不会自动调用,而且这个施放是另一个方向。
        • 我没说是自动调用的。
        • 编辑后,您的答案似乎根本没有引用 toString,这是适当的,因为它无关紧要。如果我真的投了反对票,我现在会撤回它,但我的反对票似乎没有接受。
        • "你没有在你的对象上调用任何特定于字符串的方法" 但事实上,不可能在那个引用上调用任何特定于字符串的方法,因为它的类型是T,可以是任何引用类型的类型参数。
        猜你喜欢
        • 2021-03-27
        • 2012-04-20
        • 2010-10-26
        • 1970-01-01
        • 2013-05-01
        • 2013-01-02
        • 1970-01-01
        • 2018-08-26
        • 1970-01-01
        相关资源
        最近更新 更多