web-dev-qa-db-ja.com

ラムダによる遅延フィールドの初期化

Ifステートメントを使用せずにラムダを利用して、遅延フィールド初期化(または遅延初期化)を実装したいと思います。したがって、次のFooプロパティと同じ動作にしたいのですが、ifを使用しません。

_class A<T>{
    private T fooField;

    public T getFoo(){
        if( fooField == null ) fooField = expensiveInit();
        return fooField;
    }
}
_

このソリューションは以下の安全な使用を保証するものではないという事実を無視してください。1)マルチスレッド。 2)nullの有効な値としてのT

したがって、最初の使用までfooFieldの初期化を延期するという意図を表すために、次のようなタイプ_Supplier<T>_のfooFieldを宣言したいと思います。

_class A<T>{
   private Supplier<T> fooField = () -> expensiveInit();

   public T getFoo(){
      return fooField.get();
   }
}
_

getFooプロパティでfooField.get()を返すだけです。しかし、今、私はgetFooプロパティへの次の呼び出しでexpensiveInit()を避け、前のTインスタンスを返すようにしたいだけです。

ifを使用せずにこれを達成するにはどうすればよいですか?

命名規則と_->_ by _=>_の置き換えにもかかわらず、この例はC#でも考慮することができます。ただし、NET Frameworkバージョン4は、必要なセマンティクスを持つ_Lazy<T>_を既に提供しています。

45
rodolfino

実際のラムダ内で、次のような新しいラムダでfooFieldを単純に更新できます。

class A<T>{
    private Supplier<T> fooField = () -> {
       T val = expensiveInit();
       fooField = () -> val;
       return val;
    };

    public T getFoo(){
       return fooField.get();
    }
}

繰り返しますが、このソリューションは.Net Lazy<T>、およびgetFooプロパティへの同時呼び出しが同じ結果を返すことを保証しません。

36
Miguel Gamboa

Miguel Gamboaのソリューション を採用し、エレガンスを犠牲にせずにフィールドごとのコードを最小化しようとして、次のソリューションに至りました。

interface Lazy<T> extends Supplier<T> {
    Supplier<T> init();
    public default T get() { return init().get(); }
}
static <U> Supplier<U> lazily(Lazy<U> lazy) { return lazy; }
static <T> Supplier<T> value(T value) { return ()->value; }

Supplier<Baz> fieldBaz = lazily(() -> fieldBaz=value(expensiveInitBaz()));
Supplier<Goo> fieldGoo = lazily(() -> fieldGoo=value(expensiveInitGoo()));
Supplier<Eep> fieldEep = lazily(() -> fieldEep=value(expensiveInitEep()));

フィールドごとのコードは Stuart Marksのソリューション よりもわずかに大きいだけですが、最初のクエリの後、無条件に軽量のSupplierのみが存在する元のソリューションのNiceプロパティを保持しますすでに計算された値を返します。

22
Holger

Miguel Gamboa's answer によるアプローチは素晴らしいものです:

private Supplier<T> fooField = () -> {
   T val = expensiveInit();
   fooField = () -> val;
   return val;
};

これは、1回限りの遅延フィールドに適しています。ただし、この方法で複数のフィールドを初期化する必要がある場合は、ボイラープレートをコピーして変更する必要があります。次のように別のフィールドを初期化する必要があります。

private Supplier<T> barField = () -> {
   T val = expensiveInitBar();          // << changed
   barField = () -> val;                // << changed
   return val;
};

初期化後にアクセスごとに1つの余分なメソッド呼び出しに耐えることができる場合、次のようにします。最初に、キャッシュされた値を含むSupplierのインスタンスを返す高階関数を作成します。

static <Z> Supplier<Z> lazily(Supplier<Z> supplier) {
    return new Supplier<Z>() {
        Z value; // = null
        @Override public Z get() {
            if (value == null)
                value = supplier.get();
            return value;
        }
    };
}

無名クラスは、初期化された値のキャッシュされた可変状態を持っているため、ここで呼び出されます。

その後、遅延初期化された多くのフィールドを簡単に作成できます。

