Как извлечь из потока только один разрешенный элемент? - PullRequest
9 голосов
/ 25 мая 2020

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

Могу ли я сделать лучше или более элегантно, чем следующее?

Set<String> matches = fields.stream().map(f -> f.getField()).collect(Collectors.toSet());
if (matches.size() != 1) throw new IllegalArgumentException("could not match one exact element");
String distrinctVal = matches.iterator().next(); //continue to use the value

Возможно ли это напрямую с использованием потоковых методов, например, используя reduce?

Ответы [ 6 ]

7 голосов
/ 25 мая 2020

Ваше текущее решение хорошее. Вы также можете попробовать этот способ, чтобы избежать сбора.

Используйте distinct(), затем count()

if (fields.stream().map(f -> f.getField()).distinct().count() != 1) 
      throw new IllegalArgumentException("could not match one exact element");

Чтобы получить значение

String distrinctVal = fields.get(0).getField();
4 голосов
/ 25 мая 2020

Ну, вы, конечно, можете сделать это несколькими способами, но то, что более элегантно, может варьироваться от человека к человеку.

В любом случае, если бы вы попытались сделать это через потоки, я бы сделал это следующим образом:

С небольшой модификацией мой ответ здесь вы могли бы сделать:

boolean result = fields.stream()
                       .map(f -> f.getField())
                       .distinct()
                       .limit(2) // ENABLE SHORT CIRCUITING
                       .count() != 1;

if (result) throw new IllegalArgumentException("could not match one exact element");

String distinctVal = fields.get(0).getField();

Преимущество этого подхода заключается в том, что в основном используется limit(2) для включения оптимизации, где это возможно.

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

3 голосов
/ 25 мая 2020

Это будет решение для сокращения.

Optional<String> distinctVal = fields.stream()
    .map(f -> f.getField())
    .reduce((a, b) -> {
        if(a != b) throw new IllegalArgumentException("could not match one exact element");
        return a;
    });
1 голос
/ 25 мая 2020

В зависимости от частоты вызова и размера вашего набора итеративный код может быть значительно быстрее.

public boolean allEqual(Collection<Fields> fields) {
    if (fields.size() > 1) {
        String last;
        boolean first = true;
        for (Field field : fields) {
            String thisString = field.getField();
            if (first) {
                last = thisString;
            } else {
                if (!StringUtils.equals(last, thisString)) {
                    return false;
                }
            }
        }
    }
    return true;
}

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

0 голосов
/ 25 мая 2020

Как говорили другие, это во многом дело вкуса. Вот мой.

    String distinctVal = fields.iterator().next().getField();
    if (fields.stream().map(Field::getField).anyMatch(e -> ! e.equals(distinctVal)) {
        throw new IllegalArgumentException("could not match one exact element");
    }
    //continue to use the value

(Код не протестирован; простите за опечатки.)

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

0 голосов
/ 25 мая 2020

Аналогично это :

String distrinctVal = fields.stream()
    .map(f -> f.getField())
    .reduce((a, b) 
        -> { throw new IllegalArgumentException("could not match one exact element");}
    ).get();
...