【问题标题】:Why is Object.clone() native in Java?为什么 Object.clone() 在 Java 中是原生的?
【发布时间】:2015-01-29 05:35:51
【问题描述】:

Object 上的 clone 方法创建对象的精确副本,声明为:

protected native Object clone() throws CloneNotSupportedException;

为什么是native

【问题讨论】:

  • 请取消您的问题。
  • 相关(重复?):stackoverflow.com/a/557606/1225328
  • 对于为什么 clone 是原生的,这个问题并没有确切的答案,只是某人的猜测。
  • 同意。我已提名重新开放。 “什么是native”和“为什么将这个方法声明为native”之间存在巨大差异?
  • 由于克隆不使用构造函数来创建对象的副本,并且由于 clone 方法通过调用 super.clone() 创建一个克隆,这将调用 Object 的 clone 方法,它 必须是原生的,才能创建 new 和相等的对象。

标签: java object clone native


【解决方案1】:

基本上,因为clone() 方法做了一些您在Java 语言中无法做到的事情:它克隆对象的状态,包括其实际的类指定。

Java 中的克隆机制基于每个类调用超类的clone 方法,一直到Object。然后 Object 使用这个“神奇的”原生 clone 方法来复制原始对象,包括它的实际类。

想一想:

class A implements Cloneable {

    public A clone() {

        A obj = (A) super.clone();

        // Do some deep-copying of fields

        return obj;
    }

}

class B extends A {

    public B clone() {

        B obj = (B) super.clone();

        // Do some deep-copying of fields not known to A

        return obj;

    }
}

现在假设您有一个B 类型的对象,并在其上调用clone。您期望得到一个B 对象,其类在内部被识别为B,而不是ObjectB不知道A中所有内容的实现,因此需要调用Aclone方法。但是如果A 在Java 语言中实现clone 而不是调用super.clone(),那么它将返回的对象必须是A。它不能使用new B()(假设在创建 A 时 B 是未知的)。

它可以通过反射做一些事情,但它如何知道调用哪个构造函数以便正确填充所有最终字段?

所以诀窍是A 自己不会这样做,它会调用super.clone(),这会一直追溯到Object,它使用一个逐字节执行的本机方法复制原始对象,调整新的堆位置。因此,新对象神奇地变成了B 对象,并且类型转换不会失败。

那为什么不返回Object呢?因为那不会是克隆。当您调用clone 时,您希望得到一个具有相同状态(字段)和相同(被覆盖和添加的方法)的对象。如果它返回一个内部类指定为Object 的对象,您将只能访问Object 提供的内容,例如toString(),并且您将无法从另一个B 访问其私有字段对象,或将其分配给B 类型变量。

【讨论】:

  • Object.clone 进行浅拷贝,而不是深拷贝
  • @Ibalazscs true,但它在用户类中的实现通常必须自己进行深度复制。这就是为什么他们通常需要覆盖它而不是保持原样。
  • @ReakSkeptic 不确定答案最后一行中“从另一个 B 对象访问私有字段”是什么意思。无法使用克隆、子类化等方式协商私有字段。
  • @PrabhatGaur 如果你在B 类的源代码中并且你得到一个B 类型的对象并克隆它,你就可以访问它的所有私有字段。
  • @RealSkeptic "如果你在 B 类的源代码中" 你不需要克隆来访问这样一个对象的私有字段
【解决方案2】:

查看克隆文档:

否则,此方法会创建 this 类的新实例 对象并完全使用对象的内容初始化其所有字段 这个对象的对应字段,就像通过赋值一样;内容 的字段本身没有被克隆。

使用本机代码可以非常有效地完成此操作,因为必须直接复制一些内存。在这方面它与System.arrayсopy 类似,后者也是原生的。详情看这个问题:Is it possible to find the source for a Java native method?

请注意,通常您应该避免使用 Object.clone(),而是使用例如复制构造函数,请参阅 How do I copy an object in Java?

