【问题标题】:Does a Java method exist that can "finalize" private state of Object?是否存在可以“最终确定”对象的私有状态的 Java 方法?
【发布时间】:2021-08-06 22:27:38
【问题描述】:

我有一门课,我想在上面执行以下操作:

  • 创建类的实例
  • 在单独的类中更改对象的私有属性
  • 冻结对象的属性,以便在我完成初始设置后无法修改它们

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

【问题讨论】:

  • 不。我认为瓦尔哈拉计划可能正在研究“冻结”物体,但它仍在烤箱中。在 Java 中,您唯一的选择是“变通办法”,例如构建器模式、final 关键字以及省略更改对象私有字段的方法。
  • 如果可以,您可以使用 Java 16 记录。否则,请参阅不可变类(和构建器模式)。
  • @markspace:请发布一个答案(如果只是为了提供一个投票而不是 Basil 的)

标签: java object oop final


【解决方案1】:

没有

不,Java 不提供任何此类解冻/冻结功能。

建造者

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

构建器应从其设置器返回对自身的引用,以提供method-chaining

如果适合您的问题域,构建器可能会为某些设置设置默认值。

builder 类提供了一些好处:

  • 实现您的目标,即生成一个仅在调整其预期值之后才不可变的对象。
  • 确保有效值。调用程序员可以使用isValid 方法验证构建器,然后更正设置。
  • 允许在适当的时候返回子类,具体取决于您设置的值。

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

示例

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

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

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

虽然记录是一种特殊的类,但它仍然是一个类。所以我们可以嵌套另一个类,一个static builder 类。

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

这是整个示例类。

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()
        );

运行时。

员工 = [员工[id=9736cb4c-1b32-4924-976b-7340f7f2fdc4,姓名=爱丽丝,受雇=2018-03-23],员工[id=0ac4ff54-51b6-45c9-bb57-59f6efe40cd5,姓名=鲍勃, 雇用=2014-01-28], 雇员[id=52cc9d03-3846-464a-bbed-49f022175bee, 姓名=卡罗尔, 雇用=2013-06-17]]

【讨论】:

  • 我认为甚至没有“您可以通过反思/内省采取的严厉措施” 行之有效。 (即使是字节码工程也无法让您免于更改已通过常规方式冻结的对象的坚定努力;即由类方法逻辑强制执行的“冻结”标志。)
  • @StephenC 我的意思是,一个原本不可变的对象仍然可以被操纵来改变它的值。
  • 在这种情况下,我认为您在错误意义上使用了“short of”。在这种情况下,您使用“short of”表示如果您使用反射,您可以实现冻结/解冻机制。
  • 顺便说一下,通过反射改变不可变对象不像以前那么容易了。例如,record 实例的字段不能以这种方式更改。从 JDK 15 开始,这也适用于生成的 lambda 类的字段。我预计案件数量会增加。
【解决方案2】:

我知道有一些变通方法,例如不提供任何 setter 方法,只允许在构造函数中设置属性。

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

【讨论】:

    【解决方案3】:

    另一个想法可能是将布尔字段 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;
      }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-18
      • 2010-10-26
      • 1970-01-01
      • 2017-02-05
      • 1970-01-01
      • 2018-06-11
      • 1970-01-01
      • 2012-01-06
      相关资源
      最近更新 更多