3

Для приложений бизнес-решений я сталкиваюсь с множеством случаев, когда я должен кэшировать дорогостоящее значение с ленивой инициализацией. Поэтому я использовал дженерики и лямбда Поставщика, чтобы инкапсулировать ленивую инициализацию.Ссылка «это» в ленивом поставщике инициализации?

import java.util.function.Supplier; 

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

    private LazyProperty(Supplier<T> supplier) { 
     this.supplier = supplier; 
    } 
    public T get() { 
     if (value == null) { 
      synchronized(this) { 
       if (value == null) { 
        value = supplier.get(); 
       } 
      } 
     } 
     return value; 
    } 

    public static <T> LazyProperty<T> forSupplier(Supplier<T> supplier) { 
     return new LazyProperty<T>(supplier); 
    } 
} 

Но я хотел бы быть в состоянии использовать это также в тех случаях, когда я не могу инициализировать свойство только после создания объекта, так как объект может вычислить только это свойство после того, как он создается (обычно нуждающихся в контексте себя или других объектов). Однако это часто требует ссылки на функцию this.

public class MyClass { 
    private final LazyProperty<BigDecimal> expensiveVal = 
     LazyProperty.forSupplier(() -> calculateExpensiveVal(this)); 

    public BigDecimal getExpensiveVal() { 
     return expensiveVal.get(); 
    } 
} 

Пока я могу гарантировать get() функция LazyProperty, называется только после того, как MyClass построен (с помощью метода getExpensiveVal()), не должно быть никаких частичных вопросов строительства в связи с this ссылкой на поставщика, корректные ?

+0

Я не обязательно вижу проблему с этим, так как 'this' относится к' MyClass', а не лямбда. Вы сталкивались с проблемой, или вы были обеспокоены тем, что это было концептуально необоснованным? – Makoto

+0

Нет, у меня не было никаких проблем, и да, я просто был обеспокоен тем, что это было концептуально необоснованным. Я всегда читал, что ссылки на 'this' при инициализации или построении являются рискованными, потому что они могут выставлять частично сконструированный объект. Но с функциональным программированием имеет смысл, что любые lambdas, содержащие ссылки на 'this' (ссылаясь на MyClass), являются безобидными, если они не выполняются при построении. – tmn

+1

Да; вот почему я не вижу здесь проблемы. Вы не используете поле 'дорогоеVal' в конструкторе в любом месте, поэтому я не верю, что вы столкнетесь с проблемой. У вас может быть интересное время с безопасностью типа, так как вы должны убедиться, что 'calculateExpensiveVal' будет использовать нужный вам тип. – Makoto

ответ

1

Ваш код будет иметь одну проблему, которая зависит от реализации метод calculateExpensiveVal.

  1. если calculateExpensiveVal вызывает getExpensiveVal на пройденную ссылку на MyClass, вы получите NullPointerException.

  2. если calculateExpensiveVal создает поток и передать ссылку на MyClass, вы снова можете столкнуться с той же проблемой, как точка 1.

Но если вы гарантировать calculateExpensiveVal не делает какой-либо из вещи, то ваш код стоит правильно с точки зрения безопасности потока. MyClass никогда не будет видно частично построен из-за окончательных gaurantees предоставленных JMM

Сказав, что даже если ваш * calculateExpensiveVal может использовать одну или обе эти точки вы только собираетесь иметь проблемы в getExpensiveVal метод с NullPointerException.

Ваш lazyProperty.get метод уже потокобезопасен, поэтому там будет проблема.

Потому что вы будете всегда видеть полностью построен объект поставщика из-за конечным ключевое слово (только если вы не избежали «этого» ссылки на другую нить), и вы уже использовали летучими для значением поля, которое принимает уход за наблюдением полностью построенный значение объект.

+0

Конечно. Вероятно, я мог бы сделать свой абстрактный пример немного более похожим на SSCCE, но я абсолютно согласен с вашими точками и возможностью многопоточности. – tmn

2

Основываясь на небольшой код, который вы показали, вы не должны иметь никаких проблем, но я бы, вероятно, написать свой класс, как это будет более явным:

public class MyClass { 
    private final LazyProperty<BigDecimal> expensiveVal; 

    public MyClass() { 
     this.expensiveVal = LazyProperty.forSupplier(() -> calculateExpensiveVal(MyClass.this)); 
    } 

    public BigDecimal getExpensiveVal() { 
     return expensiveVal.get(); 
    } 
}