2017-02-22 27 views
3

Я пытаюсь написать универсальную функциональность, которая по требованию создает поточную инициализацию по требованию. Я не могу использовать стандартный шаблон, так как значение не является окончательным и могло быть установлено через сеттер уже.Получите или создайте шаблон в Java 7?

В Java 8 я решил, что, написав общий LazyInitializer с поставщиком:

public class LazyInitializer<T> { 

    protected final Supplier<T> initializer; 
    protected AtomicReference<T> value = new AtomicReference<>(); 

    public LazyInitializer(final Supplier<T> initializer) { 
    this.initializer = initializer; 
    } 

    public T get() { 
    T result = this.value.get(); 
    if (result == null) { 
     this.value.compareAndSet(null, this.initializer.get()); 
     result = this.value.get(); 
    } 
    return result; 
    } 

    public void setValue(final T value) { 
    this.value.set(value); 
    } 

} 

Вы бы затем использовать этот класс, как это:

final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList<String>::new); 

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

Теперь, однако, я вынужден использовать Java 7, и я не могу найти столь же элегантное решение, поскольку Java 7 не может использовать Поставщиков и поэтому требует написать много уродливого кода. Также дженерики не позволяют создавать их, если вы не дадите точный класс, что является проблемой, если вы используете общие значения классов (например, ArrayList<String>).

Насколько я могу судить, я либо вынужден писать уродливый код, либо делать магию отражения за пределами моих возможностей, или есть способ написать класс LazyInitializer элегантным способом в Java 7, который мне не хватает?

Edit: Использование ответа от Йорн Vernee я изменил класс для работы с Java 7 следующим образом:

public class LazyInitializer<T> { 

    protected final Class<?> clazz; 
    protected AtomicReference<T> value = new AtomicReference<>(); 

    public LazyInitializer(final Class<?> clazz) { 
    this.clazz = clazz; 
    } 

    public T get() { 
    T result = this.value.get(); 
    if (result == null) { 
     this.value.compareAndSet(null, constructNew()); 
     result = this.value.get(); 
    } 
    return result; 
    } 

    public void setValue(final T value) { 
    this.value.set(value); 
    } 

    protected T constructNew() { 
    try { 
     return (T) clazz.newInstance(); 
    } catch (InstantiationException | IllegalAccessException ex) { 
     throw new IllegalStateException(ex); 
    } 
    } 
} 

, который затем может быть (еще раз) элегантно называется так:

final LazyInitializer<List<String>> value = new LazyInitializer<>(ArrayList.class); 

Однако этот класс больше не может проверить, действительно ли поставляемый класс соответствует (поскольку Generics), и он работает только с конструкторами по умолчанию. Но по крайней мере это решает мое дело.

+0

Я ошибаюсь или вы можете пропустить первый 'this.value.get()' и if-предложение в вашем примере кода Java8? – Alexander

+0

В случае, когда 'значение == null', значение должно быть установлено на начальное значение. Так что нет, вы не можете пропустить это. – TwoThe

+1

@TwoThe Я ошибаюсь, или ваш код может фактически инициировать выполнение поставщика более одного раза? Поскольку оценка параметра для 'compareAndSet' не является атомарной с самой функцией. – chimmi

ответ

1

Одна из хороших вещей о лямбды/метод рефов, является то, что он сокращает количество кода на X. Таким образом, если вернуться назад, конечно , он снова увеличит количество кода на X.Если вам нужно обернуть любой код в функторе, анонимный класс - лучший способ сделать это imho.

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

Вы можете сделать динамический конструктор поиск, вам все равно нужен тип Supplier:

interface Supplier<T> { 
    T get(); 
} 

Тогда у вас есть фабричный метод, который делает поиск во время выполнения:

public static <T> Supplier<T> constructorLookup(Class<?> rawtype) { 
    try { 
     Constructor<?> cons = rawtype.getConstructor(); 

     return new Supplier<T>() { 
      @SuppressWarnings("unchecked") 
      @Override 
      public T get() { 
       try { 
        return (T) cons.newInstance(); 
       } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 
         | InvocationTargetException e) { 
        throw new IllegalStateException(e); 
       } 
      }    
     }; 

    } catch (NoSuchMethodException | SecurityException e) { 
     throw new IllegalArgumentException(e); 
    }  
} 

Результирующий код будет выглядеть так:

LazyInitializer<List<String>> value 
    = new LazyInitializer<>(constructorLookup(ArrayList.class)); 

В настоящее время это работает только для defaul t, но может быть расширен и для работы с аргументами.

+0

Хакки, тенистые, работает. Мне это нравится. ;) – TwoThe

2

Если вы можете использовать Guava, вы можете использовать свою последнюю Java-7-совместимой версию, которая содержит Supplier класса, который, в свою очередь, имеет ту же сигнатуру, Java 8-х Supplier. Только импорт отличается: com.google.common.base.Supplier вместо java.util.function.Supplier.

Если вы не хотите использовать Guava, вы могли бы еще написать свой собственный Supplier с теми же подписями, как Java 8-х Supplier:

public interface Supplier<T> { 
    T get(); 
} 

Затем, в зависимости от того Supplier вы используете, вы можете написать следующее:

Supplier<List<String>> stringListSupplier = new Supplier<List<String>>() { 
    @Override public List<String> get() { return new ArrayList<>(); } 
}; 

Если дженерики беспокоит, что много, вы можете написать следующее, а затем использовать его по своему усмотрению.

public static <T> Supplier<List<T>> newArrayListSupplier() { 
    return new Supplier<List<T>>() { 
    @Override public List<T> get() { return new ArrayList<>(); } 
    }; 
} 

Ваш окончательный код становится:

final LazyInitializer<List<String>> value = new LazyInitializer<>(MyClass.<String>newArrayListSupplier()); 
+0

Спасибо за подсказку, однако это не решает мою проблему, поскольку я особенно хочу уменьшить количество кода. У меня есть несколько мест, где значения ленивы заданы в объектах JSON-POJO, и все это с анонимными классами может свести к минимуму количество кода, который у меня есть. И поскольку все эти места имеют разные классы, я не могу использовать ваше решение для списков. – TwoThe

+0

Ну, извините, что сказал, что нет другого способа лениво создать новые объекты. Такой шаблон требуется для моделирования lambdas (что в конечном итоге является вашим запросом). –