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));
Related
I have a soa xml java object, in which I need to get the total count of SItemDetails which is 4 here:
<ns57:PProductsResponse xmlns:ns57="">
<ResponseInfo xmlns="" TransactionId="test"/>
<ns57:PProductsSuccess>
<ONumber xmlns="" ONumber="7">
<OItemNumber>
<PItemDetails Price="0.00" >
<SItemDetails FIdRef="01-01" SId="12D"/>
<SItemDetails FIdRef="01-02" SId="10F"/>
</PItemDetails>
</OItemNumber>
</ONumber>
</ns57:PProductsSuccess>
<ns57:PProductsSuccess>
<ONumber xmlns="" ONumber="7">
<OItemNumber>
<PItemDetails Price="0.00">
<SItemDetails FIdRef="01-02" SId="10G"/>
<SItemDetails FIdRef="01-01" SId="12E"/>
</PItemDetails>
</OItemNumber>
</ONumber>
</ns57:PProductsSuccess>
</ns57:PProductsResponse>
PProductsSuccessType[] pProductSuccess = pProductsResponse.getPProductsResponse().getPProductsSuccess();
long sItemDetailsCount1 = Arrays.stream(pProductSuccess).filter(PProductsSuccessType
-> (PProductsSuccessType.getONumber().getOItemNumber()[0].getPItemDetails().getSItemDetails()!=null)).count();
OR
long sItemDetailsCount2 = Arrays.stream(pProductSuccess)
.flatMap(p -> Arrays.stream(p.getONumber().getOItemNumber()))
.filter(o -> o.getPItemDetails().getSItemDetails() != null).count();
OR
long sItemDetailsCount3 = Arrays.stream(pProductSuccess)
.map(p -> p.getONumber().getOItemNumber())
.flatMap(Arrays::stream)
.filter(Objects::nonNull)
.count();
When I executed the above codes it gave the result as 2 but i am expecting 4 since we have 4 SItemDetails in the pProductsResponse.
Can someone help me achieve it using lamda iteration.
A lambda expression is just an anonymous function. It doesn't look like lambda's are your issue here. It looks like you're confused on what your streams are doing.
I'd suggest reading through https://www.baeldung.com/java-8-streams-introduction to get a better understanding of using the streams api.
Your IDE should already do this for you but each method chained together below is returning an object. Map and flatmap both return Stream<>. I've added some comments of what I can guess is being returned from your method calls.
long sItemDetailsCount3 = Arrays.stream(pProductSuccess) //Stream<pProductSuccess>
.map(p -> p.getONumber().getOItemNumber()) //Stream<List<OItemNumber>>
.flatMap(Arrays::stream) // Stream<OItemNumber>
.filter(Objects::nonNull) // Stream<OItemNumber>
.count(); //The number of items in the Stream<OItemNumber>
As you can see, your count would be returning the count of OItemNumber's since that's what the stream is iterating over.
To get the SItemDetails we need to have a stream of SItemDetails. This should work for your needs. I am using flatmap since I assume your getters are returning a Collection<> of items. In short, flatmap will flatten the collection and give a Stream while map would give
Stream<List<item>>
Arrays.stream(pProductSuccess) //Stream<pProductSuccess>
.flatmap(p -> p.getONumber().getOItemNumber().stream()) //Stream<OItemDetails>
.flatmap(oItemNumber -> oItemNumber.getPItemDetails().stream())//Stream<PItemDetails>
.flatmap(pItemDetail -> pItemDetail.getSItemDetails().stream())//Stream<SItemDetails>
.filter(Objects::nonNull) // Stream<SItemDetails>
.count();
If you are ever confused about what is being returned from a stream, remove the method chaining and do something similar to
Stream<OItemNumber> oItemNumberStream = Arrays.stream(pProductSuccess)
.flatmap(p -> p.getONumber().getOItemNumber().stream())
Stream<> anotherStream = oItemNumberStream.flatmap(....);
By assigning local variables it is easier to see what object is returned from each call. Your IDE should help do this by displaying what would be returned on each line as long as you format your code so a method call is each on it's own line (as shown in your examples above).
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)
I would be currious to know how to propagate variable into a stream in java 8.
An example is better than a long explaination, so how would you convert the following (abstract) code into streams:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
List param2=entry.getValue().getParam2();
for (B b : param2)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+b.toString());
}
}
Knowing that this example is a simplification of the problem (for example, i need "param1" more than once in the next for loop)
So far, the only idea i have is to store all the informations i need into a tuple to finally use the forEach stream method over this tuple.
(Not sure to be very clear....)
Edit:I simplified my example too much. My case is more something like that:
Map<Integer,A> myMap = new HashMap();
for (Entry<Integer,A> entry : myMap)
{
int param1=entry.getValue().getParam1();
CustomList param2=entry.getValue().getParam2();
for (int i = 0; i<param2.size(); i++)
{
System.out.println(""+entry.getKey()+"-"+param1+"-"+param2.get(i).toString());
}
}
I could write something like that with stream:
myMap.entrySet().stream()
.forEach(
e -> IntStream.range(0, e.getValue.getParam2().getSize())
.forEach(
i -> System.out.println(e.getKey()+"-"+e.getValue().getParam1()+"-"+e.getValue.getParam2.get(i))
)
);
However, what i have instead of "e.getValue.getParam2()" in my real case is much more complex (a sequence of 5-6 methods) and heavier than just retrieving a variable (it executes some logic), so i would like to avoid to repeat e.getValue.getParam2 (once in just before the forEach, and once in the forEach)
i know that it's maybe not the best use case for using stream, but I am learning about it and would like to know about the limits
Thanks!
Something like this:
myMap.forEach(
(key, value) -> value.getParam2().forEach(
b -> System.out.println(key+"-"+value.getParam1()+"-"+b)
)
);
That is, for each key/value pair, iterate through value.getParam2(). For each one of those, print out string formatted as you specified. I'm not sure what that gets you, other than being basically what you had before, but using streams.
Update
Responding to updates to your question, this:
myMap.forEach((key, value) -> {
final CustomList param2 = value.getParam2();
IntStream.range(0, param2.getSize()).forEach(
i -> System.out.println(key+"-"+value.getParam1()+"-"+param2.get(i))
)
});
Here we assign the result of getParam2() to a final variable, so it is only calculated once. Final (and effectively final) variables are visible inside lambda functions.
(Thank you to Holger for the suggestions.)
Note that there are more features in the Java 8 API than just streams. Especially, if you just want to process all elements of a collection, you don’t need streams.
You can simplify every form of coll.stream().forEach(consumer) to coll.forEach(consumer). This applies to map.entrySet() as well, however, if you want to process all mappings of a Map, you can use forEach on the Map directly, providing a BiConsumer<KeyType,ValueType> rather than a Consumer<Map.Entry<KeyType,ValueType>>, which can greatly improve the readability:
myMap.forEach((key, value) -> {
int param1 = value.getParam1();
CustomList param2 = value.getParam2();
IntStream.range(0, param2.size()).mapToObj(param2::get)
.forEach(obj -> System.out.println(key+"-"+param1+"-"+obj));
});
It’s worth thinking about adding a forEach(Consumer<ElementType>) method to your CustomList, even if the CustomList doesn’t support the other standard collection operations…
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;
I'm trying to come up to speed on the Streams API, but I'm really used to the simplicity of the C# Linq Extension functions and the ability to use the yield keyword to create iterators. Normally I would use:
list.Aggregate(set, (acc, a) => { acc.add(a.Id); return acc});
Or something like that, but I'm not immediately seeing how this maps to the Streams API.
List<SomeObject> objs = ...
Set<String> ids = new HashSet<>();
for (SomeObject a : objs) {
ids.add(a.getId());
}
assertThat(ids.size(), objs.size());
EDIT:
Changed SomeObject.getId() to a.getId() in the for loop.
The following statement should be equivalent to the for-loop in your example.
Set<String> ids = objs.stream()
.map(a -> a.getId())
.collect(Collectors.toSet());
You can also use a method reference instead of a lambda expression:
Set<String> ids = objs.stream()
.map(SomeObject::getId)
.collect(Collectors.toSet());