假如说你想复制一个简单变量。很简单:

int apples = 5;
int pears = apples;

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。

class Student {
private int number; public int getNumber() {
return number;
} public void setNumber(int number) {
this.number = number;
} }
public class Test { public static void main(String args[]) { Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1; System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());
}
}

打印结果:

学生1:12345
学生2:12345

这里我们自定义了一个学生类,该类只有一个number字段。

我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)

再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,

难道真的是这样吗?

我们试着改变stu2实例的number字段,再打印结果看看:

stu2.setNumber(54321);

System.out.println("学生1:" + stu1.getNumber());
System.out.println("学生2:" + stu2.getNumber());

打印结果:

学生1:54321
学生2:54321

这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,这样,stu1和stu2指向内存堆中同一个对象。

那么,怎样才能达到复制一个对象呢?

是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。

该方法的签名是:

protected native Object clone() throws CloneNotSupportedException;

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

要想对一个对象进行复制,就需要对clone方法覆盖。

一般步骤是(浅复制):

1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常) 该接口为标记接口(不含任何方法)

2. 覆盖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());
}
}

打印结果:

学生1:12345
学生2:12345
学生1:12345
学生2:54321

如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

System.out.println(stu1 == stu2); // false

上面的复制被称为浅复制(Shallow Copy),还有一种稍微复杂的深度复制(deep copy):

我们在学生类里再加一个Address类。

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());
}
}

打印结果:

学生1:123,地址:杭州市
学生2:123,地址:杭州市

乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。

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,地址:西湖区

这就奇怪了,怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

package abc;

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,地址:杭州市

这样结果就符合我们的想法了。

最后我们可以看看API里其中一个实现了clone方法的类:

java.util.Date:

    /**
* Return a copy of this object.
*/
public Object clone() {
Date d = null;
try {
d = (Date)super.clone();
if (cdate != null) {
d.cdate = (BaseCalendar.Date) cdate.clone();
}
} catch (CloneNotSupportedException e) {} // Won't happen
return d;
}

该类其实也属于深度复制。

java基础(十七)----- 浅谈Java中的深拷贝和浅拷贝 —— 面试必问的更多相关文章

  1. Java基础之浅谈接口

    前言 前几篇文章我们已经把Java的封装.继承.多态学习完了,现在我们开始比较便于我们实际操作的学习,虽然它也是Java基础部分,但是其实入门容易,精通很难. 我认真的给大家整理了一下这些必须学会.了 ...

  2. JAVA系列:浅谈Java中的equals和==

    在初学Java时,可能会经常碰到下面的代码: 1 String str1 = new String("hello"); 2 String str2 = new String(&qu ...

  3. 【Java虚拟机】浅谈Java虚拟机

    跨平台 Java的一大特性是跨平台,而Java是如何做到跨平台的呢? 主要依赖Java虚拟机,具体来说,是Java虚拟机在各平台上的实现. Java虚拟机在不同的平台有不同的实现.同一份字节码,通过运 ...

  4. 浅谈.net平台下深拷贝和浅拷贝

    在.net类库中,对象克隆广泛存在于各种类型的实现中,凡是实现了ICloneable接口的类型都具备克隆其对象实例的能力.所以本文讲述的深拷贝和浅拷贝也是在实现ICloneable接口的基础上进行的 ...

  5. Java基础之浅谈集合

    Java基础知识.关于List.Set.Map接口的了解,以及ArrayList.LinkedList.HashSet.TreeSet.HashMap.TreeMap...

  6. Java基础:浅谈数据输入流/数据输出流《DataInputstream类与DataOutputstream类》

     一.理论概述 数据输入/输出流(DataInputStream类与DataOutputStream类) 允许应用程序以与机器无关的方式从底层输入流中读取基本Java数据类型. 说白了就是,当读取一个 ...

  7. Java基础之浅谈继承、多态

    一.继承的理解 继承:简单通俗的来讲,继承就是一个类继承另一个类,通常用extends表示继承. 继承的类叫子类,被继承的类叫父类. 子类可以使用父类的变量和方法,同时也可以重写父类的方法. 在Jav ...

  8. Java基础之浅谈异常与了解断言

    一.产生错误原因 用户输入错误 设备错误 物理限制 代码错误 二.解决错误---异常 在Java中异常对象都是派生于Throwable类的一个实例. 我们一般将异常分为两种:①Error和②Excep ...

  9. Java基础之浅谈泛型

    简单的介绍了Java泛型类型的使用.上手容易,深入很难.

随机推荐

  1. Beginning Python Games Development

    Like music and movies, video games are rapidly becoming an integral part of our lives. Over the year ...

  2. 实验一《Java开发环境的熟悉》实验报告

    (一)基础操作 实验要求: 1 .建立"自己学号exp1"的目录 2 .在"自己学号exp1"目录下建立src,bin等目录 3 .javac,java的执行在 ...

  3. Ubuntu安装Hadoop

    系统:Ubuntu16.04 JDK:jdk-8u201 Hadoop:3.1.2 一.安装JDK https://www.cnblogs.com/tanrong/p/10641803.html 二. ...

  4. 基于Cmake+QT+VS的C++项目构建开发编译简明教程

    目录 一.工具下载与安装 1.     Qt 2.     Visual Studio 2015 3.     Cmake 二.C++及Qt项目构建 1.     基于VS构建Qt项目 2.     ...

  5. [R]R语言的module工程化

    很遗憾,这还是一个挖坑的问题,解决方案并不是很确定. 需求是,大多数的语言都提供import包或module的功能,避免全部代码写到一个文件中,方便管理与维护. 如常用的database模块,每次写R ...

  6. 新版CSDN-markdown编辑器使用指南

    本文来自CSDN官方,分markdown原文和实际显示部分,推荐开两个窗口对比浏览 Markdown部分 @[TOC](这里写自定义目录dd标题) # 欢迎使用Markdown编辑器 你好! 这是你第 ...

  7. 将普通用户添加到sudo

    将普通用户添加到sudo组 可以编辑/etc/sudoers文件将普通用户加入sudo组.要注意的是修改该文件只能使用visudo命令:1.首先切换到root #su - (注意有 “-” ,这和su ...

  8. 机器学习——KNN算法(k近邻算法)

    一 KNN算法 1. KNN算法简介 KNN(K-Nearest Neighbor)工作原理:存在一个样本数据集合,也称为训练样本集,并且样本集中每个数据都存在标签,即我们知道样本集中每一数据与所属分 ...

  9. vuex的一些学习

    刚开始学vuex看文档看的一脸懵逼,故而网上各种查找资料,视频去观看学习,虽然看了很多还是很蒙圈,最近看了一个讲vuex的视频还有一个 类似的简书文档从中学到了很多,慢慢理清了头绪,至此记录一下,共同 ...

  10. golang ntp协议客户端

    NTP(Network Time Protocol,网络时间协议)是由RFC 1305定义的时间同步协议,用来在分布式时间服务器和客户端之间进行时间同步.NTP基于UDP报文进行传输,使用的UDP端口 ...