Joining all subsets within a stream in Java optionals - java

This is the original piece of code:
Set<StatuteType> statuteTypes = registration.getStudent().getStudentStatutesSet()
.stream()
.map(StudentStatute_Base::getType)
.collect(Collectors.toSet());
I want to wrap everything in an Optional to avoid null pointers and all. If the student does not exist or the statutesSet does not exist.
What I have:
Set<StatuteType> statuteTypes = Optional.of(registration)
.map(Registration_Base::getStudent)
.map(student -> student.getStudentStatutesSet())
.flatMap(Collection::stream)
.map(StudentStatute_Base::getType)
.collect(Collectors.toSet())
.orElse(null);
Would something like this be in someway possible? I want to avoid null checks in this chain, and if there's any null just return a simple null as well instead getting an exception.
Normally what I think would be logical would be to use a flatMap as described here but it doesn't seem to be correct in this case, because the Optional flatmap returns an Optional.

Here's a simple way of doing it:
Set<StatuteType> statuteTypes = Optional.ofNullable(registration)
.map(Registration_Base::getStudent)
.map(student -> student.getStudentStatutesSet())
.map(Collection::stream)
.orElseGet(Stream::empty) // Exit Optional, enter stream
.map(StudentStatute_Base::getType)
.collect(Collectors.toSet());
However, it does not result in a null set. Collections should never be null, only empty. I would recommend this approach. The whole point of using an Optional object is so you never have to deal with null values.

Collection::stream does not return an Optional, so you should not use flatMap here. You should keep using map on the optional.
.map(Collection::stream) gives you an Optional<Stream<Statute>>. You seem to be trying to call the stream's map and collect methods on this. But you need to first call Optional.map before you can do that.
You should also use Optional.ofNullable if registration could be null:
Set<StatuteType> statuteTypes = Optional.ofNullable(registration)
.map(Registration_Base::getStudent)
.map(student -> student.getStudentStatutesSet())
.map(Collection::stream)
.map(x -> // Optional.map
x.map(StudentStatute_Base::getType) // Stream.map
.filter(Objects::nonNull) // I assume you want to filter out the statute types which are null?
.collect(Collectors.toSet())
)
.orElse(null);

Related

Java chaining map methods and using optional

I am using Java 8
method1() -> returns a Map<String,Map<String,Set<String>>>.
If I chain the method calls this way -> method1().get("A").get("B") a NullPointerException could happen.
So I am using the following strategy to avoid using the traditional if( != null) code:
Optional.ofNullable(method1()).map(x -> x.get("A")).map(y -> y.get("B")) but the this code return an Optional and I need that returns the Set<String>.
How can I cast it, and in case of null (in the case of the get methods returns null) how returns null?
Thanks in advance.
Polishing the code by gamgoon a little bit:
Set<String> result = Optional.of(method1())
.map(x -> x.get("A"))
.map(y -> y.get("B"))
.orElse(Collections.emptySet());
System.out.println(result);
Output in case either key is not in a map where looked up:
[]
You may recognize the result of printing an empty collection. In places where you want a collection, you should not accept a null. Use an empty collection to signify that there are no elements. In the same vein I have assumed that your method1() may return an empty map but not null. So we don’t need ofNullable() when converting into an Optional — the simple of() is fine.
use orElse like
Set<String> result = Optional.ofNullable(method1())
.map(x -> x.get("A"))
.map(y -> y.get("B"))
.orElse(null);

Get rid of duplicates in one line instead of two with java 8 stream

Could it be written in one line of code instead of two separate? Because I tried adding .distinct() in the first line and somehow it didn't work. I'm not getting the difference here.
List<BgwContract> contractListWithDuplicates = monthlyFeePaymentList
.stream()
.map(MonthlyFeePayment::getBgwContract)
.collect(Collectors.toList());
List<BgwContract> contractListWithoutDuplicates = contractListWithDuplicates
.stream()
.distinct()
.collect(Collectors.toList());
You can use distinct with your existing Stream itself :
List<BgwContract> contractListWithDuplicates = monthlyFeePaymentList
.stream()
.map(MonthlyFeePayment::getBgwContract) // Stream<BgwContract>
.distinct() // here
.collect(Collectors.toList());
Since you wanted an explanation regarding the correct place of distinct:
When you write:
List<BgwContract> contractListWithDuplicates = monthlyFeePaymentList
.stream()
.distinct()
.map(MonthlyFeePayment::getBgwContract)
.collect(Collectors.toList());
you get a Stream of distinct MonthlyFeePayment instances (based on the equals implementation of MonthlyFeePayment class) and then map them to BgwContract instances. Two distinct MonthlyFeePayment instance may be mapped to the same BgwContract instance, so the output List may have duplicates.
When you write:
List<BgwContract> contractListWithDuplicates = monthlyFeePaymentList
.stream()
.map(MonthlyFeePayment::getBgwContract)
.distinct()
.collect(Collectors.toList());
you first map the MonthlyFeePayment instances to BgwContract instances, and only then remove the duplicates with distinct(), which is what you want.
With dintinct() the elements are compared using the equals() method.
You have to override equals() for your BgwContract object.

