如何在java中从可变对象创建不可变对象? [复制]

Posted

技术标签:

【中文标题】如何在java中从可变对象创建不可变对象? [复制]【英文标题】:How to create immutable object from mutable in java? [duplicate] 【发布时间】:2021-05-04 12:33:29 【问题描述】:

如何创建不可变的 Planet 以保持名称不变?我正在苦苦挣扎,因为我认为它是具有可变对象的不可变项目。如果我错了,请纠正我。

每次我在输出中更改名称时也会更改。我错过了什么吗?

我尝试将所有字段设置为私有和最终字段(不在此示例中),但我认为我缺少一些工作代码。

我知道 java.util.Date 已被弃用,但这只是示例。

import java.util.Date;   

public final class Planet   
    String name;                                                      
    private final Date discoveryDate;  

    public Planet (String name, Date discoveryDate)                
        this.name = name;
        this.discoveryDate = new Date(discoveryDate.getTime());    
    

    public String getName() 
        return name;
    

    public Date getDiscoveryDate()                
        return new Date(discoveryDate.getTime());     
    

    public static void main(String [] args) 
        Planet Earth = new Planet("Earth Planet", new Date(2020,01,16,17,28));

        System.out.println("Earth");
        System.out.println("------------------------------------");
        System.out.println("Earth.getName: " + Earth.getName());
        System.out.println("Earth.getDiscoveryDate: " + Earth.getDiscoveryDate());
    

【问题讨论】:

java.util 的日期时间 API 及其格式 API SimpleDateFormat 已过时且容易出错。建议完全停止使用它们并切换到modern date-time API。此外,自 JDK 1.1(24 年前发布)以来,Date(int year, int month, int date, int hrs, int min) 已被弃用。 您没有将name 标记为final,这是为什么呢? Planet 应该是不可变的。 Date 类不仅设计不佳而且早已过时,正如您所说,它也是可变的!因此,对于不可变类中的字段,这是一个有问题的选择(有办法绕过它,但由于 java.time 的日期时间类已经是不可变的,简单的解决方案是切换到其中之一)。 【参考方案1】:

tl;博士

要么:

像这样在 Java 16 及更高版本中创建recordpublic record Planet( String name , LocalDate discovered ) 或者,在 Java 16 之前,创建一个类,您可以: 标记所有成员字段finalprivate。 根据需要创建 getter 方法,但不创建 setter 方法。

记录

只需使用 Java 16 中的新 records feature (previewed in Java 15)。

将您的类定义为record,因为它的主要工作是透明且不可变地携带数据。编译器隐式创建构造函数、getter、hashCode & equalstoString

请注意,在记录中隐式定义的 getter 方法以 JavaBeans 样式的 get… 措辞开头。 getter 方法只是类名后面括号中定义的成员字段的名称。

当然,如果您的 getter 方法提供对本身可变的对象的访问,则包含在记录中并不会阻止调用程序员更改包含的对象。请注意,在接下来的示例类中,StringLocalDate 类本身在设计上都是不可变的。因此,包含对象的可变性在这里不是问题。

package org.example;

import java.time.LocalDate;

public record Planet( String name , LocalDate discovered )


使用该记录。

Planet Earth = new Planet( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );

System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.name: " + Earth.name() );
System.out.println( "Earth.discovered: " + Earth.discovered() );

运行时。

Earth
------------------------------------
Earth.name: Earth
Earth.discovered: 2020-01-16

如果没有记录功能,要确保类是不可变的,您应该:

标记成员字段final。这意味着在构造函数完成后不能为该字段分配不同的对象。 标记成员字段private。这意味着其他类的对象将无法直接读取或更改这些字段。 如果需要,提供 getter 方法,但不提供 setter 方法。按照惯例,使用 JavaBeans 风格的 get…is… 命名。

您还应该提供hashCodeequalstoString 的适当覆盖实现。您的IDE 将帮助生成这些源代码。

package org.example;

import java.time.LocalDate;
import java.util.Objects;

public class Planète

    // Member fields
    final String name;
    final LocalDate discovered;

    // Constructors
    public Planète ( String name , LocalDate discovered )
    
        Objects.requireNonNull( name );
        Objects.requireNonNull( discovered );
        this.name = name;
        this.discovered = discovered;
    

    // Getters (read-only immutable class, no setters)
    public String getName ( )  return this.name; 

    public LocalDate getDiscovered ( )  return this.discovered; 

    // Object class overrides
    @Override
    public boolean equals ( Object o )
    
        if ( this == o ) return true;
        if ( o == null || getClass() != o.getClass() ) return false;
        Planète planète = ( Planète ) o;
        return getName().equals( planète.getName() ) && getDiscovered().equals( planète.getDiscovered() );
    

    @Override
    public int hashCode ( )
    
        return Objects.hash( getName() , getDiscovered() );
    

    @Override
    public String toString ( )
    
        return "Planète " +
                "name='" + name + '\'' +
                " | discovered=" + discovered +
                " ";
    

使用那个类。

Planète Earth = new Planète( "Earth" , LocalDate.of( 2020 , 1 , 16 ) );

System.out.println( "Earth" );
System.out.println( "------------------------------------" );
System.out.println( "Earth.getName: " + Earth.getName() );
System.out.println( "Earth.getDiscoveryDate: " + Earth.getDiscovered() );

附带问题

不要以 0 开头的十进制整数文字。前导零使数字octal 而不是decimal。所以你通过2020,01,16的代码应该是2020,1,16

切勿使用Date 类,也不要使用CalendarSimpleDateFormat。这些可怕的类现在是遗留的,几年前被 JSR 310 中定义的现代 java.time 类所取代。在上面的代码中,我们使用java.time.LocalDate 表示仅日期值,没有时间-无时区。

【讨论】:

【参考方案2】:

Planet 是不可变的,但字段 name 应该是私有的。

【讨论】:

如果name 不是privatePlanet 对象如何不可变? private name 就是这个意思。

以上是关于如何在java中从可变对象创建不可变对象? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

JAVA不可变类(immutable)机制与String的不可变性

Java 的不可变类 (IMMUTABLE CLASS) 和 可变类 (MUTABLE CLASS)

java String不可变对象,但StringBuffer是可变对象

java不可变类和不可变对象

JAVA中创建了多个对象,为什么只重复出现一个对象

合理利用Java不可变对象,让你的代码更加优雅