2016-02-11 2 views
17

В библиотеке guava есть свой собственный Supplier, который не расширяет Java 8 Supplier. Также guava предоставляет кэш для поставщиков - Suppliers#memoize.Поддерживает ли Java 8 поддержку кэшей для поставщиков?

Есть ли что-то подобное, но для Java 8 поставщиков? не

+8

Не совсем, но вы можете легко конвертировать между j.u.f.Suppliers и c.g.c.b.Suppliers, просто написав ':: get' в конце. –

+2

как @LouisWasserman предлагает, вы можете сделать оболочку для guava. Поставщики :: memoize, в основном делая «return Suppliers.memoize (delegate :: get) :: get;» – 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», не так ли? Кажется, он используется как изменчивый контейнер, который лямбда может закрыться. Если вы хотите сохранить одно распределение объектов, я думаю, вы могли бы вернуть экземпляр анонимного класса с изменчивым полем «значение». Синхронизировать на 'this'. – Lii

+1

@Lii, 'AtomicReference' - это просто объект с одним полем, который дает вам нестабильную семантику чтения/записи. Его можно заменить анонимным полем неустойчивого класса (во втором примере, а не в первом), но не очень очевидно, имеет ли значение такая оптимизация.Кроме того, блокировка на общедоступном объекте считается плохой практикой. –

+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, он может быть безоговорочно возвращен без какой-либо дополнительной синхронизации.

Внутри synchronized метода firstTime(), есть еще initialized флаг необходимо потому, что в случае одновременного доступа во время инициализации, несколько потоков могут ждать на входе методы перед делегатом был заменен. Следовательно, эти потоки должны обнаружить, что инициализация уже выполнена. Все последующие обращения будут читать новый поставщик делегатов и быстро получить значение.

+1

Очень интересный ответ. Можете ли вы объяснить, как возвращение 'delegate.get()' напрямую без синхронизации или 'volatile'-модификатор является потокобезопасным? Как гарантируется, что все потоки, прибывающие туда, видят обновленного делегата, когда они назовут 'get'? – glts

+1

@glts: 'delegate.get()' будет либо в методе 'synchronized'' firstTime() 'для первого вызова (-ов), либо в экземпляре, связанном с выражением'() -> value lambda , тогда как 'value' является фактически окончательным. Доступ к полученному значению эквивалентен чтению поля «final», которое безопасно без дополнительной синхронизации. В случае, если поток видит устаревшее значение для ссылки «делегировать», он будет проходить через метод «synchronized' firstTime()» для одного вызова и впоследствии будет знать последнее значение, поэтому все последующие вызовы идут тогда быстрый путь. – Holger

+2

Почему «делегат» не должен быть помечен «volatile» в этом случае? –

 Смежные вопросы

  • Нет связанных вопросов^_^