可变对象 不可变对象区别

在面向对象的编程中,如果对象的状态在创建后无法修改,则它是不可变的 。

在Java中,不可变对象的一个​​很好的例子是String 。 创建后,我们无法修改其状态。 我们可以要求它创建新的字符串,但是它自己的状态永远不会改变。

但是,JDK中没有那么多不可变的类。 以类Date为例。 可以使用setTime()修改其状态。

我不知道为什么JDK设计师决定以不同的方式来制作这两个非常相似的类。 但是,我认为可变Date的设计有许多缺陷,而不变的String则更多地体现了面向对象范例的精神。

而且,我认为在一个完美的面向对象的世界所有类都应该是不变的 。 不幸的是,有时由于JVM的限制在技术上是不可能的。 尽管如此,我们应该始终追求最佳。

这是支持不变性的参数的不完整列表:

  • 不变的对象更易于构造,测试和使用
  • 真正不可变的对象始终是线程安全的
  • 它们有助于避免时间耦合
  • 它们的使用无副作用(无防御性副本)
  • 避免了身份变异性问题
  • 他们总是有失败原子性
  • 它们更容易缓存
  • 他们防止NULL引用, 这是不好的

让我们一一讨论最重要的论点。

线程安全

第一个也是最明显的论点是,不可变对象是线程安全的。 这意味着多个线程可以同时访问同一对象,而不会与另一个线程发生冲突。

如果没有对象方法可以修改其状态,那么无论它们有多少个以及被调用的频率是多少,它们都将在自己的堆栈内存空间中工作。

Goetz等。 在非常著名的《 Java Concurrency in Practice》 (强烈推荐)一书中,更详细地介绍了不可变对象的优点。

避免时间耦合

这是时间耦合的示例(代码发出两个连续的HTTP POST请求,其中第二个包含HTTP正文):

Request request = new Request("http://example.com");
request.method("POST");
String first = request.fetch();
request.body("text=hello");
String second = request.fetch();

此代码有效。 但是,您必须记住,在配置第二个请求之前,应该先配置第一个请求。 如果我们决定从脚本中删除第一个请求,则将删除第二行和第三行,并且不会从编译器中得到任何错误:

Request request = new Request("http://example.com");
// request.method("POST");
// String first = request.fetch();
request.body("text=hello");
String second = request.fetch();

现在,该脚本已损坏,尽管它编译时没有错误。 这就是时间耦合的意义所在—代码中总是有一些程序员必须记住的隐藏信息。 在此示例中,我们必须记住,第一个请求的配置也用于第二个请求。

我们必须记住,第二个请求应始终保持在一起,并在第一个请求之后执行。

如果Request类是不可变的,则第一个代码片段将一开始就无法使用,并且将被重写为:

final Request request = new Request("");
String first = request.method("POST").fetch();
String second = request.method("POST").body("text=hello").fetch();

现在,这两个请求没有耦合。 我们可以安全地删除第一个,第二个仍然可以正常工作。 您可能会指出存在代码重复。 是的,我们应该摆脱它,然后重新编写代码:

final Request request = new Request("");
final Request post = request.method("POST");
String first = post.fetch();
String second = post.body("text=hello").fetch();

看,重构没有破坏任何东西,而且我们仍然没有时间耦合。 可以安全地从代码中删除第一个请求,而不会影响第二个请求。

我希望这个例子能证明操作不可变对象的代码更具可读性和可维护性,因为它没有时间耦合。

避免副作用

让我们尝试在新方法中使用我们的Request类(现在它是可变的):

public String post(Request request) {request.method("POST");return request.fetch();
}

让我们尝试发出两个请求-第一个请求使用GET方法,第二个请求使用POST:

Request request = new Request("http://example.com");
request.method("GET");
String first = this.post(request);
String second = request.fetch();

方法post()具有“副作用”,它对可变对象request进行了更改。 在这种情况下,这些更改并不是真正预期的。 我们希望它发出POST请求并返回其主体。 我们不想阅读其文档只是为了发现它还在后台修改了我们作为参数传递给它的请求。

不用说,这种副作用会导致错误和可维护性问题。 使用不可变的Request会更好:

