Object源码分析(一)-- clone方法

Object是所有类的基类,当你没有显示extends一个父类时,编译期会自动为你加上一个Object类。


1.这是一个native方法,说明这个方法的实现不是在java中,而是由C/C++实现,并编译成.dll文件,由java调用。registerNatives主要是将C/C++的方法映射到java中的native方法,实现方法命名的解耦。了解即可,知道是注册,细节暂时不研究。

private static native void registerNatives();
    static {
        registerNatives();
    }

2.第二点来关注 clone方法。

protected native Object clone() throws CloneNotSupportedException;
通过源代码可以发现几点:

1.clone方法是native方法,native方法的效率远高于非native方法,因此还是使用clone方法去做对象的拷贝而不是使用new的方法,copy。

2.此方法被protected修饰。这就意味着想要使用,必须继承它(废话,默认都是继承的)。然后重载它,如果想要使得其他类能使用这个类,需要设置成public。

3.返回值是一个Object对象,所以要强制转换才行。

测试clone方法,代码如下,

public class TestReen{
	
	public static void main(String[] args) throws Exception{
		
		TestReen tReen = new TestReen();
		
		TestReen copy = (TestReen)tReen.clone();
	}
	
}
但是运行时,发现抛异常了。

Exception in thread "main" java.lang.CloneNotSupportedException: com.test.count.TestReen
	at java.lang.Object.clone(Native Method)
	at com.test.count.TestReen.main(TestReen.java:11)
进入clone方法,看注释发现如下信息:表明此类不支持Cloneable接口的话就会报错。

@exception  CloneNotSupportedException  if the object's class does not
     *               support the {@code Cloneable} interface. Subclasses
     *               that override the {@code clone} method can also
     *               throw this exception to indicate that an instance cannot
     *               be cloned.
去看看 Cloneable接口,空的,
public interface Cloneable {
}
总结其注释的话,差不多三点:

(1)此类实现了Cloneable接口,以指示Object的clone()方法可以合法地对该类实例进行按字段复制;
(2)如果在没有实现Cloneable接口的实例上调用Object的clone()方法,则会导致抛出CloneNotSupporteddException;
(3)按照惯例,实现此接口的类应该使用公共方法重写Object的clone()方法,Object的clone()方法是一个受保护的方法;

因此想实现clone的话,除了继承Object类外,还需要实现Cloneable接口;

创建并返回此对象的一个副本。对于任何对象x,表达式:
(1)x.clone() != x为true
(2)x.clone().getClass() == x.getClass()为true
(3)x.clone().equals(x)一般情况下为true,但这并不是必须要满足的要求

测试以下例子:

public class TestReen implements Cloneable{
	
	private int id;
	
	private static int i = 1;
	
