Java Class.cast()与强制转换运算符

时间:2009-10-12 15:47:47

标签: java generics casting compiler-warnings

在我的C ++日期间接受过关于C风格演员的邪恶的教导,我很高兴首先发现在Java 5中java.lang.Class获得了cast方法。

我认为最终我们有一种处理铸造的OO方式。

结果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 ++的转换操作符吗?

8 个答案:

答案 0 :(得分:103)

我只使用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)),但我从未见过任何人使用它 - 这使得在编译器中构建此优化的努力有些毫无价值。

答案 1 :(得分:17)

首先,你强烈劝阻我几乎不做任何演员,所以你应该尽可能地限制它!您将失去Java编译时强类型功能的好处。

在任何情况下,Class.cast()主要应该在您通过反射检索Class令牌时使用。写作

更具惯用性
MyObject myObject = (MyObject) object

而不是

MyObject myObject = MyObject.class.cast(object)

编辑:编译时的错误

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

答案 2 :(得分:16)

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

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

Java和C ++中的泛型也大不相同。不要过分关注你如何用Java做C ++事情。你需要学习如何用Java方式做事。

答案 3 :(得分:10)

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

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

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

通常将C ++强制转换操作符与Java转换进行比较非常困难,因为在Java中,您只能转换引用,并且对象不会发生任何转换(只能使用此语法转换原始值)。

答案 4 :(得分:3)

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

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

答案 5 :(得分:0)

除了最常提到的删除丑陋的强制转换警告之外,Class.cast是运行时强制转换,主要用于通用转换,因为通用信息将在运行时被删除,而某些通用将如何被视为对象,这导致不要抛出早期的ClassCastException。

例如serviceLoder在创建对象时使用此技巧,检查S p = service.cast(c.newInstance());这将抛出一个类强制转换异常 当S P =(S)c.newInstance();不会且可能会显示警告'类型安全:未选中从对象投射到S'。(与对象P =(对象)相同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
    }

答案 6 :(得分:0)

就个人而言,我之前使用过它来构建一个JSON到POJO转换器。在使用函数处理的JSONObject包含数组或嵌套的JSONObjects(暗示这里的数据不是基本类型或String)的情况下,我尝试使用{{1}调用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)))); } ... } 的极少数合法用例之一,至少你现在有另一个例子。

答案 7 :(得分:0)

通常,强制转换运算符是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);
}