【问题标题】:Java Class.cast() vs. cast operatorJava Class.cast() 与强制转换运算符
【发布时间】:2009-10-12 15:47:47
【问题描述】:

在我的 C++ 时代,我被教导 C 风格强制转换运算符的弊端,起初我很高兴地发现在 Java 5 中 java.lang.Class 获得了 cast 方法。

我认为我们终于有了一种面向对象的方式来处理强制转换。

原来Class.cast 与 C++ 中的static_cast 不同。它更像reinterpret_cast。它不会在预期的地方产生编译错误,而是会推迟到运行时。这是一个简单的测试用例来演示不同的行为。

package test;

import static org.junit.Assert.assertTrue;

import org.junit.Test;


public class TestCast
{
    static final class Foo
    {
    }

    static class Bar
    {
    }

    static final class BarSubclass
        extends Bar
    {
    }

    @Test
    public void test ( )
    {
        final Foo foo = new Foo( );
        final Bar bar = new Bar( );
        final BarSubclass bar_subclass = new BarSubclass( );

        {
            final Bar bar_ref = bar;
        }

        {
            // Compilation error
            final Bar bar_ref = foo;
        }
        {
            // Compilation error
            final Bar bar_ref = (Bar) foo;
        }

        try
        {
            // !!! Compiles fine, runtime exception
            Bar.class.cast( foo );
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }

        {
            final Bar bar_ref = bar_subclass;
        }

        try
        {
            // Compiles fine, runtime exception, equivalent of C++ dynamic_cast
            final BarSubclass bar_subclass_ref = (BarSubclass) bar;
        }
        catch ( final ClassCastException ex )
        {
            assertTrue( true );
        }
    }
}

所以,这些是我的问题。

  1. 应该将Class.cast() 放逐到泛型领域吗?它有很多合法用途。
  2. 当使用Class.cast() 并且可以在编译时确定非法条件时,编译器是否应该产生编译错误?
  3. Java 是否应该将强制转换运算符作为类似于 C++ 的语言结构提供?

【问题讨论】:

  • 简单回答:(1)“泛型之地”在哪里?这与现在使用 cast 运算符有什么不同? (2) 大概。但在 99% 的所有 Java 代码中,如果在编译时可以确定非法条件,那么任何人极不可能使用Class.cast()。在这种情况下,除了您之外的每个人都只使用标准转换运算符。 (3) Java 确实有一个强制转换运算符作为语言结构。它与 C++ 不同。这是因为 Java 的许多语言结构与 C++ 不同。尽管有表面上的相似之处,Java 和 C++ 却大不相同。
  • 本教程优雅地解释了泛型中 cast() 方法的必要性:baeldung.com/java-type-casting

标签: java generics casting compiler-warnings


【解决方案1】:

我只使用过Class.cast(Object) 来避免“泛型领域”中的警告。我经常看到方法做这样的事情:

@SuppressWarnings("unchecked")
<T> T doSomething() {
    Object o;
    // snip
    return (T) o;
}

通常最好将其替换为:

<T> T doSomething(Class<T> cls) {
    Object o;
    // snip
    return cls.cast(o);
}

这是我遇到的Class.cast(Object) 的唯一用例。

关于编译器警告:我怀疑Class.cast(Object) 对编译器来说并不特殊。它可以在静态使用时进行优化(即Foo.class.cast(o) 而不是cls.cast(o)),但我从未见过有人使用它——这使得将这种优化构建到编译器中的努力变得毫无价值。

