Карта потока на фильтре

Когда у вас есть поток объектов, вы можете довольно элегантно их фильтровать.

swimmingAnimalStream = animalStream
    .filter(Animal::canSwim);

Если у вас есть несколько более сложные фильтры, вместо использования ссылок на методы вы должны использовать Lambdas.

greenAnimals = animalStream
    .filter(animal -> animal.getColor().equals(Colors.GREEN));

Есть ли способ сопоставить значение перед его фильтрацией, но при этом иметь полный объект после фильтра? Итак, парение - это не то, что я хочу:

animalStream
    .map(Animal::getColor)
    .filter(Colors.GREEN::equals)

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

animalStream
    .filter(Colors.GREEN::equals, Animal::getColor);

Сигнатура этого метода фильтра будет выглядеть следующим образом.

<MAPPED> Stream<T> filter(Predicate<MAPPED> filter, Function<? super T, MAPPED> mappingFunction);

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

Решение также должно иметь возможность использовать любой предикат, который можно себе представить. Equals — это просто пример. Другим примером может быть проверка наличия поля из объекта.

animalWithMotherStream = animalStream
    .filter(Optional::isPresent, Animal::getMother);

Есть ли у кого-нибудь более чистое решение или библиотека, которая уже это делает?

Что не так с .filter(animal -> animal.getColor().equals(Colors.GREEN))? Действительно ли речь идет о десяти символах, которые вы спасете с помощью гипотетического .filter(Colors.GREEN::equals, Animal::getColor)? Тогда просто используйте .filter(a -> a.getColor().equals(Colors.GREEN)), дело закрыто.

Holger 18.11.2022 13:46
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
5
2
98
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

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

Если вы хотите отфильтровать всех зеленых животных, вы должны использовать

animalStream
    .filter(a -> Colors.GREEN.equals(a.getColor()))

или

Predicate<Animal> isGreen = (a) -> Colors.GREEN.equals(a.getColor());
Stream<Animal> greenAnimals = animalStream.filter(isGreen);

Не используйте map, если вы не хотите Stream<COLOR>

присоединиться к нескольким функциям сопоставления

Вы можете связать их, а не соединять - .stream().map().map(), но, как вы обнаружили, это не сохраняет исходный тип.

StreamEx , библиотека, предоставляющая расширенные потоковые методы и классы, имеет filterBy:

public <K> StreamEx<T> filterBy​(Function<? super T,​? extends K> mapper, K value)

Возвращает поток, состоящий из элементов этого потока, для которых предоставленная функция сопоставления возвращает заданное значение.

Этот метод ведет себя как filter(t -> Objects.equals(value, mapper.apply(t))).

Эй, Джон StreamEx выглядит многообещающе. Но я искал более общее решение. Этот работает только тогда, когда фильтр равен. Я отредактирую вопрос, чтобы прояснить это.

Dario Viva 18.11.2022 00:56

Ява 16

Да, есть решение с использованием Stream#mapMulti начиная с Java 16.

animalStream
    .mapMulti((animal, consumer) -> {
        if (Colors.GREEN.equals(animal.getColor())) {  // condition
            consumer.accept(animal);                   // qualify the instance
        }
    })
    ...                                                // further operations

Предоставленный Consumer<R> принимает экземпляр, который соответствует вашим критериям.

Плюсы: Преимущество этого императивного подхода с точки зрения производительности заключается в том, что вы не обязательно вызываете две операции Stream, а можете заменить только одну, например, комбинацию Stream#map и Stream#filter. Хотя самым большим преимуществом является то, что Consumer#accept можно вызывать столько раз, сколько вы хотите, поэтому вы можете эффективно увеличить количество записей в Stream.

Минусы: Тем не менее, вы потеряли часть декларативного подхода, и если использовать его только как во фрагменте без дальнейшей обработки, стоит использовать довольно простой цикл for или придерживаться операции filter (см. ниже):

Старые версии Java

Просто запишите условие в Stream#filter, это правильное использование Stream:

animalStream
     .filter(animal -> Colors.GREEN.equals(animal.getColor()))
     ...

В рамках этого вопроса использование mapMulti() не дает никаких преимуществ. Как вы можете видеть в своем собственном ответе, старый подход более читаем, и я сомневаюсь, что он будет менее эффективным (может быть, даже более). OP не спрашивает об отображении и фильтрации одновременно.

Didier L 18.11.2022 18:18

@DidierL Я не согласен, ответ, кажется, отвечает на вопрос, и SO всегда является хорошим местом для изучения новых способов решения проблем. ОП пытался сообщить нам о новом альтернативном способе, который был представлен недавно с новой версией Java.

Panagiotis Bougioukos 18.11.2022 20:20

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

Dario Viva 18.11.2022 22:18