public String post(Request request) {return request.method("POST").fetch();
}

在这种情况下,我们可能没有任何副作用。 任何人都不能修改我们的request对象,无论它在何处使用以及方法调用传递给调用堆栈的深度如何:

Request request = new Request("http://example.com").method("GET");
String first = this.post(request);
String second = request.fetch();

此代码是绝对安全且无副作用的。

避免身份变异

通常,如果对象的内部状态相同,我们希望它们相同。 Date类是一个很好的例子:

Date first = new Date(1L);
Date second = new Date(1L);
assert first.equals(second); // true

有两个不同的对象。 但是,它们彼此相等,因为它们的封装状态相同。 通过自定义的equals()hashCode()方法的重载实现,可以实现这一点。

这种方便的方法与可变对象一起使用的结果是,每次我们修改对象的状态时,它都会更改其身份:

Date first = new Date(1L);
Date second = new Date(1L);
first.setTime(2L);
assert first.equals(second); // false

在您开始将可变对象用作地图中的键之前,这看起来很自然:

Map<Date, String> map = new HashMap<>();
Date date = new Date();
map.put(date, "hello, world!");
date.setTime(12345L);
assert map.containsKey(date); // false

修改date状态对象时,我们不希望它更改其身份。 我们不希望仅因为其键的状态已更改而在映射中丢失条目。 但是,这正是以上示例中发生的情况。

当我们向地图添加对象时,其hashCode()返回一个值。 HashMap使用此值将条目放置到内部哈希表中。 当我们调用containsKey()时,对象的哈希码是不同的(因为它基于其内部状态),并且HashMap在内部哈希表中找不到它。

调试可变对象的副作用非常烦人且困难。 不可变的对象完全避免了它。

失效原子性

这是一个简单的示例:

public class Stack {private int size;private String[] items;public void push(String item) {size++;if (size > items.length) {throw new RuntimeException("stack overflow");}items[size] = item;}
}

显然,如果Stack类在溢出时引发运行时异常,则该对象将处于断开状态。 它的size属性将增加,而items将不会获得新元素。

不变性可以防止此问题。 对象永远不会处于损坏状态,因为它的状态仅在其构造函数中被修改。 构造函数将失败,拒绝对象实例化,或者成功,则将生成有效的固态对象,该对象永远不会更改其封装状态。

有关此主题的更多信息,请阅读Joshua Bloch撰写的有效Java,第二版 。

反对不变性的争论

有许多反对不变性的论点。

  1. “不可迁移性不适用于企业系统”。 我经常听到人们说不变性是一种奇特的功能,而在真正的企业系统中绝对不可行。 作为反驳,我只能显示一些仅包含不可变Java对象的实际应用程序示例: jcabi-http , jcabi-xml , jcabi-github , jcabi-s3 , jcabi-dynamo , jcabi-simpledb所有仅与不可变类/对象一起使用的Java库。 netbout.com和stateful.co是仅与不可变对象一起使用的Web应用程序。
  2. “更新现有对象比创建新对象便宜”。 Oracle 认为 :“对象创建的影响通常被高估了,并且可以被与不可变对象相关的某些效率所抵消。 其中包括由于垃圾收集而减少的开销,以及消除了保护可变对象免受损坏所需的代码。” 我同意。

如果您还有其他论点,请在下面发表,我将尝试发表评论。

翻译自: https://www.javacodegeeks.com/2014/09/objects-should-be-immutable.html

可变对象 不可变对象区别

