Когда у вас есть поток объектов, вы можете довольно элегантно их фильтровать.
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
принимает предикат, функция которого может возвращать только логическое значение. Другой сигнатуры метода нет.
Если вы хотите отфильтровать всех зеленых животных, вы должны использовать
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 выглядит многообещающе. Но я искал более общее решение. Этот работает только тогда, когда фильтр равен. Я отредактирую вопрос, чтобы прояснить это.
Да, есть решение с использованием 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
(см. ниже):
Просто запишите условие в Stream#filter
, это правильное использование Stream:
animalStream
.filter(animal -> Colors.GREEN.equals(animal.getColor()))
...
В рамках этого вопроса использование mapMulti()
не дает никаких преимуществ. Как вы можете видеть в своем собственном ответе, старый подход более читаем, и я сомневаюсь, что он будет менее эффективным (может быть, даже более). OP не спрашивает об отображении и фильтрации одновременно.
@DidierL Я не согласен, ответ, кажется, отвечает на вопрос, и SO всегда является хорошим местом для изучения новых способов решения проблем. ОП пытался сообщить нам о новом альтернативном способе, который был представлен недавно с новой версией Java.
Мне очень жаль, но я уже знал этот метод, и я не об этом спрашивал.
@DidierL: авторов вопросов просят опубликовать минимальный и воспроизводимый код. Однако это может привести к пропуску контекста того, где и как используется фрагмент кода. Бывают ситуации, когда mapMulti
является хорошим решением, особенно для потоковой передачи, где вам нужно выполнить какую-то сложную логику. Вот почему я предоставил помимо этого варианта также описание плюсов и минусов, указывающее, когда следует использовать mapMulti
.
Я не говорю, что mapMulti
никогда не бывает полезным, я говорю, что это не входит в рамки этого вопроса. На самом деле, ваши профи здесь не имеют значения, поскольку ОП прямо сказал в вопросе, что они не хотят выполнять map
, им нужно только какое-то filter
. Вы просто не отвечаете на вопрос.
Вы можете использовать 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. Но, как и вы, я также сомневаюсь, что это возможно. Можно было бы просто опустить универсальный тип или использовать универсальный подстановочный знак <?>. Но я сильно сомневаюсь, что библиотека будет использовать это.
Подстановочные знаки не помогут. Вы не можете передать ничего, кроме нуля, в Function<?, ?>
.
Операция 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
для создания Predicate
s. Это сохранит все возможности, показанные в примере использования в начале.
(Кредиты за эту идею принадлежат @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 Я понимаю, но вы должны признать, что такие Predicate
могут помочь решить проблемы с выводом типов при цепочке предикатов без приведения. Например, это не скомпилируется filter(Animal::canFly.or(Animal::canSwim).or(...))
, и наоборот, это будет скомпилировано, поскольку реализация говорит, какой должен быть тип filter(MultiPred.of(...).or(Animal::canFly).or(Animal::canSwim))
.
А чем отличается, если MultiPred.of(...)
просто возвращается Predicate
?
@Holger И это также может помочь избежать многократного доступа к одному и тому же свойству, как в следующем сценарии: filter(a -> a.getColor() == Color.WHITE || a.getColor() == Color.GREEN)
. Потому что это позволяет указать несколько Predicate
, которые будут связаны вместе и использоваться для оценки значения, возвращаемого keyExtractor. Что является небольшим преимуществом. Тем не менее, я согласен, что это вещь излишне сложная и не пригодная для практического использования, но сама задача немного надуманная.
Вы упускаете суть. Я не возражал против ваших статических фабричных методов и их параметров, я просто сказал, что эти фабричные методы могут просто возвращать Predicate
. Тогда статические фабричные методы — это все, что вам нужно. Нет причин для MultiPred
реализовывать Predicate
или обертывать Predicate
. Если вы сохраните четыре статических метода и удалите устаревшие остальные, у вас будет половина кода.
Чтобы продемонстрировать, что это работает, если удалить все лишнее, ideone.com/6sNBP3
@Holger Извините за глупые комментарии. Я понял вашу точку зрения, я последовал вашему совету и реализовал эту функциональность как статическую. И я также заметил, что, в двух словах, это именно то, что предлагал @shmosel
, одобрил его ответ.
спасибо всем за всю эту информацию. Я посмотрю на это. Но с точки зрения пользователя это довольно много. Вот почему я принял более короткий ответ от @shmosel.
Что не так с
.filter(animal -> animal.getColor().equals(Colors.GREEN))
? Действительно ли речь идет о десяти символах, которые вы спасете с помощью гипотетического.filter(Colors.GREEN::equals, Animal::getColor)
? Тогда просто используйте.filter(a -> a.getColor().equals(Colors.GREEN))
, дело закрыто.