I need to fetch an object from each element in an Iterable and add it into a List.
I am able to do this using the code below. However, are there any ways of creating a Guava ImmutableList without instantiating a List explicitly?
List<Data> myList = new ArrayList<>();
myIterable.forEach(val ->
myList.add(val.getMetaData())
);
To apply a function to each element and turn it into an ImmutableList, today's best practice would be
Streams.stream(myIterable).map(Value::getMetaData)
.collect(ImmutableList.toImmutableList());
are there any ways of creating an ImmutableList with instantiating the List explicitly?
Solution using standard JDK features
You can use StreamSupport.stream() to generate a stream out of your Iterable and then apply map() to transform to extract Data objects from stream elements and toList() to obtain an immutable list as the result:
List<Data> result = StreamSupport.stream(
myIterable.spliterator(), // spliterator
false // denotes whether the stream should be parallel or not
)
.map(MyClass::getMetaData)
.toList(); // for Java 8 .collect(Collectors.toUnmodifiableList())
A simple Demo
JDK Stream API & Guava ImmutableList
The code might look like that:
List<Data> result = StreamSupport.stream(
myIterable.spliterator(),
false
) // Stream<MyClass>
.map(MyClass::getMetaData) // Stream<Data>
.collect(
ImmutableList::<Data>builder, // accumulation type - ImmutableList.Builder<Data>
ImmutableList.Builder::add, // adding stream element into a builder
(left, right) -> left.addAll(right.build()) // merging builders while executing in parallel
)
.build(); // building an ImmutableList
To future readers: The last code snippet which makes use of ImmutableList.Builder provided for rather educational purposes. If you are not particularly interested in ImmutableList from Guava library (for instance, you're not using this library in your project) then have a look at the solution at the very beginning of the answer. If you do want Guava's ImmutableList for some reason, then the better option would be to use the approach provided in the answer by Louis Wasserman
Related
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 on specific fields in a custom class object
I have an ArrayList of Train objects.
Each Train has three fields: source, destination, cost.
I want to get all the place names, i.e. all distinct sources + destinations.
I am using the below code, but as it can be observed, I'm using two streams to retrieve the data.
List<String> destinations = list.stream()
.map(x -> x.getDestination())
.distinct()
.collect(Collectors.toList());
List<String> sources = List.stream()
.map(x -> x.getSource())
.distinct()
.collect(Collectors.toList());
I was wondering how I could accomplish the same thing in a single stream? Can it be done using flatMap, or there's another way to achieve this?
List<String> allPlaces = ?
Also, is this possible to use Train class without getters?
You had the right idea with flatMap - you can map a train to a stream that contains the source and destination, and then flatMap it to you "main" stream:
List<String> allPlaces =
trains.stream()
.flatMap(t -> Stream.of(t.getSource(), t.getDestination()))
.distinct()
.collect(Collectors.toList());
In this case, we can utilize Java 16 method mapMulti(), which is functionally similar to flatMap(). It's meant for transforming a single stream element into a group of elements.
Here's how implementation might look like:
List<String> places = trains.stream()
.<String>mapMulti((train, consumer) -> {
consumer.accept(train.getSource());
consumer.accept(train.getDestination());
})
.distinct()
.toList();
Contrary to flatMap() it doesn't consume a stream, but operates via Consumer. mapMulti() a recommended alternative to flatMap() for situations when a new stream flatMap() requires would contain only a few elements (like in this case when we have only two elements: source and destination).
A quote from the API Note:
This method is preferable to flatMap in the following circumstances:
When replacing each stream element with a small (possibly zero)
number of elements. Using this method avoids the overhead of creating
a new Stream instance for every group of result elements, as required
by flatMap.
Addressing peripheral question:
Also is this possible without the getters methods of class Train?
Sure, you can. But it's not a recommended practice to access instance fields directly. In Java we're using access modifier to hide and protect member-variables within the class, that's one of the aspects of Encapsulation.
Faced to some interesting issue: for example, I have to choose some solutions, depending on whether my List<> is blank or not, but I don't want to use simple if-statement for refactoring reasons (for example, there are three ugly inner if-s). If I had a nullable object, I could use the Optional::ifPresentOrElse solution, but what if the array is not null but just empty? are there any built-in language solutions? I understand that I could write something like ListOptional, create my own ListOptional::isNotEmptyOrElse there, but perhaps there is another solution?
Understand Optional as a wrapper against a possibly null instance providing chainable null-safe methods. Suggested ListOptional doesn't make to me any sense as null and empty lists semantically represent the same - a bunch of nothing.
If you want to use Optional, you must filter the single list in it whether is empty or not and provide an alternative:
List<String> nonEmpty = Optional.of(list)
.filter(l -> !l.isEmpty()) // is the list empty?
.orElseGet(() -> Arrays.asList("Hi", "Bye")); // if so, yield a default one
How is this better than using a ternary operator is questionable. I see no advantage over using this or if-else construct or a wrapper from a 3rd party library:
if (list.isEmpty() {
list = Arrays.asList("Hi", "Bye");
}
A built-in language solution for handling potentially empty collections is Stream API as declarative calls of chained methods, that are not executed on Stream from an empty collection. You can understand Stream as a wrapper against a possibly empty collection with the goal of processing each element. The same goes to the Optional with a difference a single instance is handled (regardless of whether it is a collection or not).
List<String> list = Collections.emptyList(); // empty list
List<String> updated = list.stream()
.filter(str -> str.startsWith("A") // won't happen
.map(String::toLowerCase) // won't happen
.collect(Collectors.toList()); // yields empty list
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;
};
I'm looking for the most pain free way to filter a collection. I'm thinking something like
Collection<?> foo = existingCollection.stream().filter( ... ). ...
But I'm not sure how is best to go from the filter, to returning or populating another collection. Most examples seem to be like "and here you can print". Possible there's a constructor, or output method that I'm missing.
There’s a reason why most examples avoid storing the result into a Collection. It’s not the recommended way of programming. You already have a Collection, the one providing the source data and collections are of no use on its own. You want to perform certain operations on it so the ideal case is to perform the operation using the stream and skip storing the data in an intermediate Collection. This is what most examples try to suggest.
Of course, there are a lot of existing APIs working with Collections and there always will be. So the Stream API offers different ways to handle the demand for a Collection.
Get an unmodifiable List implementation containing all elements (JDK 16):
List<T> results = l.stream().filter(…).toList();
Get an arbitrary List implementation holding the result:
List<T> results = l.stream().filter(…).collect(Collectors.toList());
Get an unmodifiable List forbidding null like List.of(…) (JDK 10):
List<T> results = l.stream().filter(…).collect(Collectors.toUnmodifiableList());
Get an arbitrary Set implementation holding the result:
Set<T> results = l.stream().filter(…).collect(Collectors.toSet());
Get a specific Collection:
ArrayList<T> results =
l.stream().filter(…).collect(Collectors.toCollection(ArrayList::new));
Add to an existing Collection:
l.stream().filter(…).forEach(existing::add);
Create an array:
String[] array=l.stream().filter(…).toArray(String[]::new);
Use the array to create a list with a specific specific behavior (mutable, fixed size):
List<String> al=Arrays.asList(l.stream().filter(…).toArray(String[]::new));
Allow a parallel capable stream to add to temporary local lists and join them afterward:
List<T> results
= l.stream().filter(…).collect(ArrayList::new, List::add, List::addAll);
(Note: this is closely related to how Collectors.toList() is currently implemented, but that’s an implementation detail, i.e. there is no guarantee that future implementations of the toList() collectors will still return an ArrayList)
An example from java.util.stream's documentation:
List<String>results =
stream.filter(s -> pattern.matcher(s).matches())
.collect(Collectors.toList());
Collectors has a toCollection() method, I'd suggest looking this way.
As an example that is more in line with Java 8 style of functional programming:
Collection<String> a = Collections.emptyList();
List<String> result = a.stream().
filter(s -> s.length() > 0).
collect(Collectors.toList());
You would possibly want to use toList or toSet or toMap methods from Collectors class.
However to get more control the toCollection method can be used. Here is a simple example:
Collection<String> c1 = new ArrayList<>();
c1.add("aa");
c1.add("ab");
c1.add("ca");
Collection<String> c2 = c1.stream().filter(s -> s.startsWith("a")).collect(Collectors.toCollection(ArrayList::new));
Collection<String> c3 = c1.stream().filter(s -> s.startsWith("a")).collect(Collectors.toList());
c2.forEach(System.out::println); // prints-> aa ab
c3.forEach(System.out::println); // prints-> aa ab