不变性的来龙去脉

因此,在我的第一篇文章中,我谈到了一些构建器模式,并提到了一个非常强大但却被忽视的概念:不变性。

什么是不可变类? 这只是一个其实例无法修改的类。 类属性的每个值都在其声明或其构造函数中设置,并在对象的整个生命周期中保留这些值。 Java有很多不变的类,例如String ,所有带框的原语( DoubleIntegerFloat等), BigIntegerBigDecimal等。 这有一个很好的理由:不可变类比可变类更容易设计,实现和使用。 一旦实例化它们,它们只能处于一种状态,因此它们不易出错,并且,正如我们在本文后面会看到的那样,它们更加安全。

您如何确保类是不变的? 只需遵循以下5个简单步骤:

  1. 不要提供任何可修改对象状态的公共方法 ,也称为增变器(例如setter)。
  2. 防止扩展类 。 这不允许任何恶意或粗心的类扩展我们的类并损害其不变的行为。 这样做的通常且更简单的方法是将类标记为final ,但是我将在本文中提及另一种方法。
  3. 将所有字段定为最终值 。 这是让编译器为您强制执行第1点的方法。 此外,它清楚地使任何看到您的代码的人都知道,您不希望这些字段在设置后更改其值。
  4. 将所有字段设为私有 。 无论您是否考虑不变性,这一点都应该很明显,并且您应该遵循它 ,但是我只是为了以防万一。
  5. 永远不要提供对任何可变属性的访问 。 如果您的类具有一个可变对象作为其属性之一(例如ListMap或您的域问题中的任何其他可变对象),请确保该类的客户端永远无法获得对该对象的引用。 这意味着您永远不要从访问器(例如,getter)直接返回对它们的引用,并且永远不要在构造函数中使用从客户端作为参数传递的引用来初始化它们。 在这种情况下,您应该始终制作防御性副本。

有很多理论,没有代码,因此让我们看一个简单的不可变类是什么样子,以及它如何处理我之前提到的5个步骤:

public class Book {private final String isbn;private final int publicationYear;private final List reviews;private Book(BookBuilder builder) {this.isbn = builder.isbn;this.publicationYear = builder.publicationYear;this.reviews = Lists.newArrayList(builder.reviews);}public String getIsbn() {return isbn;}public int getPublicationYear() {return publicationYear;}public List getReviews() {return Lists.newArrayList(reviews);}public static class BookBuilder {private String isbn;private int publicationYear;private List reviews;public BookBuilder isbn(String isbn) {this.isbn = isbn;return this;}public BookBuilder publicationYear(int year) {this.publicationYear = year;return this;}public BookBuilder reviews(List reviews) {this.reviews = reviews == null ? new ArrayList() : reviews;return this;}public Book build() {return new Book(this);}}
}

我们将在这个非常简单的课程中讲解重点。 首先,您可能已经注意到,我再次使用了构建器模式。 这不仅是因为我是它的忠实拥护者,而且还因为我想说明一些我不想在之前的帖子中没有首先对不变性概念有基本了解的观点。 现在,让我们看一下我提到的5个步骤,您需要遵循这些步骤使一个类不可变,并查看它们是否对本书示例有效:

    • 不要提供任何修改对象状态的公共方法 。 请注意,该类上的唯一方法是其私有构造函数和其属性的获取器,但没有更改对象状态的方法。
    • 防止扩展类 。 这个很棘手。 我提到确保这一点的最简单方法是将班级定为最终班,但Book班显然不是最终班。 但是,请注意,唯一可用的构造函数是private 。 编译器确保没有公共或受保护的构造函数的类不能被子类化。 因此,在这种情况下,不必在类声明中使用final关键字,但无论如何将其包括在内只是一个好主意,以使看到您代码的任何人都可以清楚地知道。
    • 将所有字段定为最终值 。 非常简单,该类上的所有属性都声明为final
    • 永远不要提供对任何可变属性的访问 。 这实际上很有趣。 注意Book类如何具有一个List <String>属性,该属性被声明为final,并且其值在类构造函数上设置。 但是,此列表是可变对象。 也就是说,虽然评论参考一旦设置就无法更改,但列表的内容可以更改。 引用相同列表的客户端可以添加或删除元素,结果,在创建Book对象后更改其状态。 因此,请注意,在Book构造函数中,我们不直接分配引用。 相反,我们使用Guava库通过调用“ this.reviews = Lists.newArrayList(builder.reviews); ”来复制列表this.reviews = Lists.newArrayList(builder.reviews); ”。 在getReviews方法上可以看到相同的情况,在该方法中,我们返回列表的副本而不是直接引用。 值得注意的是,此示例可能有点过于简化,因为评论列表只能包含不可变的字符串。 如果列表的类型是可变的类,那么您还必须复制列表中的每个对象,而不仅仅是列表本身。

