【JAVA零基础入门系列】(已完结)导航目录

  • Day1 开发环境搭建
  • Day2 Java集成开发环境IDEA
  • Day3 Java基本数据类型
  • Day4 变量与常量
  • Day5 Java中的运算符
  • Day6 Java字符串
  • Day7 Java输入与输出
  • Day8 Java的控制流程
  • Day9 Java中的那个大数值
  • Day10 Java中的数组
  • Day11 Java中的类和对象
  • Day12 Java类的简单应用
  • Day13 Java类的继承与多态
  • Day14 Java对象的克隆
  • Day15 对象的比较

  今天要介绍一个概念,对象的克隆。本篇有一定难度,请先做好心理准备。看不懂的话可以多看两遍,还是不懂的话,可以在下方留言,我会看情况进行修改和补充。

  克隆,自然就是将对象重新复制一份,那为什么要用克隆呢?什么时候需要使用呢?先来看一个小栗子:

  简单起见,我们这里用的是Goods类的简单版本。

public class Goods {private String title;private double price;public Goods(String aTitle, double aPrice){title = aTitle;price = aPrice;}public void setPrice(double price) {this.price = price;}public void setTitle(String title) {this.title = title;}//用于打印输出商品信息public void print(){System.out.println("Title:"+title+" Price:"+price);}
}

  然后我们来使用这个类。

public class GoodsTest {public static void main(String[] args){Goods goodsA = new Goods("GoodsA",20);Goods goodsB = goodsA;System.out.println("Before Change:");goodsA.print();goodsB.print();goodsB.setTitle("GoodsB");goodsB.setPrice(50);System.out.println("After Change:");goodsA.print();goodsB.print();}
}

  我们创建了一个Goods对象赋值给变量goodsA,然后又创建了一个Goods变量,并把goodsA赋值给它,先调用Goods的print方法输出这两个变量中的信息,然后调用Goods类中的setTitle和setPrice方法来修改goodsB中的对象内容,再输出两个变量中的信息,下面是输出:

Before Change:
Title:GoodsA Price:20.0
Title:GoodsA Price:20.0
After Change:
Title:GoodsB Price:50.0
Title:GoodsB Price:50.0

  这里我们发现了灵异事,我们明明修改的是goodsB的内容,可是goodsA的内容也同样发生了改变,这究竟是为什么呢?别心急,且听我慢慢道来。

  在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。使用等号赋值都是进行值传递的,如将一个整数型变量赋值给另一个整数型变量,那么后者将存储前者的值,也就是变量中的整数值,对于基本类型如int,double,char等是没有问题的,但是对于对象,则又是另一回事了,这里的goodsA和goodsB都是Goods类对象的变量,但是它们并没有存储Goods类对象的内容,而是存储了它的地址,也就相当于C++中的指针,如果对于指针不了解,那我就再举个栗子好了。我们之前举过一个栗子,把计算机比作是仓库管理员,内存比作是仓库,你要使用什么类型的变量,就需要先登记,然后管理员才会把东西给你,但如果是给你分配一座房子呢?这时候不是把房子搬起来放到登记簿粒,而是登记下房子的地址,这里的地址就是我们的类对象变量里记录的内容,所以,当我们把一个类对象变量赋值给另一个类对象变量,如goodsB = goodsA时,实际上只是把A指向的对象地址赋值给了B,这样B也同样指向这个地址,所以这时候,goodsA和goodsB操作的是同一个对象。

  所以,如果只是简单的赋值的话,之后对于goodsA和goodsB的操作都将影响同一个对象,这显然不是我们的本意。也许你还会问,直接再new一个对象不就好了,确实如此,但有时候,如果我们需要保存一个goodsA的副本,那就不仅仅要new一个对象,还需要进行一系列赋值操作才能将我们的新对象设置成跟goodsA对象一样,而且Goods类越复杂,这个操作将会越繁琐,另外使用clone方法还进行本地优化,效率上也会快很多,总而言之,就是简单粗暴。

  那如何使用克隆呢?这里我们就要介绍我们牛逼哄哄的Object类了,所有的类都是Object类的子类,虽然我们并没有显式声明继承关系,但所有类都难逃它的魔掌,它有两个protected方法,其中一个就是clone方法。

  下面我来展示一波正确的骚操作:

