Java8 stream cannot resolve a variable - java

I am new in Java8 and I want to refactor this piece of code and convert it in a more Java8 style,
for (RestaurantAddressee RestaurantAddressee : consultationRestaurant.getAddressees()) {
Chain chain = chainRestService.getClient().getChainDetails(getTDKUser(), RestaurantAddressee.getChain().getId());
if (chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId())) {
chainIds.add(restaurantAddressee.getChain().getId());
}
}
so I change it for this code:
consultationRestaurant.getAddressees()
.stream()
.map( ma -> chainRestService.getClient().getChainDetails(getTDKUser(), ma.getChain().getId()))
.filter(chain -> chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId()))
.forEach(chainIds.add(chain.getId()));
But I have this compilation error:
chain cannot be resolved

You forgot to specify the lambda expression parameter in your forEach call.
That said, you shouldn't use forEach to add elements to a collection. Use collect:
List<String> chainIds =
consultationRestaurant.getAddressees()
.stream()
.map( ma -> chainRestService.getClient().getChainDetails(getTDKUser(), ma.getChain().getId()))
.filter(chain -> chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId()))
.map(Chain::getId)
.collect(Collectors.toList());

Here. Your loop defines:
Chain chain = chainRestService.getClient()...
But your stream statement simply misses to define that variable.
So: in places that need that variable, you have to provide, for example as parameter:
filter(chain -> chain.getOrganisation().getId().equalsIgnoreCase(event.getOrganisationId()))

Related

Replacing isPresent with ifPresent and orElse

I have the following logic in my method where I check for the value of an optional parameter, and depending on that I build another object.
AtomicReference<Employee> employeeValue = null;
questions.forEach(question -> {
if(question.isBoolean().isPresent()) {
employeeValue.set(Employee.builder()
.withBooleanValue(Boolean.valueOf(question.value()))
.build());
} else {
employeeValue.set(Employee.builder()
.withStringValue(question.value())
.build());
}
Record record = Record.builder()
.withId(question.id())
.withValue(employeeValue.get())
.build();
answers.add(record);
});
How can I replace the above with ifPresent and orElse? I'm using Java 8 and therefore ifPresentOrElse method is not available. If I am to use ifPresent and orElse separately with anonymous inner function, how do I go about it?
Any help would be much appreciated.
You neither need isPresent() nor ifPresent(). You don’t need peek() (as in the other answer) nor an AtomicReference (as in the question). I believe that this does it:
questions.forEach(question -> {
Employee empl = question.isBoolean()
.map(b -> Employee.builder()
.withBooleanValue(Boolean.valueOf(question.value()))
.build())
.orElseGet(() -> Employee.builder()
.withStringValue(question.value())
.build());
Record record = Record.builder()
.withId(question.id())
.withValue(empl)
.build();
answers.add(record);
});
You can probably apply this idea inside the stream from the other answer if you want. Rather than using Stream.forEach() I’d prefer to collect into a collection like a list and then use answers.addAll().
You can stream through questions and use peek and map-orElse construction to achieve the same result:
questions.stream()
.peek(question -> {
Employee employee = question.isBoolean()
.map(b -> Employee.builder().withBooleanValue(Boolean.valueOf(question.value())).build())
.orElse(Employee.builder().withStringValue(question.value()).build());
employeeValue.set(employee);
}
)
.map(question -> Record.builder().withId(question.id()).withValue(employeeValue.get()).build())
.forEach(answers.add(answer)); // did you mean 'record'?
But to be honest it does not change a lot - your implementation looks maybe less "java eightish" but is fine :)

xml class object get child nodes count using (lamba-> or other optimized) iteration java

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).

Stream: Filter on children, return the parent

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)

Check instanceof in stream

I have the following expression:
scheduleIntervalContainers.stream()
.filter(sic -> ((ScheduleIntervalContainer) sic).getStartTime() != ((ScheduleIntervalContainer)sic).getEndTime())
.collect(Collectors.toList());
...where scheduleIntervalContainers has element type ScheduleContainer:
final List<ScheduleContainer> scheduleIntervalContainers
Is it possible to check the type before the filter?
You can apply another filter in order to keep only the ScheduleIntervalContainer instances, and adding a map will save you the later casts :
scheduleIntervalContainers.stream()
.filter(sc -> sc instanceof ScheduleIntervalContainer)
.map (sc -> (ScheduleIntervalContainer) sc)
.filter(sic -> sic.getStartTime() != sic.getEndTime())
.collect(Collectors.toList());
Or, as Holger commented, you can replace the lambda expressions with method references if you prefer that style:
scheduleIntervalContainers.stream()
.filter(ScheduleIntervalContainer.class::isInstance)
.map (ScheduleIntervalContainer.class::cast)
.filter(sic -> sic.getStartTime() != sic.getEndTime())
.collect(Collectors.toList());
A pretty elegant option is to use method reference of class:
scheduleIntervalContainers
.stream()
.filter( ScheduleIntervalContainer.class::isInstance )
.map( ScheduleIntervalContainer.class::cast )
.filter( sic -> sic.getStartTime() != sic.getEndTime())
.collect(Collectors.toList() );
There is a small problem with #Eran solution - typing class name in both filter and map is error-prone - it is easy to forget to change the name of the class in both places. An improved solution would be something like this:
private static <T, R> Function<T, Stream<R>> select(Class<R> clazz) {
return e -> clazz.isInstance(e) ? Stream.of(clazz.cast(e)) : null;
}
scheduleIntervalContainers
.stream()
.flatMap(select(ScheduleIntervalContainer.class))
.filter( sic -> sic.getStartTime() != sic.getEndTime())
.collect(Collectors.toList());
However there might be a performance penalty in creating a Stream for every matching element. Be careful to use it on huge data sets. I've learned this solution from #Tagir Vailev
Instead of a filter + map like other answers suggest, I would recommend this utility method:
public static <Super, Sub extends Super> Function<Super, Stream<Sub>> filterType(Class<Sub> clz) {
return obj -> clz.isInstance(obj) ? Stream.of(clz.cast(obj)) : Stream.empty();
}
Use it as:
Stream.of(dog, cat fish)
.flatMap(filterType(Dog.class));
Compared to filter + map it has the following advantages:
If the class does not extend your class you will get a compile error
Single place, you can never forget to change a class in either filter or map
Filter by class type with StreamEx
StreamEx.of(myCollection).select(TheThing.class).toList();

Java lambda expression -- mapping and then modifying a list?

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));

Categories