Basically I look for a way to avoid working with
entry -> entry.getValue
and
entry -> entry.getKey
similar to what Map.forEach()
does.
If only I could get a way to work as map.stream().filter((k,v) -> )
... and so on
It seems the interface is called BiConsumer. Perhaps with a converter to BiConsumer or a Stream.generate() someway
Stream is an interface and T is the type of stream elements. mapper is a stateless function which is applied to each element and the function returns the new stream. Example 1 : Stream map() function with operation of number * 3 on each element of stream. // Java code for Stream map(Function mapper)
Streams cannot directly be created from maps because a map is not a collection.
The map() function is a method in the Stream class that represents a functional programming concept. In simple words, the map() is used to transform one object into other by applying a function. That's why the Stream.
Since this is a repeating question, I will throw a full solution into the ring. It’s a PairStream
type that is by default a simple wrapper around an ordinary Stream
(though, being an interface
, alternatives are possible).
It focuses on providing the convenient intermediate operations and those terminal operations which can not be easily performed by calling one of the methods keys()
, values()
or entries()
to return to a conventional single-element Stream
and chain a terminal operation. So for example, PairStream.from(map).filterValue(predicate).keys().findAny()
is the straight-forward way to get a key for which the mapped value matches the predicate. filterValue
is a convenience intermediate operation and keys
turn back to an ordinary Stream
allowing an arbitrary terminal operation for the keys.
Some examples
Map<String,Integer> m=new HashMap<>();
m.put("foo", 5);
m.put("bar", 7);
m.put("baz", 42);
// {b=49, f=5}
Map<Character,Integer> m2=PairStream.from(m)
.mapKey(s->s.charAt(0))
.toMap(Integer::sum);
// foo bar
String str=PairStream.from(m)
.filterValue(i->i<30)
.keys().sorted(Comparator.reverseOrder())
.collect(Collectors.joining(" "));
Map<String,Integer> map=new HashMap<>();
map.put("muhv~", 26);
map.put("kfool", 3);
String str = PairStream.from(map)
.sortedByValue(Comparator.naturalOrder())
.flatMapToInt((s,i)->s.codePoints().map(c->c^i))
.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
.toString();
Here is the complete class (I haven’t tested all operations, however most of it is straight-forward):
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public interface PairStream<K,V> {
static <K,V> PairStream<K,V> from(Map<K,V> map) {
return from(map.entrySet().stream());
}
static <K,V> PairStream<K,V> from(Stream<Map.Entry<K,V>> s) {
return ()->s;
}
static <K,V> PairStream<K,V> from(Stream<K> s, Function<? super K, ? extends V> f) {
return ()->s.map(k->new AbstractMap.SimpleImmutableEntry<>(k, f.apply(k)));
}
default PairStream<K,V> distinct() {
return from(entries().distinct());
}
default PairStream<K,V> peek(BiConsumer<? super K, ? super V> action) {
return from(entries().peek(e->action.accept(e.getKey(), e.getValue())));
}
default PairStream<K,V> skip(long n) {
return from(entries().skip(n));
}
default PairStream<K,V> limit(long maxSize) {
return from(entries().limit(maxSize));
}
default PairStream<K,V> filterKey(Predicate<? super K> mapper) {
return from(entries().filter(e->mapper.test(e.getKey())));
}
default PairStream<K,V> filterValue(Predicate<? super V> mapper) {
return from(entries().filter(e->mapper.test(e.getValue())));
}
default PairStream<K,V> filter(BiPredicate<? super K, ? super V> mapper) {
return from(entries().filter(e->mapper.test(e.getKey(), e.getValue())));
}
default <R> PairStream<R,V> mapKey(Function<? super K,? extends R> mapper) {
return from(entries().map(e->new AbstractMap.SimpleImmutableEntry<>(
mapper.apply(e.getKey()), e.getValue()
)));
}
default <R> PairStream<K,R> mapValue(Function<? super V,? extends R> mapper) {
return from(entries().map(e->new AbstractMap.SimpleImmutableEntry<>(
e.getKey(), mapper.apply(e.getValue())
)));
}
default <R> Stream<R> map(BiFunction<? super K, ? super V,? extends R> mapper) {
return entries().map(e->mapper.apply(e.getKey(), e.getValue()));
}
default DoubleStream mapToDouble(ToDoubleBiFunction<? super K, ? super V> mapper) {
return entries().mapToDouble(e->mapper.applyAsDouble(e.getKey(), e.getValue()));
}
default IntStream mapToInt(ToIntBiFunction<? super K, ? super V> mapper) {
return entries().mapToInt(e->mapper.applyAsInt(e.getKey(), e.getValue()));
}
default LongStream mapToLong(ToLongBiFunction<? super K, ? super V> mapper) {
return entries().mapToLong(e->mapper.applyAsLong(e.getKey(), e.getValue()));
}
default <RK,RV> PairStream<RK,RV> flatMap(
BiFunction<? super K, ? super V,? extends PairStream<RK,RV>> mapper) {
return from(entries().flatMap(
e->mapper.apply(e.getKey(), e.getValue()).entries()));
}
default <R> Stream<R> flatMapToObj(
BiFunction<? super K, ? super V,? extends Stream<R>> mapper) {
return entries().flatMap(e->mapper.apply(e.getKey(), e.getValue()));
}
default DoubleStream flatMapToDouble(
BiFunction<? super K, ? super V,? extends DoubleStream> mapper) {
return entries().flatMapToDouble(e->mapper.apply(e.getKey(), e.getValue()));
}
default IntStream flatMapToInt(
BiFunction<? super K, ? super V,? extends IntStream> mapper) {
return entries().flatMapToInt(e->mapper.apply(e.getKey(), e.getValue()));
}
default LongStream flatMapToLong(
BiFunction<? super K, ? super V,? extends LongStream> mapper) {
return entries().flatMapToLong(e->mapper.apply(e.getKey(), e.getValue()));
}
default PairStream<K,V> sortedByKey(Comparator<? super K> comparator) {
return from(entries().sorted(Map.Entry.comparingByKey(comparator)));
}
default PairStream<K,V> sortedByValue(Comparator<? super V> comparator) {
return from(entries().sorted(Map.Entry.comparingByValue(comparator)));
}
default boolean allMatch(BiPredicate<? super K,? super V> predicate) {
return entries().allMatch(e->predicate.test(e.getKey(), e.getValue()));
}
default boolean anyMatch(BiPredicate<? super K,? super V> predicate) {
return entries().anyMatch(e->predicate.test(e.getKey(), e.getValue()));
}
default boolean noneMatch(BiPredicate<? super K,? super V> predicate) {
return entries().noneMatch(e->predicate.test(e.getKey(), e.getValue()));
}
default long count() {
return entries().count();
}
Stream<Map.Entry<K,V>> entries();
default Stream<K> keys() {
return entries().map(Map.Entry::getKey);
}
default Stream<V> values() {
return entries().map(Map.Entry::getValue);
}
default Optional<Map.Entry<K,V>> maxByKey(Comparator<? super K> comparator) {
return entries().max(Map.Entry.comparingByKey(comparator));
}
default Optional<Map.Entry<K,V>> maxByValue(Comparator<? super V> comparator) {
return entries().max(Map.Entry.comparingByValue(comparator));
}
default Optional<Map.Entry<K,V>> minByKey(Comparator<? super K> comparator) {
return entries().min(Map.Entry.comparingByKey(comparator));
}
default Optional<Map.Entry<K,V>> minByValue(Comparator<? super V> comparator) {
return entries().min(Map.Entry.comparingByValue(comparator));
}
default void forEach(BiConsumer<? super K, ? super V> action) {
entries().forEach(e->action.accept(e.getKey(), e.getValue()));
}
default void forEachOrdered(BiConsumer<? super K, ? super V> action) {
entries().forEachOrdered(e->action.accept(e.getKey(), e.getValue()));
}
default Map<K,V> toMap() {
return entries().collect(
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
default Map<K,V> toMap(BinaryOperator<V> valAccum) {
return entries().collect(
Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, valAccum));
}
}
It's a bit unfortunate that the main way to process a map using streams is to stream the map's entries. That means you have to extracting the entries' keys and values. This is somewhat verbose, but it isn't really all that bad. Still, it might be reasonable to consider some kind of helper methods that will adapt a BiPredicate
or BiFunction
so that they can be used in the filter
or map
stages of a stream of map entries. They look like this:
static <K,V> Predicate<Map.Entry<K,V>> p(BiPredicate<? super K, ? super V> bip) {
return entry -> bip.test(entry.getKey(), entry.getValue());
}
static <K,V,R> Function<Map.Entry<K,V>,R> m(BiFunction<? super K, ? super V, R> bif) {
return entry -> bif.apply(entry.getKey(), entry.getValue());
}
Once you have these, you can use them to simplify(?) a map entry stream. Let's say you have a Map<String,Integer>
and you want to select the entries where the string key's length is greater than the integer value, and then format the key and value into a string. Conventionally you'd do this:
map.entrySet().stream()
.filter(e -> e.getKey().length() > e.getValue())
.map(e -> e.getKey() + ":" + e.getValue())
.forEach(System.out::println);
With the helper functions above, you could rewrite this pipeline as:
map.entrySet().stream()
.filter(p((k, v) -> k.length() > v))
.map(m((k, v) -> k + ":" + v))
.forEach(System.out::println);
OK, you save a few characters. Is it worth it?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With