//要使用克隆方法需要实现Cloneable接口
public class Goods implements Cloneable{private String title;private double price;public Goods(String aTitle, double aPrice){title = aTitle;price = aPrice;}public void setPrice(double price) {this.price = price;}public void setTitle(String title) {this.title = title;}public void print(){System.out.println("Title:"+title+" Price:"+price);}//这里重载了接口的clone方法
    @Overrideprotected Object clone(){Goods g = null;    //这里是异常处理的语句块,可以先不用了解,只要知道是这样使用就好,之后的文章中会有详细的介绍try{g = (Goods)super.clone();}catch (CloneNotSupportedException e){System.out.println(e.toString());}return g;}
}

  其实修改的地方只有两个,一个是定义类的时候实现了Cloneable接口,关于接口的知识在之后会有详细说明,这里只要简单理解为是一种规范就行了,然后我们重载了clone方法,并在里面调用了父类也就是(Object)的clone方法。可以看到我们并没有new一个新的对象,而是使用父类的clone方法进行克隆,关于try catch的知识这里不做过多介绍,之后会有文章做详细说明,这里只需要理解为try语句块里是一个可能发生错误的代码,catch会捕获这种错误并进行处理。

  接下来我们再使用这个类的克隆方法:

public class GoodsTest {public static void main(String[] args){Goods goodsA = new Goods("GoodsA",20);Goods goodsB = (Goods)goodsA.clone();System.out.println("Before Change:");goodsA.print();goodsB.print();goodsB.setTitle("GoodsB");goodsB.setPrice(50);System.out.println("After Change:");goodsA.print();goodsB.print();}
}

  我们仅仅是把赋值改成了调用goodsA的clone方法并进行类型转换。输出如下:

Before Change:
Title:GoodsA Price:20.0
Title:GoodsA Price:20.0
After Change:
Title:GoodsA Price:20.0
Title:GoodsB Price:50.0

  看,这样不就达到我们目的了吗?是不是很简单?

  但是别高兴的太早,关于克隆,还有一点内容需要介绍。

  克隆分为浅克隆和深克隆。我们上面使用的只是浅克隆,那两者有什么区别呢?这里再举一个栗子,使用的是简化版的Cart类:

public class Cart implements Cloneable{//实例域Goods goodsList = new Goods("",0);//简单起见,这里只放了一个商品double budget = 0.0;//预算//构造函数public Cart(double aBudget){budget = aBudget;}//获取预算public double getBudget() {return budget;}//修改预算public void setBudget(double aBudget) {budget = aBudget;}//这里只是简单的将商品进行了赋值public void addGoods(Goods goods){goodsList = (Goods) goods.clone();}//这是为了演示加上的代码,仅仅将商品标题修改成新标题public void changeGoodsTitle(String title){goodsList.setTitle(title);}//打印商品信息public void print(){System.out.print("Cart内的预算信息:"+budget+" 商品信息:");goodsList.print();}//重载clone方法
    @Overrideprotected Object clone(){Cart c = null;try{c = (Cart)super.clone();}catch (CloneNotSupportedException e ){e.printStackTrace();}return c;}
}

  这里将goodsList由数组改成了单个对象变量,仅仅用于演示方便,还增加了一个changeGoodsTitle方法,用于将商品的标题修改成另一个标题,接下来修改一下GoodsTest类:

public class GoodsTest {public static void main(String[] args){Goods goodsA = new Goods("GoodsA",20);//新建一个商品对象Cart cartA = new Cart(5000);//新建一个购物车对象cartA.addGoods(goodsA);//添加商品Cart cartB = (Cart) cartA.clone();//使用浅克隆
     //输出修改前信息System.out.println("Before Change:");cartA.print();cartB.print();
     //修改购物车A中的商品标题cartA.changeGoodsTitle("NewTitle");     //重新输出修改后的信息System.out.println("After Change:");cartA.print();cartB.print();}
}

  输出信息:

Before Change:
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
After Change:
Cart内的预算信息:5000.0 商品信息:Title:NewTitle Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:NewTitle Price:20.0

