Статьи

Функциональное программирование на Java 8: ленивая реализация

Синглтоны часто создают себя лениво, а иногда, если объект достаточно тяжелый, поля классов могут создаваться лениво.

Как правило, при прохождении ленивого маршрута метод-получатель (или метод доступа ) должен иметь блок кода, который проверяет, был ли еще создан объект (и, если это не так, его нужно создать) перед его возвратом. Эта проверка не имеет смысла, как только объект был создан; он служит только для замедления метода, который уже (обычно) блокирует синхронизацию или блокировку. Давайте посмотрим, как удалить этот код?

отказ

Я не придумал это. Я получил это от функционального программирования на Java от Venkat Subramaniam. Я настоятельно рекомендую и книгу, и автора. Все, что я прочитал от Venkat, было хорошо сделано и легко усваивалось.

Как это работает?

Основная идея состоит в том, чтобы:

  1. замените лениво созданное поле на Поставщика нужного вам типа.
  2. Поставщик создает объект (но пока не возвращает его)
  3. затем он устанавливает поле для нового поставщика, который возвращает только экземпляр объекта
  4. вернуть экземпляр

Итак, давайте посмотрим на это в действии. У нас будет класс Holder, который хочет лениво создавать объекты типа Heavy. Этот код скомпилирован прямо из книги Венката:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class Holder
{
   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();
   }
}

Теперь этот код работает просто отлично, но я считаю, что реализация createAndCacheHeavy неоправданно сбивает с толку. Когда я впервые увидел этот код, мне понадобилось много времени, чтобы понять, что он делает.

Итак, давайте сделаем несколько изменений, не так ли? Мы сделаем так, чтобы это выглядело так, как будто вы выполняете шаги, которые я изложил ранее.

1
2
3
4
5
6
private Heavy createAndCacheHeavy()
{
   Heavy instance = new Heavy();
   heavy = () -> instance;
   return instance;
}

Разве это не лучше? На мой взгляд, это намного проще и чище, чем раньше. И это все еще работает! Ну, есть небольшое предостережение: чтобы сделать код потокобезопасным, вам нужно синхронизировать метод getInstance () вместо метода createAndCacheHeavy. Это изменение немного замедлит код по сравнению с Venkat, так как его код не использует синхронизацию после установки HeavyFactory. Но это все еще быстрее, чем старый метод, который каждый раз требовал синхронизации и условной проверки.

Итак, это некоторый полезный код, но хотите ли вы вводить этот код каждый раз, когда хотите что-то лениво создавать? Я так не думаю. Итак, давайте создадим класс, который будет многократно использоваться и сделает нашу жизнь намного проще.

Но сначала, просто чтобы показать вам, как легче использовать, позвольте мне показать вам, как его использовать.

1
Supplier<Heavy> heavy = LazilyInstantiate.using(() -> new Heavy());

Это оно! Давайте посмотрим на это немного ближе и углубимся в это, чтобы увидеть, что происходит, прежде чем мы сделаем это.

Бит объявления строки такой же, как и раньше; Поставщик Heavy назвал Heavy. Но затем мы вызываем статический метод LazilyInstantiate, который оказывается статическим фабричным методом, который возвращает объект LazilyInstantiate, который реализует поставщика. Аргумент, передаваемый в метод, является Heavy Supplier, который существует, чтобы пользователь мог предоставить инстанциатору правильный код для создания экземпляра объекта.

Итак, вам интересно, как это работает? Ну вот код для LazilyInstantiate:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class LazilyInstantiate implements Supplier
{
   public static  LazilyInstantiate using(Supplier supplier)
   {
      return new LazilyInstantiate<>(supplier);
   }
      
   public synchronized T get()
   {
      return current.get();
   }
      
   private LazilyInstantiate(Supplier supplier)
   {
      this.supplier = supplier;
      this.current = () -> swapper();
   }
      
   private final Supplier supplier;
   private Supplier current;
      
   private T swapper()
   {
      T obj = supplier.get();
      current = () -> obj;
      return obj;
   }
}

Вы можете найти порядок моих методов и такие, которые немного отличаются от того, что обычно. Я предпочитаю сначала иметь общедоступный материал, затем пакетный и защищенный, а затем частный. Внутри этих блоков я делаю статические поля, затем конструкторы, затем статические методы, затем нормальные поля, затем нормальные методы. В общем, это, кажется, сортирует вещи в порядке наиболее важного для пользователя, читающего мой код, по наименее важному.

Вы можете свободно копировать этот код в любое место или можете проверить мою функциональную библиотеку java на github , которая имеет полностью документированную версию этого класса (func.java.lazy.LazilyInstantiate) и многие другие полезные функциональные классы.