如何从 java 中的可变对象创建不可变对象?

How to create immutable object from mutable in java?

如何创建不可变的 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());
    }
}

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

tl;博士

或者:

  • 在 Java 16 及更高版本中制作这样的 record
    public record Planet( String name , LocalDate discovered ) {}
  • 或者,在 Java 16 之前,创建一个 class,您可以:
    • 标记所有成员字段 finalprivate
    • 根据需要创建 getter 个方法,但没有 setter 个方法。

记录

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

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

请注意,记录中隐式定义的 getter 方法 而不是 JavaBeans 风格的 get… 措辞开头。 getter 方法只是在 class 名称后的括号中定义的成员字段的名称。

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

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

Class

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

  • 标记成员字段final。这意味着在构造函数完成后不能为该字段分配不同的对象。
  • 标记成员字段private。这意味着其他 classes 的对象将无法直接访问读取或更改这些字段。
  • 提供 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 +
                " }";
    }
}

使用 class。

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 rather decimal。所以你的代码传递 2020,01,16 应该是 2020,1,16.

永远不要使用 Date class,也不要使用 CalendarSimpleDateFormat。这些可怕的 classes 现在是遗留的,多年前被 JSR 310 中定义的现代 java.time classes 取代。在上面的代码中,我们使用java.time.LocalDate 表示仅日期值,没有时间和时区。