上一节讲了获取类,也就是获取class文件的三种方式,这一节主要讲获取对象的四种方式.
获取对象的四种方式包括有:
这是我们最常用的方式,生成的对象置于内存中的堆空间中,堆空间的构成,一个old区,一个eden区,两个survivor区。通常生成的对象会置于Eden区中,但是当生成的对象过大,超过jvm设置的一个值的时候,也会将该对象直接置于old区中。具体的关于创建对象时,jvm对于内存分配以及内存回收的相关知识,这里也就不再累述了。
利用clone,在内存中进行数据块的拷贝,复制已有的对象,也是生成对象的一种方式。前提是类实现Cloneable接口,Cloneable接口没有任何方法,是一个空接口,也可以称这样的接口为标志接口,只有实现了该接口,才会支持clone操作。有的人也许会问了,java中的对象都有一个默认的父类Object。
首先看 复制引用 和 复制对象 的区别:
Person p = new Person(23, "zhang");
Person p1 = p;
System.out.println(p);
System.out.println(p1)
new操作符的本意是分配内存。程序执行到new操作符时, 首先去看new操作符后面的类型,因为知道了类型,才能知道要分配多大的内存空间。分配完内存之后,再调用构造函数,填充对象的各个域,这一步叫做对象的初始化,构造方法返回后,一个对象创建完毕,可以把他的引用(地址)发布到外部,在外部就可以使用这个引用操纵这个对象。
而clone在第一步是和new相似的, 都是分配内存,调用clone方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,同样可以把这个新对象的引用发布到外部。
被复制对象的所有变量都含有与原来的对象相同的值,而所有的对其他对象的引用仍然指向原来的对象。换言之,浅复制仅仅复制所考虑的对象,而不复制它所引用的对象。
被复制对象的所有变量都含有与原来的对象相同的值,除去那些引用其他对象的变量。那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的对象。换言之,深复制把要复制的对象所引用的对象都复制了一遍。
下面通过代码进行验证。如果两个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;
}
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】、必须实现序列化接口 Serializable: Java.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