可变对象 不可变对象区别_对象应该是不可变的相关推荐

  1. java 对象和实例有什么区别_对象和实例之间的区别

    " 类别"一词来自" 分类"(将类别放入其中的" 类别"),现在我们都听说" 类别"就像一个蓝图,但这到底是什么意思?这 ...

  2. 可变悬挂与空气悬挂的区别_可变悬架和空气悬架的的区别是什么

    可变悬架是指可以手动或车辆自动改变悬架的高低或软硬来适应不同路面的行驶需求.关于悬架的问题是消费者比较关心的一个因素,因为它直接影响到车辆的舒适性和操控性.然而以当今的科技水平来说,普通的弹簧避震很难 ...

  3. java数组可以包含对象吗_数组可以包含对象类型的元素吗_对象数组

    对象数组就是数组里的每个元素都是类的对象,赋值时先定义对象,然后将对象直接赋给数组就行了. 怎样声明包含 5 个元素的对象数组,每个元素都是 Employee 类型的对象 浏览次数:4875 bill ...

  4. python可变类型与不可变类型作为函数参数区别_不要用可变类型对象做函数默认参数...

    不要用可变类型对象做函数默认参数 1. 可变对象做默认参数 内置数据类型int,float,bool,str,tuple 是不可变对象, 字典,集合,列表是可变对象. 在定义python函数时,千万不 ...

  5. java 对象构造函数_20.Java基础_对象的构造函数

    package pack1; public class Student { private String name; private int age; //构造方法 //如果没有任何构造函数,系统会自 ...

  6. java对象强转 新增字段_对象属性转成表字段

    这几天在恶补正则表达式,今天刚好遇到一个小需求,大致就对象属性转成数据表字段的名字: 也就是userName ---> user_name,很简单的替换,不会用java的正则表达式,耽误了一会时 ...

  7. 捡对象引流脚本 内容_对象和索引流

    捡对象引流脚本 内容 我本来要写一篇关于如何将流与每个元素的索引混合的文章,但是Baeldung上的人们已经很好地涵盖了这个主题 ! 鉴于我是他们编辑团队的一员,我为他们/我们感到自豪. 有趣的是,特 ...

  8. php 对象 final,PHP7_OOP_对象重载以及魔术方法_对象遍历_final关键字

    //对象遍历: class MyClass{ public $var1 = "value 1"; public $var2 = "value 2"; publi ...

  9. 可变悬挂与空气悬挂的区别_可变悬架与空气悬架的区别

    在买车时,悬挂一定是消费者们十分关心的一个因素,因为它直接影响到车辆的舒适性和操控性.不同的需求应该选择什么样的悬架?可变悬架与空气悬架有什么区别,就让我们一起来了解一下. 可变悬架系统 一辆车的风格 ...

最新文章

  1. Linux创建线程时 内存分配的那些事
  2. dijkstra 算法_最短路径问题Dijkstra算法详解
  3. 皮一皮:35岁后你做什么?
  4. NOI2018 Day1 归程(return)
  5. [javaweb] servlet-session 会话跟踪技术 与 session保存作用域 (三)
  6. C语言实用算法系列之学生管理系统_单向链表外排序_堆内数组存储链表节点指针
  7. Android - Okhttp拦截器
  8. 【Spring】Bean instantiation via constructor failed nested exception Constructor threw exception
  9. python 文件及文件夹操作
  10. 你给客户报完价,客户就没消息了,什么原因呢?
  11. 《JavaScript高级程序设计2》学习笔记——Ajax与JSON
  12. Hibernate三大类查询总结
  13. 在GeoServer里设置图层的默认自定义样式,出现不显示预览图的情况(不起作用)...
  14. java -jar 命令隐藏黑窗口
  15. diskpart 删除磁盘OEM分区 及设置活动分区
  16. u盘/U盘启动盘插入电脑后,不显示文件,但有保留占用内存
  17. 莫队算法(最小曼哈顿生成树或者分块处理)
  18. 微信分享中将链接图标替换成自定义图片的实例
  19. 台湾科技挣扎,人祸大于天灾?
  20. Win10 VC++运行库集合|VC++ 2005 2008 2010 2012 2015

热门文章

  1. P3365,jzoj3894-改造二叉树【LIS,BST】
  2. 数学基础知识(高精、快速幂、龟速乘……)
  3. 【dfs】年会小游戏
  4. 【bfs】WJ的逃离
  5. 2017西安交大ACM小学期数据结构 [分块、二维矩阵]
  6. 动态规划训练5 [回文词]
  7. 面试官问:为什么 Java 线程没有 Running 状态?我懵了
  8. JavaFX UI控件教程(二)之JavaFX UI控件
  9. 一文告诉你如何导出 Git 变更文件
  10. Java多线程面试问题