您当前的位置:首页 > 电脑百科 > 程序开发 > 语言 > JAVA

如何在Java中创建不可变类?

时间:2020-03-01 14:02:50  来源:  作者:

如果对象的状态在构造后无法更改,则该对象是不可变的。不可变的对象不会让其他对象修改其状态。对象的字段在构造函数内部仅初始化一次,以后再也不会更改。

在本文中,我们将定义在JAVA中创建不可变类的典型步骤,并阐明开发人员在创建不可变类时遇到的常见错误。

1.不可变类的用法

如今,每个软件应用程序的“必备”规范都将被分发和使用多线程。多线程应用程序总是使开发人员感到头疼,因为要求开发人员保护其对象的状态,以防止同时修改多个线程。同时,为此,开发人员通常在修改对象状态时使用“ 同步”块。

对于不可变的类,状态永远不会被修改。状态的每次修改都会产生一个新实例,因此每个线程将使用不同的实例,并且开发人员不必担心并发修改。

2.一些流行的不可变类

字符串 是Java中最流行的不可变类。初始化后,其值将无法修改。操作像修剪(),子串(),替换()总是返回一个新的实例,并不会影响当前的实例,这就是为什么我们通常所说的修剪() ,如下所示:

String alex = "Alex";

alex = alex.trim();

JDK的另一个示例是包装器类,例如:Integer,Float,Boolean……这些类不会修改其状态,但是,每次您尝试对其进行修改时,它们都会创建一个新实例。

Integer a =3;

a += 3;

调用+ = 3之后,将创建一个新实例,其值保持为:6,并且第一个实例丢失。

3.我们如何创建一个不可变的类

为了创建一个不可变的类,您应该遵循以下步骤:

1.

将您的班级定为最终以便其他班级无法对其进行扩展。

2.

3. 将所有字段定为最终字段以使它们在构造函数中仅初始化一次,之后再也不会修改。

4. 不要公开setter方法。

5. 在公开修改类状态的方法时,必须始终返回该类的新实例。

6. 如果该类包含一个可变对象:

1. 在构造函数内部,请确保使用传递的参数的克隆副本,并且切勿将可变字段设置为通过构造函数传递的真实实例,这是为了防止传递对象的客户端事后对其进行修改。

2. 确保始终返回该字段的副本,而不返回真实对象实例。

3.1简单不可变类

让我们按照上述步骤操作,并创建自己的不可变类(ImmutableStudent.java)。

package com.programmer.gate.beans;