最后一点说明了为什么不可变的类导致更简洁的设计和更易于阅读的代码。 您只需共享那些不可变的对象,而不必担心防御性副本。 实际上,绝对不要制作任何副本,因为对象的任何副本都将永远等于原始副本。 一个必然的结论是,不变的对象仅仅是简单的。 他们只能处于一种状态,并且一生都保持这种状态。 您可以使用类构造函数来检查任何不变量(即,需要在该类上有效的条件,例如其属性之一的值范围),然后可以确保这些不变量保持真实状态而无需付出任何努力您或您的客户。

不变对象的另一个巨大好处是它们本质上是线程安全的。 它们不能被同时访问对象的多个线程破坏。 到目前为止,这是在应用程序中提供线程安全性的最简单且不易出错的方法。

但是,如果您已经有一个Book实例并且想要更改其属性之一的值怎么办? 换句话说,您想要更改对象的状态。 在不可变的类上,根据定义,这是不可能的。 但是,就像软件中的大多数事情一样,总有一种解决方法。 在这种情况下,实际上有两个。

第一种选择是在Book类上使用Fluent Interface技术,并具有类似于setter的方法,这些方法实际上创建一个对象,该对象的所有属性都具有相同的值,但要更改的对象除外。 在我们的示例中,我们将必须在Book类中添加以下内容:

private Book(BookBuilder builder) {this(builder.isbn, builder.publicationYear, builder.reviews);}private Book(String isbn, int publicationYear, List reviews) {this.isbn = isbn;this.publicationYear = publicationYear;this.reviews = Lists.newArrayList(reviews);}public Book withIsbn(String isbn) {return new Book(isbn,this.publicationYear, this.reviews);}

请注意,我们添加了一个新的私有构造函数,可以在其中指定每个属性的值,并修改旧的构造函数以使用新的构造函数。 另外,我们添加了一个新方法,该方法返回一个新的Book对象,该对象具有我们想要的isbn属性值。 相同的概念适用于该类的其余属性。 之所以称为功能方法,是因为方法无需修改即可返回对其参数进行操作的结果。 这与程序命令式方法形成对比,在方法式命令式方法中,方法将一个过程应用于其操作数,从而更改其状态。

这种生成新对象的方法显示了不可变类的唯一真正缺点:它们要求我们为所需的每个不同值创建一个新对象,这会在性能和内存消耗方面产生可观的开销。 如果要更改对象的几个属性,则会放大此问题,因为在每个步骤中都将生成一个新对象,并且最终将丢弃所有中间对象并仅保留最后一个结果。

我们可以在构建器模式的帮助下为多步操作提供更好的选择,例如我在上一段中描述的操作。 基本上,我们向构建器添加一个新的构造函数,该构造函数采用一个已经创建的实例来设置其所有初始值。 然后,客户端可以以通常的方式使用构建器来设置所有所需的值,然后使用build方法来创建最终对象。 这样,我们避免只使用我们需要的某些值来创建中间对象。 在我们的示例中,此技术在生成器方面看起来像这样:

public BookBuilder(Book book) {this.isbn = book.getIsbn();this.publicationYear = book.getPublicationYear();this.reviews = book.getReviews();
}

然后,在我们的客户上,我们可以:

Book originalBook = getRandomBook();Book modifiedBook = new BookBuilder(originalBook).isbn('123456').publicationYear(2011).build();

现在,显然该构建器不是线程安全的,因此您必须采取所有常用的预防措施,例如不与多个线程共享一个构建器。

