作为我最近一直在进行的一些编码访谈的一部分,有时会出现不变性问题。我自己并不过分教条,但每当不需要可变状态时,我会试图摆脱导致可变性的代码,这在数据结构中通常是最明显的。然而,似乎对不可变性的概念存在一些误解,开发人员通常认为拥有final引用,或者val在Kotlin或Scala中,足以使对象不可变。这篇博客文章深入研究了不可变引用和不可变数据结构。

不可变数据结构的好处

不可变数据结构具有显着优势,例如:

没有无效的状态

线程安全

易于理解的代码

更容易测试代码

可用于值类型

没有无效的状态

当一个对象是不可变的时,很难让对象处于无效状态。该对象只能通过其构造函数实例化,这将强制对象的有效性。这样,可以强制执行有效状态所需的参数。一个例子:

Address address = new Address();

address.setCity("Sydney");

// address is in invalid state now, since the country hasn't been set.

Address address = new Address("Sydney", "Australia");

// Address is valid and doesn't have setters, so the address object is always valid.

线程安全

由于无法更改对象,因此可以在线程之间共享它,而不会出现竞争条件或数据突变问题。

易于理解的代码

与无效状态的代码示例类似,使用构造函数通常比初始化方法更容易。这是因为构造函数强制执行必需的参数,而setter或initializer方法在编译时不会强制执行。

更易于测试的代码

由于对象更具可预测性,因此不必测试初始化方法的所有排列,即在调用类的构造函数时,该对象有效或无效。使用这些类的代码的其他部分变得更可预测,具有更少的NullPointerException机会。有时,当传递对象时,有些方法可能会改变对象的状态。例如:

public boolean isOverseas(Address address) {

if(address.getCountry().equals("Australia") == false) {

address.setOverseas(true); // address has now been mutated!

return true;

} else {

return false;

}

}

一般来说,上面的代码是不好的做法。它返回一个布尔值,并可能改变对象的状态。这使得代码更难理解和测试。更好的解决方案是从Address 类中删除setter ,并通过测试国家名称返回一个布尔值。更好的方法是将此逻辑移动到 Address 类本身(address.isOverseas())。当确实需要设置状态时,在不改变输入的情况下制作原始对象的副本。

可用于值类型

想象一下金额,比如10美元。10美元将永远是10美元。在代码中,这可能看起来像 public Money(final BigInteger amount, final Currency currency)。正如您在此代码中看到的那样,不可能将10美元的值更改为除此之外的任何值,因此,上述内容可以安全地用于值类型。

最终引用不要使对象不可变

如前所述,我经常遇到的问题之一是这些开发人员中的很大一部分并不完全理解最终引用和不可变对象之间的区别。似乎这些开发人员的共同理解是,变量成为最终的那一刻,数据结构变得不可变。不幸的是,这并不是那么简单,我想一劳永逸地把这种误解带出世界:

A final reference does not make your objects immutable!

换句话说,下面的代码并没有使对象不变:

final Person person = new Person("John");

为什么不?好吧,虽然person是最后一个字段而且无法重新分配,但是 Person类可能有一个setter方法或其他mutator方法,可以执行如下操作:

person.setName("Cindy");

无论最终修饰符如何,这都是一件非常容易的事情。或者, Person类可能会公开这样的地址列表。访问此列表允许您向其添加地址,因此,如下所示改变 person对象:

person.getAddresses().add(new Address("Sydney"));

好了,既然我们已经解决了这个问题,那么让我们深入了解一下我们如何使类不可变。在设计我们的类时,我们需要记住几件事:

不要以可变的方式暴露内部状态

要在内部改变状态

确保子类不会覆盖上述行为

根据以下准则,让我们设计一个更好的Person class 版本 。

public final class Person {// final class, can't be overridden by subclasses

private final String name; // final for safe publication in multithreaded applications

private final List

addresses;

public Person(String name, List

addresses) {

this.name = name;

this.addresses = List.copyOf(addresses); // makes a copy of the list to protect from outside mutations (Java 10+).

// Otherwise, use Collections.unmodifiableList(new ArrayList<>(addresses));

}

public String getName() {

return this.name; // String is immutable, okay to expose

}

public List

getAddresses() {

return addresses; // Address list is immutable

}

}

public final class Address { // final class, can't be overridden by subclasses

private final String city; // only immutable classes

private final String country;

public Address(String city, String country) {

this.city = city;

this.country = country;

}

public String getCity() {

return city;

}

public String getCountry() {

return country;

}

}

现在,可以使用以下代码:

import java.util.List;

