为啥 Object.clone() 在 Java 中是原生的?
Posted
技术标签:
【中文标题】为啥 Object.clone() 在 Java 中是原生的?【英文标题】: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
?
【问题讨论】:
请取消您的问题。 相关(重复?):***.com/a/557606/1225328 对于为什么 clone 是原生的,这个问题并没有确切的答案,只是某人的猜测。 同意。我已提名重新开放。 “什么是native
”和“为什么将这个方法声明为native
”之间存在巨大差异?
由于克隆不使用构造函数来创建对象的副本,并且由于 clone
方法通过调用 super.clone()
创建一个克隆,这将调用 Object 的 clone
方法,它 必须是原生的,才能创建 new 和相等的对象。
【参考方案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
,而不是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
类型变量。
【讨论】:
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
-
类方法是共享的 => 不是克隆的;它们在内部用指针标识(方法无非是一组指令,因此要指向该分组,您只需指向该分组的第一条指令)
对象只是实例变量的特定状态
要克隆一个对象,clone() 方法只需要复制内存中的实例变量。它似乎以字节块的形式执行此操作 - 没有像调用构造函数 new SomeClassname() 那样进行分析 - 找出类在内存中的位置,构造函数在哪里,然后找出变量类型,传递初始化值(如果有),并执行分配(如果有)。你明白了。
由于对给定类型的特定对象调用 clone(),因此类型本身是已知的,不需要上面 (8) 中的分析。甚至实例变量的值都是一样的,所以我们不需要传递任何东西。
剩下的唯一问题是 DEEP 副本 - 通过在每个依赖项对象上调用 clone() 并将引用分配给包装器克隆中的实例变量来处理对依赖项的引用。
我知道这很长,但它非常清楚地说明了为什么 clone() 比“new”更快 - 只是将对象的状态作为二进制字符串抓取并复制它而无需任何“思考”/检查。
要了解有关字节码的更多信息,请查看this article in DZone。 请记住....计算机科学中的一切都是假的,包括类对象,因为它们只是将相关结构和函数组合在一起的另一种抽象,可能除了硬件:),大多数人实际上认为属于计算机工程,而不是 CS。
【讨论】:
以上是关于为啥 Object.clone() 在 Java 中是原生的?的主要内容,如果未能解决你的问题,请参考以下文章