119

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

我认为我们终于有了一种面向对象的方式来处理演员阵容。

事实证明Class.caststatic_castC++ 中的不同。它更像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++ 的语言结构?
4

8 回答 8

129

我只习惯于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)),但我从未见过有人使用它——这使得将这种优化构建到编译器中的努力有些毫无价值。

于 2009-10-12T15:58:49.793 回答
23

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

无论如何,Class.cast()应该主要在您Class通过反射检索令牌时使用。写起来更习惯

MyObject myObject = (MyObject) object

而不是

MyObject myObject = MyObject.class.cast(object)

编辑:编译时的错误

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

于 2009-10-12T15:54:38.383 回答
17

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

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

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

于 2009-10-12T15:50:41.120 回答
14

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

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

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

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

于 2009-10-12T15:55:43.543 回答
5

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

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

Class#cast 肯定有用例,特别是在反射操作方面。

自从 lambda 进入 java 以来,我个人喜欢在使用抽象类型时将 Class#cast 与 collections/stream 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);
}
于 2019-05-29T20:15:15.763 回答
3

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

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

于 2009-10-12T15:57:38.813 回答
0

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

例如 serviceLoder 在创建对象时使用这个技巧,检查 S p = service.cast(c.newInstance()); 当 SP =(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
    }
于 2017-12-07T16:01:55.167 回答
0

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

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()我能想到的极少数合法用例之一,至少你现在有另一个例子。

于 2018-03-02T09:30:17.680 回答