I have a couple of predicates that I all want to be satisfied.
The things that can satisfy those predicates are a handful of strings. An individual string doesn't have to satisfy all (or any) of those predicates, but after I've looked at the last string, all of the predicates have to be satisified.
My first take to represent this problem in Java was to use Stream's allMatch and anyMatch since I want all of the predicates to match any of the things to test:
Stream<String> thingsToTest = Stream.of("Hi", "predicates!", "oddball");
Predicate<String> startsWithH = string -> string.startsWith("H");
Predicate<String> endsWithBang = string -> string.endsWith("!");
Stream<Predicate<String>> predicates = Stream.of(startsWithH, endsWithBang);
// All of the strings have the chance to satisfy any predicate
boolean predicatesSatisfied = predicates.allMatch(pred -> thingsToTest.anyMatch(pred::test));
// I expect this to print "true"
System.out.println(predicatesSatisfied);
Sadly, this doesn't work but terminates with an IllegalStateException, telling me that the stream has already been operated upon or closed, which shouldn't come as a big surprise since for each predicate I give the strings a new chance to satisfy the predicate, using the string stream over and over.
And streams are not meant to be reused for good reasons.
So how do I avoid this exception? Is there a more elegant alternative to anyMatch or allMatch?
To get around the IllegalStateException I use a List of strings and call its stream() method:
// Use List instead of Stream
List<String> thingsToTest = Arrays.asList("Hi", "predicates!", "oddball");
// Same old
Predicate<String> startsWithH = string -> string.startsWith("H");
Predicate<String> endsWithBang = string -> string.endsWith("!");
Stream<Predicate<String>> predicates = Stream.of(startsWithH, endsWithBang);
// Call stream() on the List
boolean predicatesSatisfied = predicates.allMatch(pred -> thingsToTest.stream().
anyMatch(pred::test));
Although this works fine, I'm not sure if it is the most elegant way to do this, so if you have a better idea, please go ahead and post your code or suggestion.
When you need to use the Stream several times, the common solution is to create a Supplier<Stream> instead:
Supplier<Stream<String>> thingsToTest = () -> Stream.of("Hi", "predicates!", "oddball");
....
boolean predicatesSatisfied = predicates.allMatch(
pred -> thingsToTest.get().anyMatch(pred::test));
Unlike #MatthiasBraun suggestion, using the Supplier it's not always necessary to actually store all the stream elements in the verbatim collection. For example, such thing is possible:
Supplier<Stream<String>> thingsToTest =
() -> IntStream.range(0, 10000).mapToObj(String::valueOf);
You just have to care that supplier always returns the same stream elements.
If you already have a collection, then you can create a supplier as well:
List<String> list = Arrays.asList("Hi", "predicates!", "oddball");
Supplier<Stream<String>> thingsToTest = list::stream;
Related
I am new to Java streams but need to master by practice really!
The collection input is made up of strings e.g. [name][dot][country], example as follows:
JAMES.BRITAIN
JOHN.BRITAIN
LEE.BRITAIN
GEORGE.FRANCE
LEON.FRANCE
MARSELLE.FRANCE
KOFI.GHANA
CHARLIE.GHANA
Please, how do I return a list of unique countries in a single stream statement?
Expected result will be a distinct list as follows:
BRITAIN
FRANCE
GHANA
In the real code the streams statement below gives me the list to be filtered i.e.:
List<String> allSolrCollections = (List<String>) findAllCollections()
.getJsonArray(SOLR_CLOUD_COLLECTION)
.getList()
.stream()
.map(object -> Objects.toString(object, null))
.collect(Collectors.toList());
for the first part of problem, you need to convert each entry to corresponding country. so, you could use String.split function and keep the country part.
for the second part you could take advantage of the Stream.distinct function that will remove duplicates from incoming stream.
finally, this should work:
List<String> res = list.stream()
.map(s -> s.split("\\.")[1])
.distinct()
.collect(Collectors.toList());
Alternative solution
You can use the advantage of the method Pattern#splitAsStream(CharSerquence). Once you split each line into a new Stream, skip the first item, flatMap the result into a new Stream and produce a Set.
final Pattern pattern = Pattern.compile("\\.");
final Set<String> result = list.stream()
.flatMap(string -> pattern.splitAsStream(string).skip(1))
.collect(Collectors.toSet());
[GHANA, FRANCE, BRITAIN]
If you want to be careful about format it would be worth using a regular expression. This also makes the meaning of the code clearer to a reader.
Pattern inputPattern = Pattern.compile("- (?<name>[A-Z]+)\\.(?<country>[A-Z]+)");
list.stream()
.map(inputPattern::match)
.filter(Matcher::matches)
.map(m -> m.group("country"))
.distinct()
.toList();
This ignores lines that don't match the expected format.
I have a list of names and a list of versions. I want to get all permutations which are constructed by concatenating the string from two lists. I am using two for loop to do this but I want to switch to a more functional style approach. Here is my solution:
List<String> names = new ArrayList<>();
List<String> versions = new ArrayList<>();
List<String> result = new ArrayList<>();
names.forEach(name -> versions.stream().map(version -> result.add(name.concat(version))));
Is there a better way to do it?
You are looking for the "Cartesian Product" of names and versions — basically the return set/list from the aforementioned sets/lists.
final Stream<List<String>> result = names.stream()
.flatMap(s1 -> versions.stream().flatMap(s2 -> Stream.of(Arrays.asList(s1, s2))));
result.forEach(System.out::println);
Keep in mind that operation is super expensive. Google's Guava have this implemented also under com.google.common.collect.Sets.cartesianProduct(s1, s2).
You should look forward to use flatMap while streaming over names and then performing map operation further correctly as:
List<String> result = names.stream() // for each name
.flatMap(name -> versions.stream() // for each version
.map(version -> name.concat(version))) // concat version to the name
.collect(Collectors.toList()); // collect all such names
Or a bit tidier:
final List<String> result = names.stream() // Stream the Names...
.flatMap(name -> versions.stream() // ...together with Versions.
.map (version -> name.concat(version))) // Combine Name+Version
.collect(Collectors.toList()); // & collect in List.
I have this piece of code and I want to return a list of postCodes:
List<String> postcodes = new ArrayList<>();
List<Entry> entries = x.getEntry(); //getEntry() returns a list of Entry class
for (Entry entry : entries) {
if (entry != null) {
Properties properties = entry.getContent().getProperties();
postcodes.addAll(Arrays.asList(properties.getPostcodes().split(",")));
}
}
return postcodes;
Here's my attempt to use stream() method and the following chained methods:
...some other block of code
List<Entry> entries = x.getEntry.stream()
.filter(entry -> recordEntry != null)
.flatMap(entry -> {
Properties properties = recordEntry.getContent().getProperties();
postCodes.addAll(Arrays.asList(properties.getPostcodes().split(",")));
});
you've got several issues with your code i.e:
postCodes.addAll is a side-effect and therefore you should avoid doing that otherwise when the code is executed in parallel you'll receive non-deterministic results.
flatMap expects a stream, not a boolean; which is what your code currently attempts to pass to flatMap.
flatMap in this case consumes a function that also consumes a value and returns a value back and considering you've decide to use a lambda statement block then you must include a return statement within the lambda statement block specifying the value to return. this is not the case within your code.
stream pipelines are driven by terminal operations which are operations that turn a stream into a non-stream value and your code currently will not execute at all as you've just set up the ingredients but not actually asked for a result from the stream.
the receiver type of your query should be List<String> not List<Entry> as within your current code the call to Arrays.asList(properties.getPostcodes().split(",")) returns a List<String> which you then add to an accumulator with the call addAll.
thanks to Holger for pointing it out, you're constantly failing to decide whether the variable is named entry or recordEntry.
That said here's how I'd rewrite your code:
List<String> entries = x.getEntry.stream()
.filter(Objects::nonNull)
.map(Entry::getContent)
.map(Content::getProperties)
.map(Properties::getPostcodes)
.flatMap(Pattern.compile(",")::splitAsStream)
.collect(Collectors.toList());
and you may want to use Collectors.toCollection to specify a specific implementation of the list returned if deemed appropriate.
edit:
with a couple of good suggestions from shmosel we can actually use method references throughout the stream pipelines and therefore enabling better intent of the code and a lot easier to follow.
or you could proceed with the approach:
List<String> entries = x.getEntry.stream()
.filter(e -> e != null)
.flatMap(e -> Arrays.asList(
e.getContent().getProperties().getPostcodes().split(",")).stream()
)
.collect(Collectors.toList());
if it's more comfortable to you.
Assume a class MyClass:
public class MyClass {
private final Integer myId;
private final String myCSVListOfThings;
public MyClass(Integer myId, String myCSVListOfThings) {
this.myId = myId;
this.myCSVListOfThings = myCSVListOfThings;
}
// Getters, Setters, etc
}
And this Stream:
final Stream<MyClass> streamOfObjects = Stream.of(
new MyClass(1, "thing1;thing2;thing3"),
new MyClass(2, "thing2;thing3;thing4"),
new MyClass(3, "thingX;thingY;thingZ"));
I want to return every instance of MyClass that contains an entry "thing2" in myCSVListOfThings.
If I wanted a List<String> containing myCSVListOfThings this could be done easily:
List<String> filteredThings = streamOfObjects
.flatMap(o -> Arrays.stream(o.getMyCSVListOfThings().split(";")))
.filter("thing2"::equals)
.collect(Collectors.toList());
But what I really need is a List<MyClass>.
This is what I have right now:
List<MyClass> filteredClasses = streamOfObjects.filter(o -> {
Stream<String> things = Arrays.stream(o.getMyCSVListOfThings().split(";"));
return things.anyMatch(s -> s.equals("thing2"));
}).collect(Collectors.toList());
But somehow it does not feel right. Any cleaner solution than opening a new Stream inside of a Predicate?
Firstly, I recommend you to add extra method to MyClass public boolean containsThing(String str), so you can transform you code like this:
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> o.containsThing("thing2"))
.collect(Collectors.toList());
Now you can implement this method as you want depends on input data: splitting into Stream, splitting into Set, even searching of substring (if it's possible and has sense), caching result if you need.
You know much more about usage of this class so you can make right choice.
One solution is to use a pattern matching that avoids the split-and-stream operation:
Pattern p=Pattern.compile("(^|;)thing2($|;)");
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> p.matcher(o.getMyCSVListOfThings()).find())
.collect(Collectors.toList());
Since the argument to String.split is defined as regex pattern, the pattern above has the same semantic as looking for a match within the result of split; you are looking for the word thing2 between two boundaries, the first is either, the beginning of the line or a semicolon, the second is either, the end of the line or a semicolon.
Besides that, there is nothing wrong with using another Stream operation within a predicate. But there are some ways to improve it. The lambda expression gets more concise if you omit the obsolete local variable holding the Stream. Generally, you should avoid holding Stream instances in local variables as chaining the operations directly will reduce the risk of trying to use a Stream more than one time. Second, you can use the Pattern class to stream over the resulting elements of a split operation without collecting them all into an array first:
Pattern p=Pattern.compile(";");
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> p.splitAsStream(o.getMyCSVListOfThings()).anyMatch("thing2"::equals))
.collect(Collectors.toList());
or
Pattern p=Pattern.compile(";");
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> p.splitAsStream(o.getMyCSVListOfThings()).anyMatch(s->s.equals("thing2")))
.collect(Collectors.toList());
Note that you could also rewrite your original code to
List<MyClass> filteredClasses = listOfObjects.stream()
.filter(o -> Arrays.asList(o.getMyCSVListOfThings().split(";")).contains("thing2"))
.collect(Collectors.toList());
Now, the operation within the predicate is not a Stream but a Collection operation, but this doesn’t change the semantic nor the correctness of the code…
As I see it you have three options.
1) look for particular entry in the String without spliting it - still looks messy
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> o.getMyCSVListOfThings().contains(";thing2;"))
.collect(Collectors.toList());
2) map twice - still messy
List<MyClass> filteredClasses = streamOfObjects
.map(o -> Pair<MyClass, List<String>>.of(o, toList(o.getMyCSVListOfThings()))
.filter(pair -> pair.getRight().contains("thing2"))
.map(pair -> pair.getLeft())
.collect(Collectors.toList());
where toList is a method that will convert String to List
3) create additional field - method I'd suggest
Extend class MyClass - add field to the class
List<String> values;
And initialize it in the constructor:
public MyClass(Integer myId, String myCSVListOfThings) {
this.myId = myId;
this.myCSVListOfThings = myCSVListOfThings;
this.values = toList(myCSVListOfThings);
}
And then in the stream simply:
List<MyClass> filteredClasses = streamOfObjects
.filter(o -> o.getValues().contains("thing2"))
.collect(Collectors.toList());
Of course field values can be initialized in LAZY mode during first getValues method call if you want.
This is similar to the issue, Getting only required objects from a list using Java 8 Streams, posted a year earlier. I think the solution I left there is applicable here.
There's a library called com.coopstools.cachemonads. It extends the java stream (and Optional) classes to allow caching of entities for later use.
The solution can be found with:
List<Parent> goodParents = CacheStream.of(parents)
.cache()
.map(Parent::getChildren)
.flatMap(Collection::stream)
.map(Child::getAttrib1)
.filter(att -> att > 10)
.load()
.distinct()
.collect(Collectors.toList());
where, parents is an array or stream.
For clarity, the cache method is what stores the parents; and the load method is what pulls the parents back out. And If a parent does not have children, a filter will be needed after the first map to remove the null lists.
More specifically, for your issue:
List<Parent> goodParents = CacheStream.of(streamOfObjects)
.cache()
.map(o -> o.getMyCSVListOfThings().split(";"))
.flatMap(Collection::stream)
.filter("thing2"::equals)
.load()
.collect(Collectors.toList())
This library can be used in any situation where operations need to be performed on children, including map/sort/filter/etc, but where an older entity is still needed. There may be more lines than some of the other answers, but each line is very clean and straight forward.
Please let me know if this answer is helpful.
The code can be found at https://github.com/coopstools/cachemonads or can be downloaded from maven:
<dependency>
<groupId>com.coopstools</groupId>
<artifactId>cachemonads</artifactId>
<version>0.2.0</version>
</dependency>
(or, gradle, com.coopstools:cachemonads:0.2.0)
Using a Java 8 lambda expression, I'm trying to do something like this.
List<NewObject> objs = ...;
for (OldObject oldObj : oldObjects) {
NewObject obj = oldObj.toNewObject();
obj.setOrange(true);
objs.add(obj);
}
I wrote this code.
oldObjects.stream()
.map(old -> old.toNewObject())
.forEach({new.setOrange("true")})
.collect(Collectors.toList());
This is invalid code because I'm then trying to do .collect() on what's returned by .forEach(), but forEach is void and does not return a list.
How should this be structured?
You can use Stream's peek method, which returns the Stream because it's an intermediate operation. It normally isn't supposed to have a side effect (it's supposed to be "non-interfering"), but in this case, I think the side effect (setOrange(true)) is intended and is fine.
List<NewObject> newObjects =
oldObjects.stream()
.map(OldObject::toNewObject)
.peek( n -> n.setOrange(true))
.collect(Collectors.toList());
It's about as verbose as your non-streams code, so you can choose which technique to use.
You can use peek.
List<NewObject> list = oldObjects.stream()
.map(OldObject::toNewObject)
.peek(o -> o.setOrange(true))
.collect(Collectors.toList());
Alternatively, you can mutate the elements after forming the list.
List<NewObject> list = oldObjects.stream()
.map(OldObject::toNewObject)
.collect(Collectors.toList());
list.forEach(o -> o.setOrange(true));