如何从 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,您可以:
- 标记所有成员字段
final
和 private
。
- 根据需要创建 getter 个方法,但没有 setter 个方法。
记录
只需使用新的 records feature in Java 16 (previewed in Java 15).
将您的 class 定义为 record
,因为它的主要工作是透明且不变地携带数据。编译器隐式创建一个构造函数,getters、hashCode
& equals
和 toString
.
请注意,记录中隐式定义的 getter 方法 而不是 以 JavaBeans 风格的 get…
措辞开头。 getter 方法只是在 class 名称后的括号中定义的成员字段的名称。
当然,如果您的 getter 方法提供对本身可变对象的访问,则包含在记录中不会阻止调用程序员改变包含的对象。请注意,在接下来的示例 class 中,String
和 LocalDate
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…
命名。
您还应该提供 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 +
" }";
}
}
使用 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,也不要使用 Calendar
或 SimpleDateFormat
。这些可怕的 classes 现在是遗留的,多年前被 JSR 310 中定义的现代 java.time classes 取代。在上面的代码中,我们使用java.time.LocalDate
表示仅日期值,没有时间和时区。
如何创建不可变的 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,您可以:
- 标记所有成员字段
final
和private
。 - 根据需要创建 getter 个方法,但没有 setter 个方法。
- 标记所有成员字段
记录
只需使用新的 records feature in Java 16 (previewed in Java 15).
将您的 class 定义为 record
,因为它的主要工作是透明且不变地携带数据。编译器隐式创建一个构造函数,getters、hashCode
& equals
和 toString
.
请注意,记录中隐式定义的 getter 方法 而不是 以 JavaBeans 风格的 get…
措辞开头。 getter 方法只是在 class 名称后的括号中定义的成员字段的名称。
当然,如果您的 getter 方法提供对本身可变对象的访问,则包含在记录中不会阻止调用程序员改变包含的对象。请注意,在接下来的示例 class 中,String
和 LocalDate
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…
命名。
您还应该提供 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 +
" }";
}
}
使用 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,也不要使用 Calendar
或 SimpleDateFormat
。这些可怕的 classes 现在是遗留的,多年前被 JSR 310 中定义的现代 java.time classes 取代。在上面的代码中,我们使用java.time.LocalDate
表示仅日期值,没有时间和时区。