Mapping over a list in Java - java

In Java 8 I can map over streams with the map method, e.g.
Stream.of("Hello", "world").map(s -> s.length())
gives me a stream containing the integers [5, 5]. I am trying to do the same with lists. I have come up with
List<String> list = ...
list.stream().map(s -> s.length()).collect(Collectors.toList())
This works but is rather verbose. Is there a more concise solution? Ideally, there would be a similar map method for lists, but I haven't found any. So, are there any alternatives?

As compact as possible
Just wrap it into your own utility function:
public <T, S> List<S> mapBy(List<T> items, Function<T, S> mapFn) {
return items.stream().map(mapFn).collect(Collectors.toList());
}
Now you can just use mapBy(students, Student::getName). It doesn't get less verbose than that.
Note that this is only useful if that's the only data mutation you want to make. Once you have more stream operators you want to apply it'd be better to do just that as otherwise you keep creating intermediate lists, which is quite wasteful.

Think practically to do operation on each element in list you need to either stream it or loop it, so stream is more concise than loop. for more info you can replace lambda expression with method reference operator
list.stream().map(String::length).collect(Collectors.toList());

Related

How to apply flatMap() to implement the following logic with Streams

I have the following for loop:
List<Player> players = new ArrayList<>();
for (Team team : teams) {
ArrayList<TeamPlayer> teamPlayers = team.getTeamPlayers();
for (teamPlayer player : teamPlayers) {
players.add(new Player(player.getName, player.getPosition());
}
}
and I'm trying to convert it to a Stream:
List<Player> players = teams.forEach(t -> t.getTeamPlayers()
.forEach(p -> players.add(new Player(p.getName(), p.getPosition())))
);
But I'm getting a compilation error:
variable 'players' might not have been initialized
Why is this happening? Maybe there's an alternative way to create the stream, I was thinking of using flatMap but not sure how to apply it.
First of all, you need to understand that Streams don't act like Loops.
Hence, don't try to mimic a loop. Examine the tools offered by the API. Operation forEach() is there for special cases when you need to perform side-effects, not in order to accumulate elements from the stream into a Collection.
Note: with teams.forEach() you're not actually using a stream, but method Iterable.forEach() which is available with every implementation of Iterable.
To perform reduction on streams, we have several specialized operations like collect, reduce, etc. (for more information refer to the API documentation - Reduction).
collect() operation is meant to perform mutable reduction. You can use to collect the data into a list by providing built-in Collector Collectors.toList() as an argument. And since Java 16 operation toList() was introduced into API, which is implemented on top of the toArray() operation and performs better than namesake collector (therefore it's a preferred option if your JDK version allows you to use it).
I was thinking of using flatMap but not sure how to apply it.
Operation flatMap() is meant to perform one-to-many transformations. It expects a Function which takes a stream element and generates a Stream of the resulting type, elements of the generated stream become a replacement for the initial element.
Note: that general approach to writing streams to use as fewer operations as possible (because one of the main advantages that Functional programming brings to Java is simplicity). For that reason, applying flatMap() when a stream element produces a Collection in a single step is idiomatic, since it's sorter than performing map().flatMap() in two steps.
That's how implementation might look like:
List<Team> teams = List.of();
List<Player> players = teams.stream() // Stream<Team>
.flatMap(team -> team.getTeamPlayers().stream()) // Stream<Player>
.map(player -> new Player(player.getName(), player.getPosition()))
.toList(); // for Java 16+ or collect(Collectors.toList())
This is basically the answer of Alexander Ivanchenko, but with method reference.
final var players = teams.stream()
.map(Team::getTeamPlayers)
.flatMap(Collection::stream)
.map(p -> new Player(p.getName(), p.getPosition()))
.toList();
If your Player class has a factory method like (depending on the relation between Player and TeamPlayer:
public static Player fromTeamPlayer(final TeamPlayer teamPlayer) {
return new Player(teamPlayer.getName(), teamPlayer.getPosition());
}
You could further reduce it to:
final var players = teams.stream()
.map(Team::getTeamPlayers)
.flatMap(Collection::stream)
.map(Player::fromTeamPlayer)
.toList();

Java-Stream - Combine results of two methods producing a Value and a List invoked conditionaly inside the same stream operation in Java 8

I have two methods: funca() and funcb() which return a value of type X or a List<X> respectively like shown below:
X funca(Event e) { ... }
List<X> funcb(Event e) { ... }
I want to use them in the Stream and collect the result into a list.
These method methods should be called under different conditions, like shown below in pseudocode:
List<Event> input = // initializing the input
List<X> resultList = input.values().stream()
.(event -> event.status=="active" ? funca(event) : funcb(event))
.collect(Collectors.toList());
Can someone please tell me how I can achieve this so that whether the function returns a list of values or values?
Since one of your functions produces a Collection as a result, you need a stream operation that allows performing one-to-many transformation. For now, Stream IPA offers two possibilities: flatMap() and mapMulti().
To approach the problem, you need to take a closer look at them and think in terms of these operations.
flatMap()
This operation requires a function producing a Stream, and elements of this new stream become a replacement for the initial element.
Therefore, you need to wrap the result returned by the funca() with Singleton-Stream using Stream.of() (there's no need for wrapping the element with a List, like shown in another answer flatMap() is not capable to consume Collections).
List<X> = input.values().stream()
.flatMap(event -> "active".equals(event.getStatus()) ?
Stream.of(funca(event)) : funcb(event).stream()
)
.toList(); // for Java 16+ or collect(Collectors.toList())
mapMulti()
This operation was introduced with Java 16 and is similar to flatMap() but acts differently.
Contrary to flatMap it doesn't consume a new Stream. As an argument it expects a BiConsumer. Which in turn takes a stream element and a Consumer of the resulting type. Every element offered to the Consumer becomes a part of the resulting stream.
mapMulti() might be handy if funcb() produces a list which is very moderate in size (refer to documentation linked above for more details), otherwise flatMap() would be the right tool.
List<X> = input.values().stream()
.<X>mapMulti((event, consumer) -> {
if ("active".equals(event.getStatus())) consumer.accept(funca(event));
else funcb(event).forEach(consumer);
})
.toList(); // for Java 16+ or collect(Collectors.toList())
Sidenote: don't use == to compare reference types (like String) unless you need to make sure that both references are pointing to the same object, use equals() method instead.
Embed the result of funcA into a list and flatMap the lists:
List<X> result = input.stream()
.flatMap(e -> e.status.equals("active") ? List.of(funcA(e)) : funcB(e))
.collect(Collectors.toList());

How to update each element in a List in Java 8 using Stream API

I have a List defined as follows:
List<Integer> list1 = new ArrayList<>();
list1.add(1);
list1.add(2);
How can I increment each element of the List by one (i.e. end up with a List [2,3]) using Java 8's Stream API without creating new List?
When you create a Stream from the List, you are not allowed to modify the source List from the Stream as specified in the “Non-interference” section of the package documentation. Not obeying this constraint can result in a ConcurrentModificationException or, even worse, a corrupted data structure without getting an exception.
The only solution to directly manipulate the list using a Java Stream, is to create a Stream not iterating over the list itself, i.e. a stream iterating over the indices like
IntStream.range(0, list1.size()).forEach(ix -> list1.set(ix, list1.get(ix)+1));
like in Eran’s answer
But it’s not necessary to use a Stream here. The goal can be achieved as simple as
list1.replaceAll(i -> i + 1);
This is a new List method introduced in Java 8, also allowing to smoothly use a lambda expression. Besides that, there are also the probably well-known Iterable.forEach, the nice Collection.removeIf, and the in-place List.sort method, to name other new Collection operations not involving the Stream API. Also, the Map interface got several new methods worth knowing.
See also “New and Enhanced APIs That Take Advantage of Lambda Expressions and Streams in Java SE 8” from the official documentation.
Holger's answer is just about perfect. However, if you're concerned with integer overflow, then you can use another utility method that was released in Java 8: Math#incrementExact. This will throw an ArithmeticException if the result overflows an int. A method reference can be used for this as well, as seen below:
list1.replaceAll(Math::incrementExact);
You can iterate over the indices via an IntStream combined with forEach:
IntStream.range(0,list1.size()).forEach(i->list1.set(i,list1.get(i)+1));
However, this is not much different than a normal for loop, and probably less readable.
reassign the result to list1:
list1 = list1.stream().map(i -> i+1).collect(Collectors.toList());
public static Function<Map<String, LinkedList<Long>>, Map<String, LinkedList<Long>>> applyDiscount = (
objectOfMAp) -> {
objectOfMAp.values().forEach(listfLong -> {
LongStream.range(0, ((LinkedList<Long>) listfLong).size()).forEach(index -> {
Integer position = (int) index;
Double l = listfLong.get(position) - (10.0 / 100 * listfLong.get(position));
listfLong.set(position, l.longValue());
});
});
return objectOfMAp;
};

How can I sort an IntStream in ascending order?

I've converted a 2D int array into a Stream:
IntStream dataStream = Arrays.stream(data).flatMapToInt(x -> Arrays.stream(x));
Now, I want to sort the list into ascending order. I've tried this:
dataStream.sorted().collect(Collectors.toList());
but I get the compile time error
I'm confused about this, because on the examples I've seen, similar things are done without errors.
Try with
dataStream.sorted().boxed().collect(Collectors.toList());
because collect(Collectors.toList()) does not apply to a IntStream.
I also think that should be slightly better for performance call first sorted() and then boxed().
IntStream.collect() method has the following signature:
<R> R collect(Supplier<R> supplier,
ObjIntConsumer<R> accumulator,
BiConsumer<R, R> combiner);
If you really want use this you could:
.collect(IntArrayList::new, MutableIntList::add, MutableIntList::addAll);
As suggested here:
How do I convert a Java 8 IntStream to a List?
The problem is you're trying to convert an int stream to a list, but Collectors.toList only works on streams of objects, not streams of primitives.
You'll need to box the array before collecting it into the list:
dataStream.sorted().boxed().collect(Collectors.toList());

What are the use cases for IntStream?

I've written some code that creates a List<Integer> from a Stream<List<Integer>> like so:
List<Integer> abc = stream.flatMap(e -> e.stream()).
collect(Collectors.toList());
But I'm interested how to use IntStream. I thought I could do
List<Integer> abc = stream.flatMapToInt(e -> e.stream()).
collect(Collectors.toList());
using flatMapToInt to give me an IntStream that the collector would collect, but I get a compilation error:
Type mismatch: cannot convert from Collector<Object,?,List<Object>> to Supplier<R>
What is an IntStream, how is it different to a regular stream, and when would you use it?
You more-or-less use IntStream when you have intermediate or terminal operations that don't require boxing, and frequently when you want arithmetic results. For example, you might write
students.stream().mapToInt(Student::getTestScore).average()
Generally, you'd want to use it when your intermediate results are not boxed -- not the case with a List<List<Integer>> and either you're mapping the unboxed result to a boxed thing with mapToObj, or doing something arithmeticky with it like average() here.
It doesn't buy you anything except possibly pain for this use case, which doesn't actually care that the list contents are Integers; you're not using anything about integers here.

Categories