Supplier<Baz> fieldBaz = lazily(() -> expensiveInitBaz());
Supplier<Goo> fieldGoo = lazily(() -> expensiveInitGoo());
Supplier<Eep> fieldEep = lazily(() -> expensiveInitEep());

注:質問では、「ifを使用せずに」と規定されていることがわかります。ここでの懸念がif条件付きのランタイムが高価な(実際にはかなり安い)を回避することに関するものなのか、それともすべてのgetterでif条件を繰り返す必要を回避することに関するものなのかは明確ではありませんでした。私はそれが後者であると仮定し、私の提案はその懸念に対処します。 if-conditionalの実行時のオーバーヘッドが心配な場合は、ラムダ式を呼び出すオーバーヘッドも考慮する必要があります。

20
Stuart Marks

Project Lombok は、必要なことを正確に行う @Getter(lazy = true) アノテーションを提供します。

10

サポートされている、

小さなインターフェースを作成し、Java 8:

  • @FunctionalInterface注釈(宣言時にラムダを割り当てることができます)
  • defaultキーワード(抽象クラ​​スと同じように、ただしインターフェースで実装を定義します)

C#で見たのと同じ同じLazy<T>動作を取得できます。


使用法

Lazy<String> name = () -> "Java 8";
System.out.println(name.get());

Lazy.Java(アクセス可能な場所にこのインターフェイスをコピーして貼り付けます)

import Java.util.function.Supplier;

@FunctionalInterface
public interface Lazy<T> extends Supplier<T> {
    abstract class Cache {
        private volatile static Map<Integer, Object> instances = new HashMap<>();

        private static synchronized Object getInstance(int instanceId, Supplier<Object> create) {

            Object instance = instances.get(instanceId);
            if (instance == null) {
                synchronized (Cache.class) {
                    instance = instances.get(instanceId);
                    if (instance == null) {
                        instance = create.get();
                        instances.put(instanceId, instance);
                    }
                }
            }
            return instance;
        }
    }

    @Override
    default T get() {
        return (T) Cache.getInstance(this.hashCode(), () -> init());
    }

    T init();
}

オンラインの例- https://ideone.com/3b9alx

次のスニペットは、このヘルパークラスのライフサイクルを示しています

static Lazy<String> name1 = () -> { 
    System.out.println("lazy init 1"); 
    return "name 1";
};

static Lazy<String> name2 = () -> { 
    System.out.println("lazy init 2"); 
    return "name 2";
};

public static void main (String[] args) throws Java.lang.Exception
{
    System.out.println("start"); 
    System.out.println(name1.get());
    System.out.println(name1.get());
    System.out.println(name2.get());
    System.out.println(name2.get());
    System.out.println("end"); 
}

出力します

start
lazy init 1
name 1
name 1
lazy init 2
name 2
name 2
end

オンラインデモを参照してください- https://ideone.com/3b9alx

3
Jossef Harush

これはどう?次に、Apache CommonsのLazyInitializerを使用して、このようなことを実行できます。 https://commons.Apache.org/proper/commons-lang/javadocs/api-3.1/org/Apache/commons /lang3/concurrent/LazyInitializer.html

private static Lazy<Double> _lazyDouble = new Lazy<>(()->1.0);

class Lazy<T> extends LazyInitializer<T> {
    private Supplier<T> builder;

    public Lazy(Supplier<T> builder) {
        if (builder == null) throw new IllegalArgumentException();
        this.builder = builder;
    }
    @Override
    protected T initialize() throws ConcurrentException {
        return builder.get();
    }
}
2
Hidden Dragon

expensiveInitメソッドに引数(関数型インターフェイスを初期化するときに持ってはいけない)を渡したい場合にも機能する方法を次に示します。

public final class Cache<T> {
    private Function<Supplier<? extends T>, T> supplier;

    private Cache(){
        supplier = s -> {
            T value = s.get();
            supplier = n -> value;
            return value;
        };
    }   
    public static <T> Supplier<T> of(Supplier<? extends T> creater){
        Cache<T> c = new Cache<>();
        return () -> c.supplier.apply(creater);
    }
    public static <T, U> Function<U, T> of(Function<? super U, ? extends T> creater){
        Cache<T> c = new Cache<>();
        return u -> c.supplier.apply(() -> creater.apply(u));
    }
    public static <T, U, V> BiFunction<U, V, T> of(BiFunction<? super U, ? super V, ? extends T> creater){
        Cache<T> c = new Cache<>();
        return (u, v) -> c.supplier.apply(() -> creater.apply(u, v));
    }
}