【讨论】:

  • 我认为在这种情况下正确的方法是先做一个检查实例,如下所示: if (cls.isInstance(o)){ return cls.cast(o);当然,除非你确定类型是正确的。
  • 为什么第二个变种更好?第一个变体不是更有效吗,因为调用者的代码无论如何都会进行动态转换?
  • @user1944408 只要您严格地说性能可能是这样,尽管几乎可以忽略不计。我不喜欢在没有明显案例的情况下获取 ClassCastExceptions 的想法。此外,第二个在编译器无法推断 T 的情况下效果更好,例如list.add(this.doSomething()) 与 list.add(doSomething(String.class))
  • 但是当你调用 cls.cast(o) 时你仍然可以得到 ClassCastExceptions,而你在编译时没有收到任何警告。我更喜欢第一个变体,但是当我需要返回 null 时,如果我不能进行强制转换,我会使用第二个变体。在这种情况下,我会将 cls.cast(o) 包装在 try catch 块中。我也同意你的观点,当编译器无法推断 T 时,这也更好。感谢您的回复。
  • 当我需要 Class cls 是动态的时,我也会使用第二个变体,例如,如果我使用反射,但在所有其他情况下,我更喜欢第一个。好吧,我想这只是个人口味。
【解决方案2】:

首先,强烈不鼓励您进行几乎任何演员,因此您应该尽可能限制它!您将失去 Java 编译时强类型特性的好处。

无论如何,Class.cast() 应该主要用于通过反射检索Class 令牌时。写起来比较地道

MyObject myObject = (MyObject) object

而不是

MyObject myObject = MyObject.class.cast(object)

编辑:编译时出错

总的来说,Java 只在运行时执行强制类型转换检查。但是,如果编译器可以证明这种转换永远不会成功(例如,将一个类转换为另一个不是超类型的类并将最终类类型转换为不在其类型层次结构中的类/接口),则编译器可能会发出错误。因为FooBar 是不在彼此层次结构中的类,所以强制转换永远不会成功。

【讨论】:

  • 我完全同意应该谨慎使用强制转换,这就是为什么拥有可以轻松搜索的内容对代码重构/维护有很大好处的原因。但问题是,虽然乍一看Class.cast 似乎符合要求,但它带来的问题多于解决的问题。
【解决方案3】:

尝试在不同语言之间翻译结构和概念总是有问题且经常误导。铸造也不例外。特别是因为 Java 是一种动态语言,而 C++ 则有些不同。

Java 中的所有转换,无论您如何进行,都是在运行时完成的。类型信息在运行时保存。 C++ 更像是一个混合体。您可以将 C++ 中的结构转换为另一个结构,它只是对表示这些结构的字节的重新解释。 Java 不能那样工作。

Java 和 C++ 中的泛型也有很大不同。不要过分关心你如何在 Java 中做 C++ 的事情。您需要学习如何以 Java 方式做事。

【讨论】:

  • 并非所有信息都在运行时保存。从我的示例中可以看出,(Bar) foo 在编译时确实会产生错误,但Bar.class.cast(foo) 不会。我认为如果以这种方式使用它应该。
  • @Alexander Pogrebnyak:那就不要那样做! Bar.class.cast(foo) 明确告诉编译器您要在运行时进行强制转换。如果您想在编译时检查强制转换的有效性,您唯一的选择是进行(Bar) foo 样式强制转换。
  • 你认为做同样的事情的方法是什么?因为java不支持多类继承。
【解决方案4】:

Class.cast() 在 Java 代码中很少使用。如果使用它,则通常与仅在运行时已知的类型一起使用(即通过它们各自的 Class 对象和某些类型参数)。它只在使用泛型的代码中真正有用(这也是之前没有引入它的原因)。

类似于reinterpret_cast,因为它允许您在运行时破坏类型系统,而不是普通强制转换所做的(即您可以break 泛型类型参数,但不能break“真实”类型)。

C 风格的强制转换运算符的弊端通常不适用于 Java。看起来像 C 风格转换的 Java 代码与 Java 中具有引用类型的 dynamic_cast&lt;&gt;() 最相似(记住:Java 具有运行时类型信息)。

通常将 C++ 转换运算符与 Java 转换进行比较是相当困难的,因为在 Java 中您只能转换引用并且不会对对象进行转换(只能使用这种语法转换原始值)。

【讨论】:

  • dynamic_cast&lt;&gt;() 带有引用类型。
  • @Tom:编辑正确吗?我的 C++ 非常生锈,我不得不重新搜索很多内容;-)
  • +1 表示:“C 风格的强制转换运算符的弊端通常不适用于 Java。”很真实。我正要发布这些确切的话作为对该问题的评论。
【解决方案5】:

