如何在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 及更高版本中创建record
:public record Planet( String name , LocalDate discovered )
或者,在 Java 16 之前,创建一个类,您可以:
标记所有成员字段final
和private
。
根据需要创建 getter 方法,但不创建 setter 方法。
记录
只需使用 Java 16 中的新 records feature (previewed in Java 15)。
将您的类定义为record
,因为它的主要工作是透明且不可变地携带数据。编译器隐式创建构造函数、getter、hashCode
& equals
和 toString
。
请注意,在记录中隐式定义的 getter 方法不以 JavaBeans 样式的 get…
措辞开头。 getter 方法只是类名后面括号中定义的成员字段的名称。
当然,如果您的 getter 方法提供对本身可变的对象的访问,则包含在记录中并不会阻止调用程序员更改包含的对象。请注意,在接下来的示例类中,String
和 LocalDate
类本身在设计上都是不可变的。因此,包含对象的可变性在这里不是问题。
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…
命名。
您还应该提供hashCode
、equals
和toString
的适当覆盖实现。您的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
类,也不要使用Calendar
或SimpleDateFormat
。这些可怕的类现在是遗留的,几年前被 JSR 310 中定义的现代 java.time 类所取代。在上面的代码中,我们使用java.time.LocalDate
表示仅日期值,没有时间-无时区。
【讨论】:
【参考方案2】:Planet 是不可变的,但字段 name
应该是私有的。
【讨论】:
如果name
不是private
,Planet
对象如何不可变?
private name
就是这个意思。以上是关于如何在java中从可变对象创建不可变对象? [复制]的主要内容,如果未能解决你的问题,请参考以下文章
JAVA不可变类(immutable)机制与String的不可变性
Java 的不可变类 (IMMUTABLE CLASS) 和 可变类 (MUTABLE CLASS)