你必须了解的Java:获取对象的四种方式

上一节讲了获取类,也就是获取class文件的三种方式,这一节主要讲获取对象的四种方式.

获取对象的四种方式包括有:

  1. new class
  2. clone (最大的区别就是,有没有复制对象,在堆内存中的是否为一个)
  3. reflect(最容易想到的就是 框架中的工厂模式创建了对象)
  4. deserialization

new

这是我们最常用的方式,生成的对象置于内存中的堆空间中,堆空间的构成,一个old区,一个eden区,两个survivor区。通常生成的对象会置于Eden区中,但是当生成的对象过大,超过jvm设置的一个值的时候,也会将该对象直接置于old区中。具体的关于创建对象时,jvm对于内存分配以及内存回收的相关知识,这里也就不再累述了。

clone 克隆 (利用clone复制对象,完成生成对象。)

利用clone,在内存中进行数据块的拷贝,复制已有的对象,也是生成对象的一种方式。前提是类实现Cloneable接口,Cloneable接口没有任何方法,是一个空接口,也可以称这样的接口为标志接口,只有实现了该接口,才会支持clone操作。有的人也许会问了,java中的对象都有一个默认的父类Object。

new 与 clone 的区别

首先看 复制引用 和 复制对象 的区别:

  1. 复制引用:下面p和p1只是引用而已,他们都指向了一个相同的对象Person(23, “zhang”) 。 可以把这种现象叫做引用的复制。
Person p = new Person(23, "zhang");
		Person p1 = p;
		System.out.println(p);
		System.out.println(p1)

在这里插入图片描述

  1. 复制对象:

    Person p = new Person(23, "zhang");  
    Person p1 = (Person) p.clone();
    

    [外链图片转存失败(img-Sauj3Vw1-1569330583627)(img\2.png)]

new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。

而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。

浅复制

被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。

深复制

被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。

[外链图片转存失败(img-Ev6MVzC5-1569330583628)(img\3.png)]

下面通过代码进行验证。如果两个Person对象的name的地址值相同, 说明两个对象的name都指向同一个String对象, 也就是浅拷贝, 而如果两个对象的name的地址值不同, 那么就说明指向不同的String对象, 也就是在拷贝Person对象的时候, 同时拷贝了name引用的String对象, 也就是深拷贝。验证代码如下:

Person p = new Person(23, "zhang");
Person p1 = (Person) p.clone();

String result = p.getName() == p1.getName() ? "clone是浅拷贝的" : "clone是深拷贝的";
	
System.out.println(result);

打印结果是:clone是浅拷贝的;

所以,clone方法执行的是浅拷贝, 在编写程序时要注意这个细节。

现在为了要在clone对象时进行深拷贝, 那么就要Clonable接口,覆盖并实现clone方法,除了调用父类中的clone方法得到新的对象, 还要将该类中的引用变量也clone出来。如果只是用Object中默认的clone方法,是浅拷贝的,再次以下面的代码验证:

static class Body implements Cloneable{
		public Head head;
		
		public Body() {}
 
		public Body(Head head) {this.head = head;}
 
		@Override
		protected Object clone() throws CloneNotSupportedException {
			return super.clone();
		}
	}

	static class Head /*implements Cloneable*/{
		public  Face face;
		
		public Head() {}
		public Head(Face face){this.face = face;}	
	}
public static void main(String[] args) throws CloneNotSupportedException {
		
		Body body = new Body(new Head());
		
		Body body1 = (Body) body.clone();
		
		System.out.println("body == body1:"+(body == body1));
		
		System.out.println("body.head == body1.head:" + (body.head == body1.head));	
	}

结果是:

在以上代码中, 有两个主要的类, 分别为Body和Head, 在Body类中, 组合了一个Head对象。当对Body对象进行clone时它组合的Face对象只进行浅拷贝。打印结果可以验证该结论:
body == body1 : false 也就是 相对于Body 类来说, 是深克隆的,但是对于里面的参数,组合的对象Head,还只是浅克隆;

body.head == body1.head : true

如果要使Body对象在clone时进行深拷贝, 那么就要在Body的clone方法中,将源对象引用的Head对象也clone一份。

打印结果为: body == body1 : false body.head == body1.head : false

具体了解请参阅:https://blog.csdn.net/zhangjg_blog/article/details/18369201/

结论:

