2016-02-11 3 views
17

guavaライブラリには、Java 8を拡張しない独自のSupplierがあります。Supplier。また、guavaはサプライヤのキャッシュを提供します(Suppliers#memoize)。Java 8はサプライヤのサポートをキャッシュしていますか?

は似たものはありますが、Java 8のサプライヤーのために?

+8

ないが、あなただけで簡単に '書き込むことによってj.u.f.Suppliersとc.g.c.b.Suppliersの間で変換することができます:: GET'終わり。 –

+2

@LouisWassermanが示唆しているように、基本的に "return Suppliers.memoize(delegate :: get):: get;"を実行することで、guava Suppliers :: memoizeのラッパーを作成できます。 – jvdneste

+1

Suppliers.memoizeがjdk8標準ライブラリに入っていないことは間違いありません。 – jvdneste

答えて

16

ありが全く組み込まれていない-でのJava機能メモ化のために、それはそれを実装するのは非常に難しいことではありませんが、例えば、次のように:

public static <T> Supplier<T> memoize(Supplier<T> delegate) { 
    AtomicReference<T> value = new AtomicReference<>(); 
    return() -> { 
     T val = value.get(); 
     if (val == null) { 
      val = value.updateAndGet(cur -> cur == null ? 
        Objects.requireNonNull(delegate.get()) : cur); 
     } 
     return val; 
    }; 
} 

注異なる実装のアプローチが存在すること。上記の実装は、メモを取られたサプライヤが異なるスレッドから同時に複数回要求した場合に、デリゲートを数回呼び出すことがあります。時には、そのような実装がロック付きの明示的な同期よりも優先されることもあります。ロックが好まれている場合は、DCLを使用することができる:

public static <T> Supplier<T> memoizeLock(Supplier<T> delegate) { 
    AtomicReference<T> value = new AtomicReference<>(); 
    return() -> { 
     T val = value.get(); 
     if (val == null) { 
      synchronized(value) { 
       val = value.get(); 
       if (val == null) { 
        val = Objects.requireNonNull(delegate.get()); 
        value.set(val); 
       } 
      } 
     } 
     return val; 
    }; 
} 

をまた@LouisWassermanが正しくコメントで述べたように、あなたは簡単にメソッド参照を使用してグアバサプライヤー、その逆にJDKのサプライヤーを変換することができ、注意してください。

java.util.function.Supplier<String> jdkSupplier =() -> "test"; 
com.google.common.base.Supplier<String> guavaSupplier = jdkSupplier::get; 
java.util.function.Supplier<String> jdkSupplierBack = guavaSupplier::get; 

したがって、Guava関数とJDK関数を切り替えるのは大きな問題ではありません。

+1

この場合、AtomicReferenceは本当に必要ありませんか?それはラムダが閉じることができる変更可能なコンテナとしてちょうど使用されるようです。 1つのオブジェクトの割り当てを保存したい場合は、揮発性の 'value'フィールドを持つ匿名クラスのインスタンスを返すことができると思います。 'this'を同期させます。 – Lii

+1

@Lii、 'AtomicReference'は、ここで必要な揮発性の読み書きのセマンティクスを提供する単一のフィールドを持つオブジェクトに過ぎません。匿名クラスのvolatileフィールド(最初のサンプルではなく、2番目のサンプルのみ)と置き換えることは可能ですが、そのような最適化が重要かどうかはそれほど明確ではありません。また、公開されているオブジェクトをロックすることは悪い習慣と考えられます。 –

+1

'() - > val'という形式の別のサプライヤを思い出すことで、揮発性のセマンティクスを排除することができます。こうすることで、取得した値の 'final'フィールドセマンティクスを使用しています。 – Holger

18

最も簡単な解決策は、最も簡単なのは常に最も効率的ではありません、しかし、

public static <T> Supplier<T> memoize(Supplier<T> original) { 
    ConcurrentHashMap<Object, T> store=new ConcurrentHashMap<>(); 
    return()->store.computeIfAbsent("dummy", key->original.get()); 
} 

だろう。

あなたはクリーンで効率的なソリューションをしたい場合は、変更可能な状態を保持するために匿名の内部クラスに頼ることは報われる:

public static <T> Supplier<T> memoize1(Supplier<T> original) { 
    return new Supplier<T>() { 
     Supplier<T> delegate = this::firstTime; 
     boolean initialized; 
     public T get() { 
      return delegate.get(); 
     } 
     private synchronized T firstTime() { 
      if(!initialized) { 
       T value=original.get(); 
       delegate=() -> value; 
       initialized=true; 
      } 
      return delegate.get(); 
     } 
    }; 
} 

をこれが初めて操作を行い、その後になるデリゲートのサプライヤーを使用して、取得された最初の評価の結果を無条件に返すサプライヤと置き換えます。それはfinalのフィールドセマンティクスを持っているので、追加の同期化なしで無条件に返すことができます。​​方法firstTime()インサイド

、デリゲートが交換される前に、初期化時の同時アクセスの場合には、複数のスレッドがメソッドのエントリで待機可能性があるため、必要に応じinitializedフラグが依然として存在しています。したがって、これらのスレッドは初期化が既に完了したことを検出する必要があります。その後のすべてのアクセスでは、新しいデリゲートサプライヤが読み込まれ、値がすぐに取得されます。

正確
+1

非常に興味深い答え。 'delegate.get()'を同期なしで直接返す方法や 'volatile'修飾子がスレッドセーフであることを説明できますか? 'get'を呼び出すと、そこに到着するすべてのスレッドが更新されたデリゲートをどのように参照することが保証されますか? – glts

+1

@glts: 'delegate.get()'は、最初の呼び出しの 'synchronized'メソッド' firstTime() 'または'() - > value'ラムダ式に関連するインスタンスのいずれかで終了します'value'は事実上最終的です。捕捉された値にアクセスすることは、追加の同期化なしで安全な 'final'フィールドを読み取ることと同じです。スレッドが 'delegate'参照の失効した値を参照する場合、単一の呼び出しに対して' synchronized'' firstTime() 'メソッドを実行し、その後の最新の値を知っているので、以降のすべての呼び出しが行われますその後、高速パス。 – Holger

+2

なぜ、 'delegate'にそのような場合に' volatile'とマークする必要はありませんか? –

関連する問題