final Person person = new Person("John", List.of(new Address(“Sydney”, "Australia"));

现在,上面的代码是不可变的,但是由于Person 和 Address 类的设计 ,同时还有最终引用,因此无法将person变量重新分配给其他任何东西。

更新:正如有些人提到的,上面的代码仍然是可变的,因为我没有在构造函数中复制地址列表。因此,如果不在ArrayList() 构造函数中调用new ,仍然可以执行以下操作:

final List

addresses = new ArrayList<>();

addresses.add(new Address("Sydney", "Australia"));

final Person person = new Person("John", addressList);

addresses.clear();

但是,由于在构造函数中创建了一个新副本,上面的代码将不再影响类中复制的地址列表引用Person ,从而使代码安全。

我希望上述内容有助于理解最终和不变性之间的差异。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

为什么数据结构不用java_泛谈Java中的不可变数据结构相关推荐

  1. setyear java_如何在Java中创建不可变类

    如果对象在构造后无法更改,则该对象是不可变的.不可变对象不会以任何方式暴露其他对象来修改其状态; 对象的字段仅在构造函数内初始化一次,并且永远不会再次更改. 在本文中,我们将定义在Java中创建不可变 ...

  2. java创建一个不可变对象_如何在Java中创建不可变类?

    java创建一个不可变对象 Today we will learn about the immutable class in Java. What are immutable classes? The ...

  3. 图解java_图解 Java 中的数据结构及原理!

    作者:大道方圆 cnblogs.com/xdecode/p/9321848.html 最近在整理数据结构方面的知识, 系统化看了下Java中常用数据结构, 突发奇想用动画来绘制数据流转过程. 主要基于 ...

  4. java 中几种常用数据结构

    原文地址:http://blog.csdn.net/u010947402/article/details/51878166 JAVA中常用的数据结构(java.util. 中) Java中有几种常用的 ...

  5. 形式参数内存在哪java_深入浅出Java中JVM内存管理

    原标题:深入浅出Java中JVM内存管理 Java岗位面试,JVM是对程序员基本功考察,通常会问你对JVM了解吗?可以分几部分回答这个问题,首先JVM内存划分 | JVM垃圾回收的含义 | 有哪些GC ...

  6. tlab java_浅析java中的TLAB

    好久,好久....没有更博客了.这一次利用闲暇时间,来扯一下关于JVM中的TLAB. 什么是TLAB?它是干什么的?咋们先抛开这个问题,一切的开始得从new对象到指针碰撞开始讲起. new对象与指针碰 ...

  7. throws java_基于Java中throw和throws的区别(详解)

    系统自动抛出的异常 所有系统定义的编译和运行异常都可以由系统自动抛出,称为标准异常,并且 Java 强烈地要求应用程序进行完整的异常处理,给用户友好的提示,或者修正后使程序继续执行. 语句抛出的异常 ...

  8. 10.JAVA中的集合(数据结构)

    Java中的集合 包含以下结构: 数组-线性表 链表 栈 队列 散列表 二叉树 映射关系(key-value) List集合  特点:[有序.重复] [线性表--数组] ArrayList 定义 线程 ...

  9. cloning java_深入浅出Java中的clone克隆方法,写得太棒了!

    作者:张纪刚 blog.csdn.net/zhangjg_blog/article/details/18369201/ 2019-03-24 10:33:04 Java中对象的创建 clone 顾名思 ...

最新文章

  1. Substance Painter实时角色制作视频教程
  2. 如何在Github网页端处理不同分支之间的冲突
  3. 结构型设计模式在公司项目中的运用实践
  4. java xml 画表格_用js+xml自动生成表格的东西
  5. 探讨IOS应用在中国的盈利模式
  6. 透视宝移动端对Unity手机游戏引擎监控实现
  7. Java笔记(day12)
  8. java 解析ttf字体文件_stb_truetype解析ttf字体并将其保存到图片中
  9. vim 的配置详解/键盘映射配置详解
  10. 在网页中实现透明flash的代码
  11. 分类学 · 狡兔为何偏要有三窟???
  12. 学习笔记整理:Photoshop软件应用-基础-图像选择
  13. Apache+PHP+VC14环境搭建及测试
  14. 【Windows 问题系列第 8 篇】如何查看电脑磁盘属于固态盘还是机械硬盘?
  15. 就绪函数的定义_准备就绪的定义被认为是有害的
  16. 玩 Spring框架
  17. 2022数学建模国赛如何安排进度?川川学长精心讲解
  18. 赛门铁克扩展验证EV SSL证书
  19. 获取图片的EXIF信息如此困难?
  20. Java学习资源 | Java编程最新教学视频大全,推荐

热门文章

  1. linux vnc检查,检查Ubuntu VNC设置(避免远程登陆)
  2. julia在mac环境变量_在Julia中确定值/变量的类型
  3. 这8种常见的SQL错误用法,你还在用吗?
  4. Java中不可或缺的59个小技巧,贼好用!
  5. Redis 有哪些数据类型?
  6. 虹软安卓人脸识别初学
  7. 计算机网络作业6,计算机网络作业 6
  8. java文本框背景_用Java编写小程序(包含组合框下拉和文本框)变换背景颜色
  9. 解释型语言和编译型语言的区别_从泛型的使用情况看出你对语言的理解程度(2)...
  10. linux内核模块实验,linux内核模块实验(2学时).doc