我提到过这样一个事实,即我们必须为状态的每个变化都创建一个新对象,这可能会增加性能,这是不可变类的唯一真正的缺点。 但是,对象创建是JVM不断改进的方面之一。 实际上,除特殊情况外,对象创建比您想象的要高效得多。 无论如何,提出一个简单明了的设计通常是一个好主意,然后仅在进行测量后才重构性能。 当您尝试猜测代码在何处花费大量时间时,十分之九会发现您错了。 此外,不变对象可以自由共享而不必担心后果,这一事实使您有机会鼓励客户端尽可能重用现有实例,从而大大减少了创建对象的数量。 一种常见的方法是为最常见的值提供公共静态最终常量。 此技术在JDK上大量使用,例如在Boolean.FALSEBigDecimal.ZERO中

总结一下这篇文章,如果您想从中学到一些东西,那就这样吧: 除非有充分的理由使类可变,否则类应该是不可变的 。 不要为每个类属性自动添加设置器。 如果由于某种原因您绝对不能使您的类不可变,那么请尽可能限制其可变性。 一个对象可以处于的状态越少,就越容易考虑该对象及其不变量。 而且不必担心不变性的性能开销,很有可能您不必担心它。

参考: JCG合作伙伴 Jose Luis在开发上的 不变性的来龙去脉 。

翻译自: https://www.javacodegeeks.com/2013/01/the-ins-and-outs-of-immutability.html

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

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

相关文章

JavaScript总结(3)

第3章 获取用户的输入 &#xff1c;script&#xff1e;10 intAprompt("请输入第一个数字","");11 intBprompt("请输入第二个数字",27);默认是2712 document.write("你输入的第一个数字是"intA);13 document.write("&#xff1c;…

css书写规范

在书写css样式的时候总是无意中就写乱了&#xff0c;无论是命名或者是样式的书写顺序&#xff0c;这里做一个总结&#xff0c;提醒自己在书写css的时候时刻注意&#xff0c;大家可以参考哈。 1. 样式属性顺序 单个样式规则下的属性在书写时&#xff0c;应按功能进行分组&…

android 协程,关于android:Kotlin协程实现原理SuspendCoroutineContext

明天咱们来聊聊Kotlin的协程Coroutine。如果你还没有接触过协程&#xff0c;举荐你先浏览这篇入门级文章What? 你还不晓得Kotlin Coroutine?如果你曾经接触过协程&#xff0c;置信你都有过以下几个疑难&#xff1a;协程到底是个什么货色&#xff1f;协程的suspend有什么作用&…

清空easyui checkbox选中项

