alright so let's now look at the flat map method now we've already seen map
which can transform one object so that takes a function as an argument which
takes one object and returns a value so we may want to map a single object more
than one object and we can use a flat map method to actually achieve that the
method accepts a function that returns a stream value so we can pass an object
as the function argument and returns a string containing several objects which
means that were effectively mapping one object too many in fact that's the
intended use of the flat map method let's take a look at an example of using that
we're going to add a couple of classes to our project so i'm going to
create a new class employee and you can create them either as separate files
or just add them to the main java file i'm going to create a separate files
employee and one this employees obviously the one that we've used in the
past so I'm just going to copy and paste the code so you can refer to that
previous definition for employee we had the string name and the age
but whet we are going to do is we're going to add a two-string method there just to be able
to print the name so we're going to just override that to string we're
going....
so that's the only change we are making alright the other thing we're going to do is add
another class we're going to call this one department
this is a new class so we're going to create a string first....
....
.....
....
....so let's start create a few employees and add them to a couple of
department so....
....
.....
....
ok so now that we've done that let's suppose that we want to print all the
employees that work for the company going to start by creating a list called
departments and all our department to it so....
....
.....and
of course we could add a get employees or print employees method to the
department class and then iterate over this list and print the employees but
because we're working on streams let's use streams and have a look at how the
flat map method might help us do that instead we can do that with....
....
.....and noticing we got the option here department
I can press ENTER for that first declaration like so and then for the
expression i'm going to put....
.....that's how consice
it can be so lets actually run this to see that it works and theres our 4
employees so you can see that the flat map method wants a function that returns
a stream now each department in the source stream becomes the argument to the
function for each department we call the get employees method which returns a
list and then we call the stream method on that list to return a stream of
employees now the items in this stream were added to the stream that will be
returned from the flat map method in the case of a HR department we have gone
from one department object to 3 employee objects method is called flat map
because it's often used to flatten nested lists now in this case there are
lists of employees nested within the department list so the flat map method
is the one to use when we want to perform operations on the list but the
list isn't the source so an object containing the list as a source in this
scenario and we use the method to create a stream of all the objects in those
lists now another handy method is the collect method and this is a terminal
operation so it collects the elements in a stream into a different type of result
for example we can add all the items in the stream to list or set after all
unless the purpose of the chain is to produce a list of items to print we're
going to usually want to save the result somewhere now you might be using streams
to selectively reduce and sort a large list because you may want to use the
reduced list in subsequent code so i would use the collect method to create
the reduced list
so let's say that we wanted to save the result of our first example where we
uppercase filtered and sorted bingo numbers instead of printing the list we
could call collect
the collect method and store their contents of the stream at the end of the
chain into a list and then we'll print the items in the list so let's actually
rewrite that code to do that.....
....
.....
....
....and once we've done that the next line we can put
something like.....
and just before we do that let's actually put something here....
.....
....alright so let's actually try running that and there's our g numbers and this
is just telling us by the way that it could be replaced with the for each which we
discuss previously
alright so we got the same result as last time but we the difference here is
using the . collect is that we have a list that we can continue to work with
if we wanted to
now there are two versions of the collect method one accepts a collector
which is an interface to the java.util.stream package now underneath the
covers this version of the method maps the collector to the arguments required
by the second version of the method which accepts or expects rather three
arguments a supplier a by consumer accumulator and a by consumer combiner
so these arguments that must be
specific about how we want the items to be added to the result of collect the
collect method for example let's say we wanted to end up with an arraylist
rather than a list we can use the more specific collect version of the method to
actually do this let's just take a copy of this i'm going to just commented out
so we still got the code and I paste that back in let's actually make a change to
that so the stream the map and the filter and the assorted are all the same the
. collect is what we can change and we can do that here by changing this out
I'm going to delete that we're going to put....
....
....so on that line line 79
first argument to collect is the supplier if you recall a supplier create
objects now we want an array list so we passed the arraylist colon colon new as
the supplier which will construct a new arraylist for us by the way I went
passing method references we use the name of the class arraylist in this case
colon colon new when we want to pass the constructor
alright so moving on the accumulator is the arraylist . add method that's how
we'll add the items to the arraylist and finally the add all method is the
combiner so the accumulator is used when the runtime needs to add a single
version or a single item into the list the accumulator is used when the runtime
needs to add a single item into a result the combiner is sometimes used to
improve the efficiency of the operation if and when to do this is really up to
the java runtime now there are many APIs that so we can map to the supplier
accumulator and combiner and this allows us to do all sorts of things
using collect for example if we have a lot of employees and their are groups of
employees with the same age we can create a map of lists based on age using
the collectors . grouping by method
now I won't actually do this but i'll just show you what the code would look like
it will be something like....
.....
....
alright so the last method that we're going to look at now is the reduced
method and that combines all the items in a string into a single result now
they're actually three versions of this object we're going to take a look at the
simplest one which reduces a stream to one of the elements in the stream for
example if you wanted to use a stream to find the youngest employee in the
company would do something like.....
....
.....
....
so here we're using the version of reduce that accepts a by function which if
you recall accepts expects two arguments and returns a result now this function
compares the ages of two employees and returns the younger employee so the
result of the reduced method will be the youngest employee in the stream now we
use the if present method on line 93 to print the result because this form of
reduced method has an optional result the reduce method is a terminal
operation so the call to if present isn't part of the stream operation chain
as such here we are just chaining a non stream method called on to the stream
chain result if we run that you can see we've got the answers Snow White and
have a look at numbers again we can see snow white in this case was the youngest
employee alright a few notes about streams firstly we can't reuse them once
we've called a terminal operation on a stream will receive the
illegal state exception if we try to operate on that stream again operations
in streams are lazily evaluated
so what that means is that intermediate operations are not performed until
there's a terminal operation and look at an example of that shortly
also we can use more specific stream interfaces when we're working with lists
of numbers so int stream long stream and double stream for example now these
interfaces have additional methods like sum min Max and a few others that are
useful when working with numbers now there's also parallel streams which we
use when we want to increase performance by executing streams in parallel now
whether you needed to do that would be really depending on your application
parallel streams are really outside the scope of this section I just
really want to point out that we can get a parallel stream from a collection
by using the parallel stream method
alright so I mentioned operations in streams are lazy evaluated let's look at
that in a bit more detail and I'm going to create here a list of....
create a stream of strings and filter them so....
...
....
....if you recall
the filter method accepts a predicate which takes one argument and returns
true or false
so there's nothing stopping us from doing other things inside the predicate
now in this case we're printing the arguments that is passed to the predicate but
when we run you can see we've got nothing printed the last thing printed
was the snow white code from my line 93 why didn't we get anything printed here
well it's because stream operations are lazily evaluated in other words nothing
happens until there's a terminal operation now there isn't a terminal
operation in this chain and that's why it was not being evaluated and we think
about this
hopefully it makes sense if we have a long chain of intermediate operations
but no terminal operation then there's nothing coming at the bottom of the pipe
so it would be a waste of time to execute all the intermediate operations
so let's actually add terminal operations to the chain so come back
here and put.....
....so now if we run that we get our answers and we're actually seeing
the argument to the predicate printed on the console
ok so before i close off the discussions on lambda expressions want
to talk a little bit about best practices now throughout this section I've
intentionally written lambda expressions in different ways and here the
variations that I've used first one is must specify the types of parameters
vs letting the compiler infer them the second one was using a return
statement with curly braces for one statement lambda expressions vs not
using return because it's implied and hencec not requiring curly braces three
using lambda expressions that contain one statement vs lambda expressions that
have more than one statement and 4 using parentheses with a lambda
expression only has one argument vs not using parentheses since
they're optional when there's only one argument so if you look at the four
variations the two alternatives offered the choice between verbosity or
vs conciseness which in turn often comes down to the choice between
readability and conciseness
not all the time of course short lambda expressions are usually readable no
matter how concise they are but when striving for conciseness we can
sometimes write lambda expressions that are difficult to decipher because we
left out too much information now there are a number of articles and tutorials on
the internet that discuss best practices when using lambda expressions now many
of them fall on the side of conciseness but keep in mind that best practices are
guidelines not rules so when reading code that i haven't written or going
back to code that I wrote months or years ago
previously it's actually not unusual for me to come across a lambda expression
that's cryptic and takes a lot of work to figure out the question I would ask
is was it concise well yes it was it but was it readable to the human no so yes we
can optionally leave out the parameter types when the compiler can infer them but
will it be easier for a developer reading the code to do the same
now we don't have to include the return key word
when a one statement lambda returns a value but will a developer be able to
tell at a glance what the or that the lambda expression returns a value in
my opinion when it comes down to practices that are syntactic and don't
make a difference to the code that's generated the key thing here is to be
consistent so don't use different styles within the same file if you're working
on code someone else wrote and they always leave out the parameter types then
you should do the same now if they always use the return key word with one
statement lambdas do the same if you're writing code from scratch do what's
clearer and the easiest for you and also what you think will be clear for other
developers who may work on the code in the future
personally I'd rather see a verbose lambda than a concise one with a comment
because i know from experience that comments aren't always updated when the
code is and that can ultimately lead to a lot of confusion and waste of time
alright so that's it now i'm going to finish the video here now the next one
we're going to start work on the challenge so see you in the next video