使用法は Stuart Marks ' answerと同じです。

private final Function<Foo, Bar> lazyBar = Cache.of(this::expensiveBarForFoo);
1
Alex

これらの線に沿って何かをすることができます:

   private Supplier heavy = () -> createAndCacheHeavy();

   public Heavy getHeavy()
   {
      return heavy.get();
   }

   private synchronized Heavy createAndCacheHeavy()
   {
      class HeavyFactory implements Supplier
      {
         private final Heavy heavyInstance = new Heavy();

         public Heavy get()
         {
            return heavyInstance;
         }
      }

      if(!HeavyFactory.class.isInstance(heavy))
      {
         heavy = new HeavyFactory();
      }

      return heavy.get();
   }

私は最近、これをVenkat Subramaniamによるアイデアと考えました。 このページ からコードをコピーしました。

基本的な考え方は、一度呼び出されたサプライヤーは、初期化されたインスタンスを返すより単純なファクトリー実装に置き換えます。

これは、シングルトンのスレッドセーフな遅延初期化のコンテキストで行われましたが、通常のフィールドにも適用できます。

1
bowmore

さて、「if」を持たないことはお勧めしませんが、問題についての私の考えは次のとおりです。

1つの簡単な方法は、AtomicReferenceを使用することです(三項演算子はまだ「if」のようなものです)。

private final AtomicReference<Something> lazyVal = new AtomicReference<>();

void foo(){
    final Something value = lazyVal.updateAndGet(x -> x != null ? x : expensiveCreate());
    //...
}

しかし、それでは、必要のないスレッドセーフの魔法があります。だから私はミゲルのように少しひねりを加えてやります:

私は単純なワンライナーが好きなので、単純に三項演算子(ここでも "if"のように読みます)を使用しますが、Javaの評価順序に魔法をかけてフィールドを設定します。

public static <T> Supplier<T> lazily(final Supplier<T> supplier) {
    return new Supplier<T>() {
        private T value;

        @Override
        public T get() {
            return value != null ? value : (value = supplier.get());
        }
    };
}

上記のgerardwのフィールド変更の例は、「if」なしで機能しますが、さらに単純化することもできます。インターフェイスは必要ありません。上記の「トリック」を再度活用する必要があります。割り当て演算子の結果は割り当てられた値です。ブラケットを使用して評価順序を強制することができます。したがって、上記の方法では次のようになります。

static <T> Supplier<T> value(final T value) {
   return () -> value;
}


Supplier<Point> p2 = () -> (p2 = value(new Point())).get();

遅延を失わずに「value(...)」メソッドをインライン化できないことに注意してください。

1
Brixomatic

C#のLazyの動作に近いものが必要な場合、スレッドの安全性を確保し、常に同じ値を取得することが保証されますが、ifを回避する簡単な方法はありません。

Volatileフィールドとダブルチェックロックを使用する必要があります。これは、C#の動作を提供するクラスのメモリフットプリントが最小のバージョンです。

public abstract class Lazy<T> implements Supplier<T> {
    private enum Empty {Uninitialized}

    private volatile Object value = Empty.Uninitialized;

    protected abstract T init();

    @Override
    public T get() {
        if (value == Empty.Uninitialized) {
            synchronized (this) {
                if (value == Empty.Uninitialized) {
                    value = init();
                }
            }
        }
        return (T) value;
    }

}

使用するのはそれほどエレガントではありません。次のような遅延値を作成する必要があります。

final Supplier<Baz> someBaz = new Lazy<Baz>() {
    protected Baz init(){
        return expensiveInit();
    }
}

次のようなファクトリーメソッドを追加することで、メモリフットプリントを追加することでいくらかの優雅さを得ることができます。

    public static <V> Lazy<V> lazy(Supplier<V> supplier) {
        return new Lazy<V>() {
            @Override
            protected V init() {
                return supplier.get();
            }
        };
    }

これで、次のようにスレッドセーフな遅延値を作成できます。