$(#dg).datagrid(unselectAll);转载于:https://www.cnblogs.com/douhuan/p/7116744.html

python 编辑excel需要什么包_Python 中操作EXCEL表格的包

今天&#xff0c;马云爸爸又来贡献金句了&#xff0c;比王健林公公一亿一个小目标还高&#xff0c;“一个月挣一二十个亿很难受&#xff01;&#xff01;&#xff01;”&#xff0c;作为在传统企业主要为电商部门提供数据分析的数据分析师&#xff0c;体验太深刻了。双11前后&a…

用Java处理大文件

最近&#xff0c;我不得不处理一组包含逐笔历史汇率市场数据的文件&#xff0c;并很快意识到使用传统的InputStream都无法将它们读取到内存中&#xff0c;因为每个文件的大小都超过4 GB。 Emacs甚至无法打开它们。 在这种特殊情况下&#xff0c;我可以编写一个简单的bash脚本&…

java IO(一):File类

1.File类简介 File类位于java.io包中。它面向文件层次级别操作、查看文件&#xff0c;而字节流、字符流操作数据时显然比之更底层。 学习File类包括以下几个重点&#xff1a;文件路径、文件分隔符、创建文件(目录)、删除文件(目录)、查看文件内容(输出目录内文件)、判断文件(是…

android listview 开发,android开发之ListView实现

今天又初步学习了一下ListView控件&#xff0c;看看效果如下&#xff1a;LisViewActivity.java源码&#xff1a;package com.jinhoward.UI_listview;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import android.os.Bundl…

input ios问题 小程序_微信小程序开发常见问题汇总

原标题&#xff1a;微信小程序开发常见问题汇总1、域名必须是https非https的域名不被微信小程序允许。2、input组件placeholder字体颜色卸载placeholder-class里面的color并不生效&#xff0c;需要写在placeholder-style里面就可以了。3、wx.navigateTo无法跳转到带tabbar的页面…

https://github.com/

https://github.com/ qq邮箱 转载于:https://www.cnblogs.com/chang1/p/7133251.html

Less 的用法

1. node.js node.js是一个前端的框架 自带一个包管理工具npm node.js 的安装 官网&#xff1a;http://nodejs.cn/ 在命令行检验是否安装成功 切换到项目目录&#xff0c;初始化了一个package.json文件 安装与卸载jQuery包&#xff08;例子&#xff09; 安装 卸载 安装淘宝…

浅谈springboot整合ganymed-ssh2远程访问linux

环境介绍 技术栈 springbootmybatis-plusmysqlganymed-ssh2 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 SSH(远程连接工具)连接原理&#xff1a;ssh服务是一个守护进程(demon)&#xff0c;系统后台监听客户…

优化Neo4j Cypher查询

上周&#xff0c;我花了很多时间尝试使用实时系统中的数据来优化大约20个执行失败的Cypher查询&#xff08;36866ms至155575ms&#xff09;。 经过一番尝试和错误&#xff0c;以及来自Michael的大量投入&#xff0c;我能够大致确定对查询进行哪些操作才能使它们性能更好-最后&a…

python 多文件知识

对于一个大型的项目&#xff0c;会存在很多个py文件&#xff0c;本文记录与多文件有关的内容。 1. python 如何在一个.py文件中调用另一个.py文件的类 如果是在同一个 module中(也就是同一个py 文件里),直接用就可以如果在不同的module里,例如a.py里有 class A:b.py 里有 class…

android pick file,LFilePicker---文件选择利器,各种样式有它就够了

LFilePicker在 Android 开发中如果需要选择某个文件&#xff0c;可以直接调取系统的文件管理器进行选择&#xff0c;但是无法保证各个厂商的手机界面一致&#xff0c;而且解析Uri 还比较繁琐&#xff0c;如果还需要多选呢&#xff1f;需要文件类型过滤呢&#xff1f;老板说界面…

老笔记整理二:网页小问题汇总

最近有一些小问题。想在这里写出来。一是方便大家排错&#xff0c;再是自己也整理一下。 1。很傻的小问题。。。参数提交方式有一个应该是form而不是from。&#xff08;英语老师&#xff0c;我对不起你。。。&#xff09; 2。用超链接传参数&#xff0c;在&#xff1f;后面不能…

在JVM之下–类加载器

在许多开发人员中&#xff0c;类加载器是Java语言的底层&#xff0c;并且经常被忽略。 在ZeroTurnaround上 &#xff0c;我们的开发人员必须生活&#xff0c;呼吸&#xff0c;饮食&#xff0c;喝酒&#xff0c;并且几乎与类加载器保持亲密关系&#xff0c;才能生产JRebel技术&a…

matplotlib绘制饼状图

源自http://blog.csdn.net/skyli114/article/details/77508430?ticketST-41707-PzNbUDGt6R5KYl3TkWDg-passport.csdn.net pyplot使用plt.pie()来绘制饼图 1 import matplotlib.pyplot as plt 2 labels frogs, hogs, dogs, logs 3 sizes 15, 20, 45, 10 # [15,20,45,10…

自适应宽度元素单行文本省略用法探究

单行文本省略是现代网页设计中非常常用的技术&#xff0c;几乎每个站点都会用到。单行文本省略适用于显示摘要信息的场景&#xff0c;如列表标题、文章摘要等。在响应式开发中&#xff0c;自适应宽度元素单行文本省略容易失效不起作用&#xff0c;对网页开发这造成困扰。因此&a…

P3390 【模板】矩阵快速幂

题目背景 矩阵快速幂 题目描述 给定n*n的矩阵A&#xff0c;求A^k 输入输出格式 输入格式&#xff1a; 第一行&#xff0c;n,k 第2至n1行&#xff0c;每行n个数&#xff0c;第i1行第j个数表示矩阵第i行第j列的元素 输出格式&#xff1a; 输出A^k 共n行&#xff0c;每行n个数&…