浅析Java中的深克隆和浅克隆
说实话,目前为止还没在项目中遇到过关于Java深克隆和浅克隆的场景。今天手抖戳开了花呗账单,双十二败家的战绩真是惨不忍睹,若能在我的客户端“篡改”下账单金额,那该(简)有(止)多(做)好(梦)啊!于是乎,有了以下的设想。采用工厂模式,根据所传入的帐户名accountName
得到账单bill
返回客户端client
,代码实现如下:
账单类
Bill
代码:
/*** @Author: mollychin* @Date: 2019/1/1 20:39*/
@Data
public class Bill{/*** 账单流水号*/private Long id;/*** 账单总金额*/private BigDecimal totalAmount;/*** 对应的账户*/private String accountName;
}
账单工厂类
BillFactory
代码:
/*** @Author: mollychin* @Date: 2019/1/1 20:44*/
public class BillFactory {private Bill bill = null;public Bill getBill(String name) {if (bill == null) {synchronized (this) {if (bill == null) {bill = new Bill();bill.setAccountName(name);bill.setTotalAmount(BigDecimal.valueOf(5000.0));}}}return bill;}
}
客户端类
BillClient
代码:
/*** @Author: mollychin* @Date: 2019/1/1 20:51*/
public class BillClient {public static void main(String[] args) {BillFactory billFactory = new BillFactory();Bill bill = billFactory.getBill("mollychin");System.out.println("original bill amount:"+bill.getTotalAmount());}
}
// 输出:
original bill amount:5000.0
显而易见,此时我的账单金额是工厂类返回的5000.00,没毛病。但万一遇上吃土少女想动点歪脑筋呢?嘿嘿。请看下面:
试图篡改账单金额的客户端代码:
/*** @Author: mollychin* @Date: 2019/1/1 20:51*/
public class BillClient {public static void main(String[] args) {BillFactory billFactory = new BillFactory();Bill bill = billFactory.getBill("mollychin");System.out.println("bill before:"+bill.getTotalAmount());Bill fakeBill = bill;fakeBill.setTotalAmount(BigDecimal.valueOf(2000.00));System.out.println("bill after:"+bill.getTotalAmount());System.out.println("fakeBill:"+fakeBill.getTotalAmount());Bill newMollychin = billFactory.getBill("mollychin");System.out.println("get bill again:"+newMollychin.getTotalAmount());}
}
// 输出:
bill before:5000.0
bill after:2000.0
fakeBill:2000.0
get bill again:2000.0
bill.setTotalAmount(BigDecimal.valueOf(2000.00));
代码里自有黄金屋啊,一行代码三千块欸。可花呗账单怎可能被我们如此轻易修改呢?值得思考的是为什么会出现上述情况呢?因为bill
和fakeBill
都是对同一对象的引用,任何一方的改动都会影响另一方的变化。那么如何避免这种及其不安全的情况呢?即希望fakeBill是一个新对象,它和bill的初始状态一样,但随后会有互不影响的改动,这时可实现Cloneable
并重写clone()
方法。
实现了
Cloneable
接口的账单类CloneableBill
代码:
@Data
public class CloneableBill implements Cloneable {/*** 账单流水号*/private Long id;/*** 账单总金额*/private BigDecimal totalAmount;/*** 对应的账户*/private String accountName;/*** 重写了父类的克隆方法.* @return*/@Overridepublic CloneableBill clone() {CloneableBill cloneableBill = null;try {cloneableBill = (CloneableBill) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return cloneableBill;}
}
> 工厂类`CloneableBillFactory`的返回也发生了改变:
/*** @author: mollychin* @date: 2019/1/2*/
public class CloneableBillFactory {private CloneableBill cloneableBill = null;public CloneableBill getCloneableBill(String name) {if (cloneableBill == null) {synchronized (this) {if (cloneableBill == null) {cloneableBill = new CloneableBill();cloneableBill.setTotalAmount(BigDecimal.valueOf(5000.00));cloneableBill.setAccountName(name);}}}// 新的改动return cloneableBill.clone();}
}
执行客户端
CloneableBillClient
代码,调用clone()
方法查看结果,此时的账单金额:
/*** @author: mollychin* @date: 2019/1/2*/
public class CloneableBillClient {public static void main(String[] args) {CloneableBillFactory cloneableBillFactory = new CloneableBillFactory();CloneableBill originalBill = cloneableBillFactory.getCloneableBill("mollychin");System.out.println("Before clone:" + originalBill.getTotalAmount());CloneableBill fakeBill = null;try {fakeBill = originalBill.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}fakeBill.setTotalAmount(BigDecimal.valueOf(2000.00));System.out.println("After clone:" + originalBill.getTotalAmount());}
}
输出:
Before clone:5000.0
After clone:5000.0
失不失望!现在已经无法通过客户端随意篡改我们的账单金额了=.= 那么究竟
Cloneable
接口以及clone()
方法对我们的代码做了什么操作呢?戳进源码一探究竟!
你会发现,Cloneable接口里没有一个方法,俗称“标记接口”
tagging interface
,实现了该接口的类可调用Object
类的clone()
。若未实现该接口,当调用clone()
会抛出CloneNotSupportedException
异常。
通过上述操作,妄想篡改账单金额貌似是不可能的了。但一般情况下,账单类里面会含有账单明细类的一个对象,用来描述该账单的明细账单。这样可通过账单对象get到账单明细对象,优化代码如下:
持有账单明细对象的账单类
DoubleCloneableBill
:
/*** @description:* @author: mollychin* @date: 2019/1/2*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DoubleCloneableBill implements Cloneable {/*** 账单流水号*/private Long id;/*** 账单总金额*/private BigDecimal totalAmount;/*** 对应的账户*/private String accountName;/*** 账单明细对象*/private BillDetail billDetail;@Overridepublic DoubleCloneableBill clone() {DoubleCloneableBill doubleCloneableBill = null;try {doubleCloneableBill = (DoubleCloneableBill) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return doubleCloneableBill;}
}
工厂类
DoubleCloneableBillFactory
:
/*** @description:* @author: mollychin* @date: 2019/1/2*/
public class DoubleCloneableBillFactory {private DoubleCloneableBill doubleCloneableBill;public DoubleCloneableBill getDoubleCloneableBill(String name) {if (doubleCloneableBill == null) {synchronized (this) {if (doubleCloneableBill == null) {doubleCloneableBill = new DoubleCloneableBill();doubleCloneableBill.setAccountName(name);doubleCloneableBill.setTotalAmount(BigDecimal.valueOf(5000.00));doubleCloneableBill.setBillDetail(new BillDetail("camera", BigDecimal.valueOf(5000.00)));}}}return doubleCloneableBill;}
}
客户端
DoubleCloneableBillClient
:
/*** @description:* @author: mollychin* @date: 2019/1/2*/
public class DoubleCloneableBillClient {public static void main(String[] args) {DoubleCloneableBillFactory doubleCloneableBillFactory = new DoubleCloneableBillFactory();DoubleCloneableBill originalBill = doubleCloneableBillFactory.getDoubleCloneableBill("mollychin");System.out.println("Bill Detail:Before clone:"+originalBill.getBillDetail().getPrice());System.out.println("Bill Amount Before clone:"+originalBill.getTotalAmount());DoubleCloneableBill clonedBill = originalBill.clone();clonedBill.getBillDetail().setPrice(BigDecimal.valueOf(2000.00));System.out.println("Bill Detail:After clone:"+originalBill.getBillDetail().getPrice());System.out.println("Bill Amount After clone:"+originalBill.getTotalAmount());}
}
// 输出:
Bill Detail:Before clone:5000.0
Bill Amount Before clone:5000.0
Bill Detail:After clone:2000.0
Bill Amount After clone:5000.0
由输出可见,虽然我们篡改不了账单金额,但账单明细的金额居然可以轻松被改??之所以会发生这种“怪异”事件,是因为这里采用的是Java中的浅克隆
shadowClone
,接下来我们尝试下深克隆deepClone
可否避免这种情况。
账单
DoubleCloneableBill
以及账单明细类BillDetail
:
/*** @description:* @author: mollychin* @date: 2019/1/2*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class DoubleCloneableBill implements Cloneable {/*** 账单流水号*/private Long id;/*** 账单总金额*/private BigDecimal totalAmount;/*** 对应的账户*/private String accountName;/*** 账单明细对象*/private BillDetail billDetail;@Overridepublic DoubleCloneableBill clone() {DoubleCloneableBill doubleCloneableBill = null;try {doubleCloneableBill = (DoubleCloneableBill) super.clone();BillDetail clonedBillDetail = doubleCloneableBill.getBillDetail().clone();doubleCloneableBill.setBillDetail(clonedBillDetail);} catch (CloneNotSupportedException e) {e.printStackTrace();}return doubleCloneableBill;}
}/*** @description:* @author: liuyiMao* @date: 2019/1/2*/
@Data
@AllArgsConstructor
public class BillDetail implements Cloneable {/*** 商品名称*/private String goodName;/*** 商品价格*/private BigDecimal price;@Overridepublic BillDetail clone() {BillDetail billDetail = null;try {billDetail = (BillDetail) super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return billDetail;}
}
工厂类
DoubleCloneableBillFactory
:
/*** @description:* @author: mollychin* @date: 2019/1/2*/
public class DoubleCloneableBillFactory {private DoubleCloneableBill doubleCloneableBill;public DoubleCloneableBill getDoubleCloneableBill(String name) {if (doubleCloneableBill == null) {synchronized (this) {if (doubleCloneableBill == null) {doubleCloneableBill = new DoubleCloneableBill();doubleCloneableBill.setAccountName(name);doubleCloneableBill.setTotalAmount(BigDecimal.valueOf(5000.00));doubleCloneableBill.setBillDetail(new BillDetail("camera", BigDecimal.valueOf(5000.00)));}}}return doubleCloneableBill;}
}
客户端(妄图篡改金额)
DoubleCloneableBillClient
:
/*** @description:* @author: mollychin* @date: 2019/1/2*/
public class DoubleCloneableBillClient {public static void main(String[] args) {DoubleCloneableBillFactory doubleCloneableBillFactory = new DoubleCloneableBillFactory();DoubleCloneableBill originalBill = doubleCloneableBillFactory.getDoubleCloneableBill("mollychin");System.out.println("Bill Detail:Before clone:"+originalBill.getBillDetail().getPrice());System.out.println("Bill Amount Before clone:"+originalBill.getTotalAmount());DoubleCloneableBill clonedBill = originalBill.clone();clonedBill.getBillDetail().setPrice(BigDecimal.valueOf(2000.00));System.out.println("Bill Detail:After clone:"+originalBill.getBillDetail().getPrice());System.out.println("Bill Amount After clone:"+originalBill.getTotalAmount());}
}
// 输出:
Bill Detail:Before clone:5000.0
Bill Amount Before clone:5000.0
Bill Detail:After clone:5000.0
Bill Amount After clone:5000.0
由此可见,采用了深克隆之后,不管是值类型还是引用类型,都无法在客户端被随意篡改,是不是相对来说健壮了不少呢?
- 最后一点叨叨:
借着花呗账单的案例,逐渐了解了Java中深克隆和浅克隆,并感受到它们在系统健壮性方面发挥的巨大作用。使用深克隆可以解决引用对象克隆的问题,但假若类之间嵌套的层次很多,其复杂程度是显而易见的。譬入一个保险系统,类的嵌套可能是这样的:账单类->账单明细类->条款类->条款费用类。前者都持有后者的一个或者多个引用对象,这时该如何实现深克隆所达到的效果呢?此时可以使用org.apache.commons.lang3.SerializationUtils.clone()
方法使用Java序列化来简易地达到同样的效果。这将会在后面的博文中继续介绍。
浅析Java中的深克隆和浅克隆相关推荐
- Java中的深克隆和浅克隆的原理及三种方式实现深克隆
本文详细介绍了Java中的浅克隆和深克隆的概念,及案例演示如何实现深克隆! 文章目录 1 克隆概述 2 深克隆实现 3 案例 3.1 测试普通clone方法--浅克隆 3.2 使用重写后的clon ...
- Java中的深克隆与浅克隆
浅克隆: 实现Cloneable接口即可实现,浅克隆只对象内部的基础数据类型(包括包装类)被克隆,引用数据类型(负责对象)会被使用引用的方式传递. 简单来说,就是浅克隆属性如果是复杂对象,对象是不会被 ...
- 浅析Java中的final关键字
浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...
- java中的深度克隆浅克隆_了解Java中的可克隆接口
java中的深度克隆浅克隆 什么是对象克隆? 对象克隆是生成具有不同名称的对象的精确字段到字段副本的过程. 克隆的对象在内存中有自己的空间,可在其中复制原始对象的内容. 这就是为什么在克隆后更改原始对 ...
- java printf与println_浅析Java中print、printf、println的区别
我们的程序员在开发的时候,都会使用到很多不同的功能,但是有些功能是大同小异,别着急,下文是爱站技术频道小编为大家带来的浅析Java中print.printf.println的区别,希望对你学习有帮助! ...
- 【转】浅析Java中的final关键字
谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...
- 浅析Java中的Steam流
Stream流 文章目录 Stream流 1. 集合遍历 2. 流式思想 3. Stream流 3.1 概念 3.2 流的获取 3.3 forEach 3.4 filter 3.5 map 3.6 c ...
- java深度克隆_浅析Java中clone()方法浅克隆与深度克隆
现在Clone已经不是一个新鲜词语了,伴随着"多莉"的产生这个词语确实很"火"过一阵子,在Java中也有这么一个概念,它可以让我们很方便的"制造&qu ...
- 浅析java中clone()方法
本文转载自:http://blog.csdn.net/mengxiangyue/article/details/6818611 Java中我们可能都遇到过这样的情况,在我们将一个对象做为参数传给一个函 ...
最新文章
- 来自 IsayNo (@IsayNooo) 的推文
- “数字强市 数创未来” | 山东省数据应用创新创业大赛烟台赛场火热招募中!...
- JS域:加载(它的页面的)域 -(所在页面的域)--------- 资源域
- 兰州大学第一届『飞马杯』程序设计竞赛 - ★★飞马祝福语★★(动态dp)
- 这些Java基础面试知识点,你都掌握了吗?
- 修改FTP服务器端口后无法访问
- git 创建分支提交远程分支
- 初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。
- React.js入门基础一
- 2009年3月全国计算机等级考试二级Java笔试试题及答案
- vue安装(linux)
- 关于Windows mobile设备中心,同步软件不出现,打开卡住问题的解决
- Pyspark获取hdfs上多个文件
- 线程优先级 Priority
- 什么是write-allocate policy?
- 蓝牙路由器解决方案行业应用
- 垂直搜索 vs 通用搜索
- 无符号整数--拼数字
- 今日金融词汇---网格交易,是什么?
- 这几个游戏玩不通关你还算黑客?