Используйте Java lambda вместо 'if else'
С Java 8 у меня есть этот код:
if(element.exist()){
// Do something
}
Я хочу преобразовать в лямбда-стиль,
element.ifExist(el -> {
// Do something
});
с помощью метода ifExist
:
public void ifExist(Consumer<Element> consumer) {
if (exist()) {
consumer.accept(this);
}
}
Но теперь у меня еще есть случаи, чтобы позвонить:
element.ifExist(el -> {
// Do something
}).ifNotExist(el -> {
// Do something
});
Я могу написать аналогичный ifNotExist
, и я хочу, чтобы они были взаимоисключающими (если exist
условие истинно, нет необходимости проверять ifNotExist
, потому что иногда метод exist() требует столько рабочей нагрузки для проверки), но у меня всегда есть проверить два раза. Как я могу избежать этого?
Возможно, слово "существует" заставляет кого-то неправильно понять мою идею. Вы можете себе представить, что мне также нужны некоторые методы:
ifVisible()
ifEmpty()
ifHasAttribute()
Многие говорили, что это плохая идея, но:
В Java 8 можно использовать лямбда Foreach вместо традиционной for
цикла. В программировании for
и if
есть два основных элемента управления потоком. Если мы можем использовать лямбда для for
цикла, почему используется лямбда для if
плохая идея?
for (Element element : list) {
element.doSomething();
}
list.forEach(Element::doSomething);
В Java 8 есть Optional
с ifPresent, аналогичная моей идее ifExist:
Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);
И о поддержке коды и читабельности, что вы думаете, если у меня есть следующий код со многими повторять простой, if
условие?
if (e0.exist()) {
e0.actionA();
} else {
e0.actionB();
}
if (e1.exist()) {
e0.actionC();
}
if (e2.exist()) {
e2.actionD();
}
if (e3.exist()) {
e3.actionB();
}
Сравнить с:
e0.ifExist(Element::actionA).ifNotExist(Element::actionB);
e1.ifExist(Element::actionC);
e2.ifExist(Element::actionD);
e3.ifExist(Element::actionB);
Что лучше? И, oops, вы заметили, что в традиционном коде кода if
есть ошибка в:
if (e1.exist()) {
e0.actionC(); // Actually e1
}
Я думаю, что если мы используем лямбда, мы можем избежать этой ошибки!
Ответы
Ответ 1
Поскольку это почти, но не совсем соответствует Дополнительно, возможно, вы можете пересмотреть логику:
Java 8 имеет ограниченную выразительность:
Optional<Elem> element = ...
element.ifPresent(el -> System.out.println("Present " + el);
System.out.println(element.orElse(DEFAULT_ELEM));
Здесь map
может ограничить представление элемента:
element.map(el -> el.mySpecialView()).ifPresent(System.out::println);
Java 9:
element.ifPresentOrElse(el -> System.out.println("Present " + el,
() -> System.out.println("Not present"));
В общем случае две ветки асимметричны.
Ответ 2
Он назвал "свободный интерфейс". Просто измените тип return this;
и return this;
чтобы вы могли объединить методы:
public MyClass ifExist(Consumer<Element> consumer) {
if (exist()) {
consumer.accept(this);
}
return this;
}
public MyClass ifNotExist(Consumer<Element> consumer) {
if (!exist()) {
consumer.accept(this);
}
return this;
}
Вы могли бы немного поучаствовать и вернуться к промежуточному типу:
interface Else<T>
{
public void otherwise(Consumer<T> consumer); // 'else' is a keyword
}
class DefaultElse<T> implements Else<T>
{
private final T item;
DefaultElse(final T item) { this.item = item; }
public void otherwise(Consumer<T> consumer)
{
consumer.accept(item);
}
}
class NoopElse<T> implements Else<T>
{
public void otherwise(Consumer<T> consumer) { }
}
public Else<MyClass> ifExist(Consumer<Element> consumer) {
if (exist()) {
consumer.accept(this);
return new NoopElse<>();
}
return new DefaultElse<>(this);
}
Использование образца:
element.ifExist(el -> {
//do something
})
.otherwise(el -> {
//do something else
});
Ответ 3
Вы можете использовать один метод, который принимает двух потребителей:
public void ifExistOrElse(Consumer<Element> ifExist, Consumer<Element> orElse) {
if (exist()) {
ifExist.accept(this);
} else {
orElse.accept(this);
}
}
Затем назовите его:
element.ifExistOrElse(
el -> {
// Do something
},
el -> {
// Do something else
});
Ответ 4
Эта проблема
(1) Кажется, вы смешиваете разные аспекты - контроль потока и логику домена.
element.ifExist(() -> { ... }).otherElementMethod();
^ ^
control flow method business logic method
(2) Неясно, как должны вести себя методы после метода потока управления (например, ifExist
, ifNotExist
). Должны ли они всегда выполняться или называться только при условии (аналогично ifExist
)?
(3) Имя ifExist
подразумевает операцию терминала, поэтому возвращать нечего - void
. Хорошим примером является void ifPresent(Consumer)
из Optional
.
Решение
Я бы написал полностью разделенный класс, который был бы независим от любого конкретного класса и любого конкретного условия.
Интерфейс прост и состоит из двух бесконтактных методов управления потоком - ifTrue
и ifFalse
.
Существует несколько способов создания объекта Condition
. Я написал статический заводский метод для вашего экземпляра (например, element
) и условия (например, Element::exist
).
public class Condition<E> {
private final Predicate<E> condition;
private final E operand;
private Boolean result;
private Condition(E operand, Predicate<E> condition) {
this.condition = condition;
this.operand = operand;
}
public static <E> Condition<E> of(E element, Predicate<E> condition) {
return new Condition<>(element, condition);
}
public Condition<E> ifTrue(Consumer<E> consumer) {
if (result == null)
result = condition.test(operand);
if (result)
consumer.accept(operand);
return this;
}
public Condition<E> ifFalse(Consumer<E> consumer) {
if (result == null)
result = condition.test(operand);
if (!result)
consumer.accept(operand);
return this;
}
public E getOperand() {
return operand;
}
}
Более того, мы можем интегрировать Condition
в Element
:
class Element {
...
public Condition<Element> formCondition(Predicate<Element> condition) {
return Condition.of(this, condition);
}
}
Модель, которую я продвигаю, такова:
- работа с
Element
; - получить
Condition
; - контролировать поток по
Condition
; - вернитесь к
Element
; - продолжайте работать с
Element
.
Результат
Получение Condition
по Condition.of
:
Element element = new Element();
Condition.of(element, Element::exist)
.ifTrue(e -> { ... })
.ifFalse(e -> { ... })
.getOperand()
.otherElementMethod();
Получение Condition
по Element#formCondition
:
Element element = new Element();
element.formCondition(Element::exist)
.ifTrue(e -> { ... })
.ifFalse(e -> { ... })
.getOperand()
.otherElementMethod();
Обновление 1:
Для других методов тестирования идея остается прежней.
Element element = new Element();
element.formCondition(Element::isVisible);
element.formCondition(Element::isEmpty);
element.formCondition(e -> e.hasAttribute(ATTRIBUTE));
Обновление 2:
Это хорошая причина переосмыслить дизайн кода. Ни один из 2 фрагментов не велик.
Представьте, что вам нужно actionC
внутри e0.exist()
. Как будет изменен метод ссылки Element::actionA
?
Он будет возвращен в лямбду:
e0.ifExist(e -> { e.actionA(); e.actionC(); });
если вы не actionA
и actionC
в один метод (что звучит ужасно):
e0.ifExist(Element::actionAAndC);
Лямбда теперь даже меньше, читабельная тогда, if
была.
e0.ifExist(e -> {
e0.actionA();
e0.actionC();
});
Но сколько усилий мы сделаем для этого? И сколько усилий мы будем поддерживать в этом?
if(e0.exist()) {
e0.actionA();
e0.actionC();
}
Ответ 5
Если вы выполняете простую проверку объекта, а затем выполняете некоторые операторы, основанные на условии, тогда одним из подходов было бы иметь карту с предикатом в качестве ключа и желаемое выражение в качестве значения, например.
Map<Predicate<Integer>,Supplier<String>> ruleMap = new HashMap<Predicate<Integer>,Supplier<String>>(){{
put((i)-> i<10,()->"Less than 10!");
put((i)-> i<100,()->"Less than 100!");
put((i)-> i<1000,()->"Less than 1000!");
}};
Позднее мы могли бы передать следующую карту, чтобы получить значение, когда предикат возвращает true, что может заменить весь код if/else
ruleMap.keySet()
.stream()
.filter((keyCondition)->keyCondition.test(numItems,version))
.findFirst()
.ifPresent((e)-> System.out.print(ruleMap.get(e).get()));
Поскольку мы используем findFirst(), это эквивалентно if/else if/else if......
Ответ 6
Как некоторые предложения, у меня есть идея кэшировать логическое значение:
public class Element {
private Boolean exist;
public void ifExist(Consumer<Element> consumer) {
if (exist == null) {
exist = exist();
}
if (exist) {
consumer.accept(this);
}
}
public void ifNotExist(Consumer<Element> consumer) {
if (exist == null) {
exist = exist();
}
if (!exist) {
consumer.accept(this);
}
}
}