public final class ImmutableStudent {

private final int id;

private final String name;

public ImmutableStudent(int id, String name) {

this.name = name;

this.id = id;

}

public int getId() {

return id;

public String getName() {

return name;

}

上面的类是一个非常简单的不可变类,它不包含任何可变对象,并且从不以任何方式公开其字段;这些类型的类通常用于缓存。

3.2将可变对象传递给不可变类

现在,让我们的示例复杂一点,我们创建一个名为Age的可变类,并将其作为字段添加到ImmutableStudent中:

public class Age {

private int day;

private int month;

private int year;

public int getDay() {

return day;

public void setDay(int day) {

this.day = day;

public int getMonth() {

return month;

public void setMonth(int month) {

this.month = month;

public int getYear() {

return year;

public void setYear(int year) {

this.year = year;

private final Age age;

public ImmutableStudent(int id, String name, Age age) {

this.name = name;

this.id = id;

this.age = age;

return id;

return name;

public Age getAge() {

return age;

因此,我们在不可变类中添加了类型为Age的新可变字段,并在构造函数中按常规分配了该字段。

让我们创建一个简单的测试类,并验证ImmutableStudent是否不再不可变:

public static void main(String[] args) {

Age age = new Age();

age.setDay(1);

age.setMonth(1);

age.setYear(1992);

ImmutableStudent student = new ImmutableStudent(1, "Alex", age);

System.out.println("Alex age year before modification = " + student.getAge().getYear());

age.setYear(1993);

System.out.println("Alex age year after modification = " + student.getAge().getYear());

运行上面的测试后,我们得到以下输出:

Alex age year before modification = 1992

Alex age year after modification = 1993

我们声称ImmutableStudent是一个不可变的类,其状态在构造之后就不会修改,但是在上面的示例中,即使在构造Alex对象之后,我们也能够修改Alex的年龄。如果我们返回ImmutableStudent构造函数的实现,则会发现age字段已分配给Age参数的实例,因此,只要在类外部修改了引用的Age,该更改就会直接反映在Alex的状态上请查看我的“ 按价值传递”或“按参考传递”文章,以更深入地了解此概念。

为了解决这个问题并使我们的类再次变得不可变,我们遵循上面提到的创建不可变类的步骤中的步骤5。因此,我们修改了构造函数,以克隆传递的Age参数并使用其实例。

public ImmutableStudent(int id, String name, Age age) {

Age cloneAge = new Age();

cloneAge.setDay(age.getDay());

cloneAge.setMonth(age.getMonth());

cloneAge.setYear(age.getYear());

this.age = cloneAge;

现在,如果运行测试,将得到以下输出:

Alex age year after modification = 1992

正如您现在所看到的,Alex的年龄在构建之后再也不会受到影响,我们的班级又回到了不变的状态。

3.3从不可变类返回可变对象

但是,我们的类仍然有泄漏,并且不是完全不变的,让我们采取以下测试方案:

student.getAge().setYear(1993);

输出:

再次根据步骤4,从不可变对象返回可变字段时,您应该返回它们的克隆实例,而不是该字段的真实实例。

因此,我们修改了getAge()以便返回该对象年龄的副本:

public Age getAge() {

cloneAge.setDay(this.age.getDay());

cloneAge.setMonth(this.age.getMonth());

cloneAge.setYear(this.age.getYear());

return cloneAge;

现在,该类变得完全不可变,并且没有为其他对象提供任何方法或方法来修改其状态。

4.结论

不可变的类具有很多优点,尤其是在多线程环境中正确使用时。唯一的缺点是它们比传统类消耗更多的内存,因为每次对其进行修改后,都会在内存中创建一个新对象...但是,与这些类提供的优点相比,开发人员不应高估内存消耗,因为它可以忽略不计类的类型。

最后,如果一个对象仅能向其他对象呈现一种状态,则无论它们如何以及何时调用其方法,该对象都是不可变的。如果是这样,则通过任何线程安全的定义都是线程安全的。



Tags:Java 不可变类   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
如果对象的状态在构造后无法更改,则该对象是不可变的。不可变的对象不会让其他对象修改其状态。对象的字段在构造函数内部仅初始化一次,以后再也不会更改。在本文中,我们将定义...【详细内容】
2020-03-01  Tags: Java 不可变类  点击:(46)  评论:(0)  加入收藏
▌简易百科推荐
面向对象的特征之一封装 面向对象的特征之二继承 方法重写(override/overWrite) 方法的重载(overload)和重写(override)的区别: 面向对象特征之三:多态 Instanceof关键字...【详细内容】
2021-12-28  顶顶架构师    Tags:面向对象   点击:(2)  评论:(0)  加入收藏
一、Redis使用过程中一些小的注意点1、不要把Redis当成数据库来使用二、Arrays.asList常见失误需求:把数组转成list集合去处理。方法:Arrays.asList 或者 Java8的stream流式处...【详细内容】
2021-12-27  CF07    Tags:Java   点击:(3)  评论:(0)  加入收藏
文章目录 如何理解面向对象编程? JDK 和 JRE 有什么区别? 如何理解Java中封装,继承、多态特性? 如何理解Java中的字节码对象? 你是如何理解Java中的泛型的? 说说泛型应用...【详细内容】
2021-12-24  Java架构师之路    Tags:JAVA   点击:(5)  评论:(0)  加入收藏
大家好!我是老码农,一个喜欢技术、爱分享的同学,从今天开始和大家持续分享JVM调优方面的经验。JVM调优是个大话题,涉及的知识点很庞大 Java内存模型 垃圾回收机制 各种工具使用 ...【详细内容】
2021-12-23  小码匠和老码农    Tags:JVM调优   点击:(12)  评论:(0)  加入收藏
前言JDBC访问Postgresql的jsonb类型字段当然可以使用Postgresql jdbc驱动中提供的PGobject,但是这样在需要兼容多种数据库的系统开发中显得不那么通用,需要特殊处理。本文介绍...【详细内容】
2021-12-23  dingle    Tags:JDBC   点击:(13)  评论:(0)  加入收藏
Java与Lua相互调用案例比较少,因此项目使用需要做详细的性能测试,本内容只做粗略测试。目前已完成初版Lua-Java调用框架开发,后期有时间准备把框架进行抽象,并开源出来,感兴趣的...【详细内容】
2021-12-23  JAVA小白    Tags:Java   点击:(11)  评论:(0)  加入收藏
Java从版本5开始,在 java.util.concurrent.locks包内给我们提供了除了synchronized关键字以外的几个新的锁功能的实现,ReentrantLock就是其中的一个。但是这并不意味着我们可...【详细内容】
2021-12-17  小西学JAVA    Tags:JAVA并发   点击:(11)  评论:(0)  加入收藏
一、概述final是Java关键字中最常见之一,表示“最终的,不可更改”之意,在Java中也正是这个意思。有final修饰的内容,就会变得与众不同,它们会变成终极存在,其内容成为固定的存在。...【详细内容】
2021-12-15  唯一浩哥    Tags:Java基础   点击:(17)  评论:(0)  加入收藏
1、问题描述关于java中的日志管理logback,去年写过关于logback介绍的文章,这次项目中又优化了下,记录下,希望能帮到需要的朋友。2、解决方案这次其实是碰到了一个问题,一般的情况...【详细内容】
2021-12-15  软件老王    Tags:logback   点击:(19)  评论:(0)  加入收藏
本篇文章我们以AtomicInteger为例子,主要讲解下CAS(Compare And Swap)功能是如何在AtomicInteger中使用的,以及提供CAS功能的Unsafe对象。我们先从一个例子开始吧。假设现在我们...【详细内容】
2021-12-14  小西学JAVA    Tags:JAVA   点击:(22)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条