欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 教育 > 锐评 > 如何创建一个不可变类

如何创建一个不可变类

2025/11/12 22:05:13 来源:https://blog.csdn.net/lvoyee/article/details/148004922  浏览:    关键词:如何创建一个不可变类

写在前面

如果对象在构造后无法更改,则该对象是不可变的。不可变对象不会以任何方式暴露其他对象来修改其状态; 对象的字段仅在构造函数内初始化一次,并且永远不会再次更改。

1.不可变类的用法

如今,每个软件应用程序的*“必备”规范都是分布式的,而多线程多线程应用程序总是会给开发人员带来麻烦,因为开发人员需要保护其对象的状态,使其免受多个线程的并发修改。同时,为此,开发人员通常在修改对象状态时使用Synchronized*块。

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

2.一些流行的不可变类

String 是Java中最流行的不可变类。初始化后,其值无法修改。像 trim(),substring(),replace()这样的操作总是返回一个新实例而不影响当前实例,这就是我们通常调用 **trim()**的原因如下:

String alex = "Alex";
alex = alex.trim();

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

Integer a =3;
a += 3;

在调用**+ = 3之后,**创建一个新实例,其值为:6,第一个实例丢失。

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

要创建不可变类,您应该按照以下步骤操作:

  1. 让你的类成为**最终,**这样其他类就无法扩展它。
  2. 使所有字段成为最终字段**,**以便它们仅在构造函数中初始化一次,之后从不修改。
  3. 不要暴露setter方法。
  4. 在公开修改类状态的方法时,必须始终返回该类的新实例。
  5. 如果该类包含一个可变对象:
    • 在构造函数中,确保使用传递参数的克隆副本,并且永远不要将可变字段设置为通过构造函数传递的实例,这是为了防止传递对象的客户端在之后修改它。
    • 确保始终返回该字段的克隆副本,并且永远不会返回真实对象实例。

3.1简单的不可变类

让我们按照上面的步骤创建我们自己的不可变类(ImmutableStudent.java

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;}
}
public final class ImmutableStudent {private final int id;private final String name;private final Age age;public ImmutableStudent(int id, String name, Age age) {this.name = name;this.id = id;this.age = age;}public int getId() {return id;}public String getName() {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 = 1992Alex age year after modification = 1993

我们声称ImmutableStudent是一个不可变类,其状态在构造之后永远不会被修改,但是在上面的例子中,我们甚至可以在构造Alex对象之后修改Alex的年龄。如果我们回到ImmutableStudent构造函数的实现,我们发现age字段被分配给Age参数的实例,因此每当在类外部修改引用的Age时,更改将直接反映在Alex的状态上.

为了解决这个问题并使我们的类再次成为不可变的,我们按照上面提到的步骤**#5来创建一个不可变类。因此,我们修改构造函数以克隆Age**的传递参数并使用它的克隆实例。

public ImmutableStudent(int id, String name, Age age) {this.name = name;this.id = id;Age cloneAge = new Age();cloneAge.setDay(age.getDay());cloneAge.setMonth(age.getMonth());cloneAge.setYear(age.getYear());this.age = cloneAge;
}

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

亚历克斯 年龄 一年 之前 修改 = 1992年
亚历克斯 年龄 一年 后 修改 = 1992年

正如你现在所看到的,亚历克斯的时代在创建之后从未受到影响,我们的类又回归到不可改变的状态。

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

但是,我们的类仍然存在泄漏并且不是完全不可变的,让我们采取以下测试场景:

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());student.getAge().setYear(1993);System.out.println("Alex age year after modification = " + student.getAge().getYear());
}

输出:

亚历克斯 年龄 一年 之前 修改 =  1992年亚历克斯 年龄 一年 后 修改 =  1993年

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

所以我们修改**getAge()**以返回对象年龄的克隆:

public Age getAge() {Age cloneAge = new Age();cloneAge.setDay(this.age.getDay());cloneAge.setMonth(this.age.getMonth());cloneAge.setYear(this.age.getYear());return cloneAge;
}

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

亚历克斯 年龄 一年 之前 修改 =  1992年亚历克斯 年龄 一年 后 修改 =  1992年

4结论

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

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


The end.

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词