是否存在可以 "finalize" 私有对象状态的 Java 方法?

Does a Java method exist that can "finalize" private state of Object?

我有一个 class,我想对其执行以下操作:

我知道有一些解决方法,例如不提供任何 setter 方法,只允许在构造函数中设置属性。我可以毫无问题地实现它,但它让我想知道 是否有一些更简单的方法来 'freeze' 对象的属性就位 。我相信 JavaScript 中有一个 Object.freeze() 方法可以做类似的事情。

没有

不,Java 不提供任何此类 thawed/frozen 功能。

建设者

您可能可以通过“构建器”满足您的需求。这个想法是您定义第二个 class 负责生成第一个 class 的实例。此构建器 class 具有 setter 方法,适用于您要调整的所有各种属性。完成所有设置后,调用 .build() 以生成所需 class 的实例。如果您愿意,所需的实例可能是 immutable

构建器应该 return 从其 setter 中引用自身,以提供 method-chaining.

构建器可以为某些设置设置默认值,如果适用于您的问题域。

生成器 class 提供了一些好处:

  • 实现您的目标,即在调整其预期值后生成一个不可变的对象。
  • 确保有效值。调用程序员可以使用 isValid 方法验证生成器,然后更正设置。
  • 允许子classes returned,在适当的情况下,取决于您设置的值。

您可以在与 Java 捆绑在一起的 API 中看到此类构建器。

例子

这是一个构建器的简短示例。

如果需要一个不可变的对象,那么 record 可能是合适的。在 Java 16+ 中,记录是定义 class 的一种简短方式,其主要目的是透明且不可变地传递数据。您只需声明其成员字段的类型和名称。编译器隐式创建默认构造函数、getter、equals & hashCodetoString.

public record Employee( UUID id , String name , LocalDate hired ) {}

虽然记录是一种特殊的 class,但它仍然是 class。所以我们可以嵌套另一个 class,一个 static 构建器 class.

public record Employee( UUID id , String name , LocalDate hired ) {
    public static class Builder { … }
}

这是整个示例 class。

package work.basil.building;

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

public record Employee( UUID id , String name , LocalDate hired ) {
    public static class Builder {
        // ----------- Members
        private UUID id;
        private String name;
        private LocalDate hired;

        // ----------- Constructor
        public Builder () {
            this.id = UUID.randomUUID();
        }

        // ------- Accessors

        public UUID getId () {
            return id;
        }

        public Employee.Builder setId ( UUID id ) {
            this.id = Objects.requireNonNull( id );
            return this;
        }

        public String getName () {
            return name;
        }

        public Employee.Builder setName ( String name ) {
            Objects.requireNonNull( name );
            if ( name.isBlank() ) {
                throw new IllegalStateException( "Name must have some text, cannot be blank. Message # 346624fd-cb97-447a-9f56-e09ccf2e97f3." );
            } else {
                this.name = name;
            }
            return this;
        }

        public LocalDate getHired () {
            return hired;
        }

        public Employee.Builder setHired ( LocalDate hired ) {
            Objects.requireNonNull( hired );
            if ( hired.isAfter( LocalDate.now() ) ) {
                throw new IllegalStateException( "Hired date cannot be after today. Message # 181717b8-e2b0-4b5c-9fd2-ee45a2339b09." );
            } else {
                this.hired = hired;
            }
            return this;
        }

        // -------- Logic
        public boolean isValid () {
            return Objects.nonNull( this.id ) && Objects.nonNull( this.name ) && Objects.nonNull( this.hired );
        }

        public Employee build () {
            if ( this.isValid() ) {
                return new Employee( this.id , this.name , this.hired );
            } else {
                throw new IllegalStateException( "Builder is not valid, so cannot build new object. Message # c0021179-243c-4da5-b265-85208aaaf072" );
            }
        }
    }
}

用法示例。

 List <Employee> employees =
        List.of(
                new Employee.Builder().setName( "Alice" ).setHired( LocalDate.of( 2018 , Month.MARCH, 23) ).build() ,
                new Employee.Builder().setName( "Bob" ).setHired( LocalDate.of( 2014 , Month.JANUARY, 28) ).build() ,
                new Employee.Builder().setName( "Carol" ).setHired( LocalDate.of( 2013 , Month.JUNE, 17) ).build()
        );

当运行.

employees = [Employee[id=9736cb4c-1b32-4924-976b-7340f7f2fdc4, name=Alice, hired=2018-03-23], Employee[id=0ac4ff54-51b6-45c9-bb57-59f6efe40cd5, name=Bob, hired=2014-01-28], Employee[id=52cc9d03-3846-464a-bbed-49f022175bee, name=Carol, hired=2013-06-17]]

I understand there are some workarounds, such as not providing any setter methods and only allowing the properties to be set in the constructor.

是的,这种类型的设计以及将私有字段定义为 final 都行得通。

另一个想法可能是将布尔字段 frozen 添加到对象(或者更好的是,作为接口),并在该布尔值上设置所有 setter 方法。

像这样:

public class MyObject {
  boolean frozen = false;

  int intField;

  public void freeze(){
    frozen = true;
  }

  public boolean setIntField(int val){
    if(! frozen){
      intField = val
    }

    return frozen;
  }

}