关于泛型:Java Class.cast()与强制转换运算符

Java Class.cast() vs. cast operator

在C ++的日子里,当我学习了有关C样式转换运算符的弊端时,我很高兴首先发现在Java 5中java.lang.Class已经获得了cast方法。

我以为最终我们有了一种面向对象的处理铸造的方法。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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 );
        }
    }
}

所以,这些是我的问题。

  • 应该将Class.cast()放逐到"泛型"土地吗? 那里有很多合法用途。
  • 使用Class.cast()且在编译时可以确定非法条件时,编译器是否应生成编译错误?
  • Java是否应提供强制转换运算符作为类似于C ++的语言构造?

  • 我只用过Class.cast(Object)来避免在"泛型领域"中发出警告。我经常看到这样的方法:

    1
    2
    3
    4
    5
    6
    @SuppressWarnings("unchecked")
    < T > T doSomething() {
        Object o;
        // snip
        return (T) o;
    }

    通常最好将其替换为:

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


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

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

    1
    MyObject myObject = (MyObject) object

    而不是

    1
    MyObject myObject = MyObject.class.cast(object)

    编辑:编译时错误

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


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

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

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


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

    它与reinterpret_cast不同,因为它不允许您在运行时破坏类型系统,而不能像普通强制类型转换那样(即,您可以"破坏"通用类型参数,但不能"破坏""真实"类型)。

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

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


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

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


    通常,强制转换运算符比Class#cast方法更可取,因为它更简洁,并且可以由编译器进行分析,以消除代码中的公然问题。

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

    当然,有一些Class#cast用例,特别是涉及反射操作时。

    自从lambda来到Java以来??,我个人喜欢在例如抽象类型的情况下将Class#cast与collections / stream API结合使用。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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);
    }


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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()的极少数合法用例之一,至少现在有了另一个示例。


    除了消除最常提及的难看的转换警告外,Class.cast是运行时转换,通常与泛型转换一起使用,由于泛型信息将在运行时被删除,并且某些泛型将被视为Object,这导致不引发早期的ClassCastException。

    例如serviceLoder在创建对象时使用此技巧,请检查S p = service.cast(c.newInstance());这将引发类强制转换异常
    当S P =(S)c.newInstance();不会并且可能显示警告"类型安全性:未经检查的从Object到S的强制转换"。(与Object P =(Object)c.newInstance();相同)

    -仅检查铸造对象是否为铸造类的实例,然后将使用铸造运算符通过抑制它来铸造和隐藏警告。

    动态转换的Java实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    @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
        }