通常,强制转换运算符优于 Class#cast 方法,因为它更简洁,并且可以由编译器分析以找出代码中的明显问题。

Class#cast 负责在运行时而不是在编译期间进行类型检查。

Class#cast 肯定有一些用例,尤其是在反射操作方面。

自从 lambda 进入 java 以来,我个人喜欢在使用抽象类型时将 Class#cast 与集合/流 API 结合使用。

Dog findMyDog(String name, Breed breed) {
    return lostAnimals.stream()
                      .filter(Dog.class::isInstance)
                      .map(Dog.class::cast)
                      .filter(dog -> dog.getName().equalsIgnoreCase(name))
                      .filter(dog -> dog.getBreed() == breed)
                      .findFirst()
                      .orElse(null);
}

【讨论】:

    【解决方案6】:

    C++ 和 Java 是不同的语言。

    Java C 风格的强制转换运算符比 C/C++ 版本更受限制。实际上,Java 转换类似于 C++ dynamic_cast,如果您拥有的对象无法转换为新类,您将获得运行时(或者如果代码中有足够的信息,则为编译时)异常。因此,不使用 C 类型转换的 C++ 想法在 Java 中不是一个好主意

    【讨论】:

      【解决方案7】:

      除了像大多数提到的那样删除丑陋的转换警告之外,Class.cast 是运行时转换,主要用于泛型转换,由于泛型信息将在运行时被删除,以及每个泛型将如何被视为 Object ,这导致不要抛出早期的 ClassCastException。

      例如 serviceLoder 在创建对象时使用这个技巧,检查 S p = service.cast(c.newInstance());这将抛出一个类转换异常 当 S P =(S) c.newInstance();不会并且可能会显示警告'类型安全:未经检查的从 Object 转换为 S'。(与 Object P =(Object) c.newInstance(); 相同)

      - 只需检查被转换的对象是否是转换类的实例,然后它将使用转换运算符进行转换并通过抑制警告来隐藏它。

      动态转换的java实现:

      @SuppressWarnings("unchecked")
      public T cast(Object obj) {
          if (obj != null && !isInstance(obj))
              throw new ClassCastException(cannotCastMsg(obj));
          return (T) obj;
      }
      
      
      
      
          private S nextService() {
              if (!hasNextService())
                  throw new NoSuchElementException();
              String cn = nextName;
              nextName = null;
              Class<?> c = null;
              try {
                  c = Class.forName(cn, false, loader);
              } catch (ClassNotFoundException x) {
                  fail(service,
                       "Provider " + cn + " not found");
              }
              if (!service.isAssignableFrom(c)) {
                  fail(service,
                       "Provider " + cn  + " not a subtype");
              }
              try {
                  S p = service.cast(c.newInstance());
                  providers.put(cn, p);
                  return p;
              } catch (Throwable x) {
                  fail(service,
                       "Provider " + cn + " could not be instantiated",
                       x);
              }
              throw new Error();          // This cannot happen
          }
      

      【讨论】:

        【解决方案8】:

        就我个人而言,我之前使用它来构建 JSON 到 POJO 转换器。如果用函数处理的 JSONObject 包含一个数组或嵌套的 JSONObjects(暗示这里的数据不是原始类型或 String),我尝试以这种方式使用 class.cast() 调用 setter 方法:

        public static Object convertResponse(Class<?> clazz, JSONObject readResultObject) {
            ...
            for(Method m : clazz.getMethods()) {
                if(!m.isAnnotationPresent(convertResultIgnore.class) && 
                    m.getName().toLowerCase().startsWith("set")) {
                ...
                m.invoke(returnObject,  m.getParameters()[0].getClass().cast(convertResponse(m.getParameters()[0].getType(), readResultObject.getJSONObject(key))));
            }
            ...
        }
        

        不确定这是否非常有用,但正如之前所说,反射是我能想到的class.cast() 的极少数合法用例之一,至少你现在有另一个例子。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-20
          • 2012-09-02
          • 1970-01-01
          • 2019-05-15
          • 1970-01-01
          • 2015-07-09
          • 1970-01-01
          • 2023-03-20
          相关资源
          最近更新 更多