【JAVA零基础入门系列】(已完结)导航目录html
今天要介绍一个概念,对象的克隆。本篇有必定难度,请先作好心理准备。看不懂的话能够多看两遍,仍是不懂的话,能够在下方留言,我会看状况进行修改和补充。java
克隆,天然就是将对象从新复制一份,那为何要用克隆呢?何时须要使用呢?先来看一个小栗子:数组
简单起见,咱们这里用的是Goods类的简单版本。ide
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中的对象内容,再输出两个变量中的信息,下面是输出:post
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操做的是同一个对象。this
因此,若是只是简单的赋值的话,以后对于goodsA和goodsB的操做都将影响同一个对象,这显然不是咱们的本意。也许你还会问,直接再new一个对象不就行了,确实如此,但有时候,若是咱们须要保存一个goodsA的副本,那就不只仅要new一个对象,还须要进行一系列赋值操做才能将咱们的新对象设置成跟goodsA对象同样,并且Goods类越复杂,这个操做将会越繁琐,另外使用clone方法还进行本地优化,效率上也会快不少,总而言之,就是简单粗暴。spa
那如何使用克隆呢?这里咱们就要介绍咱们牛逼哄哄的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方法 @Override protected 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方法 @Override protected 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方法修改一下便可:
@Override protected 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 = null;
ObjectOutputStream oo = null;
ObjectInputStream oi = null;
ByteArrayInputStream bi = null;
try{
bo = new ByteArrayOutputStream();
oo = new ObjectOutputStream(bo); oo.writeObject(this); // 反序列化 bi = new ByteArrayInputStream(bo.toByteArray()); oi = new ObjectInputStream(bi);
Object obj = oi.readObject();
}
catch(Exception e){
e.printStackTrace();
}
finally {
// 关闭流
try{
bo = null;
bi = null;
if(oo != null){
oo.close();
}
if(oi != null){
oi.close();
}
}catch(Eception e){
e.printStackTrace();
}
}
return obj; } }
关于这种方法我就很少作介绍了,你们只须要知道有这样一种方法就好了,之后若是遇到了须要使用这种状况,就知道该怎样处理了。
这里总结一下,对象的克隆就是把一个对象的当前状态从新拷贝一份到另外一个新对象中,两个对象变量指向不一样的对象,浅克隆仅仅调用super.clone()方法,对成员变量也只是简单的值拷贝,因此当成员变量中有数组,对象等复杂类型的时候,就会存在藕断丝连的混乱关系,深拷贝不只仅调用super.clone()方法进行对象拷贝,将对象中的复杂类型一样进行了拷贝,这样两个对象就再无瓜葛,井水不犯河水了。
至此,对象的克隆就真正的结束了,欢迎你们继续关注!若有不懂的问题能够留言。也欢迎各位大佬来批评指正。喜欢个人教程的话记得动动小手点下推荐,也欢迎关注个人博客。