@DidierL: авторов вопросов просят опубликовать минимальный и воспроизводимый код. Однако это может привести к пропуску контекста того, где и как используется фрагмент кода. Бывают ситуации, когда mapMulti является хорошим решением, особенно для потоковой передачи, где вам нужно выполнить какую-то сложную логику. Вот почему я предоставил помимо этого варианта также описание плюсов и минусов, указывающее, когда следует использовать mapMulti.

Nikolas Charalambidis 20.11.2022 09:32

Я не говорю, что mapMulti никогда не бывает полезным, я говорю, что это не входит в рамки этого вопроса. На самом деле, ваши профи здесь не имеют значения, поскольку ОП прямо сказал в вопросе, что они не хотят выполнять map, им нужно только какое-то filter. Вы просто не отвечаете на вопрос.

Didier L 20.11.2022 16:06
Ответ принят как подходящий

Вы можете использовать Predicates.compose() от Guava или создать свой собственный:

public static <A, B> Predicate<A> compose(
        Predicate<B> predicate, Function<A, ? extends B> function) {
    return a -> predicate.test(function.apply(a));
}

Теперь просто передайте это в свой фильтр:

animalStream.filter(compose(Colors.GREEN::equals, Animal::getColor))

Что касается концепции varargs, я сомневаюсь, что это возможно в дженериках Java, если только они не все одного типа, и в этом случае вы просто apply() каждый в цикле или уменьшаете их с помощью Function.andThen().

коротко и просто, почти то, что я искал, за исключением отсутствующей части varargs. Но, как и вы, я также сомневаюсь, что это возможно. Можно было бы просто опустить универсальный тип или использовать универсальный подстановочный знак <?>. Но я сильно сомневаюсь, что библиотека будет использовать это.

Dario Viva 18.11.2022 22:23

Подстановочные знаки не помогут. Вы не можете передать ничего, кроме нуля, в Function<?, ?>.

shmosel 18.11.2022 22:24

Операция Stream.filter() ожидает Predicate, которая является функцией, производящей boolean результат. Это определение filter очень интуитивно понятно и самодостаточно, и других разновидностей фильтра нет (и я сомневаюсь, что они появятся в будущем).

Однако вы можете создать свою реализацию Predicate и придать ей все необходимое поведение. И как предикат, его можно было бы использовать в filter.

Прежде чем представить реализацию, я покажу некоторые возможности, которыми можно наделить такой кастом Predicate.

Stream<Animal> greenAnimals = animalStream
    .filter(
        MultiPred.ofOr(Animal::getColor, Color.WHITE::equals, Color.GREEN::equals)
            .or(
                MultiPred.of(Animal::getType, AnimalType.CARNIVORE::equals)
                .and(Animal::canFly) // we can chain custom Predicates with regular ones
            )
            .or(
                MultiPred.of(Animal::getType, AnimalType.HERBIVORE::equals)
                .and(MultiPred.of(Animal::getColor, Color.PURPLE::equals)
                    .or(Animal::canSwim)
                )
            )
    );

Вот фиктивный класс Animal и перечисления, использованные в приведенном выше примере:

public class Animal {
    private AnimalType type;
    private boolean canSwim;
    private boolean canFly;
    private Color color;
    
    // getters
}

public enum AnimalType {
    CARNIVORE, HERBIVORE
}

public enum Color {
    GREEN, WHITE, PURPLE
}

Реализация

Вы можете предоставить пользовательский Predicate с любыми необходимыми вам возможностями.

Следующий предикат expects предоставляет методы, ожидающие функцию keyExtractor и предикат или группу предикатов, которые должны быть связаны либо логическим ИЛИ ||, либо логическим И &&.

public class MultiPred<T, K> implements Predicate<T> {

    private final BiFunction<Function<T, K>, Predicate<K>, Predicate<T>>
        predicateProducer = (f, p) -> t -> p.test(f.apply(t));
    
    private final Predicate<T> p;
    
    private MultiPred(Function<T, K> keyExtractor,
                      Predicate<K> predicate) {
        
        this.p = predicateProducer.apply(keyExtractor, predicate);
    }
    
    @SafeVarargs
    public static <T, K> MultiPred<T, K> ofAnd(Function<T, K> keyExtractor,
                                               Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> true, Predicate::and, predicates);
    }
    
    @SafeVarargs
    public static <T, K> MultiPred<T, K> ofOr(Function<T, K> keyExtractor,
                                              Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> false, Predicate::or, predicates);
    }
    
    @SafeVarargs
    public static <T, K> MultiPred<T, K> of(Function<T, K> keyExtractor,
                                            Predicate<K> identity,
                                            BinaryOperator<Predicate<K>> op,
                                            Predicate<K>... predicates) {
        
        Objects.requireNonNull(predicates);
        
        Predicate<K> predicate = Arrays.stream(predicates).reduce(identity, op);
        
        return new MultiPred<>(keyExtractor, predicate);
    }
    
    public static <T, K> MultiPred<T, K> of(Function<T, K> keyExtractor,
                                            Predicate<K> predicate) {
        
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(predicate);
    
        return new MultiPred<>(keyExtractor, predicate);
    }
    
    @Override
    public boolean test(T t) {
        Objects.requireNonNull(t);
        return p.test(t);
    }
    
    @Override
    public Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return p.and(other);
    }
    
    @Override
    public Predicate<T> negate() {
        return p.negate();
    }
    
    @Override
    public Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return p.or(other);
    }
}

