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


107

在我的C ++时代里,学习过C风格的强制转换运算符的弊端后,我最初很高兴地发现Java 5中java.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
简单回答:(1)“泛型土地”在哪里?这与现在使用强制转换运算符有何不同?(2)可能。但是在有史以来编写的所有Java代码中,有99%的情况是,在编译时可以确定非法条件的情况下,任何人都不可能使用Class.cast()。在这种情况下,除了您之外的所有人都只使用标准的强制转换运算符。(3)Java确实具有强制转换运算符作为语言构造。它与C ++不同。这是因为Java的许多语言结构与C ++不相似。尽管表面上有相似之处,但Java和C ++还是有很大不同。
Daniel Pryden 09年

Answers:


117

我只曾经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))可以对其进行优化,但是我从未见过有人在使用它-这使得将这种优化构建到编译器中的努力有些毫无价值。


8
我认为在这种情况下,正确的方法是先进行如下检查:if(cls.isInstance(o)){return cls.cast(o); }当然,除非您确定类型正确。
Puce 2012年

1
为什么第二个变种更好?第一个变体不是更有效吗,因为调用者的代码无论如何都会进行动态转换?
user1944408 2014年

1
@ user1944408,只要您严格地讲性能,就可以了,尽管可以忽略不计。我不希望在没有明显情况的情况下获取ClassCastExceptions的想法。此外,第二种方法在编译器无法推断T的地方效果更好,例如list.add(this。<String> doSomething())与list.add(doSomething(String.class))
sfussenegger 2014年

2
但是,当您在编译时未收到任何警告时,调用cls.cast(o)仍然可以获取ClassCastExceptions。我更喜欢第一个变体,但是如果无法进行强制转换,例如需要返回null时,我将使用第二个变体。在那种情况下,我会将cls.cast(o)包装在try catch块中。我也同意您的看法,当编译器无法推断T时,这样做也更好。感谢您的答复。
user1944408 2014年

1
当我需要Class <T> cls是动态的时,例如,如果我使用反射,但是在所有其他情况下,我都更喜欢第一个,我也使用第二个变体。好吧,我想那只是个人喜好。
user1944408 2014年

20

首先,强烈建议您不要进行任何演员表转换,因此应尽可能限制它!您将失去Java的编译时强类型功能的好处。

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

MyObject myObject = (MyObject) object

而不是

MyObject myObject = MyObject.class.cast(object)

编辑:编译时错误

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


我完全同意应谨慎使用强制类型转换,这就是为什么拥有可以轻松搜索的内容对于代码重构/维护会大有裨益的原因。但是问题是,尽管乍Class.cast看起来似乎很合算,但它带来的问题比解决的问题还多。
亚历山大·波格勒布纳克

16

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

不管您如何执行,所有Java转换都在运行时完成。类型信息在运行时保存。C ++更加复杂。您可以将C ++中的一个结构强制转换为另一个结构,这只是对表示这些结构的字节的重新解释。Java不能那样工作。

Java和C ++中的泛型也有很大的不同。不要过度担心自己在Java中如何执行C ++事情。您需要学习如何用Java方式做事。


并非所有信息都在运行时保存。从我的示例(Bar) foo中可以看到,在编译时确实会生成错误,但Bar.class.cast(foo)不会。我认为,如果以这种方式使用它,应该这样做。
亚历山大Pogrebnyak

5
@Alexander Pogrebnyak:那别那么做! Bar.class.cast(foo)明确告诉编译器您要在运行时进行转换。如果要在编译时检查强制类型转换的有效性,则唯一的选择是进行(Bar) foo样式强制转换。
Daniel Pryden 09年

您认为相同的方式是什么?由于Java不支持多类继承。
Yamur

13

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

这是不是类似reinterpret_cast,因为它会不会让你在运行任何超过普通投不打破类型系统(即你可以打破泛型类型参数,但不能打破 “真正”的类型)。

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

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


dynamic_cast<>()带有引用类型。
汤姆·霍顿

@Tom:修改正确吗?我的C ++非常生锈,我不得不重新搜索很多它;-)
Joachim Sauer 2009年

+1:“ C样式强制转换运算符的弊端通常不适用于Java。” 确实如此。我将要发布这些确切的单词作为对该问题的评论。
Daniel Pryden 09年

4

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

3

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

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


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
    }

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.