Collect all values of a Set field

I have a collection which has a field of type Set with some values. I need to create a new set collecting all these values.
I am wondering if this is possible using lambda expressions.
Below is the code line :
Set<String> teacherId = batches.stream()
.filter(b -> !CollectionUtils.isEmpty(b.getTeacherIds()))
.map(b -> b.getTeacherIds())
.collect(Collectors.toSet());
The problem is post map operation, it contains a collection of set of strings. So collect operation returns a Set<Set<String>> but i am looking to aggregate all the values to a single set.
You need to use flatMap instead of map:
Set<String> teacherIds =
batches.stream()
.flatMap(b -> b.getTeacherIds().stream())
.collect(Collectors.toSet());
Note that the filtering is redundant for empty collections - streaming an empty collection will just result in an empty stream, which won't affect the final result.
If getTeacherIds() could return null, however, you'd still have to handle it. Using filter(Objects::nonNull) would suffice, and save you the dependency on Apache Commons.
You can use flatMap to obtain a flat Stream of all the values, and then collect to a Set<String>:
Set<String> teacherId =
batches.stream()
.filter(b -> !CollectionUtils.isEmpty(b.getTeacherIds()))
.flatMap(b -> b.getTeacherIds().stream())
.collect(Collectors.toSet());
If you care that that getTeacherIds() is not null, use it explicitly via !=, that CollectionUtils.isEmpty just hides stuff. Especially since if getTeacherIds() returns an Empty collection - that is handled just fine by flatMap, so to me that is not needed at all.
Set<String> teacherIds = batches
.stream()
.filter(x -> x.getTeacherIds() != null)
.flatMap(x -> x.getTeacherIds().stream())
.collect(Collectors.toSet());
I am wondering if this is possible using lambda expressions.
I capture the last fish, :).
Set<String> teacherIds = batches.stream()//v--- the class of `x`
.map(XClass::getTeacherIds)
.filter(Objects::nonNull)
.flatMap(Collection::stream)
.collect(Collectors.toSet());
Note: I'm sorry I'm forget to tell you if the getTeacherIds copy the internal IDs to a new set of IDs, the code above is appropriate for you. since it is read the IDs from XClass once.

Flattening streams from a singleton Optional

