在 Java 中投射对象的不同方法

Different methods for casting an object in Java

我知道以下三种方法可以用于铸造对象。

Object o = "str";

String str1 = (String) o;                // Method 1

String str2 = o.toString();              // Method 2

String str3 = String.class.cast(o);      // Method 3
  1. 哪种方法更好,一种方法与其他方法相比的 pros/cons 是什么?
  2. 对象在内部施法的过程中发生了什么?

开始了:

Object o = "str";

String str1 = (String) o;

只有当对象实际上是一个字符串时才有效。

String str2 = o.toString();

当您在 String 对象上使用 toString() 时,您将获得字符串本身。当对象o为null时会抛出异常

String str3 = String.class.cast(o);

主要在使用反射时使用,即当你想通过反射取回Class令牌时。

您展示的第二种方法不是铸造;它只是在一个对象上调用 toString() 方法,这与任何其他方法调用没有什么不同:

String str2 = o.toString();

第一种和第三种方法的效果本质上是一样的。我更喜欢使用第一种方法。

What happened with object on the time of casting internally?

对象没有任何反应。转换不是一种以某种方式自动将对象从一种类型转换为另一种类型的方法。强制转换所做的唯一一件事就是告诉编译器接受赋值语句而不检查该语句的类型。你是在对编译器说 "I know better than you what kind of object this is, so let me do this assignment and don't complain about the type".

在你的例子中,变量 o 的类型是 Object。当您将 o 分配给 String 类型的变量时,编译器将不允许这样做,因为它会检查类型,并且无法确定 o 实际上是指一个String 对象。所以你使用强制转换告诉编译器 "I know this is a String object, so let me do this assignment".

类型仍然会被检查,但在运行时,而不是在编译时。如果在运行时对象的类型不是 String,您将得到 ClassCastException.

第一个将 o 引用(其声明类型为 Object,其实际具体类型为 String)转换为 String。这是规范的铸造方式。这仅在 o 引用的对象实际上是 String 类型时才有效。如果不是,您将遇到 ClassCastException。

第二个根本不投。它在 o 引用的对象上调用 toString()。这将永远有效,但它与演员表真的非常不同。

第三个用反射做一个cast。这将与第一个具有相同的效果,并且将在相同的情况下工作。但这通常仅在代码实际上不知道要转换为 class 的类型时使用:

Class<?> someClassToCastTo = ...; // could be String.class or anything else, you don't know)

String str3 = someClassToCastTo.cast(o);

施放对象时没有任何反应。转换检查对象确实是 class 字符串,否则失败,仅此而已。但是一旦转换为 String 类型的变量,您就可以访问 String 中存在的方法,并且当您拥有 Object 类型的变量时无法调用这些方法。

Java 是强类型语言,它只允许将对象转换为它的父 classes 或接口之一。也就是说,如果您有以下条件:

class A {}

interface I {}

class B extends A implements I {}

class C {}

您可以像这样转换类型 B 的对象:

B b = new B();
A a = b; // no need for explicit casting
I i = b;
Object o = b; // Object is implicit parent of A
C c = b; // ERROR C isn't a parent class of b

这称为向上转型。你也可以垂头丧气:

A a = new B();
B b = (B) b;

您需要在此处使用显式强制转换,JVM 将在运行时检查是否确实允许强制转换。

当您不知道在编译时要转换成的特定类型时,String.class.cast(o) 转换很有用。

.toString() 没有投射。它只是一个方法,应该 return 对象的 String 表示形式。但是表示不是 对象,它只是一个表示。此方法在 Object class 中定义,因此它存在于所有 class 中,并且在某种程度上在语言中被烘焙。也就是说,如果你写

String s = "Hell " + o;

JVM 会调用 o.toString() 方法来获取它的表示。

其实你可以自己推理出来。

$ cat Cast.java 
public class Cast {
    private Cast() {}
    public static void main(String[] args) {
        Object o = "str";
        String str1 = (String) o;                // Method 1
        String str2 = o.toString();              // Method 2
        String str3 = String.class.cast(o);      // Method 3
    }
}
$ javac Cast.java 
$ javap -c Cast
Compiled from "Cast.java"
public class Cast {
  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String str
       2: astore_1
       3: aload_1
       4: checkcast     #3                  // class java/lang/String
       7: astore_2
       8: aload_1
       9: invokevirtual #4                  // Method java/lang/Object.toString:()Ljava/lang/String;
      12: astore_3
      13: ldc           #3                  // class java/lang/String
      15: aload_1
      16: invokevirtual #5                  // Method java/lang/Class.cast:(Ljava/lang/Object;)Ljava/lang/Object;
      19: checkcast     #3                  // class java/lang/String
      22: astore        4
      24: return
}

如您所见,

  1. 方法 1 只是一个 checkcast 操作码。
  2. 方法 2 是一个 invokevirtual 操作码。
  3. 方法3是一个ldc(加载class),然后是invokevirtualcheckcast

显然,方法 3 在冗长性、可读性和性能方面较差。

1 和 2 哪个更好?

checkcast 表示 "look at this object: is it really a String?" — 如果是,则继续赋值;如果不是,抛出一个 ClassCastException.

invokevirtual 表示 "look up which toString() method to call based on the class of o" — 在这种情况下,它是 String.toString()。该方法的明显实现是

public String toString() {
    return this;
}

在询问 "Is it a String? and "它是什么类型之间进行选择?结果我们调用哪个方法?让我们用 str2 作为隐含参数执行 String.toString()。”— 方法 1 应该简单得多。