	public TestReen() {
		System.out.println("i = " + i);
		System.out.println("执行了构造函数"+i);
		i++;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public static void main(String[] args) throws Exception{
		
		TestReen t1 = new TestReen();
		t1.setId(1);
		
		TestReen t2 = (TestReen)t1.clone();
		
		TestReen t3 = new TestReen();
		
		System.out.println("t1 id: "+ t1.getId());
		System.out.println("t2 id: "+ t2.getId());
		System.out.println("t1 == t2 ? " + (t1 == t2));
		System.out.println("t1Class == t2Class ? " + (t1.getClass() == t2.getClass()));
		System.out.println("t1.equals(t2) ? " + t1.equals(t2));
		
	}
}

结果如下,有几个发现:

1. 构造函数除了new执行以外,clone并没有调用到构造函数,也就是clone方法是不走构造方法的。

2. t1 和 t2 是不等的,说明指向了不同的堆地址空间,是两个对象。

3. getClass是相同的,getClass是什么?是获取这个实例的类型类,有点拗口,其实就是TestReen类型,可见clone出来的类型还是一样的。

i = 1
执行了构造函数1
i = 2
执行了构造函数2
t1 id: 1
t2 id: 1
t1 == t2 ? false
t1Class == t2Class ? true
t1.equals(t2) ? false

国际惯例所有博客都会去关注的一点,浅克隆( shallow clone)和 深克隆( deep clone)。

浅克隆:Object的clone提供的就是浅克隆,由下面的例子可以看见,只克隆了自身对象和对象内实例变量的地址引用,它内部的实例变量还是指向原先的堆内存区域。

深克隆:克隆所有的对象,包括自身以及自身内部对象。


浅克隆测试代码:

TestReen中有一个实例变量SonReen。

public class TestReen implements Cloneable{
	private int id;
	private String name;
	private SonReen sonReen;
	public TestReen(int id, String name, SonReen sonReen) {
		this.id = id;
		this.name = name;
		this.sonReen = sonReen;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public SonReen getSonReen() {
		return sonReen;
	}
	public void setSonReen(SonReen sonReen) {
		this.sonReen = sonReen;
	}
	public static void main(String[] args) throws Exception{
		
		SonReen sonReen = new SonReen(1, "张三");
		TestReen t1 = new TestReen(1, "李四", sonReen);
		TestReen t3 = new TestReen(2, "李四1", sonReen);
		TestReen t4 = new TestReen(3, "李四", sonReen);
		TestReen t2 = (TestReen)t1.clone(); 
		System.out.println("t1 ? "+ (t1 == t2));
		System.out.println("sonReen: ? "+ (t1.getSonReen() == t2.getSonReen()));
		System.out.println("t1.name == t3.name: ? " + (t1.getName() == t3.getName()));
		System.out.println("t1.name == t4.name: ? " + (t1.getName() == t4.getName()));
		System.out.println("T name: ? " + (t1.getName() == t2.getName()));
		System.out.println("S name: ? " + (t1.getSonReen().getSonName() == t2.getSonReen().getSonName()));
	}
}
class SonReen{
	private int sonId;
	private String sonName;
	public SonReen(int sonId, String sonName) {
		super();
		this.sonId = sonId;
		this.sonName = sonName;
	}
	public int getSonId() {
		return sonId;
	}
	public void setSonId(int sonId) {
		this.sonId = sonId;
	}
	public String getSonName() {
		return sonName;
	}
	public void setSonName(String sonName) {
		this.sonName = sonName;
	}
}

得到的结果如下:可以发现几点:

首先TestReen 不是同一个对象,但是它内部的实例变量sonReen 确实同一个对象。内部的String变量的行为和new出来的t1和t4行为一致,也是相等的。

t1 ? false
sonReen: ? true
t1.name == t3.name: ? false
t1.name == t4.name: ? true
T name: ? true
S name: ? true


深克隆:方法有两种:

1.先对对象进行序列化操作,然后立马反序列化出。

2.先调用super.clone()方法克隆出一个新对象来,然后在子类的clone()方法中手动给克隆出来的非基本数据类型(引用类型)赋值。(这个是网上别人的观点,稍后会去研究各个集合内的克隆原理,也是一个知识点)

序列化例子:

public class TestReen implements Cloneable,Serializable{
	private int id;
	private String name;
	private SonReen sonReen;
	public TestReen(int id, String name, SonReen sonReen) {
		this.id = id;
		this.name = name;
		this.sonReen = sonReen;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public SonReen getSonReen() {
		return sonReen;
	}
	public void setSonReen(SonReen sonReen) {
		this.sonReen = sonReen;
	}
	public Object deepClone() throws Exception{
		
		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		ObjectOutputStream out = new ObjectOutputStream(bo);
		out.writeObject(this);
		
		ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
		ObjectInputStream oi = new ObjectInputStream(bi);
		return oi.readObject();
		
	}
	public static void main(String[] args) throws Exception{
		
		SonReen sonReen = new SonReen(1, "张三");
		TestReen t1 = new TestReen(1, "李四", sonReen);
		TestReen t2 = (TestReen)t1.deepClone(); 
		System.out.println("t1 ? "+ (t1 == t2));
		System.out.println("sonReen: ? "+ (t1.getSonReen() == t2.getSonReen()));
		System.out.println("T name: ? " + (t1.getName() == t2.getName()));
		System.out.println("S name: ? " + (t1.getSonReen().getSonName() == t2.getSonReen().getSonName()));
		
		SonReen sonReen1 = new SonReen(1, "王五");
		t2.setSonReen(sonReen1);
		System.out.println("son : "+ t1.getSonReen().getSonName());
		System.out.println("son1 : "+ t2.getSonReen().getSonName());
		System.out.println("son == son1 : ? "+ (t1.getSonReen() ==  t2.getSonReen()));
		
	}
}
class SonReen implements Serializable{
	private int sonId;
	private String sonName;
	public SonReen(int sonId, String sonName) {
		super();
		this.sonId = sonId;
		this.sonName = sonName;
	}
	public int getSonId() {
		return sonId;
	}
	public void setSonId(int sonId) {
		this.sonId = sonId;
	}
	public String getSonName() {
		return sonName;
	}
	public void setSonName(String sonName) {
		this.sonName = sonName;
	}
}
如下结果: 发现t1 和 t2 内的sonReen比较时 已经是false了,说明克隆的同时将对象内部的实例对象也跟着一起复制了,并且下面对 t2 内部son 的名字改变也没有影响 t1的值。

t1 ? false
sonReen: ? false
T name: ? false
S name: ? false
son : 张三
son1 : 王五
son == son1 : ? false