  我们发现,虽然我们调用的是cartA中的方法修改购物车A中的商品信息,但购物车B中的信息同样被修改了,这是因为使用浅克隆模式的时候,成员变量如果是对象等复杂类型时,仅仅使用的是值拷贝,就跟我们之前介绍的那样,所以cartB虽然是cartA的一个拷贝,但是它们的成员变量goodsList却共用一个对象,这样就藕断丝连了,显然不是我们想要的效果,这时候就需要使用深拷贝了,只需要将Cart类的clone方法修改一下即可:

    @Overrideprotected Object clone(){Cart c = null;try{c = (Cart)super.clone();c.goodsList = (Goods) goodsList.clone();//仅仅添加了这段代码,将商品对象也进行了克隆}catch (CloneNotSupportedException e ){e.printStackTrace();}return c;}

  现在再来运行一下:

Before Change:
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0
After Change:
Cart内的预算信息:5000.0 商品信息:Title:NewTitle Price:20.0
Cart内的预算信息:5000.0 商品信息:Title:GoodsA Price:20.0

  这样就得到了我们想要的结果了。

  这样,对象的拷贝就讲完了。

  吗?

  哈哈哈哈,不要崩溃,并没有,还有一种更复杂的情况,那就是当你的成员变量里也包含引用类型的时候,比如Cart类中有一个CartB类的成员变量,CartB类中同样存在引用类型的成员变量,这时候,就存在多层克隆的问题了。这里再介绍一个骚操作,只需要了解即可,那就是序列化对象。操作如下:

import java.io.*;public class Cart implements Serializable{//实例域Goods goodsList = new Goods("",0);//简单起见,这里只放了一个商品double budget = 0.0;//预算//构造函数public Cart(double aBudget){budget = aBudget;}//获取预算public double getBudget() {return budget;}//修改预算public void setBudget(double aBudget) {budget = aBudget;}//这里只是简单的将商品进行了赋值public void addGoods(Goods goods){goodsList = (Goods) goods.clone();}//这是为了演示加上的代码,仅仅将商品标题修改成新标题public void changeGoodsTitle(String title){goodsList.setTitle(title);}//打印商品信息public void print(){System.out.print("Cart内的预算信息:"+budget+" 商品信息:");goodsList.print();}//这里是主要是骚操作public Object deepClone() throws IOException, OptionalDataException,ClassNotFoundException {// 将对象写到流里ByteArrayOutputStream bo = new ByteArrayOutputStream();ObjectOutputStream oo = new ObjectOutputStream(bo);oo.writeObject(this);// 从流里读出来ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());ObjectInputStream oi = new ObjectInputStream(bi);return (oi.readObject());}
}

  关于这种方法我就不多做介绍了,大家只需要知道有这样一种方法就行了,以后如果遇到了需要使用这种情况,就知道该怎样处理了。

  这里总结一下,对象的克隆就是把一个对象的当前状态重新拷贝一份到另一个新对象中,两个对象变量指向不同的对象,浅克隆仅仅调用super.clone()方法,对成员变量也只是简单的值拷贝,所以当成员变量中有数组,对象等复杂类型的时候,就会存在藕断丝连的混乱关系,深拷贝不仅仅调用super.clone()方法进行对象拷贝,将对象中的复杂类型同样进行了拷贝,这样两个对象就再无瓜葛,井水不犯河水了。

  至此,对象的克隆就真正的结束了,欢迎大家继续关注!如有不懂的问题可以留言。也欢迎各位大佬来批评指正。喜欢我的教程的话记得动动小手点下推荐,也欢迎关注我的博客。

真正重要的东西,用眼睛是看不见的。

【JAVA零基础入门系列】Day14 Java对象的克隆相关推荐

  1. 【JAVA零基础入门系列】Day2 Java集成开发环境IDEA

    [JAVA零基础入门系列](已完结)导航目录 Day1 开发环境搭建 Day2 Java集成开发环境IDEA Day3 Java基本数据类型 Day4 变量与常量 Day5 Java中的运算符 Day ...

  2. 有小数点是什么类型_「JAVA零基础入门系列」Day3 Java基本数据类型

    前两篇已经将开发环境搭建完成,如果你已经按之前的教程按部就班的完成了部署,那么世界上最优秀的编程语言之一和世界上最优秀的IDE之一已经出现在你的电脑上(此处应有掌声),如果你还没入门,或者正在台阶上踱 ...

  3. java数据类型入门程序_「JAVA零基础入门系列」Day3 Java基本数据类型

    前两篇已经将开发环境搭建完成,如果你已经按之前的教程按部就班的完成了部署,那么世界上最优秀的编程语言之一和世界上最优秀的IDE之一已经出现在你的电脑上(此处应有掌声),如果你还没入门,或者正在台阶上踱 ...

  4. 【JAVA零基础入门系列】Day1 开发环境搭建

    一.安装JDK java的sdk简称JDK ,去其官方网站下载最近的JDK即可. http://www.oracle.com/technetwork/java/javase/downloads/jdk ...

  5. Java零基础入门:实战教程(二)

    博主简介: 博客主页:Java知识分享博主 Java零基础入门专栏:Java零基础入门专栏 Java交流社区:飞鸟社区 欢迎阅读,如果文章对你有帮助点赞,支持一下! 推荐阅读 Java零基础入门:概论 ...

  6. Java零基础入门:方法的重写

    博主简介: 博客主页:Java知识分享博主 Java零基础入门专栏:Java零基础入门专栏 Java交流社区:飞鸟社区 欢迎阅读,如果文章对你有帮助点赞,支持一下! 文章目录 推荐阅读 Java零基础 ...

  7. 一篇文章让你从JAVA零基础入门`OOP`编程12.19

    一篇文章让你从JAVA零基础入门OOP编程 前言: 此文为玄子,复习ACCP-S1课程后,整理的文章,文中对知识点的解释仅为个人理解. 配套PPT,站点源码,等学习资料 一.预科 1.1 JAVA 介 ...

  8. 一篇文章让你从JAVA零基础入门`OOP`编程12.20

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(QQ_3336392096.png)] 一篇文章让你从JAVA零基础入门OOP编程 前言: 此文为玄子,复习ACCP-S1课程后, ...

  9. 一期完结《一篇文章让你从JAVA零基础入门`OOP`编程》12.21

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(QQ_3336392096.jpg)] 一篇文章让你从JAVA零基础入门OOP编程 前言: 此文为玄子,复习ACCP-S1课程后, ...

最新文章