【讨论】:

    【解决方案3】:

    它是原生的,因为一些系统类的Clone()方法是用C++编写的以提高性能。

    【讨论】:

    • 这如何回答这个问题?
    • 这是抛出异常的原因
    • 再次阅读问题。
    • 好的。我认为我应该阅读所有不断变化的问题,并且应该根据当前问题更改答案。顺便说一句,这是原始问题的答案
    • 这里有些混乱。最初的问题措辞不佳(现在仍然如此),而这个答案,在它的第一个版本中,实际上是无关紧要的。我相信情况不再如此。的确,这个答案远非完美,但请停止抨击。
    【解决方案4】:

    clone() 是原生的,因为操作依赖于底层平台,也就是操作系统。 以下是一些有助于掌握实际情况的事实: 1.JVM用C++实现 2. C++要求你在目标平台/OS上编译代码 3. clone() 操作发生在内存中 4.那部分内存由JVM控制(一个C++程序) 5.一个类被编译成bytecode = text(忽略下面所有冗长的细节,它们只是为了说明) 所以,这个:

    public static void main(String[] args) {
        int a = 1;
        int b = 2;
        int c = calc(a, b);
    }
    static int calc(int a, int b) {
        return (int) Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    }
    

    变成这样:

    public static void main(java.lang.String[]);
      descriptor: ([Ljava/lang/String;)V
      flags: (0x0009) ACC_PUBLIC, ACC_STATIC
      Code:
        stack=2, locals=4, args_size=1
           0: iconst_1
           1: istore_1
           2: iconst_2
           3: istore_2
           4: iload_1
           5: iload_2
           6: invokestatic  #2         // Method calc:(II)I
           9: istore_3
          10: return
    static int calc(int, int);
      descriptor: (II)I
      flags: (0x0008) ACC_STATIC
      Code:
        stack=6, locals=2, args_size=2
           0: iload_0
           1: i2d
           2: ldc2_w        #3         // double 2.0d
           5: invokestatic  #5         // Method java/lang/Math.pow:(DD)D
           8: iload_1
           9: i2d
          10: ldc2_w        #3         // double 2.0d
          13: invokestatic  #5         // Method java/lang/Math.pow:(DD)D
          16: dadd
          17: invokestatic  #6         // Method java/lang/Math.sqrt:(D)D
          20: d2i
          21: ireturn
    
    1. 类方法是共享的 => 不是克隆的;它们在内部用指针标识(方法无非是一组指令,因此要指向该分组,您只需指向该分组的第一条指令)
    2. 对象只是实例变量的特定状态
    3. 要克隆一个对象,clone() 方法只需要复制内存中的实例变量。它似乎以字节块的形式执行此操作 - 没有像调用构造函数 new SomeClassname() 那样进行分析 - 找出类在内存中的位置,构造函数在哪里,然后找出变量类型,传递初始化值(如果有),并执行分配(如果有)。你明白了。
    4. 由于对给定类型的特定对象调用 clone(),因此类型本身是已知的,不需要上面 (8) 中的分析。甚至实例变量的值都是一样的,所以我们不需要传递任何东西。
    5. 剩下的唯一问题是 DEEP 副本 - 通过在每个依赖项对象上调用 clone() 并将引用分配给包装器克隆中的实例变量来处理对依赖项的引用。

    我知道这很长,但它非常清楚地说明了为什么 clone() 比“new”更快 - 只是将对象的状态作为二进制字符串抓取并复制它而无需任何“思考”/检查。

    要了解有关字节码的更多信息,请查看this article in DZone。 请记住....计算机科学中的一切都是假的,包括类对象,因为它们只是将相关结构和函数组合在一起的另一种抽象,可能除了硬件:),大多数人实际上认为属于计算机工程,而不是 CS。

    【讨论】:

      猜你喜欢
      • 2011-02-15
      • 2011-12-14
      • 2011-02-22
      • 2013-05-17
      • 1970-01-01
      • 2010-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多