2017-02-22 14 views
3

私は、オンデマンドでスレッドセーフなオプションの遅延初期化を行い、一般的な機能を記述しようとしています。値は最終的なものではなく、すでにセッターを介して設定されている可能性があるので、標準パターンは使用できません。Java 7でパターンを取得または作成しますか?

final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList<String>::new); 

これは、スレッドセーフで、処理することができます:

public class LazyInitializer<T> { 

    protected final Supplier<T> initializer; 
    protected AtomicReference<T> value = new AtomicReference<>(); 

    public LazyInitializer(final Supplier<T> initializer) { 
    this.initializer = initializer; 
    } 

    public T get() { 
    T result = this.value.get(); 
    if (result == null) { 
     this.value.compareAndSet(null, this.initializer.get()); 
     result = this.value.get(); 
    } 
    return result; 
    } 

    public void setValue(final T value) { 
    this.value.set(value); 
    } 

} 

あなたは、このように、このクラスを使用します。

は、Java 8で、私は、サプライヤーとのLazyInitializerジェネリックを書くことでことを解決しましたsettersは、オーバーヘッドが非常に低く、特に:定型コードをほとんど必要としません。

は今、しかし私は、Java 7を使用するように強制していると私は、同じようにエレガントな解決策を見つけるように見えることはできませんのJava 7とサプライヤーを使用することはできませんので、醜い多くのコードを記述する必要があります。また、ジェネリック医薬品は、あなたが( ArrayList<String>のような)ジェネリッククラス値を使用する場合に問題となる、あなたは正確なクラスを提供しない限り、それらをインスタンス化することはできません。

私が知る限り、醜いコードを書いたり、私の能力を超えてリフレクションマジックを書いたりすることができますか?または、LazyInitializerクラスをJava 7のエレガントな方法で書く方法がありますか?

編集:次のようにジョーンVerneeからの回答を使用するには、私は、Java 7で動作するようにクラスを変更:

final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList.class); 
:そしてエレガントに、このように呼ばれる(再び)することができ

public class LazyInitializer<T> { 

    protected final Class<?> clazz; 
    protected AtomicReference<T> value = new AtomicReference<>(); 

    public LazyInitializer(final Class<?> clazz) { 
    this.clazz = clazz; 
    } 

    public T get() { 
    T result = this.value.get(); 
    if (result == null) { 
     this.value.compareAndSet(null, constructNew()); 
     result = this.value.get(); 
    } 
    return result; 
    } 

    public void setValue(final T value) { 
    this.value.set(value); 
    } 

    protected T constructNew() { 
    try { 
     return (T) clazz.newInstance(); 
    } catch (InstantiationException | IllegalAccessException ex) { 
     throw new IllegalStateException(ex); 
    } 
    } 
} 

しかし提供されたクラスは、実際に(ジェネリックので)と一致し、それが唯一のデフォルトコンストラクタで動作している場合、このクラスは、もは​​や確認することはできません。しかし、少なくともそれは私の場合を解決する。

+0

私は間違っているか、あなたのJava8サンプルコードの最初の 'this.value.get()'とif節をスキップしてもよいですか? – Alexander

+0

'value == null 'の場合、値は初期値に設定する必要があります。だから、それをスキップすることはできません。 – TwoThe

+1

@TwoThe私は間違っているか、コードが実際に複数回サプライヤの実行をトリガすることができますか? 'compareAndSet'のためのパラメータ評価は関数自身のために原子的ではないので。 – chimmi

答えて

1

一つ、もちろん、あなたが戻って行くのであれば、それはXによってコードの量を減少させることですXでコードの量をもう一度増やすつもりです。ファンクタでコードをラップする必要がある場合は、匿名のクラスがimhoを実行する最善の方法です。

リフレクションを使用するこれを行うには、あまり効率的ではない方法があります。そして意図したとおりに使用する限り、例外をスローしません。

public static <T> Supplier<T> constructorLookup(Class<?> rawtype) { 
    try { 
     Constructor<?> cons = rawtype.getConstructor(); 

     return new Supplier<T>() { 
      @SuppressWarnings("unchecked") 
      @Override 
      public T get() { 
       try { 
        return (T) cons.newInstance(); 
       } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 
         | InvocationTargetException e) { 
        throw new IllegalStateException(e); 
       } 
      }    
     }; 

    } catch (NoSuchMethodException | SecurityException e) { 
     throw new IllegalArgumentException(e); 
    }  
} 

結果を:

interface Supplier<T> { 
    T get(); 
} 

はその後、実行時にルックアップを行いファクトリメソッドを持っている:

あなたはダイナミックコンストラクタの検索を行うことができ、あなたはまだSupplierタイプを必要とするだろうコードは次のようになります:

LazyInitializer<List<String>> value 
    = new LazyInitializer<>(constructorLookup(ArrayList.class)); 

現在のところ、これはデフオルトtのコンストラクタを使用することもできますが、引数を使用して動作するように拡張することもできます。

+0

ハッキー、日陰、作品。私はそれが好きです。 ;) – TwoThe

2

Guavaを使用できる場合は、Supplierクラス(Java 8のSupplierと同じ署名)を含む最新のJava-7互換バージョンを使用できます。インポートのみが異なっている:com.google.common.base.Supplierの代わりjava.util.function.Supplier

あなたはグアバを使用しない場合、あなたはまだJavaの8のSupplierと同じシグネチャを持つ独自のSupplierを書くことができます:

public interface Supplier<T> { 
    T get(); 
} 

その後、あなたは次のように書くことができる方Supplier使用、:

Supplier<List<String>> stringListSupplier = new Supplier<List<String>>() { 
    @Override public List<String> get() { return new ArrayList<>(); } 
}; 

ジェネリックスがそれほど気になる場合は、実際に次のように記述して自由に再利用できます。その後、

public static <T> Supplier<List<T>> newArrayListSupplier() { 
    return new Supplier<List<T>>() { 
    @Override public List<T> get() { return new ArrayList<>(); } 
    }; 
} 

あなたの最終的なコードは次のようになります。ラムダ/方法、参考文献についての素晴らしい事の

final LazyInitializer<List<String>> value = new LazyInitializer<>(MyClass.<String>newArrayListSupplier()); 
+0

ヒントをお寄せいただきありがとうございますが、特にコードの量を減らしたいので、これで問題は解決しません。私は値がJSON-POJOオブジェクトで怠惰に設定されているいくつかの場所を持っています。匿名クラスを使用すると、現在持っているコードの量が増えます。これらの場所にはそれぞれ異なるクラスがあるので、あなたのソリューションをリストに使うことはできません。 – TwoThe

+0

さて、残念ながら新しいオブジェクトをインスタンス化する方法は他にないことを申し訳ありません。そのようなパターンはラムダをシミュレートするために必要です(これは最終的にあなたの要求です)。 –

関連する問題