final Supplier<Foo> lazyFoo = lazy(() -> fooInit());
final Supplier<Bar> lazyBar = lazy(() -> barInit());
final Supplier<Baz> lazyBaz = lazy(() -> bazInit());
1
Phil S

明示的なクラスを持つスチュアートマークのソリューション。 (これが「より良い」かどうかは個人的な好みの問題だと思います。)

public class ScriptTrial {

static class LazyGet<T>  implements Supplier<T> {
    private T value;
    private Supplier<T> supplier;
    public LazyGet(Supplier<T> supplier) {
        value = null;
        this.supplier = supplier;
    }

    @Override
    public T get() {
        if (value == null)
            value = supplier.get();
        return value;
    }

}

Supplier<Integer> lucky = new LazyGet<>(()->seven());

int seven( ) {
    return 7;
}

@Test
public void printSeven( ) {
    System.out.println(lucky.get());
    System.out.println(lucky.get());
}

}

0
gerardw

2つのソリューション、1つの機能的なthenと1つのオブジェクト(同じコード)、スレッドセーフ「if」なし、および適切なタイプの例外処理に注意伝搬(ここでは解決策はありません)。

かなり短いです。ランタイムによって処理されるより良い遅延フィールドのサポートは、最終的にこのコードを廃止します...

使用法 :

// object version : 2 instances (object and lambda)
final Lazy<Integer, RuntimeException> lazyObject = new LazyField<>(() -> 1);

// functional version : more efficient than object, 1 instance
// usage : wrap computed value using eval(arg), and set the lazy field with result
Lazy<Service, IOException> lazyFunc = lazyField(() -> this.lazyFunc = eval(new Service()));

// functional final version, as field is final this is less efficient than object :
// 2 instances and one "if". null check removal may remove the "if"...
final Lazy<Integer, RuntimeException> finalFunc = lazyField(() -> eval(1));

// Here the checked exception type thrown in lambda can only be ServiceException
static Lazy<Integer, ServiceException> lazyTest = lazyField(() -> {throw new ServiceException();});

最初に、例外を使用してラムダを定義します。

@FunctionalInterface
interface SupplierWithException<T, E extends Exception> {
    T get() throws E;
}

次にレイジータイプ:

interface Lazy<T, E extends Exception> extends SupplierWithException<T, E> {}

機能バージョン:

上記のサンプルのように最終フィールドで使用されない場合、最終的に少ないメモリフットプリントを取得するラムダを直接返します。

static <T, E extends Exception> Lazy<T, E> lazyField(Lazy<Lazy<T, E>, E> value) {
    Objects.requireNonNull(value);
    Lazy<T, E>[] field = new Lazy[1];
    return () -> {
        if(field[0] == null) {
            synchronized(field) {
                if(field[0] == null) {
                    field[0] = value.get();
                }
            }
        }
        return field[0].get();
    };
}

static <T, E extends Exception> Lazy<T, E> eval(T value) {
    return () -> value;
}

正しい値のコールバックを強制することはできません。少なくとも常に同じ結果を返しますが、「最終」フィールドの場合のように「if」を避けることはできません。

オブジェクトバージョン:

外部から完全に安全です。

public final class LazyField<T, E extends Exception> implements Lazy<T, E> {

    private Lazy<T, E> value;

    public LazyField(SupplierWithException<T, E> supplier) {
        value = lazyField(() -> value = eval(supplier.get()));
    }

    @Override
    public T get() throws E {
        return value.get();
    }
}

楽しい

0
Charles Briquel

これはどう。各アクセスでifsを回避するためのいくつかのJ8機能スイッチ。警告:スレッド対応ではありません。

import Java.util.function.Supplier;

public class Lazy<T> {
    private T obj;
    private Supplier<T> creator;
    private Supplier<T> fieldAccessor = () -> obj;
    private Supplier<T> initialGetter = () -> {
        obj = creator.get();
        creator = null;
        initialGetter = null;
        getter = fieldAccessor;
        return obj;
    };
    private Supplier<T> getter = initialGetter;

    public Lazy(Supplier<T> creator) {
        this.creator = creator;
    }

    public T get() {
        return getter.get();
    }

}
0
JasonW