我可以使用setter方法替换最终变量,方法如下

Mu *_* Pi 2 java java-8

我们大多数人都了解共享可变性的后果,并且据说如果你有机会,总是更喜欢不变性(最终修饰符,设置一次,你不能改变它).我已经看到很少的编码示例,其中People实际上将字段final或setter设置为private或甚至删除类的setter(可以是DTO,Model或Entity类)和另一个类(可以将值设置为不可变的一个构建器类) class)用于创建和设置Immutable类的字段确保没有其他类能够修改状态.这似乎是我的负担.所以我想出了这个想法(下面我举一个例子)

public class TestDataClass {

    private String name;

     public String getName() {
        return name;
    }

    public void setName(Supplier<String> supplier) throws Exception {
        if(Objects.isNull(name))
            this.name = supplier.get();
        throw new Exception("This field is immutable and already has a value "+this.name);
    }
}
Run Code Online (Sandbox Code Playgroud)

通过这种方式,你可以通过这样的setter设置值,objectOfTestDataClass.setName(() -> {return Perform_Desired_Logic;}); 或者 objectOfTestDataClass.setName(() ->"My Name");它是普通的setter.然后你也不必创建一个bulder类或使setter私有或省略setter方法

通过这种方式,一旦你将字段设置为Immutable(我不考虑反射),我也可以在实例化期间摆脱变量初始化.

我希望您的专家意见能够验证我的想法是否合法,我可以将其视为不变性吗?我错过了什么吗?在那种情况下,请纠正我.

Hol*_*ger 13

有几点需要注意

  1. 使用a Supplier<String>代替a String不会改善代码.只有两种情况,即只执行一次方法并且供应商的代码立即执行的情况以及供应商未执行的错误情况,但优化错误情况的意义何在?

  2. 如果某个特定属性支持存在null,那么该方法在将其固定为"null"时无法处理该情况.

  3. API签名并不表示该类应该被视为不可变的.大多数读者会认为它是可变的.因此,如果您要删除异常抛出语句,他们可能会尝试修改对象,甚至不会注意到错误,如评论中所述.

  4. 构建器创建的不可变对象肯定已完成,并且一旦构建就真正不可变.相反,您的类允许创建者忘记将某些属性设置为固定值,从而生成实际可变对象.

  5. 由于不保证此类的实例是不可变的,因此它们也没有保证通常与不可变对象相关联的线程安全.

  • 不,如果一个线程同时调用getter来进行setter调用,那么只使setter"synchronized"不提供线程安全性.你必须保护,setter和getter,然后,你只需要一个可变对象,但有一些限制.通过不变性来确保线程安全的全部意义在于,保证不进行修改意味着本质安全性不需要任何同步. (2认同)

dav*_*xxx 10

你写的内容不允许多次设置对象的字段.
但它不是一种创建具有完全初始化状态的对象的方法,因为构造函数或构建器可以提供.
因此,如果客户端操作具有未完成已损坏状态的对象,则它无法按预期工作.

此外,提供可由客户端在编译时调用但仅在运行时(通过抛出异常)才知道错误的 setter 并不是友好且设计良好的API.


具有不完整状态的对象的问题示例.

以一个Rectangle类.
它由4个强制信息(高度,重量,x和y坐标)组成.这些由4个实例字段表示:

int x,int y,int width,int height

假设该类提供了一个实例方法boolean contains(Point p)来确定是否包含Point(x,y坐标).

如果将方法应用于此类的值字段,则可以创建 Rectangle具有不完整/部分状态的实例.
contains()方法无法工作.
它应该执行不同的检查,如果缺少一个字段Rectangle,它甚至应该抛出异常.

状态可能已损坏的对象问题示例

如果您的对象可能被多个线程操纵,则使用您的方式来估计对象的字段可能会将对象设置为意外和不一致的状态.
两个线程可以同时操作对象并更改两个不应以这种方式更改的字段.
为了避免这种情况,您不得不使用显式同步机制来读取和写入字段.

即使你使用构造函数或构建器,你就没有这些问题,因为你开箱即用一个完整的,不可修改的(事实上​​的 线程安全)对象和一个清晰的API.

  • *你能解释一下"操纵具有部分状态的对象"是什么意思* - 这意味着在第一次调用你的setter之前,其他线程可以使用该对象**.你*可能*目前正在避免它的事实并不意味着它永远有效. (3认同)
  • @Mu Pi向实例添加信息,例如状态意味着修改其状态.所以实例是可变的.此外,如果必须检查状态变化,它会使客户端代码变得复杂.如果你想保证不变性,在你的`set ...()`方法中,你应该从当前对象创建一个新对象,并在新对象上应用字段修改并返回它.这样,当前对象保持不变. (3认同)
  • @Mu Pi如果你没有抛出异常,你将如何向客户端表明该方法的修改没有发生? (2认同)