Cast()与 cast 操作符

在我学习 C + + 的日子里,我学到了 C 风格的强制转换操作符的弊端,起初我很高兴地发现,在 Java 5中,java.lang.Class已经获得了一个 cast方法。

我以为我们终于有了一个 OO 的方式来处理选角。

Turns out Class.cast is not the same as static_cast in C++. It is more like reinterpret_cast. It will not generate a compilation error where it is expected and instead will defer to runtime. Here is a simple test case to demonstrate different behaviors.

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 + + 的强制转换操作符吗?
185884 次浏览

尝试在语言之间转换结构和概念总是有问题,而且经常会误导人。选角也不例外。特别是因为 Java 是一种动态语言,而 C + + 有些不同。

所有 Java 中的强制转换,无论您如何做,都是在运行时完成的。类型信息在运行时保存。C + + 更像是一种混合体。你可以把 C + + 中的结构强制转换成另一个结构,它只是表示这些结构的字节的翻唱。Java 不是这样的。

而且 Java的类型擦除和 c + + 也有很大的不同。不要过度关注如何在 Java 中使用 C + + 。你需要学习如何用 Java 的方式做事。

First, you are strongly discouraged to do almost any cast, so you should limit it as much as possible! You lose the benefits of Java's compile-time strongly-typed features.

在任何情况下,应该主要在通过反射检索 Class令牌时使用 Class.cast()。写起来更加地道

MyObject myObject = (MyObject) object

而不是

MyObject myObject = MyObject.class.cast(object)

编辑: 编译时的错误

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

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

它是 not类似于 reinterpret_cast,因为它将 not允许您在运行时破坏类型系统任何超过正常强制转换(即,您可以 休息泛型类型参数,但不能 休息“实际”类型)。

C 风格的强制转换操作符的弊端通常不适用于 Java。看起来像 C 风格强制转换的 Java 代码与 Java 中具有引用类型的 dynamic_cast<>()最为相似(记住: Java 有执行期型态讯息)。

一般来说,比较 C + + 类型转换操作符和 Java 类型转换操作符是相当困难的,因为在 Java 中你只能转换引用,对象没有发生转换(只有原始值可以用这种语法转换)。

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

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

我只在“通用领域”使用过 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)) ,但我从未见过任何人使用它——这使得在编译器中构建这种优化的努力有些无用。

除了删除最常提到的丑陋的强制转换警告之外,Class.cast 是运行时强制转换,主要用于泛型强制转换,由于泛型信息将在运行时被擦除,并且每个泛型将被认为是 Object,这导致不抛出早期的 ClassCastException。

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

就我个人而言,我以前曾使用它来构建 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))));
}
...
}

Not sure if this is extremely helpful, but as said here before, reflection is one of the very few legitimate use case of class.cast() I can think of, at least you have another example now.

通常情况下,转换操作符优于 Class # cast 方法,因为它更简洁,编译器可以通过分析转换操作符来找出代码中的明显问题。

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

Class # 强制转换肯定有用例,特别是在反射操作方面。

因为 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);
}