说实话,目前为止还没在项目中遇到过关于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)); 代码里自有黄金屋啊,一行代码三千块欸。可花呗账单怎可能被我们如此轻易修改呢?值得思考的是为什么会出现上述情况呢?因为billfakeBill都是对同一对象的引用,任何一方的改动都会影响另一方的变化。那么如何避免这种及其不安全的情况呢?即希望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中的深克隆和浅克隆相关推荐

  1. Java中的深克隆和浅克隆的原理及三种方式实现深克隆

      本文详细介绍了Java中的浅克隆和深克隆的概念,及案例演示如何实现深克隆! 文章目录 1 克隆概述 2 深克隆实现 3 案例 3.1 测试普通clone方法--浅克隆 3.2 使用重写后的clon ...

  2. Java中的深克隆与浅克隆

    浅克隆: 实现Cloneable接口即可实现,浅克隆只对象内部的基础数据类型(包括包装类)被克隆,引用数据类型(负责对象)会被使用引用的方式传递. 简单来说,就是浅克隆属性如果是复杂对象,对象是不会被 ...

  3. 浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  4. java中的深度克隆浅克隆_了解Java中的可克隆接口

    java中的深度克隆浅克隆 什么是对象克隆? 对象克隆是生成具有不同名称的对象的精确字段到字段副本的过程. 克隆的对象在内存中有自己的空间,可在其中复制原始对象的内容. 这就是为什么在克隆后更改原始对 ...

  5. java printf与println_浅析Java中print、printf、println的区别

    我们的程序员在开发的时候,都会使用到很多不同的功能,但是有些功能是大同小异,别着急,下文是爱站技术频道小编为大家带来的浅析Java中print.printf.println的区别,希望对你学习有帮助! ...

  6. 【转】浅析Java中的final关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...

  7. 浅析Java中的Steam流

    Stream流 文章目录 Stream流 1. 集合遍历 2. 流式思想 3. Stream流 3.1 概念 3.2 流的获取 3.3 forEach 3.4 filter 3.5 map 3.6 c ...

  8. java深度克隆_浅析Java中clone()方法浅克隆与深度克隆

    现在Clone已经不是一个新鲜词语了,伴随着"多莉"的产生这个词语确实很"火"过一阵子,在Java中也有这么一个概念,它可以让我们很方便的"制造&qu ...

  9. 浅析java中clone()方法

    本文转载自:http://blog.csdn.net/mengxiangyue/article/details/6818611 Java中我们可能都遇到过这样的情况,在我们将一个对象做为参数传给一个函 ...

最新文章

  1. 来自 IsayNo (@IsayNooo) 的推文
  2. “数字强市 数创未来” | 山东省数据应用创新创业大赛烟台赛场火热招募中!...
  3. JS域:加载(它的页面的)域 -(所在页面的域)--------- 资源域
  4. 兰州大学第一届『飞马杯』程序设计竞赛 - ★★飞马祝福语★★(动态dp)
  5. 这些Java基础面试知识点,你都掌握了吗?
  6. 修改FTP服务器端口后无法访问
  7. git 创建分支提交远程分支
  8. 初入c++(六)虚函数实现多态,虚析构函数,虚函数表和多态实现机制,纯虚函数。
  9. React.js入门基础一
  10. 2009年3月全国计算机等级考试二级Java笔试试题及答案
  11. vue安装(linux)
  12. 关于Windows mobile设备中心,同步软件不出现,打开卡住问题的解决
  13. Pyspark获取hdfs上多个文件
  14. 线程优先级 Priority
  15. 什么是write-allocate policy?
  16. 蓝牙路由器解决方案行业应用
  17. 垂直搜索 vs 通用搜索
  18. 无符号整数--拼数字
  19. 今日金融词汇---网格交易,是什么?
  20. 这几个游戏玩不通关你还算黑客?

热门文章

  1. AcWing 606. 平均数1
  2. 【书影观后感 三】1587—雪后的大明
  3. 【数据库】解释关系代数中的象集、除运算
  4. 还愁没壁纸?Python爬取5K分辨率高清桌面壁纸
  5. eas报错日记_EAS新建数据中心报错
  6. 一加手机·关闭应用双开储存空间
  7. ai 如何导出html格式,Adobe Illustrator导出SVG的设置方法
  8. 五十音图平假名随机生成
  9. 静雅小学学生信息管理系统的设计与实现
  10. [springboot]springboot启动流程