  • 7
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
### 回答1: xargs命令用于将标准输入的数据转换成命令行参数,并将这些参数传递给其他命令来执行。而-n1参数指定了每次执行命令的参数个数为1。 假设我们有一个包含多个git仓库地址的文件,我们可以使用xargs命令结合git clone来批量克隆这些仓库。通过在命令行中执行以下命令: ``` xargs -n1 git clone < 保存路径设置 ``` 其中,`保存路径设置`是一个文件,文件中每一行包含一个git仓库地址。 xargs命令解析文件的内容并将每一行作为一个参数传递给git clone命令,-n1参数指定每次执行命令的参数个数为1。这样,xargs将依次执行git clone命令来克隆每个仓库,克隆后的仓库将保存到指定的路径设置中。 举个例子,如果保存路径设置文件内容如下: ``` https://github.com/user/repo1.git https://github.com/user/repo2.git https://github.com/user/repo3.git ``` 那么执行上述xargs命令后,将依次执行以下git clone命令: ``` git clone https://github.com/user/repo1.git git clone https://github.com/user/repo2.git git clone https://github.com/user/repo3.git ``` 每个仓库将被克隆到指定的路径设置中。 总结来说,xargs -n1 git clone < 保存路径设置 是通过xargs命令和git clone命令结合,批量克隆多个git仓库,将克隆后的仓库保存到指定的路径设置中。 ### 回答2: xargs是一个命令行工具,用于将标准输入的数据作为参数传递给另一个命令。-n1选项表示每次传递一个参数给后面的命令。 git clone是一个用于复制git仓库的命令。它将指定的git仓库复制到本地,并自动创建一个新的目录来存放该仓库的所有文件。 < 是一个重定向操作符,用于将保存路径设置为从标准输入中获取参数。 假设我们有一个文件,其中包含多个git仓库的URL链接,每行一个链接。我们可以使用xargs命令结合git clone命令来从该文件中一次克隆一个仓库。 命令如下: cat 文件名 | xargs -n1 git clone 其中,文件名是包含git仓库URL链接的文件的名称。 xargs -n1会将每行的链接作为参数传递给git clone命令,从而逐个克隆每个仓库。每个仓库会被克隆到当前路径下的一个新目录中。 如果我们想要将克隆的仓库保存到特定的路径中,可以使用以下命令: cat 文件名 | xargs -n1 -I {} sh -c 'git clone {} /保存路径/$(basename {})' 其中,/保存路径/是我们想要将仓库保存的路径,{}是xargs传递的参数,$(basename {})会获取链接中的仓库名称作为新目录的名称。 这样,每次git clone命令执行时,都会将仓库克隆到指定的保存路径下,并自动创建一个新目录来存放该仓库的文件。 ### 回答3: xargs命令是用来构建和执行命令行参数的实用工具。它从标准输入中读取数据,并将其作为命令行参数传递给其他命令进行处理。 在这个命令中,xargs -n1 git clone将从标准输入中读取的数据(每行一个)作为参数传递给git clone命令来执行对应的克隆操作。-n1选项表示每次只传递一个参数,这样每行的数据都可以作为一个单独的克隆命令执行。 通过<操作符,我们可以将保存路径设置为标准输入的源。这意味着我们可以通过重定向输入流的方式,将保存路径作为输入提供给xargs命令。 举个例子,假设我们有一个文件paths.txt,其中包含了多个保存路径,每行一个。我们可以使用以下命令来执行克隆操作: cat paths.txt | xargs -n1 git clone 这将从paths.txt文件中读取每行的保存路径,并将其作为参数传递给git clone命令执行克隆操作。 译文如下: xargs命令可以用来将以标准输入方式提供的数据作为参数传递给其他命令来执行操作。在xargs -n1 git clone命令中,-n1选项表示每次只传递一个参数给git clone命令,而<操作符可以用来设置保存路径。例如,通过将保存路径作为输入提供给xargs命令,可以使用cat paths.txt | xargs -n1 git clone命令来执行多个路径的克隆操作。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值