为什么Java中的Object.clone()是原生的?

17

Object 上的 clone 方法可以创建一个对象的精确副本,并声明如下:

protected native Object clone() throws CloneNotSupportedException;

为什么是native


3
请取消加粗您的问题。 - Maroun
7
相关(重复?):https://dev59.com/Y3RB5IYBdhLWcg3wpoxm#557606 - sp00m
那个问题并没有一个确定的答案来解释为什么clone是本地的,只是某人的猜测。 - Chris Martin
同意。我已经被提名重新开放。"什么是native"与"为什么声明此方法为'native'"之间有很大的区别。 - Chris Hayes
1
由于克隆不使用构造函数来创建对象的副本,而且clone方法通过调用super.clone()来创建克隆,这将调用Object的clone方法,因此它必须是本地的才能创建新的和相等的对象。 - Maroun
4个回答

13

基本上,因为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而不是Object的对象。因为B并不知道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执行的是浅拷贝,而不是深拷贝。 - lbalazscs
@Ibalazscs 是的,但是在用户类中实现它通常需要自己进行深度复制。这就是为什么他们通常需要覆盖它而不只是保持原样的原因。 - RealSkeptic
@ReakSkeptic 不确定你在回答的最后一行所说的“从另一个B对象访问私有字段”的意思是什么。私有字段不能通过克隆、子类化等方式进行访问。 - Prabhat Gaur
如果您在类B的源代码内,并获取了一个B类型的对象并进行克隆,那么您将能够访问其所有私有字段。 - RealSkeptic
如果你在类B的源代码内部,你不需要克隆对象来访问其私有字段。 - Prabhat Gaur
@PrabhatGaur 我认为你有些误解。假设你在类中有一个方法,在该方法中,你首先创建了一个相同类的对象的克隆(this 或者是传递给该方法的某个对象),然后对克隆进行更改并返回它。你可以在该方法中访问私有字段。如果克隆不是正确的类,则无法访问。 - RealSkeptic

6

查看克隆文档:

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

这个操作可以用本机代码非常高效地完成,因为必须直接复制一些内存。在这方面,它类似于 System.arrayсopy,后者也是本机的。有关详细信息,请参见此问题:是否可能找到Java本机方法的源代码?

请注意,通常应避免使用Object.clone(),而应使用例如副本构造函数,参见 如何在Java中复制对象?


0

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


这个回答如何解决问题? - Maroun
它是抛出异常的原因。 - Adem
请再次阅读问题。 - Maroun
好的。我认为我应该阅读所有变化的问题,并根据当前问题更改我的答案。顺便说一下,这是原始问题的答案。 - Adem
2
这里有些混淆。原始问题表述不清(现在仍然如此),而这个答案在其最初版本中实际上离题了。我相信现在不再是这种情况了。确实,这个答案远非完美,但请停止抨击。 - RandomSeed
显示剩余3条评论

0

clone() 是本地的,因为操作取决于底层平台,也就是操作系统。 以下是一些事实,以帮助理解实际发生的情况: 1. JVM 是用 C++ 实现的。 2. C++ 要求您在目标平台/操作系统上编译代码。 3. clone() 操作发生在内存中。 4. 该内存部分由 JVM(一个 C++ 程序)控制。 5. 类被编译为字节码 = 文本(忽略下面所有冗长的细节,它们仅用于说明)。 因此,这个:

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

6. 类方法是共享的,而不是克隆的;它们通过指针在内部标识(方法只是一组指令的集合,因此要指向该集合,只需指向该集合的第一条指令)。 7. 然后,对象只是实例变量的特定状态。 8. 要克隆对象,clone()方法只需要复制内存中的实例变量。看起来它是作为字节块执行此操作 - 不像调用构造函数new SomeClassname()进行分析 - 找出类在哪里,构造函数在哪里,然后找出变量类型,传递初始化值(如果有),并执行赋值(如果有)。你明白了。 9. 由于clone()是对给定类型的特定对象调用的,所以类型本身已知,不需要进行上述的分析。即使实例变量的值相同,我们也不需要传递任何东西。 10. 唯一剩下的问题是处理依赖项的引用,通过对每个依赖项对象调用clone()并将引用分配给包装器克隆中的实例变量来处理深度复制。

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

如果想了解更多关于字节码的知识,请参阅DZone上的这篇文章。 并且记住...计算机科学中的所有内容都是虚假的,包括类对象,因为它们只是另一个将相关的结构体和函数组合在一起的抽象概念,除了可能是硬件 :), 这让大多数人认为实际上属于计算机工程,而不是计算机科学。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接