1. 首页
  2. IT资讯

你不知道怎么在Java中创建不可变类?详细教程

“u003Cpu003E如果对象的状态在构造后无法更改,则该对象是不可变的。不可变的对象不会让其他对象修改其状态。对象的字段在构造函数中仅初始化一次,就再也不会更改。u003Cu002Fpu003Eu003Cpu003E在本文中,我们将定义在Java中创建不可变类的典型步骤,并阐明开发人员在创建不可变类时所犯的常见错误。u003Cu002Fpu003Eu003Ch2 class=”pgc-h-arrow-right”u003E1.不可变类的用法u003Cu002Fh2u003Eu003Cpu003E如今,每个软件应用程序的“必备”规范都是分布式的。多线程应用程序总是使程序员们感到脑壳疼,因为开发人员需要同时保护其对象的状态不受多个线程的并发修改,所以呢,开发人员通常在修改对象状态时使用同步块。u003Cu002Fpu003Eu003Cpu003E对于不可变的类,状态永远不会被修改。状态的每次修改都会产生一个新实例,因此每个线程使用不同的实例,并且开发人员不必担心并发修改。u003Cu002Fpu003Eu003Cpu003Eu003Cbru002Fu003Eu003Cu002Fpu003Eu003Ch2 class=”pgc-h-arrow-right”u003E2.一些流行的不可变类u003Cu002Fh2u003Eu003Cpu003E字符串(string)是Java中最流行的不可变类。初始化后,其值将无法修改。trim()、substring()、replace()总是返回一个新的实例,不会影响当前的实例,这就是为什么我们通常所说的trim(),如下所示:u003Cu002Fpu003Eu003Cpreu003EString alex = "Alex";u003Cu002Fpreu003Eu003Cpreu003Ealex = alex.trim();u003Cu002Fpreu003Eu003Cpu003EJDK的另一个例子是包装类,比如:Integer、Float、Boolean……这些类不会修改它们的状态,但你每次试图修改,都会创建一个新实例。u003Cu002Fpu003Eu003Cpreu003EInteger a =3;u003Cu002Fpreu003Eu003Cpreu003Ea += 3;u003Cu002Fpreu003Eu003Cpu003E调用+ = 3之后,将创建一个新实例,其值保持为:6,并且第一个实例就丢失了。u003Cu002Fpu003Eu003Cpu003Eu003Cbru002Fu003Eu003Cu002Fpu003Eu003Ch2 class=”pgc-h-arrow-right”u003E3.我们如何创建不可变的类u003Cu002Fh2u003Eu003Cpu003E为了创建一个不可变的类,就要遵循以下步骤:u003Cu002Fpu003Eu003Col start=”1″ class=””u003Eu003Cliu003E将您的类定为最终类,以便其他类无法对其进行扩展。u003Cu002Fliu003Eu003Cliu003E将所有字段定为最终字段,以使它们在构造函数中仅初始化一次,之后再也不会修改。u003Cu002Fliu003Eu003Cliu003E不要公开setter方法。u003Cu002Fliu003Eu003Cliu003E在公开修改类状态的方法时,必须始终返回该类的新实例。u003Cu002Fliu003Eu003Cliu003E如果该类包含一个可变对象:在构造函数内部,请确保使用传递的参数的克隆副本,并且永远不要将可变字段设置为通过构造函数传递的实际实例,这是为了防止传递对象的客户端事后对其进行修改。确保始终返回该字段的克隆副本,并且永远不要返回真实对象实例。u003Cu002Fliu003Eu003Cu002Folu003Eu003Cpu003Eu003Cbru002Fu003Eu003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E3.1 简单不可变类u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E让我们按照上述步骤创建一个自己的不可变类(ImmutableStudent.java)。u003Cu002Fpu003Eu003Cpreu003Epackage com.programmer.gate.beans;u003Cu002Fpreu003Eu003Cpreu003Epublic final class ImmutableStudent {u003Cu002Fpreu003Eu003Cpreu003E private final int id;u003Cu002Fpreu003Eu003Cpreu003E private final String name;u003Cu002Fpreu003Eu003Cpreu003E public ImmutableStudent(int id, String name) {u003Cu002Fpreu003Eu003Cpreu003E this.name = name;u003Cu002Fpreu003Eu003Cpreu003E this.id = id;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public int getId() {u003Cu002Fpreu003Eu003Cpreu003E return id;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public String getName() {u003Cu002Fpreu003Eu003Cpreu003E return name;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpu003E上面的类是一个非常简单的不可变类,它不包含任何可变对象,并且从不以任何方式公开其字段;这些类型的类通常用于缓存。u003Cu002Fpu003Eu003Cpu003Eu003Cstrongu003E3.2 将可变对象传递给不可变类u003Cu002Fstrongu003Eu003Cu002Fpu003Eu003Cpu003E现在,让我们的示例复杂一点,我们创建一个名为Age的可变类,并将其作为字段添加到ImmutableStudent中:u003Cu002Fpu003Eu003Cpreu003Epackage com.programmer.gate.beans;u003Cu002Fpreu003Eu003Cpreu003Epublic class Age {u003Cu002Fpreu003Eu003Cpreu003E private int day;u003Cu002Fpreu003Eu003Cpreu003E private int month;u003Cu002Fpreu003Eu003Cpreu003E private int year;u003Cu002Fpreu003Eu003Cpreu003E public int getDay() {u003Cu002Fpreu003Eu003Cpreu003E return day;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public void setDay(int day) {u003Cu002Fpreu003Eu003Cpreu003E this.day = day;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public int getMonth() {u003Cu002Fpreu003Eu003Cpreu003E return month;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public void setMonth(int month) {u003Cu002Fpreu003Eu003Cpreu003E this.month = month;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public int getYear() {u003Cu002Fpreu003Eu003Cpreu003E return year;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public void setYear(int year) {u003Cu002Fpreu003Eu003Cpreu003E this.year = year;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpreu003Epackage com.programmer.gate.beans;u003Cu002Fpreu003Eu003Cpreu003Epublic final class ImmutableStudent {u003Cu002Fpreu003Eu003Cpreu003E private final int id;u003Cu002Fpreu003Eu003Cpreu003E private final String name;u003Cu002Fpreu003Eu003Cpreu003E private final Age age;u003Cu002Fpreu003Eu003Cpreu003E public ImmutableStudent(int id, String name, Age age) {u003Cu002Fpreu003Eu003Cpreu003E this.name = name;u003Cu002Fpreu003Eu003Cpreu003E this.id = id;u003Cu002Fpreu003Eu003Cpreu003E this.age = age;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public int getId() {u003Cu002Fpreu003Eu003Cpreu003E return id;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public String getName() {u003Cu002Fpreu003Eu003Cpreu003E return name;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E public Age getAge() {u003Cu002Fpreu003Eu003Cpreu003E return age;u003Cu002Fpreu003Eu003Cpreu003E }u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpu003E因此,我们在不可变类中添加了一个类型为Age的新可变字段,并将其作为普通字段赋给构造函数。u003Cu002Fpu003Eu003Cpu003E下面我来创建一个简单的测试类,并验证ImmutableStudent是否不再不可变:u003Cu002Fpu003Eu003Cpreu003Epublic static void main(String[] args) {u003Cu002Fpreu003Eu003Cpreu003E Age age = new Age();u003Cu002Fpreu003Eu003Cpreu003E age.setDay(1);u003Cu002Fpreu003Eu003Cpreu003E age.setMonth(1);u003Cu002Fpreu003Eu003Cpreu003E age.setYear(1992);u003Cu002Fpreu003Eu003Cpreu003E ImmutableStudent student = new ImmutableStudent(1, "Alex", age);u003Cu002Fpreu003Eu003Cpreu003E System.out.println("Alex age year before modification = " + student.getAge().getYear());u003Cu002Fpreu003Eu003Cpreu003E age.setYear(1993);u003Cu002Fpreu003Eu003Cpreu003E System.out.println("Alex age year after modification = " + student.getAge().getYear());u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpu003E运行上面的测试后,我们得到以下输出:u003Cu002Fpu003Eu003Cpreu003EAlex age year before modification = 1992u003Cu002Fpreu003Eu003Cpreu003EAlex age year after modification = 1993u003Cu002Fpreu003Eu003Cpu003E我们声称ImmutableStudent是一个不可变的类,其状态在构造之后就不会修改,但是在上面的示例中,即使在构造Alex对象之后,我们也能够修改Alex的年龄。如果返回ImmutableStudent构造函数的实现,则会发现Age字段已分配给Age参数的实例,因此,只要在类外部修改了所引用的Age,该更改就会直接反映在Alex的状态上。u003Cu002Fpu003Eu003Cpu003E为了解决这个问题并使我们的类再次变得不可变,我们从上面提到的创建不可变类的步骤开始执行步骤5。因此,我们修改了构造函数,以克隆传递的Age参数并使用其克隆实例。u003Cu002Fpu003Eu003Cpreu003Epublic ImmutableStudent(int id, String name, Age age) {u003Cu002Fpreu003Eu003Cpreu003E this.name = name;u003Cu002Fpreu003Eu003Cpreu003E this.id = id;u003Cu002Fpreu003Eu003Cpreu003E Age cloneAge = new Age();u003Cu002Fpreu003Eu003Cpreu003E cloneAge.setDay(age.getDay());u003Cu002Fpreu003Eu003Cpreu003E cloneAge.setMonth(age.getMonth());u003Cu002Fpreu003Eu003Cpreu003E cloneAge.setYear(age.getYear());u003Cu002Fpreu003Eu003Cpreu003E this.age = cloneAge;u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpu003E现在,如果运行测试,将得到以下输出:u003Cu002Fpu003Eu003Cpreu003EAlex age year before modification = 1992u003Cu002Fpreu003Eu003Cpreu003EAlex age year after modification = 1992u003Cu002Fpreu003Eu003Cpu003E正如上面所见,Alex的年龄在构造之后从未受到影响,我们的类又回到了不变的状态。u003Cu002Fpu003Eu003Ch3 class=”pgc-h-arrow-right”u003E3.3。从不可变类返回可变对象u003Cu002Fh3u003Eu003Cpu003E但是,我们的类仍然有泄漏,并且不是完全不变的,所以我采取以下测试方案:u003Cu002Fpu003Eu003Cpreu003Epublic static void main(String[] args) {u003Cu002Fpreu003Eu003Cpreu003Eu003Cu002Fpreu003Eu003Cpreu003E Age age = new Age();u003Cu002Fpreu003Eu003Cpreu003E age.setDay(1);u003Cu002Fpreu003Eu003Cpreu003E age.setMonth(1);u003Cu002Fpreu003Eu003Cpreu003E age.setYear(1992);u003Cu002Fpreu003Eu003Cpreu003E ImmutableStudent student = new ImmutableStudent(1, "Alex", age);u003Cu002Fpreu003Eu003Cpreu003E System.out.println("Alex age year before modification = " + student.getAge().getYear());u003Cu002Fpreu003Eu003Cpreu003E student.getAge().setYear(1993);u003Cu002Fpreu003Eu003Cpreu003E System.out.println("Alex age year after modification = " + student.getAge().getYear());u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpu003E输出为:u003Cu002Fpu003Eu003Cpreu003EAlex age year before modification = 1992u003Cu002Fpreu003Eu003Cpreu003EAlex age year after modification = 1993u003Cu002Fpreu003Eu003Cpu003E再次根据步骤4,从不可变对象返回可变字段时,应该返回克隆实例,而不是该字段的真实实例。u003Cu002Fpu003Eu003Cpu003E所以,我们修改了getAge()以便返回该对象的年龄的副本:u003Cu002Fpu003Eu003Cpreu003Epublic Age getAge() {u003Cu002Fpreu003Eu003Cpreu003E Age cloneAge = new Age();u003Cu002Fpreu003Eu003Cpreu003E cloneAge.setDay(this.age.getDay());u003Cu002Fpreu003Eu003Cpreu003E cloneAge.setMonth(this.age.getMonth());u003Cu002Fpreu003Eu003Cpreu003E cloneAge.setYear(this.age.getYear());u003Cu002Fpreu003Eu003Cpreu003Eu003Cu002Fpreu003Eu003Cpreu003E return cloneAge;u003Cu002Fpreu003Eu003Cpreu003E}u003Cu002Fpreu003Eu003Cpu003E现在,该类变得完全不可变,并且没有为其他对象提供任何方法或方法来修改其状态。u003Cu002Fpu003Eu003Cpreu003EAlex age year before modification = 1992u003Cu002Fpreu003Eu003Cpreu003EAlex age year after modification = 1992u003Cu002Fpreu003Eu003Ch2 class=”pgc-h-arrow-right”u003E4。结论u003Cu002Fh2u003Eu003Cpu003E不可变的类具有很多优点,尤其是在多线程环境中正确使用时。唯一的缺点是它们比传统类消耗更多的内存,因为每次对其进行修改后,都会在内存中创建一个新对象……但是,与这些类提供的优点相比,开发人员不应高估其消耗,因为它可以忽略不计类的类型。u003Cu002Fpu003Eu003Cpu003E最后,如果一个对象仅能向其他对象呈现一种状态,则无论它们如何以及何时调用其方法,该对象都是不可变的。如果是这样,则通过任何线程安全的定义都是线程安全的。u003Cu002Fpu003Eu003Cpu003Eu003Cbru002Fu003Eu003Cu002Fpu003Eu003Cpu003E希望上文能对你有帮助,喜欢可以关注哦。u003Cu002Fpu003E”

原文始发于:你不知道怎么在Java中创建不可变类?详细教程

主题测试文章,只做测试使用。发布者:程序员,转转请注明出处:http://www.cxybcw.com/26696.html

联系我们

13687733322

在线咨询:点击这里给我发消息

邮件:1877088071@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

QR code