48

id1 つの変数を設定解除して (the ) のインスタンスを作成し、後でこの変数を初期化し、初期化後に不変にすることができるクラスが必要です。事実上、finalコンストラクターの外部で初期化できる変数が必要です。

現在、次のようにスローするセッターでこれを即興で作成してExceptionいます。

public class Example {

    private long id = 0;

    // Constructors and other variables and methods deleted for clarity

    public long getId() {
        return id;
    }

    public void setId(long id) throws Exception {
        if ( this.id == 0 ) {
            this.id = id;
        } else {
            throw new Exception("Can't change id once set");
        }
    }
}

これは、私がやろうとしていることについての良い方法ですか? 初期化後に何かを不変として設定できるようにするか、これをよりエレガントにするために使用できるパターンがあるように感じます。

4

12 に答える 12

37

もう少しエレガントな決定を提案させてください。最初のバリアント (例外をスローしない):

public class Example {

    private Long id;

    // Constructors and other variables and methods deleted for clarity

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = this.id == null ? id : this.id;
    }

}

2 番目のバリアント (例外をスローする):

     public void setId(long id)  {
         this.id = this.id == null ? id : throw_();
     }

     public int throw_() {
         throw new RuntimeException("id is already set");
     }
于 2013-01-03T23:14:49.230 に答える
12

「一度だけ設定する」という要件は、少し恣意的に感じます。あなたが探しているのは、初期化されていない状態から初期化された状態に永久に遷移するクラスであると確信しています。結局のところ、オブジェクトが「構築」された後に ID を変更することが許可されていない限り、(コードの再利用などによって) オブジェクトの ID を複数回設定すると便利な場合があります。

かなり合理的なパターンの 1 つは、この「構築済み」の状態を別のフィールドで追跡することです。

public final class Example {

    private long id;
    private boolean isBuilt;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        if (isBuilt) throw new IllegalArgumentException("already built");
        this.id = id;
    }

    public void build() {
        isBuilt = true;
    }
}

使用法:

Example e = new Example();

// do lots of stuff

e.setId(12345L);
e.build();

// at this point, e is immutable

このパターンでは、オブジェクトを構築し、その値を (都合のよい回数だけ) 設定してから、それbuild()を "immutify" するために呼び出します。

このパターンには、最初のアプローチよりもいくつかの利点があります。

  1. 初期化されていないフィールドを表すために使用される魔法の値はありません。たとえば、0は他のlong値と同じように有効な ID です。
  2. セッターの動作は一貫しています。呼び出される前build()に、それらは機能します。が呼び出された後build()、渡した値に関係なく、スローされます。(便宜上、非チェック例外の使用に注意してください)。
  3. クラスは とマークされfinalています。そうしないと、開発者がクラスを拡張してセッターをオーバーライドする可能性があります。

しかし、このアプローチにはかなり大きな欠点があります。このクラスを使用する開発者は、特定のオブジェクトが初期化されているかどうかをコンパイル時に知ることができません。確かに、isBuilt()メソッドを追加して、オブジェクトが初期化されているかどうかを開発者が実行時に確認できるようにすることもできますが、コンパイル時にこの情報を知る方がはるかに便利です。そのためには、ビルダー パターンを使用できます。

public final class Example {

    private final long id;

    public Example(long id) {
        this.id = id;
    }

    public long getId() {
        return id;
    }

    public static class Builder {

        private long id;

        public long getId() {
            return id;
        }

        public void setId(long id) {
            this.id = id;
        }

        public Example build() {
            return new Example(id);
        }
    }
}

使用法:

Example.Builder builder = new Example.Builder();
builder.setId(12345L);
Example e = builder.build();

これは、いくつかの理由ではるかに優れています。

  1. finalフィールドを使用しているため、コンパイラと開発者の両方がこれらの値を変更できないことを知っています。
  2. オブジェクトの初期化された形式と初期化されていない形式の違いは、Java の型システムによって記述されます。一度構築されたオブジェクトに対して呼び出すセッターはありません。
  3. 構築されたクラスのインスタンスは、スレッドセーフであることが保証されています。

はい、維持するのはもう少し複雑ですが、IMHO の利点はコストを上回ります。

于 2013-01-03T22:52:54.500 に答える
3

Google のGuava ライブラリ(強くお勧めします) には、この問題をうまく解決するクラスが付属しています: SettableFuture. これは、あなたが尋ねるset-onceセマンティクスを提供しますが、それ以上のものも提供します:

  1. 代わりに例外を伝える機能 (setExceptionメソッド)。
  2. イベントを明示的にキャンセルする機能。
  3. 値が設定されたとき、例外が通知されたとき、または将来がキャンセルされたときに通知されるリスナーを登録する機能 (ListenableFutureインターフェース)。
  4. Future一般に、マルチスレッド プログラムのスレッド間の同期に使用される型のファミリはSettableFuture、これらと非常にうまく機能します。

Java 8 には、これの独自のバージョンもありますCompletableFuture

于 2016-04-20T22:04:59.063 に答える
2

ブール値フラグを追加するだけで、setId() でブール値を設定/チェックできます。質問を正しく理解していれば、複雑な構造やパターンは必要ありません。これはどう:

public class Example {

private long id = 0;
private boolean touched = false;

// Constructors and other variables and methods deleted for clarity

public long getId() {
    return id;
}

public void setId(long id) throws Exception {
    if ( !touchted ) {
        this.id = id;
         touched = true;
    } else {
        throw new Exception("Can't change id once set");
    }
}

}

このようにsetId(0l);、IDも設定されていると思われる場合。ビジネス ロジックの要件に合わない場合は変更できます。

IDE で編集していません。タイプミスやフォーマットの問題があった場合は申し訳ありません...