拷贝也算是我们经常使用的一个方法,但是如果是不明白其中原理的程序员可能还是会入坑的。下面总结几条使用建议:
1.一定要实现Cloneable接口
2.复写clone()方法,注意:默认是浅拷贝,这里需要将引用类型进行深拷贝处理
3.特殊:String类虽然是引用类型,但是是final类,同时也有字符串常量池的存在,不必进行处理

分享一下在AngularJS 中的一个深克隆:

//添加列值  
addColumn=function(list,columnName,conlumnValues){ 
 	//新的集合 
    var newList=[];
 	for(var i=0;i<list.length;i++){ 
 	 	var oldRow= list[i]; 
 	 	for(var j=0;j<conlumnValues.length;j++){ 
 	 	 	var newRow= JSON.parse( JSON.stringify( oldRow )  );//深克隆 
 	 	 	newRow.spec[columnName]=conlumnValues[j]; 
 	 	 	newList.push(newRow); 
 	 	}      	  
 	}   	 
 	return newList; 
} 

JavaScript 中的深克隆

var newRow= JSON.parse( JSON.stringify( oldRow )  );	//深克隆 

这边是将Java对象先转化为字符串,再将自该字符串转化为对象;这个过程就是将对象进行了深克隆;

关于克隆的知识积累

利用clone,在内存中进行数据块的拷贝,复制已有的对象,也是生成对象的一种方式。前提是类实现Cloneable接口,Cloneable接口没有任何方法,是一个空接口,也可以称这样的接口为标志接口,只有实现了该接口,才会支持clone操作。有的人也许会问了,java中的对象都有一个默认的父类Object。

Object中有一个clone方法,为什么还必须要实现Cloneable接口呢,这就是Cloneable接口这个标志接口的意义,只有实现了这个接口才能实现复制操作,因为jvm在复制对象的时候,会检查对象的类是否实现了Cloneable这个接口,如果没有实现,则会报CloneNotSupportedException异常。类似这样的接口还有Serializable接口、RandomAccess接口等。

还有值得一提的是在执行clone操作的时候,不会调用构造函数。还有clone操作还会面临深拷贝和浅拷贝的问题。由于通过复制操作得到对象不需要调用构造函数,只是内存中的数据块的拷贝,那是不是拷贝对象的效率是不是一定会比new的时候的快。答案:不是。显然jvm的开发者也意识到通过new方式来生成对象占据了开发者生成对象的绝大部分,所以对于利用new操作生成对象进行了优化。

反射 获取对象

还有所谓的工厂创建对象都是根据反射而创建的框架产生的,而其中的原理就是利用了反射。

上篇博客已经讲过:反射是所有框架的基础。

从本地文件的反序列化

首先,基本概念如下:

  1. 序列化 :把对象转换为字节序列存储于磁盘或者进行网络传输的过程称为对象的序列化。
  2. 反序列化:把磁盘或网络节点上的字节序列恢复到对象的过程称为对象的反序列化。
  3. 综上,可以得出对象的序列化和反序列化主要有两种用途:
    • 把对象的字节序列永久地保存到磁盘上。(持久化对象)
    • 可以将Java对象以字节序列的方式在网络中传输。(网络传输对象)

【1】、必须实现序列化接口 SerializableJava.io.Serializable 接口。

【2】、serialVersionUID:序列化的版本号,凡是实现 Serializable 接口的类都有一个静态的表示序列化版本标识符的变量。

【3】、serialVersionUID 的取值:此值是通过 Java 运行时环境根据类的内部细节自动生成的。如果类的源代码进行了修改, 再重新编译,新生成的类文件的 serialVersionUID 的值也会发生变化。不同的编译器也可能会导致不同的 serialVersionUID。为了提高 serialVersionUID 的独立性和确定性,建议在一个序列化类中显示的定义 serialVersionUID,为它赋予明确的值。

:此值是通过 Java 运行时环境根据类的内部细节自动生成的。如果类的源代码进行了修改, 再重新编译,新生成的类文件的 serialVersionUID 的值也会发生变化。不同的编译器也可能会导致不同的 serialVersionUID。为了提高 serialVersionUID 的独立性和确定性,建议在一个序列化类中显示的定义 serialVersionUID,为它赋予明确的值。

具体可以看https://blog.csdn.net/ShuSheng0007/article/details/80629348

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值