一:概念
浅拷贝:创建了一个对象,但是这个对象的某些内容(比如A)依然是被拷贝对象的,即通过这两个对象中任意一个修改A,两个对象的A都会受到影响
深拷贝:相当于创建了一个新的对象,只是这个对象的所有内容,都和被拷贝的对象一模一样而已,但是两者是相互独立的,是不同的地址值,其修改是隔离的,相互之间没有影响

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
二:数据类型的比较
数据类型分为:基本数据类型 与 引用数据类型
基本数据类型:四类八种(byte ,short,int,long double,float,char,boolean)

引用数据类型:引用数据类型有:类、接口类型、数组类型、枚举类型、注解类型。
三:在内存中的位置:
1:基本数据类型:直接存储在栈(stack)中的数据
2:引用数据类型:栈中存储的是该对象的引用(是地址值),真实的数据存放在堆内存里
3:赋值与浅拷贝的区别:
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。
浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值;如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

四:实现对象克隆的几个方式
(1)将A对象的值分别通过set方法加入B对象中;
(2)通过重写java.lang.Object类中的方法clone();
(4)通过序列化实现对象的复制。
代码举例:
1>set赋值方式:
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = new Student();
stu2.setNumber(stu1.getNumber());
2>重写克隆方法clone 需要实现cloneable接口
浅克隆:不支持引用数据类型的复制
被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常),
该接口为标记接口(不含任何方法)
覆盖clone()方法,访问修饰符设为public。方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。
3>深克隆代码对比举例(先浅克隆后深克隆)
class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("杭州市");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:西湖区
深克隆
class Address implements Cloneable {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
@Override
public Object clone() {
Address addr = null;
try{
addr = (Address)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return addr;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone(); //浅复制
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
stu.addr = (Address)addr.clone(); //深度复制
return stu;
}
}
public class Test {
public static void main(String args[]) {
Address addr = new Address();
addr.setAdd("");
Student stu1 = new Student();
stu1.setNumber(123);
stu1.setAddr(addr);
Student stu2 = (Student)stu1.clone();
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
addr.setAdd("西湖区");
System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
}
}
结果:
学生1:123,地址:杭州市
学生2:123,地址:杭州市
学生1:123,地址:西湖区
学生2:123,地址:杭州市
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。
简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
4>:使用序列化和反序列化方式实现对象深度克隆
使用Serializable接口
利用序列化机制实现深克隆相比较重写clone()方法来说要安全、简单,但是效率不高。因为clone()方法实现的克隆是利用的本地方法,效率比基于Java虚拟机规范的序列化机制要高很多。
1.对象实现序列化,其和成员对象类都需要实现Serializable接口,标记接口,开启序列化,没有提供任何方法。
2.利用ObjectInputStream和ObjectOutputStream实现对象的反序列化与序列化
序列化与反序列化概念:
序列化:把对象转换为字节序列的过程称为对象的序列化
反序列化:把字节序列恢复为对象的过程称为对象的反序列化
什么时候会用到序列化呢?一般在以下的情况中会使用到序列化
===对象的持久化:把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中
在很多应用中,需要对某些对象进行序列化,让它们离开内存空间,入住物理硬盘,以便长期保存。比如最常见的是Web服务器中的Session对象,当有 10万用户并发访问,就有可能出现10万个Session对象,内存可能吃不消,于是Web容器就会把一些seesion先序列化到硬盘中,等要用了,再把保存在硬盘中的对象还原到内存中。
===远程调用:在网络上传送对象的字节序列
当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形式在网络上传送。发送方需要把这个Java对象转换为字节序列,才能在网络上传送;接收方则需要把字节序列再恢复为Java对象。
===序列化的基本实现
只要对象实现了Serializable接口,对象的序列化就会变得十分简单。要序列化一个对象首先要创建某些OutputStream对象,然后将其封装在一个ObejctOutputStream对象内,这时只需要调用writeObject()即可将对象序列化,并将其发送给OutputStream。
对象序列化是基于字节的,所以要使用InputStream和OutputStream继承层次结构
如果要反向上面的过程(即将一个序列还原为一个对象),需要将一个InputStream封装在ObjectInputStream内,然后调用readObject(),和往常一样,我们最后获得是一个引用,它指向了一个向上转型的Object,所以必须向下转型才能直接设置它们。
对象序列化不仅能够将实现了接口的那个类进行序列化,也能够将其引用的对象也实例化,以此类推。这种情况可以被称之为对象网。单个对象可与之建立连接。
我们举个例子可以看到在序列化和反序列过程中,对象网中的连接的对象信息都没有变。
public class TestSerializable {
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName = "/Users/doc/test.sql";
Worm w = new Worm(6,'a');
System.out.println("w:"+w);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(fileName));
out.writeObject("Worm Storage\n");
out.writeObject(w);
out.close();
ObjectInputStream in = new ObjectInputStream(new FileInputStream(fileName));
String s = (String) in.readObject();
Worm w2 = (Worm) in.readObject();
System.out.println(s+"w2:"+w2);
}
}
class Data implements Serializable{
private Integer i ;
public Data(Integer i ){
this.i = i;
}
@Override
public String toString() {
return i.toString();
}
}
class Worm implements Serializable{
private static final long serialVersionUID = 8033549288339500180L;
private static Random random = new Random(47);
private Data [] d = {
new Data(random.nextInt(10)),
new Data(random.nextInt(10)),
new Data(random.nextInt(10))
};
private Worm next;
private char c;
public Worm(int i ,char x){
System.out.println("Worm Constructor:"+i);
c = x;
if (--i>0){
next = new Worm(i,(char)(x+1));
}
}
public Worm(){
System.out.println("Default Constructor");
}
@Override
public String toString() {
StringBuffer result = new StringBuffer(":");
result.append(c);
result.append("(");
for (Data data: d){
result.append(data);
}
result.append(")");
if (next!=null){
result.append(next);
}
return result.toString();
}
}
可以看到打印信息如下
Worm Constructor:6
Worm Constructor:5
Worm Constructor:4
Worm Constructor:3
Worm Constructor:2
Worm Constructor:1
w::a(853):b(119):c(802):d(788):e(199):f(881)
Worm Storage
w2::a(853):b(119):c(802):d(788):e(199):f(881)
在生成Data对象时是用随机数初始化的,从输出中可以看出,被还原后的对象确实包含了原对象中的所有链接。
上面我们举了个如何进行序列化的例子,其中或许看到了serialVersionUID 这个字段,如果不加的话,那么系统会自动的生成一个,而如果修改了类的话,哪怕加一个空格那么这个serialVersionUID 也会改变,那么在反序列化的时候就会报错,因为在反序列化的时候会将serialVersionUID 和之前的serialVersionUID 进行对比,只有相同的时候才会反序列化成功。所以还是建议显视的定义一个serialVersionUID 。
transient(瞬时)关键字
当我们在对序列化进行控制的时候,可能需要某个字段不想让Java进行序列化机制进行保存其信息与恢复。如果一个对象的字段保存了我们不希望将其序列化的敏感信息(例如密码)。尽管我们使用private关键字但是如果经过序列化,那么在进行反序列化的时候也是能将信息给恢复过来的。我们举个例子如下:
我们定义个Student类
class Student implements Serializable{
private static final long serialVersionUID = 1734284264262085307L;
private String password;
------get set 方法
}
然后将其序列化到文件中然后再从文件中反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
String fileName="/Users/doc/test.sql";
Student student = new Student();
student.setPassword("123456");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(fileName));
objectOutputStream.writeObject(student);
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(fileName));
Student readStudent = (Student) objectInputStream.readObject();
System.out.println(readStudent.getPassword());
}
然后发现输出为
readStudent的password=123456
此时我们如果想password参数在序列化的时候存储其值,那么可以加上transient关键字,就像下面一样
private transient String password;
然后输出如下
readStudent的password=null
发现在序列化的时候参数就已经没被保存进去了
五:实现对象克隆的几个工具类:
在做业务的时候,为了隔离变化,我们会将DAO查询出来的DO和对前端提供的DTO隔离开来。大概90%的时候,它们的结构都是类似的;但是我们很不喜欢写很多冗长的b.setF1(a.getF1())这样的代码,于是我们需要简化对象拷贝方式。
- BeanUtils(简单,易用,Spring 包下的BeanUtil包更加稳定)
- BeanCopier(加入缓存后和手工set的性能接近)
- Dozer(深拷贝)
- fastjson(特定场景下使用)

1327

被折叠的 条评论
为什么被折叠?