于 2013-01-03T23:05:03.267 に答える
1

上記の回答とコメントのいくつか、特に@KatjaChristiansenからのassertの使用に関するコメントを組み合わせて私が思いついた解決策は次のとおりです。

public class Example {

    private long id = 0L;
    private boolean idSet = false;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        // setId should not be changed after being set for the first time.
        assert ( !idSet ) : "Can't change id from " + this.id + " to " + id;
        this.id = id;
        idSet = true;
    }

    public boolean isIdSet() {
        return idSet;
    }

}

結局のところ、これが必要なのは他の場所での設計上の決定が不十分であることを示しているのではないかと思います。むしろ、IDを知っている場合にのみオブジェクトを作成し、IDをfinalに設定する方法を見つける必要があります。このようにして、コンパイル時にさらに多くのエラーを検出できます。

于 2013-01-04T11:42:16.120 に答える
1

2 つの方法があります。最初のものは、他の回答で言及されている他のものと基本的に同じですが、秒とは対照的にここにあります。したがって、最初の方法として、Once は、セッターでそれを強制することにより、一度だけ設定できる値を持つことです。私の実装にはnull以外の値が必要ですが、nullに設定できるようにする場合は、他の回答で提案されているように「isSet」ブール値フラグを実装する必要があります。

2 番目の方法である Lazy は、最初に getter が呼び出されたときに値を遅延して供給する関数を提供することです。

import javax.annotation.Nonnull;

public final class Once<T> 
{
    private T value;

    public set(final @Nonnull T value)
    {
        if(null != this.value) throw new IllegalStateException("Illegal attempt to set a Once value after it's value has already been set.");
        if(null == value) throw new IllegalArgumentException("Illegal attempt to pass null value to Once setter.");
        this.value = value;
    }

    public @Nonnull T get()
    {
        if(null == this.value) throw new IllegalStateException("Illegal attempt to access unitialized Once value.");
        return this.value;
    }
}

public final class Lazy<T>
{
    private Supplier<T> supplier;
    private T value;

    /**
     * Construct a value that will be lazily intialized the
     * first time the getter is called.
     *
     * @param the function that supplies the value or null if the value
     *        will always be null.  If it is not null, it will be called
     *        at most one time.  
     */
    public Lazy(final Supplier<T> supplier)
    {
        this.supplier = supplier;
    }

    /**
     * Get the value.  The first time this is called, if the 
     * supplier is not null, it will be called to supply the
     * value.  
     *
     * @returns the value (which may be null)
     */
    public T get()
    {
        if(null != this.supplier) 
        {
            this.value = this.supplier.get();
            this.supplier = null;   // clear the supplier so it is not called again
                                    // and can be garbage collected.
        }
        return this.value;
    }
}

したがって、これらを次のように使用できます。

//
// using Java 8 syntax, but this is not a hard requirement
//
final Once<Integer> i = Once<>();
i.set(100);
i.get();    // returns 100
// i.set(200) would throw an IllegalStateException

final Lazy<Integer> j = Lazy<>(() -> i);
j.get();    // returns 100
于 2015-05-07T01:35:27.520 に答える
0

のようなintチェッカーを試してみてください

private long id = 0;
static int checker = 0;

public void methodThatWillSetValueOfId(stuff){
    checker = checker + 1

    if (checker==1){
        id = 123456;
    } 
}
于 2016-02-05T13:28:09.633 に答える
0

//これを試すことができます:

class Star
{
    private int i;
    private int j;
    static  boolean  a=true;
    Star(){i=0;j=0;}
    public void setI(int i,int j) {
        this.i =i;
        this.j =j;
        something();
        a=false;
    }
    public void printVal()
    {
        System.out.println(i+" "+j);
    }
    public static void something(){
         if(!a)throw new ArithmeticException("can't assign value");
    }
}

public class aClass
{
    public static void main(String[] args) {
        System.out.println("");
        Star ob = new Star();
        ob.setI(5,6);
        ob.printVal();
        ob.setI(6,7);
        ob.printVal();
    }
}

于 2016-05-08T15:07:13.010 に答える
-2

フィールドをプライベートとしてマークし、公開しないsetterことで十分です。

public class Example{ 

private long id=0;  

   public Example(long id)  
   {  
       this.id=id;
   }    

public long getId()  
{  
     return this.id;
}  

これでは不十分で、誰かが X 回変更できるようにしたい場合は、次のようにします。

public class Example  
{  
    ...  
    private final int MAX_CHANGES = 1;  
    private int changes = 0;    

     public void setId(long id) throws Exception {
        validateExample(); 
        changes++; 
        if ( this.id == 0 ) {
            this.id = id;
        } else {
            throw new Exception("Can't change id once set");
        }
    }

    private validateExample  
    {  
        if(MAX_CHANGES==change)  
        {  
             throw new IllegalStateException("Can no longer update this id");   
        }  
    }  
}  

このアプローチは、ミューテーター (オブジェクトの状態を変更するもの) が呼び出された後にオブジェクトの状態を検証する、契約による設計に似ています。

于 2013-01-03T19:56:07.107 に答える
-6

シングルトンパターンはあなたが調べなければならないものかもしれないと思います。このパターンが設計目標を満たしているかどうかを確認するために、Googleで少し説明します。

以下は、列挙型を使用してJavaでシングルトンを作成する方法に関するsudoコードです。これは、EffectiveJavaで概説されているJoshuaBlochのデザインに基づいていると思います。どちらにしても、まだ持っていない場合は、手に入れる価値のある本です。

public enum JavaObject {
    INSTANCE;

    public void doSomething(){
        System.out.println("Hello World!");
    }
}

使用法:

JavaObject.INSTANCE.doSomething();
于 2013-01-03T20:31:17.977 に答える