对象应该是不可变的

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

在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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362195.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

开源GIS---.Net系列

NASA World WindNASA World Wind是使用.NET开发的一个Windows窗体系统&#xff0c;以地球外观看得角度提供全球定位功能&#xff0c;类似于Google Earth。它结合了美国国家航空航天局(NASA)从卫星拍摄的图像&#xff0c;这些图像应用于Blue Marble, Landsat 7, SRTM, MODIS 以及…

TypeScript中怎么用接口(interface)描述类(静态部分与实例部分)

TypeScript是JavaScript的超集 在看TypeScript官方文档的接口一节的时候对于类的静态部分与实例部分产生了疑问&#xff0c;通过努力得到自认为相对合理的解释&#xff0c;写下此文以记所得&#xff0c;如有谬误感谢指正。 文中大部分代码示例来自TypeScript官网 TS中的接口…

Java黑科技之源:JVMTI完全解读

Java生态中有一些非常规的技术&#xff0c;它们能达到一些特别的效果。这些技术的实现原理不去深究的话一般并不是广为人知。这种技术通常被称为黑科技。而这些黑科技中的绝大部分底层都是通过JVMTI实现的。 形象地说&#xff0c;JVMTI是Java虚拟机提供的一整套后门。通过这套后…

npm install --save

1. npm install&#xff1a;本地安装 2. npm install -g&#xff1a;全局安装 我们在使用 npm install 安装模块或插件时&#xff0c;有两种命令把它们写入到 package.json 文件中去&#xff0c;在 package.json 里面体现出的区别就是&#xff0c;使用 --save 安装的插件&…

其他一些单元测试技巧

在我以前的文章中&#xff0c;我展示了有关JavaBeans单元测试的一些技巧。 在此博客文章中&#xff0c;我将提供有关单元测试某些相当常见的Java代码的另外两个技巧&#xff0c;即实用程序类和Log4J日志记录语句 。 测试实用程序类 如果您的实用程序类遵循与我倾向于编写的相同…

常见的CSS布局

各种常见的CSS布局 在工作中会经常用到很多的布局方式&#xff0c;这里总结一下所遇到的布局&#xff0c;会持续更新。 悬挂布局 实现这种布局的方式有很多&#xff0c;这边主要挑两个&#xff0c;如下&#xff1a; 方式一&#xff1a;使用浮动和块级格式化上下文特性 这种…

netflix数据处理2(转)

原始数据&#xff1a;$head -10 mv_0006890.txt6890:1735266,1,2004-04-021008399,1,2004-06-222360117,2,2003-11-081294425,2,2004-03-15439931,4,2004-03-271583311,1,2004-03-112431832,3,2005-02-13620771,2,2004-03-201110906,1,2004-03-04结果数据&#xff1a;user_id m…

jQuery 效果

显示和隐藏 1. show(speed) &#xff1a;speed 可以取&#xff1a;slow/fast/毫秒 1 $("#show").click(function(){2 $("p").show(1000);3 }); 2. hide(speed) &#xff1a; 1 $("#hide").click(function(…

OSCP-Kioptrix2014-2 漏洞利用

pChart 2.1.3 文件包含漏洞 搜索漏洞查看漏洞理由代码:hxxp://localhost/examples/index.php?ActionView&Script%2f..%2f..%2fetc/passwd 之前的8080端口禁止访问,看看apache的配置:http://192.168.1.78/pChart2.1.3/examples/index.php?ActionView&Script%2f..%2f..…

使用Drools跟踪输出

Drools 6包含一个跟踪输出&#xff0c;可以帮助您了解系统中正在发生的事情&#xff0c;事物执行的频率以及多少数据。 这也有助于理解Drools 6现在是基于目标的算法&#xff0c;它使用链接机制链接评估规则。 有关此的更多详细信息&#xff1a; http://www.javacodegeeks.co…

CodeSmith注册机,支持5.2.2和5.2.1版

CodeSmith&#xff0c;不用说了&#xff0c;大名鼎鼎的代码生成工具。最早是免费的&#xff0c;后来收费啦这个注册机是针对目前新的CodeSmith 5.2.2的&#xff0c;支持Professinal和其他版本。使用的方法&#xff1a;安装原版的试用版本&#xff0c;从官方网站下载运行试用版&…

JS中与正则相关的方法

前面有一篇文章大体介绍了一下JS中正则表达式&#xff0c;而使用正则表达式还需要配合JS中的相关方法&#xff0c;分别是String对象和RegExp对象的方法。今天就来具体介绍一下这些方法。 使用这则表达式的方法可以分为两类&#xff0c;一个是String的几个方法&#xff0c;还有…

JS基础:求一组数中的最大最小值,以及所在位置

1 var arr [0, 5, -3, 6, 2, -6, 10];2 //定义一个最大值和一个最小值&#xff0c;把他们的索引值赋值给固定的两个变量3 var maxValue arr[0];4 var minValue arr[0];5 var maxIndex 0;6 var minIndex 0;7 for …

linux epoll,poll,select

epoll函数用法&#xff0c;还有点poll和select 1&#xff0c;LT的epoll是select和poll函数的改进版。 特点是&#xff0c;读完缓冲区后&#xff0c;如果缓冲区还有内容的话&#xff0c;epoll_wait函数还会返回&#xff0c;直到把缓冲区全部读完。 2&#xff0c;ET的epoll&#…

λ和副作用

总览 Java 8添加了诸如lambda和类型推断之类的功能。 这使语言不那么冗长和简洁&#xff0c;但是它带来了更多的副作用&#xff0c;因为您不必对所做的事情那么明确。 Lambda的返回类型很重要 Java 8推断闭包的类型。 一种方法是查看返回类型&#xff08;或是否返回任何内容&a…

sessionStorage和localStorage的用法,不同点和相同点

一&#xff0c;共同点 &#xff08;1)存储时用setItem: localStorage.setItem("key","value");//以“key”为名称存储一个值“value”sessionStorage.setItem("key", "value"); &#xff08;2&#xff09;获取时用getItem: localS…

shell学习笔记1-文件安全与权限

1&#xff0c;创建文件的用户和他所属的组拥有该文件&#xff0c;文件的属主可以设定谁具有读、写、执行该文件的权限&#xff0c;根用户可以改变任何普通用户的设置。 2&#xff0c;一个文件一经创建&#xff0c;就具有三种访问权限&#xff1a;读&#xff08;可以显示该文件的…

没有IF-ELSE的工厂

面向对象语言具有非常强大的多态性功能&#xff0c;用于删除代码中的if / else或切换大小写。 没有条件的代码易于阅读。 在某些地方必须放置它们&#xff0c;其中一个示例是Factory / ServiceProvider类。 我敢肯定&#xff0c;您已经看到IF-ELSEIF的工厂课程了&#xff0c;…

最新70佳单页网站设计案例欣赏(上篇)

单页网站是指只有一个页面的网站&#xff0c;这种形式的网站曾经非常流行&#xff0c;现在依然有很多人喜欢。不过&#xff0c;并不是每个网站都适合做成单页&#xff0c;一般都是内容比较少而且将来内容也不怎么增加的情况才适合这样做。如果你打算做一个这样的网站&#xff0…

浏览器劫持者

launchpage 浏览器劫持者&#xff0c;它会在未经你的许可下就接管你的浏览器。更多 https://launchpage.org/?uidqT5KGGjMhxpsXWEzIkWR44y5McmHTuSG50ukahoC8gOClKIGNwZP0nuyPBoYUFiBINK7 https://ns.freedrive.cn/?sEB5805AD0&ghttp://item.jd.com/13300636764.html 更…