Вспомогательный класс для создания предикатов

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

(Кредиты за эту идею принадлежат @Holger и @shmosel, поскольку он ранее опубликовал статический метод, создающий предикат, поэтому код, показанный ниже, следует рассматривать как построенный на ответе @shmosel)

public static class MultiPred {

    private MultiPred() {}

    @SafeVarargs
    public static <T, K> Predicate<T> ofAnd(Function<T, K> keyExtractor,
                                            Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> true, Predicate::and, predicates);
    }
    
    @SafeVarargs
    public static <T, K> Predicate<T> ofOr(Function<T, K> keyExtractor,
                                           Predicate<K>... predicates) {
        
        return of(keyExtractor, k -> false, Predicate::or, predicates);
    }
    
    @SafeVarargs
    public static <T, K> Predicate<T> of(Function<T, K> keyExtractor,
                                         Predicate<K> identity,
                                         BinaryOperator<Predicate<K>> op,
                                         Predicate<K>... predicates) {
        
        Objects.requireNonNull(predicates);
        
        Predicate<K> predicate = Arrays.stream(predicates).reduce(identity, op);
        
        return getPredicateProducer(keyExtractor, predicate);
    }
    
    public static <T, K> Predicate<T> of(Function<T, K> keyExtractor,
                                         Predicate<K> predicate) {
        
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(predicate);
        
        return getPredicateProducer(keyExtractor, predicate);
    }
    
    private static <T, K> Predicate<T> getPredicateProducer(Function<T, K> keyExtractor,
                                                            Predicate<K> predicate) {
        return t -> predicate.test(keyExtractor.apply(t));
    }
}

Это ненужное усложнение. Зачем откладывать построение настоящего Predicate от метода of до конструктора MultiPred и далее до совершенно ненужного BiFunction … predicateProducer ? Нет даже смысла использовать MultiPred в качестве возвращаемого типа, так как он не предлагает ничего, чего еще нет у Predicate, только ненужную оболочку. Измените возвращаемые типы фабричных методов на Predicate<T> и измените операторы return методов of на return t -> predicate.test(keyExtractor.apply(t));, и вы сможете удалить половину этого кода.

Holger 18.11.2022 14:24

@Holger Я понимаю, но вы должны признать, что такие Predicate могут помочь решить проблемы с выводом типов при цепочке предикатов без приведения. Например, это не скомпилируется filter(Animal::canFly.or(Animal::canSwim).or(...)), и наоборот, это будет скомпилировано, поскольку реализация говорит, какой должен быть тип filter(MultiPred.of(...).or(Animal::canFly).or(Animal::canSw‌​im)).

Alexander Ivanchenko 18.11.2022 15:35

А чем отличается, если MultiPred.of(...) просто возвращается Predicate?

Holger 18.11.2022 15:36

@Holger И это также может помочь избежать многократного доступа к одному и тому же свойству, как в следующем сценарии: filter(a -> a.getColor() == Color.WHITE || a.getColor() == Color.GREEN). Потому что это позволяет указать несколько Predicate, которые будут связаны вместе и использоваться для оценки значения, возвращаемого keyExtractor. Что является небольшим преимуществом. Тем не менее, я согласен, что это вещь излишне сложная и не пригодная для практического использования, но сама задача немного надуманная.

Alexander Ivanchenko 18.11.2022 15:36

Вы упускаете суть. Я не возражал против ваших статических фабричных методов и их параметров, я просто сказал, что эти фабричные методы могут просто возвращать Predicate. Тогда статические фабричные методы — это все, что вам нужно. Нет причин для MultiPred реализовывать Predicate или обертывать Predicate. Если вы сохраните четыре статических метода и удалите устаревшие остальные, у вас будет половина кода.

Holger 18.11.2022 15:39

Чтобы продемонстрировать, что это работает, если удалить все лишнее, ideone.com/6sNBP3

Holger 18.11.2022 16:14

@Holger Извините за глупые комментарии. Я понял вашу точку зрения, я последовал вашему совету и реализовал эту функциональность как статическую. И я также заметил, что, в двух словах, это именно то, что предлагал @shmosel, одобрил его ответ.

Alexander Ivanchenko 18.11.2022 16:23

спасибо всем за всю эту информацию. Я посмотрю на это. Но с точки зрения пользователя это довольно много. Вот почему я принял более короткий ответ от @shmosel.

Dario Viva 18.11.2022 22:25

Другие вопросы по теме