  1. 50个“杀手级”AI项目 !(附链接)
  2. 无插件无 Flash HTML5 浏览器直接玩《Quake II》
  3. 【译】最大限度地降低多线程 C# 代码的复杂性
  4. 重构,改善既有代码的设计--第八章感悟
  5. 基于Extjs的OPOA
  6. java9 堆外内存_java堆外内存泄漏排查
  7. Caffe CuDNN版本与环境不同导致make错误
  8. 语言编写正反星星_厉害!浙理工师生原创短片《星星》入围5个国际电影节
  9. 数据可视化组件Grafana详细解读--RedHat/Fedora/CentOS/Oracle上的安装
  10. 泽元网站内容管理系统 (简称ZCMS)
  11. iptables联系一
  12. JS的正则表达式 举例
  13. 3ds Max: Substance to V-Ray Workflows 3ds Max教程:从Substance到V-Ray工作流程 Lynda课程中文字幕
  14. PX4新增自定义orb消息
  15. 一套键鼠控制多台不同平台电脑——synergy使用详解
  16. vi/vim保存报错:E37: No write since last change E162: No write since last change for buffer “[文件名称]“
  17. 网贴翻译 聆听国外的声音
  18. 行测:判断推理(逻辑判断)
  19. 【FPGA】基于OV5640的 图像边沿检测
  20. 最好的体育测试软件,体育锻炼标准测试app

热门文章

  1. 语言代码编程大赛简讯_精品干货:C语言的高效编程与代码优化
  2. c++ static allocator
  3. ed是什么梗_花泽香菜不笑了什么梗怎么回事?花泽香菜为什么是宅男女神?
  4. Excel有用的函数(ISBLANK,IF,LEFT,VALUE)
  5. [BZOJ1222/Luogu2224][HNOI2001]产品加工
  6. 【MYSQL命令】查看一个表的建表语句
  7. PHP中集成PayPal标准支付
  8. 【转】主要空间数据挖掘方法
  9. Mustache 中的html转义问题处理
  10. python入门学习:4.if语句