Object
上的 clone
方法可以创建一个对象的精确副本,并声明如下:
protected native Object clone() throws CloneNotSupportedException;
为什么是native
?
基本上,因为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
中所有东西的实现,所以它需要调用A
的clone
方法。但是,如果A
在Java语言中实现了clone
而不是调用super.clone()
,那么它返回的对象将必须是A
。它不能使用new B()
(假设在创建A时未知B)。
它可以使用反射来解决问题,但是它如何知道应该调用哪个构造函数以使得所有最终字段都被正确填充?
因此,关键在于A
并没有自己执行克隆操作,而是调用了super.clone()
,这一直回溯到Object
,并使用一个原生方法来逐字节复制原始对象,同时调整新堆位置。因此,新对象神奇地变成了一个B
对象,类型转换不会失败。
为什么不返回Object
呢?因为那不是克隆。当您调用clone
时,您期望得到一个状态(字段)和相同的类(重写和添加方法)的对象。如果它返回一个内部类名称为Object
的对象,则只能访问Object
提供的内容,例如toString()
,而您将无法从另一个B
对象访问其私有字段,也无法将其分配给B
类型变量。
B
的源代码内,并获取了一个B
类型的对象并进行克隆,那么您将能够访问其所有私有字段。 - RealSkepticthis
或者是传递给该方法的某个对象),然后对克隆进行更改并返回它。你可以在该方法中访问私有字段。如果克隆不是正确的类,则无法访问。 - RealSkeptic查看克隆文档:
否则,此方法将创建该对象类的新实例,并使用该对象的相应字段的内容初始化其所有字段,就像通过赋值一样。字段的内容本身不会被克隆。
这个操作可以用本机代码非常高效地完成,因为必须直接复制一些内存。在这方面,它类似于 System.arrayсopy
,后者也是本机的。有关详细信息,请参见此问题:是否可能找到Java本机方法的源代码?
请注意,通常应避免使用Object.clone(),而应使用例如副本构造函数,参见 如何在Java中复制对象?
这是本地的,因为一些系统类的Clone()
方法是用C++编写的,以提高性能。
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
我知道这里面的文字很长,但它清楚地说明了为什么clone()比"new"更快 - 它只是将对象的状态作为二进制字符串抓取并复制,没有任何的"思考"/检查。
如果想了解更多关于字节码的知识,请参阅DZone上的这篇文章。 并且记住...计算机科学中的所有内容都是虚假的,包括类对象,因为它们只是另一个将相关的结构体和函数组合在一起的抽象概念,除了可能是硬件 :), 这让大多数人认为实际上属于计算机工程,而不是计算机科学。
native
"与"为什么声明此方法为'native'"之间有很大的区别。 - Chris Hayesclone
方法通过调用super.clone()
来创建克隆,这将调用Object的clone
方法,因此它必须是本地的才能创建新的和相等的对象。 - Maroun