This question is related to the Java 8 map and flatMap that is present both in Streams and Optionals. It is worth to note that C# has a similar construct named SelectMany.
I have learned about the two methods, in particular that in Streams you can use flatMap to get a Collection<T> from a Collection<Collection<T>> which is what I want.
In my example I have a nested class structure (from a DTD I have no control over) in which I want to compute a sum of values. I will not redact the class names for laziness.
class DatiTelematico {
private Adempimento adempimento;
}
class Adempimento {
private List<DatiNegozio> datiNegozio;
}
class DatiNegozio {
private List<Negozio> negozio;
}
class Negozio {
private List<Tassazione> tassazione;
}
class Tassazione {
private BigDecimal importo;
}
Given an Optional instance of a DatiTelematico class I would like to sum (importo) from telematico join adempimento join datiNegozio join negozio join tassazione.
The best I could do was to use nested lambdas and plain map method
optionalTelematico.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.map(l -> l.stream().map(DatiNegozio::getNegozio)
.map(n -> n.stream()
.map(Negozio::getTassazione)
.map(t -> t.stream()
.map(Tassazione::getImporto)
.reduce(BigDecimal.ZERO,
BigDecimal::add))
.reduce(BigDecimal.ZERO, BigDecimal::add))
.reduce(BigDecimal.ZERO, BigDecimal::add))
.orElse(BigDecimal.ZERO));
I have tried to start writing something like
optionalTelematico.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.map(l->l.stream().flatMap(n->n.getNegozio().stream().flatMap(s->s.getTassazione().stream().flatMap(Tassazione::getImporto)))....TBD
But then I get a compiler error
Method binding must be directly contained in a class (OTJLD A.3.1).
How do I smartly switch from an Optional<T> (singleton) to a Collection<U> that is to be summed over?
I am asking this to increase my knowledge of Java lambdas.
Both Optional and Stream actually only represent a single piece of data - in case of optional, that piece of data might be either absent or present, in case of stream, there may be other pieces of data coming before or after, but in current moment we have this piece only.
Now,
map method is essentially a kind of type transformation for both Optional and Stream: mapping takes a function I -> R, applying which one can make transformation Optional<I> -> Optional<R> (or Stream<I> -> Stream<R>).
flatMap method is a kind of transformation that can:
Transform optional value into another optional (possibly empty). That means a function type I -> Optional<R>
Transform each item in stream into another stream (having 0..n number of elements in it). That means function type I -> Stream<R>. Note that for streams this operation can change the number of elements that are contained in stream (but it won't change the fact that there always effectively one stream element being processed at a time).
In your particular case, by making transformation to optional, you may obtain up to Optional<List<DatiNegozio>> directly:
Optional<List<DatiNegozio>> optDatiNegozio = optionalDatiTelematico
.map(DatiTelematico::getAdempimento) // Optional<Adempimento>
.map(Adempimento::getDatiNegozio);
Every List<DatiNegozio> you can easily convert to Optional<BigDecimal> summing and accessing elements via Stream:
static Optional<BigDecimal> sumImporto(List<DatiNegozio> datiNegozio) {
return datiNegozio.stream() // Stream<DatiNegozio>
.map(DatiNegozio::getNegozio) // Stream<List<Negozio>>
// unroll stream of collections into a stream of collection elements
.flatMap(List::stream) // Stream<Negozio>
.map(Negozio::getTassazione) // Stream<List<Tassazione>>
// again, unroll stream of collections into a stream of collection elements
.flatMap(List::stream)
.map(Tassazione::getImporto) // Stream<BigDecimal>
// last thing we need to do is just reduce
.reduce(BigDecimal::add);
}
As you can see, second snippet allows you to convert List<DatiNegozio> into an Optional<BigDecimal>. After this, you have two options (stylistic choice):
There is a variant of reduce that yields BigDecimal instead of Optional<BigDecimal>:
.reduce(BigDecimal.ZERO, BigDecimal::add); // it yields concrete type instead of optional because even in case when there is no elements in stream, we can at least return value from which we started - ZERO
You can use second code snippet to produce Function that is usable in flatMap-ing an optional:
optionalDatiTelematico
.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.flatMap(Example::sumImporto) // reference to method from 2nd code snippet
.orElse(BigDecimal.ZERO); // what to do if we had Optional.empty at any point
You can use Collection.stream() method to convert Collection to Stream and use it in flatMap. So combination of .map(d -> d.getList()).flatMap(Collection::stream) returns stream for all internal lists of Stream<D>.
In your case it can looks like:
Optional.of(datiTelematico)
.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
.map(Collection::stream)
.orElseGet(Stream::empty)
.map(DatiNegozio::getNegozio)
.flatMap(Collection::stream)
.map(Negozio::getTassazione)
.flatMap(Collection::stream)
.map(Tassazione::getImporto)
.reduce(BigDecimal.ZERO, BigDecimal::add);
Let say
Optional<DatiNegozio> abc = optionalTelematico.map(DatiTelematico::getAdempimento)
.map(Adempimento::getDatiNegozio)
Now when you say abc.map(xyz). xyz must be function which takes instance of DatiNegozio as one and only argument. In your case xyz is a lambda which takes one parameter l whose type should be DatiNegozio. You are now doing l.stream() which throws compile error beacsue stream() does not exist in l(instance ofDatiNegozio).

Nesting java8 streams to get a collection by Predicate

I am trying to get a collection of Objects, from a Collection using streams in Java8 based on some predicate
Here is what I have tried, but there is some syntax error in this.:
Collection<object> objectCollectionNew = objectCollection.stream().filter(o -> objectCollection.stream().filter( x- > x.isTrue == o.isTrue));
So basically I want to get a collection of objects out of the objectCollection based on my Predicate. Also I am unsure about how to collect this into my Collection<object>.
.collect(Collectors.to ?? )
Some help will be appreciated.
It's not entirely clear what you are trying to do, but if you want to find all elements of your collection whose isTrue is equal to isTrue of some other element of the collection, you would do it like this:
objectColelction.stream()
.filter(o -> objectColelction.stream().anyMatch(x -> x.isTrue == o.isTrue))
.collect(Collectors.toList());
It seems like you want to use a flatMap :
Collection<object> objectCollectionNew = objectCollection.stream()
.flatMap(o -> objectCollection.stream().filter(x -> x.isTrue == o.isTrue))
.collect(Collectors.toList());
I had a similar problem which looks like an error in the Eclipse syntax parser. I solved it by adding parenthesis
Collection<object> objectCollectionNew = objectCollection.stream()
.filter( x -> {return x.isTrue == o.isTrue;} )
.collect(Collectors.toList());
Just answering on the syntax aspect, not how to best solve your filtering itself.

Categories

Resources