- Hey everybody. My name's Donn Felker
and welcome to the Kotlin programming language course. I'm gonna be your
instructor on this journey. You're watching a nine
hour plus long video of the Kotlin programming language. Now, there are two ways to watch this. You can watch this nine
hour video if you prefer, or if you prefer to view
this in a more playlist like fashion with smaller bite-size lessons, there is a playlist
that's linked down below in the description. You can click that and
watch the same exact content just broken apart into
over 130 different lessons. The goal by the end of this
course is that you'll be able to start providing value
on any Kotlin project that's out there. We'll cover things such as
learning variables, functions, scope classes, how to work with generics, how to work with lists, manipulate lists, mapped various different
data structures, arrays, and we'll even talk about
lambda expressions a little bit. We'll wrap up the course with you creating your first Kotlin project
and be able to execute it locally on your machine. When you're done with this course, you should be able to walk
into any Kotlin code base and feel okay and familiar with the code enough to start providing value. Now, there are things, of
course, we won't be covering such as very advanced topics,
such as code routines and flow and things like that. However, once you have the
solid fundamentals covered that are in this course, you'll be able to hop right
into those topics with ease. Now there's one last
thing I wanna let you know before we get going. The installation procedures of
this can change drastically, depending upon when you're
watching this video, which means is when you
install the Kotlin IntelliJ community edition, which is
covered in the first few minutes of this video after my introduction here, it may look a little bit different. The webpage will probably look different, the installation might
look a little different. And the first page where you
start creating a new project might be a little bit different. So what I recommend is once
you get IntelliJ installed, following the instructions
you see on the website, create a new project and make
sure you just select Kotlin. And then from that point forward, everything should flow smoothly for you. Now, I look forward to you seeing you at the end of the video
to congratulate you. Until then if you have any questions, feel free to reply below,
and I'll see you at the end. Good luck. To get started with Kotlin development, you'll need an integrated
development environment, also known as an IDE. You can get one for free@jetbrains.com by going to tools IntelliJ. Next, find the download
link and click that. From here, you can download
the community edition, which is free for the
platform of your choice. From windows, Mac OS to Linux. I'll be using Mac OS in this course, so I'll download the community edition. Once the file is downloaded, you wanna go back to the download page and follow the installation
instructions for the platform that you're running. I'm not going to walk
through all the installation instructions for each
platform as they do change from operating system to operating system. And at times the installation instructions also changed themselves, which brings me to the website
that you're viewing now on your screen may be
different from the website that you visit in your browser. If that's the case, be sure
that you just find IntelliJ find the community edition and find the installation instructions. Once the application is
installed and you're ready to use IntelliJ, you can start developing with the Kotlin programming language. Once you have IntelliJ installed, you'll start the application
and you'll see a window that looks something like this. You'll click create new
project and you'll have a list of various different
options on the left here. If you wanna go down to
Kotlin and select Kotlin JVM, click next, go ahead
and give it up by name. So we'll call it Caster Kotlin. You can give it any name
that you would like. You can leave the project
STK, the libraries and everything as default,
and we'll click finish. You can go ahead and
hit close if you receive a tip of the day and you can
close these windows down here, these little notifications. On the left-hand side here,
you'll see that you have what's known as the
project structure window. So we can view the project packages and various different sustains
inside of this window. The first thing that you wanna
do is expand this folder. You'll see an SRC folder. This is where our code is going to go. So to get started, let's
create our first file. Right click on the folder,
go to new Kotlin file class and let's just call it main.kt. KT is extension for
Kotlin files, click okay. At this point, you're
ready to start writing your first line of Kotlin. If you receive a notification like this, you can choose to install
the new Kotlin plugin if you'd like. If you do not receive one
of these new notifications, you can go ahead and disregard
this part of the video. You will receive these
types of notifications when new versions of
Kotlin and the new version of Kotlin plugins have been released. You can go ahead and click install. It'll download the new plugin
in the background here, and it'll show you the progress here. This is the plugin
that's for IntelliJ here. You can actually see these
plugins by going to preferences. And then you go to plugins. Go to preferences, you type plugins here. You can see all the various
plug-ins that are installed inside of IntelliJ here. And you could search for
Kotlin and you'll see that we have just recently installed it, so we would need to restart the IDE, as you can also see through
this window down here. After you click on the restart button, IntelliJ will restart and
the new version of Kotlin will be loaded. We can close this window and we're back in our main.kt file. At this point, the Kotlin
plugin has been updated inside of IntelliJ and you're ready to go. To create your first hello
world application in Kotlin, what you'll do is create a
main function in your file. And that's just gonna be fun main. You have open and closed parentheses and then open and close curly brackets. And anytime type this code
inside of IntelliJ in any Kotlin file, you're gonna get this
little play button here. And with this play button
will do allow you to run it, debug it and so forth. And we'll talk about
that here in a second. Now, if we were to run this here, we would get nothing on the screen. So what we wanna do is we
actually wanna print something to the screen. We wanna print the words, hello world. Now the print ln function is
actually built into Kotlin. This allows us to print a given message and align separator to the
standard output stream. The standard output stream at this point is just gonna be inside of
our run window down here. So if we go back to our main button, hit play and hit run main.kt, you can see that compiles
and it completes. It may do a little bit
slower on your machine, depends on the performance
of your machine. And then here in the output,
you'll see, hello world. And with that, you've created
your first hello world in Kotlin. Variables are the underpinning
of your applications. To create a variable in Kotlin,
you'll use the var keyword, and then you provide the
name of your variable. In this case, we'll call it a full name and then you set it equal to a value. Here, I'm sitting at equal to
a string value of Donn Felker. We now have a variable called full name and its contents are Donn Felker. If we wanna print this to the screen, we can use the print ln command, and then we can go up and
run it from this play icon and hit run main KT. It'll compile, as we can see down here, and the output will show as Donn Felker. Now, I run this. We can see Donn Felker and John Felker. We can also set this value
tool to a variable that's empty and print this out as well, even though we're not
really gonna see anything, what they full name that's
empty, we can run here, we'll see, just kind of an
extra line breakdown here. Instead of one, two,
there's now two line breaks because we have an empty one here. And also working with variables
once we have access to them, we can start accessing
properties of these variables. So let's say that we wanted
inspect some properties of the full name and we
wanted to see if it was empty. I can say is empty and that's a method that's on this property. So we can check to see
if the full is empty. I'm gonna hit command B
to go to the declaration and we can see that it
returns this method, which is called is empty returns true if the char sequence is empty. So char sequence a string. If this is empty, it's gonna
return true, otherwise false. So if we run this, we'll get
Donn Felker, John Felker, a blank line and then we're
gonna see the word true. So we'll run that here. And then you can see
in the window down here that we see Donn Felker, John
Felker, blank line, and true. The key thing to remember with variables that use the word var is
if you need to change them, if you need it, change
of variables at any time, you'll want to use a var. If say age equals 32, we can reset it and there's not gonna be
any problems whatsoever. I noticed one additional thing. If I were to type the
word var again on age, we get a red squiggly line. And what this says is there's
conflicting declarations, meaning we cannot define the variable age twice within the same scope here, which is inside this main function. So if I wanted to reassign
it, I wouldn't say var again. I would just simply say age equals 32, and then I can reassign it. I can also print this out
to the console as well and type in age and we'll see that the age is going to print 32 is
initialized it with 30, reset it to 32 and now
it's gonna show as 32. One final note is that the age variable was also implicit to an integer. So again, we did not specify a type. The compiler was able to determine based upon what we set this to, that the value is an integer. You can create mutable variables in Kotlin with the var keyword, but
if you'd like to create read only variables in Kotlin, you need to use the val keyword. So let's try the same
thing that we did before. We're gonna create a new variable, but this time we're going
to use the val keyword, and we're gonna set it to Donn
Felker like we saw before, and we're gonna print this to the screen. And at this point, nothing
has really changed. The output's going to be the same. We've created a variable called full name, it contains the contents, Donn Felker. And when we, we run it, we get Donn Felker down here at the bottom. However, the big difference
here is when we use the val keyword, we're
creating a read only variable, which means I can only assign it once. Therefore, if I want to
assign the variable again or I attempt to I'll
change this to John Felker, and then we automatically get
a red squiggly and IntelliJ because IntelliJ Kotlin plugin
realizes there's a problem. And what is saying is the
val can not be reassigned. So even if I say, "Hey, know
what, I'm gonna ignore the IDE "you want it to run a compiler." I try to compile this, we're
gonna get a compiler error. It's gonna say, "Hey, look,
we couldn't compile this "because there's an error." Vals cannot be reassigned here
and it's on line number five. So we go over here to line number five, and there's our problem, full
name cannot be reassigned. So once we have a variable that's created with the val keyword,
we can not reassign it. And the same goes for other types too. So if we were to use age
and say 32, that's fine, you can print that to the screen as well. And then if we tried
again to reassign val, we could say that's 42 now, we could see that this
is not going to work. And again to verify it, we'll
run, which will fire off the compiler will say, hey, that the val cannot be reassigned. We're on line eight right here. That's what this first
number is, the line number. And we're on line eight and its
val 42 cannot be reassigned. So we cannot reassign vals, so these are read only variables. These are great for things
that you do not want to change in your application, you don't
ever expect them to change. And you'd like to verify
that and enforce that through the compiler. Up until now, all variables
have been implicitly declared. Therefore, if we have a
variable and we call a full name and we set it equal to a string
equivalent to Donn Felker, the type is going to be a string. And we can actually verify that by doing a little bit of inspection
here on the actual type. So I print the full name and
do colon colon and tell it, "Hey, what class does this
full name actually belong to?" And if we run it, we can actually see that it comes back as a Kotlin string. So we're working with a string here. Now, there are times that
you would like to specify the type of the variable explicitly, and you can do that by providing a colon after the name of the variable
and then providing the type. So the here would be string. I would see Donn Felker. If perhaps I put the wrong type here, perhaps I tried type integer
and then I set it equal to Donn Felker, we get actually an error. And it says, well, the type is mismatched. We require an int because
the full name variables and so you're trying to set a string, that's just not gonna work. And if we try to compile
it by skipping the IDE, it's gonna say the same thing. The inferred type is
string, but the actual, but int was expected. So either we have to
change the explicit type or we need to change their value here. So for example, if we wanted a age and we wanted to explicitly
provide the type, we would provide the type here. So anytime you'd like to provide
the type for either a val or a var, you just have to
type the variable name, colon and then the type that that's
going to equal to explicitly provide the type to your variables. Kotlin has many built-in types. Let's go ahead and
explore the number types. The lowest number type is a byte. So I'm gonna create a
variable called myByte and it's of type byte and I'm gonna set it to a value of eight. Bytes are equivalent to
eight bit signed integers. The next one up is a short,
so I'll call this one myShort and will explicitly
provide the value of short. And I'm gonna set this
one by two equals 16, and this is a 16 bit signed integer. So as you can see, as we move up, the numbers are going to get bigger, meaning that the values
that they can store are going to be bigger, which
brings us to the most popular, probably in regards to
program that you see, and that's going to be the integer value. And this is going to be
equivalent to a 32 bit signed integer. Next up we have a Long. Now long is a very long number, so it's kind of a good name for it. It's a long number that you could use, and that's going to be
equivalent to a 64 bit signed integer. There are gonna be the most common types that you use when you're
working with numbers. So you're gonna be using these. And most often you're
gonna be probably be using an integer value as it will
cover 90% of your use cases. If you need to use a long
number of sorts of very long, then you wanna use a Long. And those times a new do wanna use that, perhaps you're storing numerical values that are very lengthy. However, there are also
times when you do need to store decimal value. So if you do need to score decimal values, there's also something known as a float. And so say like this, a float, and we're gonna say this
one is equal to 32.00 and I'll put an F here 'cause that means in literal terms that this is a float. And what this thing is
equivalent to is a 32 bit floating point number. So, this is a smaller decimal we can use. Now, if you need to use a larger decimal, you're gonna use a double
and that's gonna be called double and that's equivalent to 64 and it's gonna be a 64
bit floating point number. So, these are kind of the basic numbers that you're going to use inside of your day-to-day
application development. Now there's something interesting
about each one of these. If we go to the definition
of any of these, and I'll start with byte, we can see that byte extends number. We can also go back to
the previous file short, when we'll look at that,
it also extends number. And as we walk down, each one of these, you're going to notice
that every one of them from integer to long to a
float, to double et cetera, all of them implement the
number abstract class. So let's go take a look at that again, what does that look like. If we go to the number implementation, we'll see that it has a
bunch of functions on here that we can access, which is interesting. Which means if we go talk
about this for a second, that every time, every
object, everything in Kotlin is an object. So even this integer value is an object. This long is an object,
this byte is an object, which means that we can call methods and properties on these. So if I want to print ln the, myByte. Perhaps I want to perform
division operation. There's a bunch of map operations
you can perform on here. These are built into there from
the various different types. If I want to say to double,
I could say to double, and that would turn my bite into a double. And this actually comes from
the number class, remember. If we go to the parent
declaration, which was command do, we could see that this
came from two double and so it returns a double. And so it would take this byte and turn it into now a double. So if we were to print this and let's get the type information by
providing colon, colon class, and then running it, you would see that it
does return a double. Now, if we were to come over here, we could actually change this to a long. We could run it again and you
see that returns say long. So here we go, eight
and we forgot to provide the values there, say class, run it again. And we'll see that we now
have class Kotlin.long. When working with certain
numbers, such as longs, we can take and we can use the literals and we can say big long, let
me say this is a long value. And then we can actually
do some cool syntax. And so what this does is it's
built in syntax to Kotlin that makes it easier to read. So this is equivalent to one million. So if I were to print
line and we were to print this big long in here and run it, we would actually see that
this is just going to print out as one million, but it
makes it just a lot easier, these underscores do,
because you can read it. This is very valuable
if you have a constant in your application that
you know is always gonna be one million, or it's always gonna be 1000 or something like that. You can make this a lot easier to read, because let's say you're
gonna go for one billion, this is much easier to read than this because you're not sure
by looking at this, is that one billion or
is that 100 million? It's really hard to tell, but just by adding
these simple separators, we can actually tell real easily that that would be one billion. One of the additional things
that we can do with numbers is we can of course add them together. So we could say my int. I'll say new int equals my int plus 12, and that would equal 44, but we can print that to
the screen and say new int. And when we run this, we
see down here in the bottom that we're going to get 44 right there. But we can also do an additional thing by using the built-in methods. Plus, I'm gonna say 12. Now we can run that here. So there's some nice
syntactic sugar to say plus, we're gonna get 44. We can also do minus and we can run this and you'll see that we also get 44. And there's a bunch of
other map operations that we can inspect inside of here by simply providing the dot
to get the code completion and implement them. And so if you're not familiar
with a much of these, you can hop into there. And let's say, for example,
you're not too familiar with the word and is,
you can take command B and go to that and say,
we perform a bit wise and operation between these two values. If you wanna do bit rise map,
you can do that with the and, you're could hit plus, you
can do multiply or times and then you could run that. So 32 times 12, it's
gonna run here by 384. So these are the basic types
from smallest to largest. We're gonna start with
byte, short, int, long, and then we'll get to decimals, you have float and you have double. These are the built-in
number types in Kotlin. In Kotlin, strings are
also known as objects. So if we stick with our traditional
example of the full name and we explicitly define
the type of string and set it to Donn Felker,
we now have an object known as full name that contains
the value of Donn Felker. We can call different
methods and properties on the full name variable by typing dot to get perhaps the length. This will provide us the length here. I'm gonna duplicate this
line by pressing command D. I can also get the actual characters which are gonna provide us
a extreme of characters. I can do a whole bunch
of different other things on-site of here as well. So for example, I can decapitalize it. So let's do decapitalize. And if I were to run these,
we're gonna see that we have Donn Felker and then decapitalize
was decapitalizing this, if this was a sentence, so
I could say, hello world. If I were to run it again,
we can see that the sentence Hello world is decapitalized
by decapitalizing the first letter. If I realized I wanted
everything to be lowercase, let's say two lower case
and I could say run, and then that would actually
allow me to create this as all lowercase. Strings are built inside of
Kotlin with double quotes. So we could just say, first name is Don, and that's a double quote. There's also another type. Anytime we're working
with like characters, we can then use what's known as a char. So I could say file C and that could be of type char equals X. Hold on a second, here's the weird thing. It's only a single character,
why is it complaining? That's because in Kotlin, if
you want to define a character, you have to define it
with single tick quotes. So single quotes allows
you to implement a char, which has a single
character representation. Now, there are some things
that are some caveats to this. So there's also characters
such as the tab character that you have to escape with a slash T, there's the end for the new line and of course the double
slash for backslash and various other
different types of escapes that you're going to try to do. In Kotlin, a char represents
a 16 bit unicode character. So anytime you need to
use a single character, you're going to wanna use char. Now this doesn't limit you from also using a single character string if you would to. You could say string foo equals Y, and this would still work. You could still work
with a value of string. However, a string is going
to be larger in memory, than a character. But you could still use a
string if you'd like to. However, if you know that
you're only going to be working with single characters, then
you would use the char type. Sometimes when you're
declaring your strings, you need to declare
them in multiple lines. So I might say, I have a message here and this message would be hello, I put a new line here. My name is Donn Felker, how are you? Then I would print this to the screen. And then once it's ran would
see a nice little message here, Hello, my name's Donn Felker, how are you? However, sometimes this gets kind of ugly and there is actually a better
way to do this in Kotlin, and you can actually use
the triple quotes here. You can say, hello, my name
is Donn Felker, how are you. And then I'm just gonna run this again. And I'm using the shortcut key to run it, which if you see here is
actually just going to be a control+shift R on a Mac and you notice the same
thing came out here. Hello, my name is Donn Felker. But what does this trim indenting? What is going on here? This is basically a
rostering triple quoted and then we have the
trim and dent function, which basically, if we look at it, does it detect the minimal
indentation of the input lines and removes it from every line. So if I remove this line, what
you're gonna see down here is this is actually going to be indented. It's actually gonna have a new line, and then it's gonna be indented. So let's watch this, we'll run this again. And you can see there's a line above and it's all been indented. So if I put the trim indent
back, what it basically is doing is removing all of this and
the new line that's up here. I'll rerun it and it
shoves it back in there. Now I can also do something
called replace indent. Like, for example, if I
know that maybe I'm building some string and I'm kind of
building this stuff up manually, I can replace that indent with something that looks like ABC, and
some, maybe a pipe symbol or whatever like that. And if I run this, it'll
replace that indent, which was all those things
with these on each line. So ABC hyphen pipe, hyphen,
which is kind of cool. So if I'm building something,
I can actually kind of provide these pre formatted things there. I can also say my trim margin
and I can provide a prefix. So maybe I've decided that I already know that I just want that prefix
to be these three arrows. So I'll run that and it
will run and it says, "All right, well, I didn't trim anything "'cause I didn't find these three arrows." So what this means is these three arrows would need to be right here. Let's say for whatever reason, that string had these three arrows. We'll run that and there we go. Now it's not gonna print those. If I remove this, we'll
actually see a print with those three arrows
and this area here. Now by default, trim
margin has default itself to pipes, which means I
could set this to a pipe. I could run that and there we go, it's just gonna replace
those pipes by default it checks for pipes. So you can do a nice string here, multi-line string with triple quotes. Another thing you can do is
let's assume that you have a name and we'll just call it Don. And sometimes you want to print
a message like we have here. Let's print this message, Don. Let's say, we wanna say hello to Don. We'll say hello traditionally
in most languages, you just do string
concatenation, which works fine. We'll hit Enter and it says, hello Don. Now it works fine. Now notice something, there's
a little squiggly here. It says convert concatenation to template. So you can actually use
string interpolation in two templates. So I hit alt enter there
and it gave me this pop up and I hit enter again and automatically converted this code for me. And this does the same exact thing. And now what this does is
actually prints Hello Don. So it's actually one big string. And then we could actually
have one here to say age, and we'll just say 32. And it would say, hello, hello Don, it'll name, your age is and
we put this thing in here to age and it would print
the age inside of here. And now one other thing
that we can also do that's cool inside of these things is say something like this. So hello name, your age
is, and your name is, we would say dollar sign. We put open curly braces
'cause now I need to call a property or method
and that's a name dot. So name.length. And that's what this says is it would say, Hello, Don, your age is 32 and your name is what,
before care actors log. So let's run that. So hello, Don, you're age 32 and your name is four characters long. So now instead of using a
very complicated replacement or anything else like that, I can use a string interpolation and interpolate these variables. If you're not going to be
calling into the object graph at all, so you're not
doing this dot anything. Then you can just call
dollar sign variable name. Otherwise you need to do dollar
sign, open curly bracket, put your code and your
expression inside here and close curly bracket and that's it. Last, but certainly not
least is the Boolean value. And we could specify one like this. So we could say is blank. It would provide us a
truer val false value, and we'll just initialize it to false. And so this just basically has two options and that's true or false when
we go to the implementation. Again, everything in Kotlin is an object. And so there's some other
built-in operators here. So we can do the inverse of that. So if we wanted to know, for
example, let's just print this. Print ln is blank and then we
can just do print ln is blank, and then not, and this will
print the inverse of it. If we run it again, we see
here in our output window, of course we had false
and then we have true. So we initialize it to
false and then true. We can also initialize it, of
course, using other variables, such as a string, so it will say is blank. And then we can use a string. So we go blank, that is blank. So function is blank on a string. So this is a char sequence
function that checks to see if they link the zero
and return the Boolean value. And if it's zero, then
it's going to return true. So we can print this off here. I'm just gonna copy this and
then we could see the value true or false. So we'll run this and of
course it's blank of course. If we change this to two first name, Don, and we run it, it's gonna end up false. Then it will say false, of
course we did the inverse, which is not. And there's many other
functions on this Kotlin object that you can also inspect that you can do, which is gonna be or, X or et cetera and a bunch of other ones inside there. You can check them out by navigating to the Boolean section there. But if you need something that
represents a true or false condition, you'll want to use Boolean. Working with conditionals
is a very common thing that you'll do in the Kotlin language. Let's assume that you're
building an application which will track your calories. And let's assume we
start with 2,500 calories and these would be the calories
that we consumed already, or the user has consumed in their tracking for their diet purposes. Now let's assume that we
had a requirement that says, if the calories that have
been consumed are over 2000, then we have to print
a message to the viewer or the end user to let them
know that they've eaten enough, they've consumed all of
their calories for a day. So, here we have see that they've consumed all their calories for a day. Now, if we run this,
you're gonna see down here in the bottom window that we can see that they've consumed all
their calories for the day down here at the bottom. Now, if we were to change
this, let's say to 1900, we're gonna notice that
the line of code here on line four does not get
run because the calories are less than 2000. So that means we're not
gonna print this line. So let's say for some reason,
we wanted to check to see. Otherwise, if they haven't eaten all 2000, we can say something else like, okay, you still have some calories left. So I'll say print line. And if we were to run
this, we would see now that you still have some calories left. So we know that again,
this is a conditional, if conditional, and this
is the else portion. So if there are over
2000 calories consumed, this line of code is gonna be printed. Otherwise, else we're going to print this. So any other types of, if it
does not match this condition, so anything less than 2000
is gonna show up here. So we even just put this
to 2001 and rerun this. You're gonna see that we have consumed all of our calories for the day. Now let's assume that
you're working with someone and they decide, or you decide in your app that you would like to make
sure that if they've consumed over 1500, then what you would like to do is give them a little bit of motivation and to let them know they
had a little bit of room to have like a snack or something. And so you can say, if
calories is less than, excuse me, is greater than 1500. So here, perhaps it's a 2000. So if the calories are greater than 1500, we can say print ln and say, you have a few more calories left to eat. And so if we run this again,
what we're gonna see here is still the top line. You've consumed all your
calories for the day. Let's go ahead and make an adjustment saying we've eaten 1750 and
we were running our program and the program would now say, you have a few more calories to eat as we can see down here in the bottom. And then for some reason,
we even set this to 1499, which means this condition will be false. And then it checks this condition, this condition will be false, which means anything in the
else block without be printed. So let's run that and we
can see that you still have some calories left, which is pretty cool. So you can keep adding on these
various different else, ifs. So, if we wanted to add another one here, we can add another else, if as well. And we could say something else, like calories is greater
than 1200 and perhaps we just wanna give another
message here like this. And then if you were to run
this and you were to set this at, let's say 1400, and
we were to run this, you could see down here at the bottom, you have some room for snacks. So we can not keep adjusting
this and play with this. And there's no limit to
the amount of else ifs that you can throw in here. So if you have many conditions, so if you only have one
condition, it may just equate to be something like this. It's very simple, your if or else. You do not have to have an else either. If you don't wanna have
an else and you just need to perform some type of
action only if one value is true here, then you
go ahead and print this. So else is not required. The else ifs are not
required if you're using an if statement. So again, just an if statement
or you continually add else ifs and we can continue
to add these if we wanted to. So we could add another one here and we could say, calories
were greater than 1000, you can say, you have plenty
of calories left, et cetera. And as we go down, so again,
if we were to go to 1100, that line would now print, you have plenty of calories left. And if again, we dropped
it down to let's say 800 and we ran it, which one
you think would run here. It's actually going to
be the bottom one here because the calories is
not greater than 2000. It's not greater than 1500,
it's not greater than 1200, it's not greater than 1000. So therefore the final
conditions going to run. And this is a very simple,
if else conditional that you can use. These are gonna be the most
traditional type of if elses that you see in your programs. In Kotlin, you can also
omit the closing parentheses and open parentheses of a
condition if it's a one-liner. So let's say if the
age is greater than 10, you can actually just write
ln is greater than 10. So let's go and change it
actually two 11 and we run it. We're gonna see that the
output in a window down here as the age is greater than 10. Now, if for some reason
we change this to nine, notice how the steel yet
we don't get an error, but we don't see that the
age is greater than 10 because a single line statement
can now just be printed without parentheses. However, this should be often avoided as it can be confusing even if I divert to do something like this. It's hard just to type this. Just basically upon the indentation, you would think that hello
there would not be printed, but however, hello there is
not part of the if condition. The highlighted area you see here is part of the if condition. This print line, hello there is not part of the if condition. So if you know that you're
gonna have code here, it can be very confusing. So it's usually often
better to just go ahead and provide the braces as
it's much more readable. And when you're writing code, you should be trying to write code that you can understand in the future. Now, one thing I did
mention for a quick second was the word one-liner,
or words one-liner. What that means is you
can actually put this all in the same line here. Now, if I were to run this
it'll compile and everything, and we'll just see the words, hello there. And that's because it'd be hello there, that's because the age is
nine and we're looking for age greater than 10. So let's change this to 11. And then if we were to run
this, this single line of code is actually pretty easy to read. So now, if you're just
gonna write a single line, this is very readable and we
know that this line below it, hello there is not going to get executed. So if you're just running a single line, it's very often you'll see
this and you can remove the brackets as I've done here. And if again, we're gonna run this, we're gonna see the two things here, the age is grater than 10,
you dropped this to nine and we're only gonna see
the words hello there because if condition has not been met. Now, you can also take
this a step further. If you know that your, if else condition might be very short, you
can actually all do this on one line. So print line, and we could
say the age is less than 10. And so we can actually have
the entire if else condition right here on a line. And it's very small,
it's very easy to read. And so if we're to run this again, we're now going to see down at the bottom, the age is less than 10 and
then the words, hello there. If I change this to 17
and we run it again, we're not gonna see that
the age is greater than 10. And of course, we're still
gonna see the words hello there, because it's printing its own statement. So you can definitely
create if else statements on a single line and it's advice to do it only when there are a single line. It's also good to mention
if your else statement becomes very, very long and
you have to continually scroll, and there's just a lot of text over here. So if I were to say, my
content went way over here and in the editor, I had
to kind of look over, you'll see this little gray line here. This gray line is kind of
the recommended line break. So if you had a bunch of
texts and I'm just gonna type a bunch of garbage in here. If I had this line and went beyond here, this recommended line is where
your line breaks should be. So if it goes past that line,
you should probably consider breaking this into a
traditional if else statement, which we can just put
brackets there and the IDE will apply them for us. Now it's much easier to read. It doesn't require that I
do any horizontal scrolling and makes it easier to understand the code by just looking at it. A fundamental understanding
of truth tables will help you understand what's going on
inside of your if statement and if it will evaluate to true, or if it will evaluate the
false and execute this line down here. So let's hop over and take a look at some simple truth tables. Here, I have created two truth tables, one on the left, which is the
logic well conjunction and which we'll talk about first. And then we'll move on to
the logical conjunction or. A logical conjunction and state
that we have two variables, P and Q. And this is red on the
light right hand side. P and Q, so you'd be at P the up arrow Q and it's red as P Q. And that's what it evaluates too. So if P is true and Q is true, then if we were to combine
them, P and Q would be true. If we have a P that is
true, but that is false, well, then that would equate
to a false representation in this truth table. So if the first expression,
so if we go back to our code, if our first expression was
true, our second expression was false and we're using a logical and, this is an and operator
that the Emerson Emerson means logical and, then
the false will be executed because the second item here is false. We can move on to the third line. If the first item is false
and the second item is true, it's still gonna be false because in order to get a true value, we need
both of them to be true. So back to the code, this one
at this point would be false and this one would be true. If we were to add these together, that would not equate to true. So we will be executing the
else statement down here. And lastly, if both the items
are false, then of course, if we logically and them together, we're going to get a false statement. So back in the code, if both
of these are false up here, this is false and this statement is false, then of course, it's
gonna execute down here. Now on the other side of the fence, we have a logical conjunction or. Now this is operated in code, let's go back to the code here. And we change a or two B with two lines. And so that's gonna be a logical or. So what this says is here is
this statement has to be true or this statement has to be true. And if either one of these are true, if this one's true or this one's true, then it's okay to continue on
and execute this line of code. However, both of them are false, well, then it's gonna
execute this false statement down here in the else. And so let's take a look at them again. We're using P and Q
and it's red as P or Q. And that's that little V thing there used to be a down arrow. And so if P is true or Q is true, then of course the resulting
truth would be true. If P is true and Q is false,
well, we're still saying, "Hey, either one of these is fine, "so let's go ahead and move forward." And that would be basically
like saying if this is true, then okay, we can continue. Or if this is true, that's
okay, let's continue. Same thing here, if the
first item in the evaluation is false, so P is false and Q is true, then our logical or conjunction
would equate to true. Then our logical or conjunction
would equate to true. so this equates to false. So false, it's gonna go
to the next item over here and it's going to return true. So it says, "Oh, I found a true here, "which means I'm going to
execute this line here." And lastly, we have the false or false. So if both of them are
false, back in the code. if both of them are
false back in the code. which means they're both false
means neither one of them are true, then it's gonna
execute the else statement. There's one thing important to note is that these conditionals, when we use the logical operation or,
or the logical operation and are known as short circuiting, which means that if this
operation here is false, it's just gonna skip this together. It will happen is Kotlin will say, "Oh, you want me to add these together. "So I'm going to need this value." What it's gonna do is at
runtime, it'll check say, "Hey, is this false? "Okay, I already know this is false, "which means this whole statement
is gonna equate to false "because the first one's already false "and you're asking me to
have both of them be true." So what you would then do
is, Kotlin would then do, is say, "All right, well, I'm
just gonna go and skip this. "I'm a short circuit this because
I know that there's no way "and no reason for me to
even run this code over here, "because I've already
determined that this first piece "is false and we need
both of them to be true "in order to continue." On the opposite side of
the spectrum we have or, or this could be short-circuited here. So for example, let's
assume that this was true. So we're basically saying if this is true, or this is true, print
this statement right here. So what this would mean is
Kotlin would short circuit. If this was true, it
wouldn't even bother checking to see if this was true at all. So it would, short-circuit
this entire operation. So, all right, this is true. All right, cool, I'm
not even going to worry about executing the statement over here. However, if this was false
right here, it would say, "Okay, this is false, so I
need to go check the next one, "because I'm going to logically or them. "And I'll check to see
if this one is true." And it says, "Oh, this one's true. "Okay, I'm going to use this." Now, this could continue on as well if you had a, for example, we could have another condition here, we
might say, my age equals equals my name.length. And so we can actually
have three conditions here. So this one right here
might equate to false. This one might equate the true. And if it does equate the
true it's going to skip the rest of the conditional here, because we've already found
a true and Kotlin knows, "Hey, we're just trying to
or all of these together." And so understanding basic
truth tables is a humongous help in understanding of what's gonna happen in your conditional statements. In Kotlin, there are
two types of equality, structural and referential. Let's assume that we
have two string variables by the name of name one and name two, both containing different strings. We could compare them and
see if they're the same by using the structural
equivalent operator, which is the equals equal sign to say, "Hey, is name one equal to name two?" And if we were to run
this, it would output to the terminal window
saying that it's false, that named one does not equal name two. Now this is very similar
to other languages such as Java's equals method,
which you can also use. And you'll see this here. And if we run this, we'll
also get false here as well. Notice this squiggly line here. If you put your cursor
there and you hit alt enter, you'll get a pop-up. Otherwise you can just click
on the little light bulb and say replace with equals equals. This is the more idiomatic
way to do it in Kotlin. So this says, hey, is name
one equal to name two. Now there's also, if
we were to change this. So let's say that this was
changed name two to Don. This would then evaluate the true because the structure of
the contents are the same. So they are the same types, same types and the same contents. So structural equality is true. And so if we were turned this
back to char, and we say, "Hey, I wanna see if these are different." We can use, what's known
as structural inequality, and that's going to be with
the exclamation and equal sign. Now, if we run this, we're going
to see that we have a true, that Donn does not equal to char. Now, if I change the
second one to Donn again, what do we think is gonna happen here? Donn does not equal Don. Well, that's gonna be
false because these two are exactly the same. So that is the way that you
can use structural equalities inside of Kotlin. You're going to get the first
one, which is equals equals is the regular equal equality. And exclamation equals is inequality. The other type of equality checking is referential equalities. And we can determine those
using a different operator. So let's assume that we have
one variable that's equal to 12 named A and another variable
that's equal to 12 is B. And since every object,
everything in Kotlin is an object, these, you would think
would be different objects. So if we were to run this, we're gonna use referential equality with
the three equal signs, which is referential quality saying, hey is A equal to the same object as B, we were gonna get true. Which if you know about
references and memory, this was not making any sense, except there is a caveat in Kotlin and that is number,
characters and Boolean. So numbers and Booleans,
all have a special internal representation and they're represented
as primitives at runtime. But to users, they look
like ordinary classes. Therefore the triples equals
is the same effective thing as the double equals when
we're using primitives. So let's get rid of these
primitives here and let's go ahead and we're gonna jump ahead real quick, and we're gonna create a class. And this is a very simple class
with a user who has a name that is a string. And let's set person A equal two person, and we'll give them the name of Don, and then person B will also
have the same name, Don. And then what we'll do
is we'll do referential, a referral equality check here, run the program to see if
object A equals object B. And as it runs down here, we have false, which means that object A
does not equal object B, meaning that it does not
share the same object, it's not the same object whatsoever. We could also change these things around and do referential inequality. So we would do exclamation equal equals, that would say object A
does not equal object B. So they're not pointing at
the same place in memory. So for, to run this, you would see that we now get back true. Then we're getting back
true because person A is a completely different
object than person B, they're stored in completely
different parts of memory. So for referential integrity,
we have the triple equals, which is equality. And then we have
exclamation equals equals, which is inequality. One of the goals of Kotlin's type system is to eliminate no reference exceptions. So if you two were to take
this existing name variable and set it equal to null, you would notice that you would not be allowed to do that. And the reason being
is this string is known as a non null reference. If you wanted to create
a nullable reference, you would still say string, but you would add the
question mark at the end. This would allow the name
to be reassigned to null. The same goes for other
types as well, such as age. We might say, this is int, we've got 32. However, if we tried
to assign age to null, this would not be allowed either. So anytime we would like to make maybe age or any other type, we
need to remember to add the question Mark at the end,
which then would allow us to set the type to null. Even if we had a simple
class such as a person class, we could say var person equals
person, that would work fine. But if we were to
reassign person to a null, this would not be allowed
because we are guaranteeing that this is a non-nullable reference. However, if we did want a null reference, we could say person two,
we would need to provide the type here 'cause it's
going to be a nullable type. And then we could say equals person Fu. Now I could set person two equal to null when working with non-nullable references as the first name variable here. If we wanted to see the
length of the first name, we would simply say, first name.length. This would give us the length. There's no need to check
to see a first name is null because we're using a
non-nullable reference and Kotlin guarantees that
that will not be null. When we were using a
non-nullable reference if we attempt to do the same thing and we'd get the last name.length, you'll notice that we
have a red squiggly here. So what we have to do is introduce what's known as a safe call. This basically says, if
the last name is null, then go ahead and make this null. Otherwise give the length. So this variable could be
equivalent to one of two things, either null or the number six, because there's six
characters in the word Felker. So if the last name is
null, it would return null. Otherwise it would return length. You could do the same thing
with an if statement as well in one line. So you can say if last
name does not equal null, then you could say last
name.length else will say zero. Now what this is doing
is saying last name. We're checking to see if
the last name is not null. They're gonna get the length. Now notice how I did not use
the safe call operator here. The reason is, is Kotlin
recognizes in this if statement, we are checking to see
if last name is null, therefore I don't have
to check for null here because it was already
taken care of in this step. However, if for some reason
the last name is null, then we'll just go ahead
and return a value of zero. So either way, the answer here
would be we're either going to get six or we're going to get zero. Now of course you could
change this to null if you would like, though to
essentially do the same thing as the above, but most
likely you probably want some type of value in your code so you can do this with a single line if. And you can also chain
these safe calls together. So let's say that we wanted
the first two letters of the last name, and
then we wanted the length of the last name. Notice how we would need. So we'll say link four, so last name says it needs a safe call and
notice now the next one needs the safe call. So what this one will do is says, hey, if last name is
null, then just go ahead and return null, if last name's not null, go ahead and pull the
sub string off of that. So grab the first two characters. And if for some reason that's null, go ahead and return null. Otherwise go ahead and return length. Now you may have another
language has seen something very similar to this. If last name does not equal
null and last name.length is greater than one, then you
might wanna continue off there then you could have your
statement to say that your length or whatever is, to check
to make sure it's not null. So this would be very common
in our languages such as Java, but here you can start
chaining these calls together. You could even take this
even further by saying last name.sub string would say 05. So we get the first five characters, then we could say something different, like drop the last two characters. Then we could get another
sub string of those and then we would say, let's
say we want the second one to the third one and
then we would say length. And so we could say
length five equals this. Now, because this could be
null all the way across, we would need to use safe calls. So every time all the
way across we need to use these safe calls, meaning
that if any of these were to return null
length five will be null. Otherwise it will be
the length when working with nullable types, it's often
to wanna retrieve a value. So let's say the length
again of a certain, maybe the last name string, you wanna get the length of that. And so the need to perform
some type of safe operation to get that value. However, this is going to
leave us in a certain situation where we could either have null or the length of the string,
last name which is six, so we could get either one. Let's say for whatever reason,
we determined that we know we wanted a non-null reference. So in that case, we're
going to get an error because last name dot length
is going to return us a null. So how would we fix that? You could fix that with
a simple if statement. So what we could do is we
could just check to see if the last name does not equal null, then give us a length,
otherwise give us zero. And we can get rid of the safe call here because again, this null
has already been checked. So what this will return is a
non-null reference of length, either the length of the
actual string variable, otherwise zero. Now this can also be
short-circuited as well. So we'll say linked to int,
and this can be short circuit, so we're gonna use in what's
known as the Elvis operator. And Elvis operator looks like
this, is a question mark, colon and then a value. And what this means is the
Elvis operator means is, if anything on the left-hand
side of this Elvis operator is null, then return this value. So if last name is null, then return it. We could even go a step further and change some things together. So we could actually say
0,2 and we could do multiple safe calls in order. So if last name is no, or
this sub string is null, then return zero, otherwise
return us the length here. And this will give us
a non-Konoll reference. Now you may be wondering
why is this called the Elvis operator? Well, if we turn it sideways and zoom in and have this image here, you can see that it looks like Elvis Presley's hair, and that's why it's
called the Elvis operator. To create a function in
Kotlin, what you'll need to do is use the keyword fun and
then the name of your function. So we can create one called hello world, and you'll have open
and closed parentheses and then open and close brackets here will give you a function
that you can call. Now, if we were to call
hello world from somewhere, to call that, again, our main
function is going to be called because we have a little run button here. If we want to execute
what's whatever is inside of the hello world function, we would need to call
the hello world function. And we do that by just
typing the word, hello world, the name of the function
here with the open and closed parentheses. Now, if I were to run this, you would see that
nothing is going to happen because inside of this function, we're not really telling
Kotlin to do anything at all. You can see it the program exited. If we want to do something in here, what we can say is we say print line. And what we could say is,
hello world inside of here with double quotes and then
ups here when we've now run it, we'll see Kotlin will
compile it and we will print hello world right here to the screen. Now we could change his
function name to something else. So let's call it something
fancy like purple cow. So we have purple cow here
and I'm gonna go ahead and copy and paste that
name there, purple cow. Anytime I call purple cow, it's
going to print hello world. Again, we'll just double
check that here to make sure. And there we go, we see hello world here. Now the cool thing about
functions is you can call them over and over. So I'm gonna copy this
and go to a new line, I'm gonna paste it again
and again and again. So if you're wondering,
what's gonna happen is due to the procedural nature of Kotlin, it's going to execute this code, which means it's going to
walk into this function and it's going to run
whatever inside of here. Now it's going to, and then after that, it's gonna come into
this function and call into whatever is inside of here. So basically it's gonna call
this purple cow function one, two, three, four times. So we should see print line
of hello world four times down in here in our run
window, which we do. We see hello world. Now the cool thing about
functions is that we can actually change what's inside of
them and it will change anytime you call it. So if I realize I have in
my application purple cow called four times, and I
wanna change this to say, "Oops, you know what,
this should actually say, "hello purple cow." I can just change here. Now when I run it, we
should see down here, hello purple cow four times. So we'll see hello world purple cow. There we go, it shows up four times. Now I can also do other things
inside of this function. So a function is just a place
where we can put other code that we would like to call
it over and over and over. So it's kind of like a reusable
piece of application code that you can use over. So if I were to do something else, I could say per line, hello there. And now what we're going
to see is hello purple cow. And then it's going to print hello there, but it's gonna print that four times. So if we run this here,
you're going to see that it's going to call hello purple cow, hello there, hello purple cow. Hello there because what's
happening is each time this line of purple cow is called, two things are being
executed inside of here. Now functions can also
call other functions. So it was just kind of makes sense because inside of the main function here, we're calling another
function called purple cow, but also inside a purple
cow, we could also call another functional call moo. And maybe this function might just say moo and we'll call function called moo. And inside of here, we'll do print ln moo. Now what will happen is
when purple cow is called, we'll see, hello purple cow
and then we're gonna see print line is gonna be called hello there it's gonna be called. So hello purple cow, hello there. And then the purple cow function
is going to call the moo function and the moo
function is going to say moo. And so if we were to run this year, you would see that's going to call. Here we go, hello purple cow, hello there moo. Hello, purple cow, hello there moo. So now I can actually change
this around if I decided, "Hey, you know what, I
wanna move this up here "and I wanted to change this
to say, hello pink cow." Or let's do blue. Let's do hello blue cow and we'll do moo. Now, if I hit run again, we're gonna see what do you think was gonna happen? We're gonna see hello purple
cow, moo, hello blue cow, moo. And so we just see move
between each one of these because that's all that moo is doing. And then we could add
something to this if we want. Of course, to say moomoo Buckaroo. And if I were to run this again, we would see all different
types of stuff here. So it kinda makes sense. Hello purple cow, moo, moo moo Buckaroo. Hello, blue cow, moo, moo moo Buckaroo. Say that 10 times fast. So as you can see, these are
just very simple functions, but the functions allow you to reuse code. Now I can also come up here and say, I can say I wanna do moo right here, and I don't really wanna have it here. So I can kind of use these
reusable chunks here. And this case, we're
gonna say purple cow once, we'll do the moo routine
and then will just print purple cow. And so, like I said, each
one of these functions allows us to have reusable chunks of code. So we started off with hello,
purple cow, hello, blue cow. And then we did the moo, moo Buckaroo, and then it went back and
executed the purple cow function three or four times. So this is how you create
a very simple function. This function has, as you currently see, no return type specified. And because we don't have
any returns type specified, what is actually returned is a unit type. And we'll talk about the hair in a second, but if we leave it off there by default, any function that does not
specify a particular return type will return a unit. So we'll get to that in a second. By default, all functions in Kotlin have a returned type of units. So if we create a function called say hi, that's just going to print
out some text to the screen, such as hello world and we call it here, it's actually, it doesn't
have a return type. And then to specify a return
type in Kotlin's functions, you're just going to,
after the parentheses here, you'll put a colon and then
the type that this function is going to return. So if it returns something
back, you could say it's gonna return back a unit. So you'll notice at this point in time, I don't have any type of
statement to tell it to return a unit or anything like that. I could do this, and it
would work just fine. But by default all
functions that don't have a return type return unit, which if you see the
IDE IntelliJ is saying that the unit is it
redundant so I can alt enter, or I can click on this
little light bulb and say, remove the explicit type declaration. So I've done that now to prove
that this does return a unit, I can actually just create a variable and I'm gonna print this
variable to the screen. So whatever X is here,
because again, say hi is going to return something. And because this function
does not have a return type specified by default, it's
actually just going to be a unit. We'll just leave that
off to show you that. And since it's returning something, I can set it equal to a value. So this is also valid to
call this just like this and have it execute the function. I don't have to do
anything with return type, but if I would like to, I
could actually take that stuff into a variable as I'm
doing here into the say at two variable X and I'm
a print to the screen. Now, once we run this here,
say high is gonna execute and return a unit and
you can see it printed the hello world text and
then it printed the type which is Kotlin.unit,
so that is a unit type. And so again, if I just
specify it here, units. Again, I'm not having to
specify a return statement because just by default, if
it's a function like this, it will return a unit. And there we go, as you can
see it returns a unit here. Now let's say for whatever reason, we want it to return a string though. And so you'll see here,
as soon as I did that, got a squiggly line here. Now what that's saying
is we haven't returned any string here. So let's say, I wanna return something. So let's say, hello, is this
return the word hello world. So instead of printing it,
let's just return it here. And it's going to return
the value hello world. And then when we print it to the screen, we can say hello world. And did go ahead and print to the screen because what happened here
is these say hi function is now returning hello world. We're gonna stuff that
value into this variable X and then we're gonna print
the variable X to the screen with the print ln statement. So now something interesting happens. You may be thinking, well,
let's just call say hi a few times here and
let's see what happens. And if I run this, you'll
notice that we're just going to get on the screen hello world one time. And the reason why we're
only getting hello world to the screen one time
is because remember, say hi is returning something,
it's returning a string. So it's not doing anything in here, but saying, hey, I'm gonna have this value and I'm going to return hello world. And at that point you can do
whatever you want with it. And in that case, we are just
assigning it to a variable X and printing it to the screen. Now, these last three,
aren't doing anything. It's calling this and it's
returning hello world, but we're not doing
anything with the results. So if I did val Y equals
this, I did val X, (indistinct) would have X, Z equals this. I'm val A equals this, those
variables would then be set and I could actually print
them to the screen as well. So I'll change these from Y to Z to A. And if we'd run this
again, you're gonna see that all the values are running
and printed to the screen. So hello world, hello world, hello world, because we had to actually do something with those return values. And if I get rid of this right here, we're gonna see that we
have an error on the screen, simply because those variables
have not been declared. So that's just not gonna
work, it won't even compile. So this is a return type. If we wanna change the return
type to something else, we can say int, so it's gonna return int. And by default, this will not work. You'll say, hey, there's a problem here. And so I can return the value
of 32 or something like that. And if we run this again,
we can then execute it. Now, of course, because
this is a function, We can say something, we
could put some if statements inside of here or whatever we wanted to, even if we wanted to
return it to the string, but we only had a integer in here. I could say dot two string
and that would then print 32 as a string to the screen. So now whatever return types are, which could be any of the built-in types that you've already learned could be here. So it could be a Boolean
value, it could be a long, it could be a double,
if it can be any float, whatever you're working
with at that point in time, you'll need to return that. So in this case, I'd say return true, and it would return that value. So you need to return
whatever the value is. And of course, as we know,
if we don't return anything at all, it's going to return a unit, which even if the function
does nothing whatsoever, it's still going to return a unit, which then we print to the screen here. Functions instead of
Kotlin are first class. So if we have a function, say hi, we might have something
that look like this. And inside of here, when I say hello. And if we were to call this function, of course, we just call
it in the main function and it's just going to
print hello to the screen. However, one a bit are cool
things that you can also do inside of Kotlin with functions since they are first-class
citizens and technically objects, which we will get to is
you create another function inside of another function. So we can say bye and that
would look something like this. So now I'm creating a function
inside of another function. And this one just gonna be bye. Now, if I wanna call this function, if I go up here and I try to say, say bye, you'll notice I can't call this up here because this function is not found. It's because the scope of this
function is within say hi. So the only place I can call say bye, is it within the say hi
function, so this won't work. So if I wanna call it, I can
come in here and call it. But if I try it right
here, you'll notice again, it's red, which means it can't be found and the reason why Kotlin can't find it is because it has not been declared yet. So by the time the code gets here, you have no idea what say bye means because it's been declared below. So we can go underneath
the function and call it. And so say bye, and if
we were to run this now, what we see to say hi and then bye. And if we run it, we
would see hello and bye. Now, of course, we should
probably rename this to something else to say hi and bye. And that was a simple rename, which is done with a short cut key or you could just do refactor rename. So I used the shift F six here on a Mac. So shifted F six giving
the highlight to change it. So say, hi and bye and it will say hello. And then it will say goodbye or just bye. And then that function inside
of their head of function. You can also put another function inside of something in here. So you could say move like a
cow, it would say print ln. And this could say moo. Now of course, I need a call
moon after I've declared it. So at this point, I'm
gonna call say, hi and bye and then print hello, this
function will be declared and then we'll call say bye
which then we'll hop into here. It'll say bye, we'll declare moo. Then moo will be called, which
will print inside of here. So all different kinds of stuff happening. We're going here to here to here kind of jumping around all over. And if we were to run this again, we're going to see hello, bye, moo as we see on the helmet here. Now there are again,
functions inside of functions. This can get really squarely
looking and look real nasty after a period of time. So I advise that you be
very careful with declaring functions inside of
functions and make sure that's the proper right thing to do, because it can make the
code very hard to read. So unless you have a really
good idea for making a function inside of a function, then you
might wanna steer clear of it until you have a good grasp on
when you might want to do it, which there are various
different implementations that are beyond the scope of this video. Functions in Kotlin typically
look something like this and this function called say
hi, what we'll actually have is a print line statement that says hi. And so if I were to call
this from the main function, we're gonna go ahead and
see high printed down here to the output screen says hi. Now, unfortunately,
with a lot of languages, this bracket and this
bracket is kind of verbose for just a simple statement. Now, of course, this
function could have multiple different things in here,
but let's just assume it did one thing for now. So Kotlin actually has a concept known as a single expression function. So we could do this here,
say hi is equivalent to what we had before. What this means is I have a
function, it's name is say hi, it doesn't have any parameters. And it's body of the
function is the equivalent to this stuff over here, which
is on the right hand side of the equals sign. So inside the body of the function is just gonna be print line. So if I run this, we'll see
the same exact thing here. So we can actually see hi. Since Kotlin supports
functions inside of functions because functions are actually objects, we can actually declare a function inside of another function,
which you could decide to do or not and we'd say do work. Let's say we had a variable
peer called age equals 32. And then it said, if age is less than 32, excuse me, less than 21, we
would go ahead and print out that the whatever person or
whoever is less than at 21, and otherwise they are say
eligible for whatever reason, maybe have some reason
someone has to be over 21. So now if we were to call do
work out or try to of course, type a here do work has
not been declared yet so I can't call it, so I'm
going to call it from down here. And if I run this, we're
going to see say hi and then we'll see eligible
because the person is eligible. Let's set this to 12 and run it. And you're going to see
that it says less than 21. Now we can also perform, this is kind of as we know multi-line statement. But since we can use
single line if statements, we can also turn this into
a single line expression. So what I'm gonna do is I'm gonna get rid of these brackets, basically all of them. And we'll do is kind of
line this stuff up here and turn this into a
single line expression. And so again, the age is
12 if it's less than 21, it'll print line here, else
that'll print this other thing. If we run it, we can
actually, we see less than 21. And of course, if I changed
it to 32, run it again, you'll see that the output is eligible, which is the other part
of the statement here. So you can clean up a lot of your code by using single line expressions. There is a heuristic that
you should follow though. Let's close this window
over here and you'll notice the right-hand margin with
this little gray line here. If you find your code
approaching this over here. So let's just go ahead and type
eligible a couple of times, if for whatever reason eligible, you've typed a bunch of stuff here, it goes beyond the right margin, it's usually advisable that
you'd turn this instead of this into an old fashioned multiline function just so that it's more readable. So here you'd wanna go
ahead and of course, add your brackets. And you'd kind of do
this thing right here. And as we've kind of seen
before, we'll put this down here. Oops, we'll clean this up a
little bit, so there we go. So now if the age is less than
21, we're gonna print line. Otherwise we're gonna
print eligible, eligible, eligible, eligible. Again, now the reason is
because the right-hand margin, it's just a common coding practice. If your code goes beyond
that right-hand margin, you should figure out a way
to break into a new line. And usually if you're using
a single line expression, you'll wanna go ahead and
that's usually an indicator that you should probably
use a regular function body instead of a single line expression. Functions in Kotlin without any parameters are going to look just like this with an open and close parentheses. So the parameters are gonna be in between the parentheses here and there are no parameters here. So if we were to call do work, of course, nothing would happen. We could say print the
line work is happening. And then if we were to run
it, of course we'll do that. Now, parameter is something that we can parse into a function to do work with it. We can perform some type of value. So what I would like to
do maybe is I would like to provide some type of value here, I wanna type in the value
of 32 into this function. So what I'll do is take
the value 32 and say, "Hey, do work, you need to do
something with this 32 value." But in order to do that, I
need to provide a parameter inside of the function declaration. And so to do that, what I'm
going to do is type the word age and its type. So here age is gonna be an
integer and its value up here is gonna be 32. So now what I can do is I can say age is, and of course, when you
string interpolation here and say age is age and whatever. Let's just do the reads a little better. You are blank. And so let's say you are
32 in this case so far. To run this, we would see
that do work functions says you are 32, which is great. Now if I call this function
with another value, let's say 12, we're gonna
see two different values returned here, you are 32 and you are 12. So what the parameter allows you to do is allows you to parse in
values to your function. So you say you are 32. Now I could also provide
other additional values here so I can put name and
access can be a string. So I need to provide the
variable argument name and the argument type, which
is string for both of them. So again, this is the argument
name, argument type comma. So I need another one in there. Argument name, argument type. Now you'll see that we have
a red squiggly up here, that is because IntelliJ says, "Hey, you're missing a additional value." So I'm gonna type in Donn
and instead of saying, I can say, string interpolation
again, name, comma, and we'll have proper
punctuation here, period. And if we hit run here,
what we'll see is 32 and Donn says, Don, you are 32 now. If I to change their own,
I could say something else like 12 and I could put Jonas, and Jonas might be 12 for whatever reason. And if we print that out here,
well, I'd have you're 12. Now the cool thing you
can do with this thing inside of the do work
function is now you can start working with these values inside of here. So maybe you might have an if statement. If the age is less than 21, you might wanna say something different, like I wanna cut this out
here and we'll say the name, you are not old enough. Now, for whatever reason,
perhaps you have some logic that states that for someone
to use this application, they have to be over the age
of 21 for whatever reason. And we wanna put it you're not enough. You are blank, let's say age. As I say, Don, you are say far
too old at the ripe age of, and then we'll put the age of 32. And so now as I run this,
we'll get two different values that we're we're working with here. So we'll see the first one is Donn and the next one is Jonah. So Donn far too old at the ripe age of 32, Jonas, you are not old enough, you are 12. And so I can continue down this road here. Now, if I wanted to start
having, if I call this function all over the place, let's
say I have a 71 year old and her name is Evelyn and
Evelyn's using the app too and she types in her age and we run it. We'll see now that
Evelyn also has an entry, Evelyn far too old at the ripe age of 71. Sorry, Evelyn, if you're really not 71. So we can actually start
various different values in here that can be any different types here. You can provide one
that's a Boolean value. So you could say, yeah is
happy and you could tap in a Boolean value. And of course, because everybody's happy, we'll just put true here. And we'll say true and you
see IntelliJ is giving us some nice little hints here of
what the actual (indistinct) this is not something I typed
here is happy or age or name. That's IntelliJ saying, "Hey, by the way, "the name of this parameter is happy." And then we've put true here because, Evelyn, everybody's happy. Then we can hit run. And of course, we're gonna run it again. Donn you're far too old age, ripe 32. You notice, hey, we didn't
do anything with that value. So we, of course, we might
need to do something with it, which has says you are happy. And then we could put something like this. We could actually put some
other type of code in here. We'll say, boom is happy
and then we'll run it again. And again, of course, now we're
gonna see each one of them's for Don, Jonas and Evelyn,
you are happy, true, you are happy, true, you're happy, true. For some reason today I was not happy, maybe I stubbed my toe or to run it again. We would see that Donn is not happy because he stubbed his toe. Jonas and Evelyn though are
still happy and so forth. So this is how we can start adding in various different parameters. Again, if you need another
one, you're gonna put a comma in here and you're going
to add another one. So we could say foo is
going to be of type long, for whatever reason, you may
need some different types here. And of course, IntelliJ is gonna say, hey, that's not gonna work 'cause you need to provide that value up top. So this how you provide
arguments to a function. Let's assume we have a function
called print and user info. It takes a bunch of arguments
such as the first name, last name, age, if the user sunburned, if they like movies and
if they love popcorn. And if you would like to
do something with this, and perhaps we just want
to print it to the screen, we could provide some
implementation that looks like this, that uses string literals
and prints it to the screen. Fair enough. Now, if we wanna call it, we're
going to call it like this. We'll say print user info. And then we have to start
providing all of the information such as Donn Felker and then
we have to provide the age. And then we say, is he sunburned? No, he's not sunburned. He loves movies or likes movies, true and he loves popcorn, of course, why not? So this now very simple,
we've covered this before, but now we have these nice
little IDE hints in here that say, Hey, this is
what this argument is. It's his sunburn. And this has works great
except until it goes outside of the IDE because
IntelliJ and other IDEs that are from IntelliJ
brains do this for us. But once we get online
or into another IDE, we don't get the same feature. So let's hop over into another IDE. Okay, we're inside of visual studio code. I'm gonna paste the code
in here and you'll notice that we don't get those
nice little previews like we see over here. So we have first name, last
name, age, sunburned, et cetera. If we go back to visual studio code, we don't have those values over here. That's a feature of the IntelliJ IDE. So at this point when
we're reading this code, and this will be the case, if
you're doing a poll request or viewing the source code
online through a web browser, you're not gonna understand
what this false even means, especially a couple of
months down the road. You're not gonna
understand what true means. True here, we have no idea
what print user info is. And most likely this
method is somewhere perhaps in another file, perhaps further
down where I can't see it unless I have to scroll
for it and find it. And it just makes it more difficult and I have to do a lot of
context switching to find it. Now, the interesting thing
here is that we can fix this inside of Kotlin. Let me go back to IntelliJ
and what we can do is actually provide the
name parameters here. So I'm gonna type first name. So this is what is known
as a named parameter. I'm using the name of the
parameter and at the call site, I'm actually providing the name here. So is sunburned, that's false. And then we could say
it likes movies, true and loves popcorn is true. Now, if I actually, I'm
gonna copy and paste this code again, go back
to visual studio code, delete this code here and paste it. You'll see we actually get
those name parameters over, By default, Kotilin will
still compile those code. So back inside of IntelliJ,
it will still compile this code right here. And if we were to run it, we
would see this as Donn Felker is of age 32 sunburned,
false, likes movies, true, loves popcorn, true. So the things all run
in, which is really nice. Now the other great thing
about named parameters is they're also positional based. So let's get rid of this one down here so you can see what I'm talking about. We get a little squiggly here. It says mixing named
and positioned arguments is not allowed, meaning that we can't... We're using a positional
based argument right now. This is a positional based argument, it's the last one here
in the list of arguments. I could also undo this and say, hey, let's get rid of the age one here. and I'd get the same thing here and say, hey, this is basically the
third item in the argument list. Kotlin says, "I don't know
what you're trying to do. "You wanna use name parameters,
but now you're using one "that's a positional based,
decide what you wanna do." But it won't compile. So if you're gonna use one name, you need to provide the
name for all of them, which is much more readable. Now because we're using name parameters, I can actually move these
around, which is very cool. So the print user info
method takes a first name, last name, age, those
are the parameter order. So in traditional languages
that you're used to working with otherwise, you always
have to provide the values in that order. However, with Kotlin you can
provide a named argument. So it says, hey, age is
32, Kotlin will know to map this value here over
into this age parameter. Now, if I wanna move his sunburned maybe a little bit closer,
'cause for whatever reason in my program, it makes more sense, I can do that here in
Cottonwood note to map this parameter value,
which is second the list in the positional sense, map it over here because we're using the
actual named parameter. So we're using the name
parameter to map it over. And that's how you can use
name parameters in Kotlin. In Kotlin, there's a concept
known as a default argument. Now, every time I call
this print user function. So let's say I had to call it a few times, I have to change this
from every time of course, these values are gonna
change to some other types of thing and it's gonna
say 31 to 37 and 12 and all these different types of things. And this person is sunburned, et cetera. But perhaps the majority of the time, I always know that these
people are not sunburned and maybe because these
people are my family, they like movies and I know
that they love popcorn, but having to repeat these
things many times as troublesome. So we can actually use the
power of default arguments. And to do so we can specify a
default value for an argument. So it will say the user loves popcorn. So we're gonna set it to true here. Now, if Love's popcorn is not provided, we can actually remove
it from this call site, it will just default to true. So I can actually remove it
from these other ones as well. And I can provide a default
argument for likes movies. And we're gonna default that the true, because everybody should
like movies, I think. And then again, I can remove
this from the call site. And because I'm removing
it, the default value of likes movies is going to be true. And of course, it's not often
that everyone is sunburned. So let's go ahead and set this to false. And it's pretty easy to
do, so I'll say false. Now we can go ahead and
remove this from here. And now this cleans up the call side of these three functions quite a bit. We can actually see the
only relevant information that is changed in each
print user info function. And if we were to run
these here, let's run it. You're gonna see it's printed three times and we see sunburned,
false, likes movies, true, loves popcorn, true. Now, if for some reason
that I know that this time, perhaps I've just eaten a lot popcorn and I'm just not really fond of it lately, I'm just gonna say it
loves popcorn, false. And maybe I know that Bob
this week is sunburned 'cause he just got back
from the beach here over in New Jersey and the Jersey shore. And perhaps Sarah, for whatever reason just does not like movies and
we're gonna put false there. So actually Bob is sunburned. So make that true. So now what will happen
is when we call the print and user function, all
these values are set. Now loves popcorn, which
is traditionally true, is gonna be set to false
for this function call. For the next function
call print user info, it's going to be the true
cause I haven't provided it up here because it's
using the default value. However I'm saying, hey, it is sunburned. This time don't use the
false value, use true 'cause Bob has actually sunburned. And then finally we have
the print user function down here at the bottom for Sarah. And she actually doesn't like movies, so we're not going to
default that to true, we're gonna set it to false. And if we run this
again, we're going to see that these values actually
render correctly here. So Donn loves popcorn is false. The Bob Felker here is a sunburned, true and Sarah Felker likes movies, false. So these are all of their
default values that you can have inside of Kotlin. Now, if there is a string, for example, we could say that a string,
let's make a string here at the end, we'll say favorite color. And it could be string and would've say that's going to be blue. And if you provide that
favorite color, then up here, you can override it, otherwise
it will just default to blue. So you can do any of these things. Could be a built-in primitive
type, could be a class. And then like that you can
set to a default value. Now, traditionally the default arguments are usually placed at the
end of the method call. Now this is not a steadfast rule. You can actually place them in the middle. So if you'd like to
say, well, the last name is actually supposed to
be defaulting to Smith because we are working
with the Smith family. But whatever reason we
realized we gave Sarah the wrong last name. Now, if we were to run
that again, we could run it and we'd see Bob Felker, Donn
Felker and Sarah Smith here. So we've used a default
argument right inside of the middle of the call site here. So right in the middle of
the definition of the method. Now, but age has not been specified. So you do need to provide that. So if we do leave off age,
we're gonna get an error here and we'll see that no value
parse for parameter age. And because we're using named
values, I can do that up here, say, our age is 32. And I can move these around
anywhere that I would like, because we are using the name parameters. And that's how you can use
default arguments parameters inside of Kotlin. A common pattern in Kotlin
is once your column length for your code exceeds the
right-hand margin length, which is his right hand line, it's common to start placing
values on the next line, such as we're doing here, makes
it much more easy to read. Now this can get quite old pretty fast and can become very slow and error prone. Now, thankfully, if we're using an IDE, such as IntelliJ, Android
Studio or anything like that, this is built into the platform. So put your cursor on the left-hand side of the open parentheses at Alt and enter, and you can say put
arguments on separate lines and now it'll put it on
separate lines, which is great. So it saves you a lot
of time really quickly. So anytime you wanna do that, you can use that little shortcut. Now we do get these nice
names here that are built in from the platform. Again, this is the IDE
giving us these names. So if we copy these into another IDE, such as Visual Studio Code, we
don't get those values back. So how do we add those
named values of actual those argument names? You can go back to IntelliJ,
Android Studio or whatever, and start adding them by hand. Again, this is gonna take a lot
of time if you're doing this over and over and over. Thankfully again, place your
cursor on the left-hand side of the parentheses and
select the add names to call arguments and boom, all of a sudden you have
now cleaned up your code by placing each of the
arguments on a separate line, making it much more readable, as well as adding the
names of the arguments to each of the parameters, which makes it even more readable and saves you time at the end of the day. Let's assume you have a
function called print book info that prints the title of
a book and the author. Here, we have green eggs and
ham, and if we were to run it, we would see that it's just going to print green eggs and ham, the
author is Dr. Seuss. Now, however, if we had
the instance where we knew that we needed to add
an additional author, because sometimes books have coauthors, we would have to maybe create
an overload of this function. And we'd say this would be author two. This would be author an author two and then perhaps we might
change it to say authors, and then we'd have another comment with another string interpolation here. It would for author two. Now, if the book has three authors, we would have to create
another function and four and five and six, and you
can see where this is going. So thankfully Kotlin has
a way for us to do that without having to write extra code really. And so what we can do is
take this last parameter and turn this into a var arg. And that's a keyword var arg,
which means variable argument. And then I'm just gonna
change this to authors, which means that this value
could take zero to many values. So there could be many authors here. And since this is going to be an array, if we were to print
this now, let's go ahead and just add another person in here. So maybe I was the coauthor,
which we know I'm not, but this is hypothetical. We'll run this here and you
can see that we actually have open bracket and it's starting
to print the information. This is now an array. And so this is printing the
information about the array. If we actually wanna print
the contents of the array, we're gonna hop into a little bit of an advanced concept here and we'll use a lambda expression and
we'll print it to the screen. And it is the key word at the front line. And if we're to run it now, we would see also this array value here,
which we can get rid of and clean it up a little
bit and run it again. And you would see that we
have green eggs and ham and the author, we should say authors would be Dr. Seuss and Donn Felker as it's now rendering here. We could also take this
and add additional ones. So if we had Jane Doe, Jon Doe, and we had all kinds of other ones, we could keep adding them in there. Now the other cool thing is too, we could also have the situation
where for whatever reason, this book just doesn't have
an author and it's unknown. We could run this as well. And we would see that
there's no values returned. So if you need to have
a function that takes in multiple inputs of that particular type, you wanna make sure that the var arg is the last argument in the function, slap on the var arg keyword
and then you can pars multiple values in so
such as we're doing here. So like X, Y, and Z, and
then all these values would then be stuffed
into this author's array and you could loop over it
using either a lambda expression or any other array manipulation
or iteration techniques. Let's assume we had a function
called print user info and it just printed a name. So very simple, we're just gonna
take the name and print it. And it says name Donn Felker. Now let's say for whatever reason, I also need to have it take sometimes it only needs the name, sometimes it only needs the age. So I'll say, you know what,
I actually kind of need another print user info function. And this one is going
to have age and this one is gonna be int. And so at this time I can actually say, all right, well, I'm
gonna call this new one, it's gonna be a new user. And this one's called Jane Doe and I'm gonna make her age of 37. So now this one print
user info is going to, if we go to the definition of it, it's gonna go to this function. And this one is gonna go to this function. This is called function overloading, we're overloading the
term print user info. So has two different has the same name, but it's taking different arguments here. Now, if I were to remove this argument, you'd see we get some errors here because we had the exact
same definition here. This is the exact same function
name and function parameters right here that are right here and that's going to be a problem. So because we have a
different parameter here age, it's now considered a
different signature to Kotlin and say, all right you
can overload that function and then of course, we're
probably going to have some information here that's
a little bit different. Now, again, we could say,
well, now I also want their favorite color to be inside of here. So I'd say fave color. And that might just be
a string for simplicity. Now I have three overloads. So what we could do here
is I say, print user info. You see, I have three different options. So one takes a string, one takes integer, a string and an integer, and one takes string
integer and favorite color. And so of course, then
we'd want to change this. So it said feed color. And this would say using
string interpolation again, their favorite color. So then I could print this user info. And I would say Frank Bendo, and we would say that his age is 22 and his favorite color is purple. And at that point in time,
if we were to run these, each one of these are going
to run a different function. So the top ones are in the top one here, the second one's going
to run this one here, and the third one's going
to run this one here. Now, if you're already
familiar with Kotlin and also it's named parameters
and his default values, well, then now I'm sure you
can already imagine that well, we could actually kind
of clean this up a little bit. And to clean this up, you can
actually kind of get right rid of this and say, hey, you know what, well, age is going to be an integer. And if they don't provide it,
I want it to default to zero. And a favorite color is going
to be a string, of course. And if they don't provide
it will default to purple. And so we'll do that. Actually no, we can actually default it to an empty string if you'd like, and then what will allow us
to do is get rid of this, see how we have this here. It says, hey, there's
a conflicting overload because we have this up
here with the same values. Now, if I get rid of this,
all of these still work because we're using the name parameters. Now, of course I would
wanna say age like this and fave color, so we can
actually render the stuff and it would be there. So if I don't provide a favorite
color, just gonna be blank, if I don't provide an age, it'll be zero and I've kind of cleaned it up. And I still have this function
overload options here, which I can say, print, user info. And then it's gonna give me the option of, hey, don't use any default values. And that's how you can
overload a function in Kotlin. In Kotlin, you can create a
class by using the class keyword and give it a name such as user we'll do open and close parentheses. Now, if you're not familiar
with what a class is, a class is a reusable templates. So we could actually have
various different variables here. So I say variable user equals user, and then I could have another one. So var friend equals user, and there's another own
different instances. So friend is a user and
this user object is a user. They're two different
instances stored in memory. Now this class, this template
per se is not doing anything, it doesn't do anything for us right now. So what we can do is actually define a couple of fields here. So we'll say a couple properties. We'll say field, call this one first name and this one's gonna be a
string, we'll just initialize it to an empty string, var last name. And we'll go ahead and
initialize this one as well to empty string, and now we
have first name and last name. So if on the user I
would like to set that, I can say user dot and that
will allow me to access the two properties last
name and first name. So I'll set the first name
to Donn and user.last name to Felker. Now let's say we have another user. Let's say it's friend, of course, this one also has a first and last name. We'll set the first name here to Jane and the friend.last name equal to Doe. So we have two different instances. Just because I've set
the last name Doe here does not mean it overwrites here. These are two different things in memory. Now class again, it's kind of a template. So you can actually, it can
provide functionality as well. So we can actually put functions
inside of this user class that are scoped to the user class. So what I might wanna
say is print full name, and then we'll just be a regular function and it's not gonna do anything,
but it will print line to the screen and or to the output, which would be the first
name and the last name. We can have another one in here that says print with a prefix, which
then would maybe taken a prefix of some sort. And we could just call that prefix and that would be a string. And then when we call this function, we can do the same thing here. We could just say prefix and then you have perhaps a last name. Now, the way we would use this is up here. We might just call user.print full name. And if we go down here and do
the same thing with friend, friend.print full name, we'll actually see two
different pieces of output. So let's run this real quick
and what's gonna happen is we'll have two things
printed to the screen. One is Donn Felker and that's
called by the print full name function here. The next one is Jane Doe and that's called from the print full name
function as well here. Again, there are two different instances. The contents of the objects,
which are basically instances of the class are being
printed to the screen with the print line function. Now we do have the prefix, so
we'll get to that in a second, but let's also do
something different here. So let's say we have a
function called update name and this update name takes in
a new name and it's a string. And what this is going
to do is actually update their first name. And so we'll say first name
and we're set to new name. And then what we'll have here
is a way to update their name. So let's come back up here and say, you know what, maybe I messed
up when I update their name and this is hypothetical and
what I call this one, Bob. And then at the same time, I'd
like to user.print full name. And so if we run this again, what we'll see here is that the
first object is Donn Felker, then we update the called
the update name function, which goes down here, sets
the name to the new name. And then we call print full
name again that says Bob Belker. And then we go into the next user and this following the line,
sequentially procedurally, we see Jane Doe here. Now the same thing we can do
say user.print with prefix. And maybe I'll say Mr. And then in this one, I might actually say friend.print with prefix,
and this one might be Ms. And if we were to run this
now, what we're going to see is of course, Donn Felker, Bob Felker, and it will say Mr. Felker,
and then we'll see Jane Doe in Ms. Doe here, because
we're actually just printing with the values in here. So now this class is basically
a template that we can use inside of our application. Now everything is stored
inside the same file here. Now, what you can do is
you don't have to have this in the same file at all. So what we can do is I'm
gonna open up the project, went over here and you see
we just have our main file. What we can actually do
is create a file manually, like say new Kotlin
and I can say, user.kt. And then what I can do
is I can go over here and I can cut and paste
this code into user.kt. If we go back to the
main, you're gonna see that everything still works because we're in the same package. So it knows that it's going
to look in the same package and it found the user class. Now this allows us to start
cleaning up the code a lot because basically have a template
called the user class here in a different file to do that for me. Now, what I'm gonna do,
actually, I'm gonna undo this. I'm gonna go back to main here,
I'm gonna undo this as well and come here and get
rid of that Kotlin class. So we're gonna go back
to how we were before. There's actually a
shortcut that you can use. When you put your cursor over
class, the class name here, you'll see this little bulb. You can click on it and
you can say, create test, rename the file or you say
move user to a separate file. Say, yeah, I'd like to do that. So right out of the box,
by default, we were given this nice little thing that
allows us to create a file. And what that did is it
created the file force and moved all the contents
of that user class over there for us. So let's go a little bit further here and make one more method. And this method might print
the length of the first names. We say, function, first name length. And what we'll do is we're
gonna print something to the screen and then
what we're gonna do here is just take the first name
as the first name.length. And that's gonna print the
length of the first name. So if we, again, this is
kind of like a template. It's a class. Go back to main Katie and
let's print the length of the friend's first name. Friend.print, excuse me,
length, first name length. There we go. And if we run this now,
Kotlin will find a user class included here for so we're
running what that user class, run the existing code,
which we've seen already, Mr. Felker, Bob Felker, et cetera. And then we're gonna call the
friend up first name length, and it's going to return four
because that's the number of characters in the
first name, which is Jane. So this is a very simple class, but this is how you can
create a class here in Kotlin. Let's assume that I
have a class called user with first and last name
and a couple of functions inside of it. If I'd like to use a constructor, what I can actually do is
use the keyword constructor. Now I can actually bring these properties into the constructor. So let's actually get rid of
these and actually actually say first name and I can say, this
is a string and last name. This is a string. Now, usually you wanna
default everything to vals because it's going to
help you with immutability and help you not make as many mistakes. But we can try that here, but we noticed that there's
actually a problem here. So we have the update name method, actually updates the first name. And if it's a val, we can't do that. So let's just change these to vars. so the properties that can be mutated, which means they can be changed. So now we have a constructor
for our user class, which takes in the first
name and last name. And if we go back to our main
file, we're actually using it. You can actually see a
couple of things here. First we have in the user class, you can actually see when we type user, open and close parentheses. We have a string for first
name and a last name as well. So we put Donn and Felker. Now, if I would like to rewrite these, I could easily do that with
this property right here on line six and seven, I can change these to perhaps it should
have been Jason Smith. I can reset those here so
I can actually change those or I can actually just get rid of them 'cause I don't need them anymore. And then here I'd actually
do Jane changes to Doe, I can get rid of the
property setting here. And if I to run this again, you'll notice that we're gonna get the same
result that we got before, this time we've just used a
constructor with the parameters and properties inside of here. So these parameters are
turned into properties, which you can use inside of your class. So you can see that down here. Now you can also, as you
can probably imagine, put your cursor here and
you can add the names to the call arguments. So you can actually see that
that's actually the first name and that's actually the last name. So you can actually see
what these values are as you parse them in. Furthermore, in the user
class, if the constructor does not have any modifiers on it, such as the internal keyword,
which we'll cover later, you can actually, so if it doesn't have any of these annotations or modifiers, you can actually get rid
of the constructor word and this will be the primary constructor. So now we've actually cleaned
up the code a little bit more. We have the user class, which takes in a first
name and a last name. So we can see, I hear to
a first name, last name works just fine. We don't have to have the
constructor keyword on there though if you could still
use if you'd like to, but it's very common if you
have a primary constructor and that's it, you just
remove the constructor word. Now, if you wanted to have perhaps this to only be for internal and
you wanna have constructor, and you tried to get rid
of the constructor word, you would see that this
is not going to work, use constructor keyword
after any modifiers of a primary constructor. So we need to put constructor
here if we were to restrict the visibility of the
user classes constructor. But we're not doing that here. So this is a basic class
with a primary constructor. One of the additional things you can do instead of a constructor
is also set default value. So we could set this to be a blank string. And perhaps we knew we were
always gonna be working with the Smith family for whatever reason, we could just say this is Smith. And so if I'd like to back
inside of our main file, I can actually get rid of the last name. If I didn't want that there
fall into override a here to be Doe, I could do that. And so now this is just gonna
say Donn Smith and Mr. Smith and Jane Doe and Ms. Doe, because we're using the default values inside of this primary constructor. Class can have more than one constructor. This is a primary constructor up top here. We can also have a secondary constructor. To create a secondary constructor, you can use the constructor keyword, and then you wanna parse
in some additional values. But first let's get that set up. Let's assume that this user
can be of a particular type. And so we'll say is platinum
and be perhaps a platinum user for a particular, you could say it's a
financial institution. And so we wanna create
another constructor. So sometimes we wanna be able to provide, instead of the first
name and the last name, we already know by default,
they're not platinum. So what we can do is we can
call this constructor here. Now this constructor will
take the first and last name, but then what we need to do is call into the original version. So we're say the first
name of parse it in. So this first name value
is coming from here. And then the last name and
they'll say is platinum, we're gonna say false. Now, we can to see here
is we no longer have any red squigglies. What this means is I can
use this new constructor. Now I don't have to provide
a body for this here. But if I would like to, I
can also provide a body, may be able to do something in here. Like print line you could say is platinum is false by default. So if I use this one, if
I use this constructor, this line of code will get run. So let's go back to our example and let's change this one here. So by default, both of
these are going to call into this here. Now, of course we have
not provided a last name, so let's provide a last name here and that's going to be Felker. And then it'll say, it's platinum is true. Now I'm a platinum user,
but in this user down here is not a platinum user. So now if we were to run this
on the second call down here, we're gonna see is platinum
is false by default. That's because when we go here, I'll go to the implementation, it's using the secondary constructor. Now we can even go even further and say, if we want another constructor, so multiple constructors
who can keep going to here. So I might say first name, and then might just skip that and say, all right, this which
means, hey, call another one of these instructors. And this one is going to call
in first name and this time, I don't know what the
last name is going to do. So I'm gonna call and perhaps I'll watch. Just for completion sake, we'll say hey in the third constructor. And if I go back to the
screen here and let's go ahead and create another one, var
we say it's your cousin, and I'll say to user, it
we'll say, first name is Nick. And then we'll say, we're
not gonna do anything here because if we look at this implementation, it's using the third constructor here, which is gonna say, hey, I'm
in the third constructor, but look, something
else is going to happen. And what's gonna happen is
when this third is called, the word Nick's gonna put
it into the first name and then it's gonna call into this. It's gonna parse this
first name value into this. And it's gonna give an unknown last name. Now this is actually gonna
call this constructor, which is going to say, boom,
all right, now it's unknown. And then it's going to print
as platinum by default. So let's actually see that run. And then all the values
of course are then parse d into the root constructor here, and then we can actually see everything. So if we run this now, what you'll notice is something interesting
is going to happen here. Now there's a lot of stuff going on here. So let's this up a bit. So let's go ahead and just kind of get rid of all this stuff here. We just wanna see when one of
the constructors is called. And so we have have those
methods inside of there that's going to call line when
the constructor is called. So if we run it now, we'll see is platinum is false by default. And we see the same thing again and then I'm in a third constructor. So if we were to go to
this first one here, this first one uses the
primary constructor. Nothing happens here,
we just set up an object and that's the end. The second one uses a constructor here that uses the first name and the last name and then defaults to
platinum field is false. And it says it is platinum
is false by default. Now the third one is
interesting 'cause the third one will say, hey, the first name is Nick. It calls into the other constructor here. So it'll say the first name
was Nick, last name is unknown. And then what happens is
this construct will call into the root constructor. So it's gonna go this
executes first, this line then this line is gonna execute, which calls in up to
the primary instructor. After that is complete,
which is this line here, which calls the primary
constructor up here, then it will procedurally call this one. And then now that this
whole method is done running which is right here,
then it will run this. So what we're seeing here is
this is being called first because again, this gets
called, which goes into here, nothing happens up here, that's complete. Then it runs this whatever's
in this constructor, which is this call here. It says, okay, that's done now. Now run finally what's
in your constructor. And so these three things are happening. So if we really wanna make
it simple, let's go ahead and say so third, just to
really make it kind of obvious, second and there we go. I'm gonna go back back and
we're going to just comment out this one. And what that'll do is we're
just gonna run this one here and we see second then third. So first thing that happened
is it printed second because it hit this constructor first from kind of walking this call chain and then it went to the third one. Now we could actually get around this here and say, hey, this one
here is, we'll just say is platinum is false. And we could even say true if we want, it doesn't really matter, but
let's just leave it false, let's do a true actually. So if we call this and
we parse in a first name or you an unknown last name
and they're a platinum user. Now when we run this,
you're just gonna see third being printed to the screen
because it's only calling the third instructor 'cause by default, what happens is this
construct will be instantiated in turn it takes its first name value and calls the primary constructor. So this are here's the primary constructor because it has three parameters. If we then took this one out, last one, it's now going to call
the second constructor. So let's assume this wasn't
here, so let's comment it out. That's not going to run. Now, you'll see that we
don't have a constructor that matches the signature, which is a string and another string. We have a constructor with
two strings and a Boolean. So that's why we're seeing
multiple different versions here. And again, if we can come
in here this time we run it, we're gonna see second and then third. Now you can have multiple
different constructors inside of here. So you can keep creating
different types of constructors. You could set them platinum, et cetera. Now, of course, some of
these things may not even be need to happen. So we can say, hey, perhaps
we don't even need this, we can use some default
parameters here as well. So if the last name is not known, then it should use unknown. And then if it's by default,
we're gonna set this to false. So now if I come back here, I can still do this exact same thing. Even this would work and still gonna work because it's using default parameters and the named arguments,
the default argument values. And so then we can see
everything how it should be. So each of the objects
is going to be created, number is going to be
allocated, et cetera. And then we kind of get
all that safety right here and the various different constructors. However, if you do need
different constructors for whatever reason,
or you need to perform some type of different values, you can call it from
functions to do different, run all different types of code in here and run different code
in here, you can do that, just provide a different constructor using the constructor keyword and you're off to the races. Now, additionally, you can also restrict some of the constructors using
modifiers, such as protected, or you could call internal, et cetera. So you can actually lock down who can call into a constructor based upon a modifier. We have a user class and
instead of a user class, you can also have what are
known as initializer blocks. And these initializer blocks are created with the init keyword and
open and closed curly brace. So this would be perhaps
somewhere where you could perform some type of initialization
for your class. Initializer blocks are called
after the primary constructor has been invoked. So again, this is the
primary constructor up here. So after the primary
constructors have been invoked, the initializer block will be called. So something interesting that we can do is we can just go back
to our main file here and we'll create an
instance of our user class. And we'll just say Donn
Felker here for the first and the last name and we're gonna run it. Now we're not putting
it into the screen here, but notice how we do see
hello one is output down here in the run window. The reason for that is
because in the user class, inside the initializer block,
I'm printing out hello one. Now there's also something
interesting you can do with initializer blocks. Is where you can have
multiple initializer blocks and this initializer
blocks will then operate in the order in which they
are defined within the class. So what will happen is
the primary constructor will be called first and last name, and then we'll get hello one
called and then hello two. So if we go back and rerun this here, what we're going to see
instead of hello one, we'll see hello one and hello
two as we see down here. If we go back to the user
class just to demonstrate this, when we move this below
hello two and we rerun this, what will the notice is
we'll see hello two show up before hello one. Now one last thing here
too, just to demonstrate that the initializer and blocks are called right after the primary constructor, let's go ahead and create
another constructor that just perhaps provides the first name, which is a string and then
it's just gonna default the last name for us. So we're gonna call into
this parsing the first name and then the last name we'll
just default to Felker here. And if I go back to this here,
I'll get rid of my last name, which will then use that
second constructor here, which we can see here. Now what's gonna happen
is we can actually provide a block here so we can do something after this constructor's call. So let's do that here, say constructor, second constructor call so we'll say here. So our expected output at this time, when we create a new class,
you might think would be okay, we're gonna call the
second constructor called. And then of course it's going
to be the primary constructor and then we'll get basically
hello one, hello two because that's the order to show up in. However, what you'll
notice if we run this, that's not the case. And as you can see here, it's
actually a little backwards. We have hello one, hello two, and then second constructor called. So it seems kind of backwards. But when you think about the
execution of the actual class, this here is called immediately. So as soon as this constructor is called, immediately those values are parse d into the primary constructor. And then after the primary
constructor is called, the initializer blocks are then called. After those are called,
then execution will return to inside of whatever
constructor you're in, which is the second one
here into that block there and you'll execute that,
which is why we see hello one, hello two and then finally the
second constructor is called. Now you can use the
initializer blocks to perform some type of initialization
for your class, perhaps some basic setup of the class and initialization type
of tasks are a great place to put it inside the init block. In classes, you can also
create properties in line here. So I will type a property
called full name, and then I can initialize
it with the values that are being provided to
me through the constructor. So we can use the first name property, and I can use the last name property to create a full name here. Now, if I go back to the main class and I decide I would like to print that, I could say print ln and
would say user.full name, and you see that I have access here too because it's in the same
package and so I have access. If I were to run this, we would just see that Donn Felker is printed. Now you also see that
we have an init block that's been printed is here as well. So we have init blocks. And one of the nice too is we can also, if let's go back to the main class, we can also change this value. So since this is a property,
it means it's mutable. And we've of course, because we set it to be a var type here. And so what I can do
here say user.full name, and I can actually reassign
this volume, say hello world. And it's gonna completely
overwrite that value. And then what I'll do here
is I will say print line, and then we'll put user.full name again. And when we run it this time, what we're going to see is the init block then we will see Donn Felker and then we'll see hello world. Now back inside of the class,
there's an interesting thing that we can do as well. Inside of the init block, I
have access to this property. So if I were to type
something inside of here for the init block, perhaps I just wanted to
print something to the screen. I'd say this class is for user
and I could say full name. Now what you're gonna notice
is I get a red squiggly here. And what that red squiggly is telling me is that it's not initialized,
but as we can tell it is initialized, but
here's what we need to do. We need to actually take that
and move the initialization up a little bit higher. Now based upon the file format and how everything's organized, the init block can now see
the full name is there. If I come back and run this, we're gonna see that the unit block runs. It says, hey, I'm in the
init block and this is class, or this is class four, user Donn Felker. That's really bad English. So this is the class for user Donn Felker. Of course we rerun that
cause we don't want typos. We'll see, this is the
class for user Donn Felker. And of course here we're
gonna print the full name shows Donn Felker. Next we reassign it and
then it shows full name. Now each one of these, you
can have multiple of these if you'd want. They don't have to be initialized from a particular instance. We could actually just set
it to something like this. Say it's zero. It could be of any type that
you want your proper to be. And I can actually come
here and say user.age equals let's say 22, which I'm not
22, but that would be fun. And then I can then print
it or do something with it at that point in time. So that's how you can work with
properties using accessories and mutators inside of Kotlin. You can also define read-only properties inside of your class. So you might wanna know
the full name length, and decide to store that as an
integer inside of your class. And so you can say full name.length. Now we'll have a full name
length read only property that we can use inside
of our main.kt file. So if we say user.full name length, we can actually get access to it. We can print it to the screen. However, if we attempt
to reassign it to 12, we'll get a little error
message saying that the val cannot be reassigned. So these values have been assigned based upon the other properties. You could also perform
this off of a property directly off of the constructor. You can also decide to set
this initially such as 12 or something like this. And if you'd like, you can
provide the type as well. And let's assume inside
of your user class, you wanted to decide that
anytime someone requested the full name property, you wanted to make sure it was
prefixed with the word name. We can do that very easily
by providing an override of the accessor for
the full name property. Now, what we can do here is
we can actually type in name and then what we can actually type in here is the word field. And what the word field means
is this field right here, whatever is stored in this property, go ahead and we're gonna
use that right here. So anytime someone calls a get
on this, which is by default, anytime we call something like this, it's actually calling the get, it's going to return name:field. So in this instance,
if we were to run this, we're gonna see something
like John Franks. But instead of that, we'll
see name: John Franks. Now, if I were to leave
this off or comment that out where to rerun this again, we would see that the
name is just John Franks, because it's just printing out the field, which is been stored by
default as initialized with the first and last name. Now here, I was able to
override the get implementation so I can say, yeah,
anytime someone requests the first or last name,
then we're going to go ahead and say name: John Frank. Now it'd probably make more
sense if we just said full name so it wouldn't match
the variable name there. And then once we run it with
the full name, John Franks. One important thing to note, this field is actually the backing field
for this full name property. So this is what contains the contents of the full name property. So this is known as the backing field. Now I can also override the setter, also known as the mutator. I could say set. And by default, the
value that's parse d in to the full name when it's
changed is gonna be called value. So if I were to say something
like this user.full name equals Jon, like this
was a school lowercase, jon without the H, then what
would happen is this Jon would be shoved into full name, which then would call this code block here and what would be inside
of this value variable would be these three letters, J-O-N. Now I can actually do some stuff, anything I'd like to do in here. So I can actually say
if value starts with, maybe I wanna know if it
starts with something. I say, hey, if it starts with Jon, J-O-N, then what I want this field to be equal to is going to be Jon Doe. So if anyone says, sets us to Jon and then our to print line anything else for user's full name, it
should now say Jon Doe. So if we've run this, we'll have in here is we'll see John Franks, of course, we're printing that the
first time we've reset this to say, hey, the full
name is actually Jon, which we know is not the real case, but if we look at the
mutator, the setter here, we're saying, hey, if
the first name is Jon, then go ahead and actually
set the backing field to actually equal Jon
Doe, for whatever reason, maybe we have some business requirement that states that we need to do that. And then once I print the full name, of course, it's going to say full name and then print that backing field, which has now been turned to Jon Doe. Now we do have a problem here actually, if I were to attempt to reassign full name to something else, such
as let's say Jane Sparks. And if I were to try to print
that to the screen as well, what we would end up seeing immediately is something we probably wouldn't expect, which is we're still
gonna see Jon Doe printed. But hold on, I set it to Jane Sparks, why isn't that working? The reason that it's not
working is because the full name property we have overrided the setter, we're checking the value that
was set to that backfield. If it started with Jon Doe and
then we set at the Jon Doe, but what we didn't take into account is, hey, what about the else condition here? So we needed to do an else condition. If this value starts with this, it starts with Jon and set the Jon Doe otherwise set the field equal to the value that was parse d in. So now if we come back here
and we were to run this, we'll now see that we have John Franks, Jon Doe and Jane Sparks that are returned. So you wanna make sure that
you handle all of the various different types of scenarios you can have. This could very well be a valid scenario for your application. You may only wanna decide
that only you can change that value to certain
values or there's some type of logic for the setter
that you need to adhere to. However, if you need an else condition, be sure to also implement
that so that your property does act like a normal property, which can have the properties
red and have them set. Let's assume that you wanna
have multiple properties in a class, you can easily do that. So of course we have
these properties up here, but let's assume that you
also want another property in your class called age,
and you're gonna default that to zero and then perhaps
you have a favorite color and that's a string, and you're
gonna default that to blue. So you can have multiple of
these inside of your application and it can easily be accessed and modified through the property
accessor's and mutators, which are default. So say first name, age X, age equals 30. And I can say, use the
user that favorite color equals green. And so these are all possible. You can have as many these
properties as you would like inside of your class, just like functions. You can have more functions
and new, like you say, say hi, and this could just be a
simple little print line that just says hi. And so of course we could say, user.say hi and that would print to the screen. I could come in here and
change that of course, to hi, full name and then it would say, hi, Donn Felker in this case. So you can easily put
multiple properties here wanting to add another
one, we can do that here, et cetera, et cetera, et cetera, and put as many there as you'd like. You can also add functions to your classes and you just go inside
of your class definition and start typing your function name. So we'll create a
function called full name, which will return a string. And then inside of here, we'll just use some string interpolation,
we'll say first name and then we'll say last
name with the dollar bracket syntax there and it will return us the full name of the user. Now you may be noticing
I'm not using the brackets around here. If I need to use the
brackets, only time we would is if we're using some
property off of this. So maybe I wanted to get
the length and you'll notice how IntelliJ added those
brackets there for me. Anytime I'm doing anything off
of this main expression here, it's going to require
we put it in brackets. Now, if I do remove this, I
could still have brackets here. This will compile and this'll work. But notice there's a
little gray squiggly here, it's kind of hard to see. If I put that there, I'll
get a little light bulb and click on it or hit
alt+enter, and it will see remove curly braces. And if I do that, it'll just
automatically remove them because we don't need them here. Now this could also be cleaned up a little bit further as well. So we could kind of turn us
into a single line function really easily, and we can
just click and get rid of this and make it equal that. And we can make it a Parry,
small, succinct little function. Now, to call this, we would
go back to our main file here and we say user.full name. So we'd actually see that
if they function here. And of course, if we now run it, we will see that we have
the name, first name John, last name, Franks, and it
just puts out John Franks. And again, if we wanted to
again, do something different, we could do dot length. And for whatever reason
it would say, John, and then the length of
the actual last name, which we'd see here,
as soon as it compiles. John six, because the word
Franks has six characters in it. And so you can continually keep adding different functions here. So you could even add a
different function here. say, full name length, and
this one could be very similar. Again, this one might
return it int this time and we could either do it
on a single line expression, or multiline like we're doing here. And we could do something
even call it one function call another function and say, let me see you get the length of that. And then we can also print
this here and I'll go ahead and go to full name length. And when we this again, we're going to see that we get the John six again,
'cause we did the full name. Let's fix that real fast
'cause we don't want the length anymore on the last name. We just want it to say John
Franks as the actual output. And then we want to actually
show the length of the name. And so we're doing that here. So we say John Franks and then
the full name length is 11. So we have one function here,
it's calling another function. We don't have to do this. I could replicate this
functionality down here by doing this, I'm gonna comment this out. And I could say return this
dot length and would give us the same exact thing. But at this point we have
some code duplication and that's just usually not a good thing because if for some reason
I decide that I want the full name to have a hyphen in it, well, now this length here
is not going to be the same as a full name length. That kind of depends on what
kind of app you're building and if you need that functionality. So I usually recommend, okay,
if you already have a function that provides that value, just go ahead and call that function and
then it will be updated. So in this case, it's gonna add us two additional characters here because we have an additional
space and a hyphen. So instead of the 11,
we should get back a 13 as we have here. And that's how you add
just very basic functions to your actual class. And you can continue
adding as many as you like inside of the class body here. They can reference the members, they can perform different operations, they can even alter the members as well. So if we wanted to do that, we'd say fun, update full name for
whatever reason with suffix. And we might have parse d
in a suffix of some sort and that suffix might be a string and we might do something like this. first name equals, let's say suffix. And then we would say first name. And then what we would
do here is we'd go back to this area here on our main file. And we wanna say user to
update full name of suffix. And it will say Mr. And then as it prints,
it should say, Mr. John, which sounds a little weird,
but that's how it's gonna look. And we see Mr. John down
here and the output, and that's how you can
add and continually add additional functions inside of your class. To create a companion
object, what you'll do is inside of your clash
you'll type companion object, and then you can start typing
the name of your function. So in this case, it's create
a function called create user. It will return a new user. In order to do that, we
actually need the first name and a last name. So we'll need that provided to us. That's not what we want first name, and then we'll have last name. And then of course, what we'll
do is we'll have this return a new user. And it's very simple, just
kind of knew what the user with the first name and the last name. Now, companion object is
a function or property that's going to be tied
directly to the class or rather than it's exact instances. And we it's very much like a static method that you would see inside
of something like Java and a companion object is a Singleton, so meaning that is tied to
the actual class itself. So what does that mean? Let's go take a look at
this implementation here. I can do this, say user.create user Fu and then we'll say bar,
and I'll say val user. And then what we can do
here is I'm just print line the user, they'll say print the user. And what we've done here
is we've actually created a basically static type of method attached to the user class. So now you notice how I didn't
create an instance of user, I just called a method that
was directly on the user class, and that is going to be
the create user method. So see, create user and we
can treat this like a factory to create objects for us. Now we could go a little
bit further too, as well. And let's say that we had
maybe we wanted to create, a way to create a bunch of
users and I say, create users. And then what we can do
inside of here to say, how many users we would like to create and maybe parse into value. What we're gonna do is
return a list of users. So in order to do that,
let's go ahead and create a quick class level Singleton variable. So it will say foul users
equals a mutable list of users. Now we'll be able to
access that via the class. And what we can say here is
for I in zero to the count, which meaning from count
from zero up to the number provided count here. We want to add an item to the users list, and we'll go ahead and say user, and we'll just kind of
do something first name, and then we'll provide some type of value such as the index here. And then we'll do the same
thing here, last name, and then we'll do something
like the index as well. And as you can see here, it says we can remove the curly braces. So we can do that here and here, because we're just doing
a very simple expression. And then of course we'll return the users, which is a steep to member variable. So let's go take a look
at how we would use this. So let's go over here, val users equals, and we could say user dot create users. So maybe you wanna have this
to create a number of users or objects or one out of a type. And then we can say print line
and they'll actually print those users out. And when we run this,
what we're gonna notice is that we see all of
them printed on one line. And the reason for that is
this is basically Kotlin's way of showing us, hey, this looks
like some type of collection because we have a square bracket
and a closed square bracket up here, open and close square brackets. And this is the first
element inside of our lists. So zero one, two, three, four, five. So actually, if we're to
make this a little bit easier to read, we can actually
use one of the collection helper methods called Foreach. And it basically iterates over each item. And then inside of there,
we can off the item, which there'll be, it's
called it by default. And if we run this, we'll see
that we actually have them printed on all on each individual line. But now the interesting thing is we said, let's create five users, one,
two, three, four, five, six. Now the reason why we have six is because this range
is starting from zero. So we could change this to
count, to count minus one, or we could just kind of
make it simple saying, hey, go from one up to the count. So now if we run this again,
we'll see that we only have one, two, three, four, five. Now we could count this
up to 15, which is great. And we can go in and
create 15 different users. And it's all pretty simple to do. Now also, the cool thing is here as well, let's go ahead and comment this out, is that we've not created our users, but perhaps we could say users too. So kind of new variable
here, I'll say user.users. Remember this is a property,
the Singleton property that's on the user class. So this method create users
actually modified this list here by adding to it. Now, again, back here,
we're not doing anything with the users, we're just
kind of leaving it there. So technically I could
just kind of get rid of it if I wanted to, and
then I could say users, and then I could say print line. Let's actually go ahead
and copy this up here. To make it easier, I'll say users two. And of course that's not there. So I'll say users two print line. And if we run this, what we'll notice is that the user's object of course, was populated via the create user. So now, if I were to come
up here and say zero, so don't create any
users, it's gonna to see, like, we have an empty basic list. We see foo bar, that's this one up here. So if we were to get
rid of this guy up here, we wouldn't have that. And we're not gonna have anything output to the output of the window
because we don't have it there. So that's how you would
build a companion object inside of your Kotlin class. You use the companion object, keywords, open and closed curly braces, and any functions and properties
that are inside of here are class level and it's
basically Singleton scoped. Let's say that you
would like to keep track of a user's favorite food, or even just a favorite
food for your application. Let's assume that it has some ingredients and those ingredients are
a mutable list of string. And let's also assume that there is a name of a favorite food here, and I should make this
one mutable as well, and this one's gonna be a
string and we can go ahead and default this to a known because maybe you wanna change it later. Now with a Singleton and
software development, we only want one instance of
this to be created at any time. So if I have one favorite
food, I can't have multiple. I mean, I could have
multiple favorite foods, but let's say my most favorite
food in the whole world is X, Y, and Z. I only want one instance
of this to be created. But however, if I were to set
this up, I say, favorite food. I can create one instance of it here, I can create another instance here. And how would we do that now in utterly. So, as you can see, I have
multiple of these instances, and I only wanna be able
to restrict the user from creating basically
one instance of this. So how would I go about doing that? Well in other languages, what you would do is you would mark the constructor private and it's a private constructor. And now of course, we're
not gonna have the ability to create this. But now what we need is
some type of helper method to create this. So maybe we have a companion object and this companion object has
a new method called instance. And this instance just
basically create a favorite food for us, so it's basically a private method that creates a favorite food for us. But now even then,
we're gonna keep getting new instances here. So even if we did this,
let's do those things. We could say favorite food at instance, we're still gonna get
multiple different instances of this food. So what we'd have to do is inside of this, we have to kind of lock this down and say, all right, it's gonna
return a favorite food. And then inside of here, we're
gonna have to possibly need a let me say vowel instance
equals favorite food. And we start getting a lot
into a lot of code here to kind of start managing this. You can kind of start to
see that we're gonna start getting out of control,
we need to start handling, create the new instance here, et cetera. Now, thankfully in Kotlin,
this is a lot easier. We don't even need to do all this. This is already handled for us. And to create a Singleton in Kotlin, it's actually really easy. So I'm gonna delete this
private constructor. All we have to do is change
this class to object. Now time, we only have one
instance of favorite food at any time. So now if I go favorite food, so just give me a favorite food.name, excuse me, I could say print line of this, favorite food.name and
we're gonna see unknown. So if we run this, we'll see unknown. Of course, this will say unknown. Now, if I were to change this, I'd say favorite food.name
equals maybe you love. I know I do, I love watermelon. So I'd say watermelon
and our to run it again. We could see still unknown
because we didn't print it here. So let's print it again. If I print it now, it'll say watermelon. So I've just only have one
instance of this at all. So now even if I were
to create a function, so I'll say fun, let's
just call it do stuff. And then do stuff is
going to do something. And so I'm gonna say, favorite food.name is maybe it's chicken now. Now, if I come back up
here, I'm just gonna say, call, do stuff. Oops. Say, do stuff. Actually we need to define
it before I call it. So I'm actually gonna take
this and move this outside of our method here. So there we go, let's say, do stuff. It's gonna call do stuff and
then we'll say print line. I want you to print my
favorite food.name again. So what we should see here
is we should see unknown then we should print watermelon. And then by the time we get
here, it should say chicken. So even though we're calling
instead of the do stuff method, we're calling favorite food
instead of a different scope. Right now we're in a main
function scope of here, we're in the do stuff method scope. What we're gonna see here
is that this is treated as a singleton because
there's only one instance of it lying around. So once we run it, we should
see unknown watermelon and chicken as we do here. So the same thing can go inside of here. For example, if we wanted
to, let's do this right here. So favorite food.ingredients.add
let's just say salt, everyone loves salt. And then I can actually print here. So print line, how many
ingredients are here, I'll just print the first one's a favorite food.ingredients.first. It'll print the first one here and then, and maybe instead of do so
let's actually run that to see what that looks like. So we'll see that the salt
is the first ingredient that's printed there. Now inside of do stuff, again
just to demonstrate this, let's just go ahead and say
favorite food.ingredients.clear. So we're gonna clear out that list and now I wanna print this. So kind of put that, so
it's a little more obvious. And there we go, kind of
print break them apart. If you run this again,
what we'll see here now is that the ingredients have been cleared and we'll see that this list is empty. So now we get an exception
here on main line 14, because I'm calling first. So what I can do here, something different like first or null and
that would actually give me a null value sheets should
print null there, which it does. So we could say print null. And so there's nothing in that
list at this point in time. So this is one way that
you can create a singleton inside of Kotlin very easily. So you're just gonna
replace the class keyword with object. And of course, you can also put methods inside of here as well. So you can say fun, you can
say number of ingredients, and this can be a very simple method that kind of just returns
the ingredients.size. And of course, we could just
call that from up here as well. So print line, we can say, favorite food.number of ingredients. And if we run that we
should see zero at the end. Oops, it's asking us for a return type that I did not specify,
so I wanna say int. So I run the (indistinct) say zero. If we come back up top here
where I added an ingredient, we should see one right after we see salt. So we'll see one then chicken,
of course it was cleared out inside of the do stuff
method, the ingredients were. And then of course we printed
the number of ingredients too. So we have a singleton,
we can have properties. We can have various
different items in here. We can have functions, but this is now traded as a singleton. I can not create multiple instances of it. And if I were to print off, if I have two, print ln, favorite food
equals equals favorite food. What do we think would happen here, is this equal to itself,
is this the same instance? Let's take a look here
and we'll see down here, we have a true, so these are actually
the same exact objects that are stored in memory. And that's how you can
create a singleton in Kotlin. To create a constant inside of Kotlin, there are a few ways to do it. The first is a local constant. Now the is actually let me
say, there's a const keyword for helping to find a constant. But what you'll see here
is the val is only allowed on top level const, excuse me. If we get rid of file
here, we'll see that const is not applicable for a member function or properties in that case. So we'll need to get rid of that. Now, constants are usually
defined in uppercase and separated with underscores is kind of the defacto standard for many JVM based languages. But let's say that we have a constant, that's a max age of 18. Perhaps this is a application
for a children's application and we want the max age to
be 18 for whatever reason. Now I can use this inside of here, I can say max age and I
can do something with it and so forth. It's a val, it can't be
changed, so it's a mutable. Now, if I'm back inside
of the main function, I can still access this though. So let's say view user. If I still want it to be locked down, I would have to actually
provide some level of modifier on it to see 'cause we can
access max age right here. If I didn't wanna access max age, what I would need to do is
throw a visibility modifier on there saying, hey, this is private, which is private to just
the scope of this class. Now main, you'll see here,
we don't have access to. So can I access max age,
it's private inside of user. So that's the first
way that you can do it. Now you can also create
this again is all is scoped to the instance of the class. So if we were to see max age over here, let's say user.max age, this max age is scoped the
instance of this user here. So it's instance of this user. So if you want it to be a
more appropriate constant, as you see in our languages,
you would think to do this, but what we'd actually need
here is a companion object inside the companion object. We say, we want to say
const, val max age equals 18. Now inside of here again, we
can access max age very easily inside of our actual class. It's local to the class
we have access to it. But however, if we say user max age, you'll notice that we don't
have a max age on user. But we can access it because it's inside of a companion object,
it's scoped to the actual to be a Singleton as part
of the user class here, it's kind of a Singleton
scoped for the value. So it user.max age. And there we go, now we can
actually view the max age, which means that we can use it outside in various different locations. So it's basically almost
users almost treated as some type of namespace. Almost, you could see it
as that way for max age. So again, we have the
local type we have inside of the companion object. Now also a lot of times
you'll notice an applications, many people prefer to put their constants and maybe to a new file,
they'll call it constants. constants.kt. And then we'll do is we'll
actually create an object and they'll call it constants. And you remember when we
use the object keyword, that's a Singleton. And so what we can say inside
of here there is const val max age equals 18. And then we might want to say, const, val, whatever minimum age requirement is five, for whatever reason. And then back inside of our main class, we can then use that as we've
seen in other languages. So constants.max age, we
have constant.mean age that we can also access as well. Now, these all don't have
to be inside of objects or classes or anything that
can be also at a top level. So it can just kind of
be floating on their own in their own files. So maybe we wanna have
implementation that looks like this. Say, const val max age equals 18. Now this could be referenced over here. There's max ages in the root of the file. I can be over here instead of user. I could say something like this, max age. Now this is again, when
we go to implementation, it's going to this root
level file over here. Now, which one should you use? I prefer to keep any of the
constants that are applicable to the user inside of the user class. So I would have a companion object for me, and I would put const val max age, if this was only pertinent to the user. If I have a constant that
is going to be shared across many different
parts of the application, at that time, I might put
a create a constants file where it's an object
and I call it constants. And then I have const val, et cetera, ABC, whatever the value is, and it could be food or whatever. And then I can access that from anywhere inside of my application
using the constants as kind of a default standard there. So that's the way that I would do it. If you're going to need
constants, I prefer to scope them to the class that they have. Again, if I don't need this
constant on the outside, I don't need it visible
on the outside of this, then what you can also do is just go ahead and you don't even have to
use the companion object if you don't want to and
you can just mark this as a private and that can
be just stored as a max age. Now, what you so think about
it as if you were on too, this is going to be
stored around as a class, part of the class as well. So if you don't want that
as part of the class, you can just put it inside
the companion object and you can mark this as private inside the companion object. It will still be visible
inside of your class. So max age here, but if we go back to the main implementation
and we say user, and we create a new user again, and I try to access that
user max age will notice that it's not available because
I've marked it as private. So now if I remove private
and I go back back to here, let's see we have access user.max age. If we go here, private
user.max age is not, we can't even access it inside of here, even though it's scoped to the class V in the companion object,
I don't have access because we've slapped the
private modifier on there. So again, just to recap, if
you wanna have your constants, I prefer to keep them
scoped locally to the class that they belong to. If they can be accessed and
are usable by many classes and have kind of cutting concerns, I will then usually put them
in some type of constants file. I rarely will ever use
them at a root level, such as this const val foo
or whatever equals var. These are something
things that I don't do, some of them, because these
are kind of just randomly floating around in some unknown space and don't seem to be scoped too well. I have to keep things
organized in my application. So it depends on what your use cases, but that's how you can
create a constant in Kotlin. Let's say you're building a
wizard to create a new user through some type of screen. And you know that you want
a to store the favorite city of a particular user in a variable. And so you could say it's a string. Now, by default, we can't
just leave this uninitialized, we're gonna get an error
here that says the property must be initialized. And so we could say empty
string, now that would work. So what we could say here
is when we print this to the screen, we don't wanna say, so say Donn Felker's favorite city is, and then we'd print the
favorite city so forth. We can print the favorite city. So let's go back to the main file here. And inside of here,
what we can say is user, we'll create the new user
and it will print the user to the screen. And when we print it, we'll
see Donn Felker's favorite city is blank, but now, however,
this isn't really useful. Kotlin does provide us with
the ability to say like, hey, I know I'm gonna need a variable and I know that I need it populated, but I kind of know that
it's not gonna be populated just yet, so just go ahead and trust me, I'm going to initialize this thing, but I'm not gonna do it in right away. So basically as soon as it's variable, it's properties declared,
it's not ready to use. And I'm telling Kotlin, hey, that's okay, because I'm going to initialize
it later at some point. But let's assume for some reason
that I don't initialize it. So what's gonna happen here when we call the two string method, let's go back to the main file. So we haven't told Kotlin
about the favorite city at all. Now, if we run this again,
what we're going to notice is we're going to get Kotlin blows up here and throws an uninitialized
property access exception. And it says that the late in it property from my favorite city
has not been initialized. So Kotlin's basically
letting us know like, hey, you told me that you
were going to initialize favorite city at some
point before you used it. However, you tried to use it
down here on user line eight, which were on here. You used it down here and
it wasn't initialized. We're not allowing that to
happen, we're gonna blow up, and Kotlin throws the uninitialized property access exceptions. So now the nice thing here is easy to fix. So this allows me some
kind of some safety to know that my application is working correctly, that I've actually initialized
this because I do need a favorite city, I do need to populate it, and I want it to blow up if it doesn't. So now it's that user, that favorite city. And I could say, let's
go ahead and go with it, Newark is a great place,
so Newark and the wide run. And that's not really my favorite city, but we have Donn Felker's
favorite city is Newark. Now we don't get a blowing up anymore. So I could even set
this to an empty string. That's fine as well. But not at least that's been initialized, I've told Kotlin like, hey,
I could say Minneapolis, which is one of my favorite
cities and we'll print off here. So we'll see Minneapolis and
we'll see it's populated. So that's one way that you can
do it with a late initialized property inside of your application. This could be a string, it
could be another object, it could be any of that nature. You're basically telling
Kotlin, hey, I'm going to need some type of variable that
I don't know what it is yet, but I do need it populated
before I can use it. So I'm not gonna initialize it here, it's just gonna be initialized
later, so late init, so it's late initialization. So just if I don't initialize it, blow up. Now, one important thing. If we say, hey, well, this
is a mutable property, I don't want it to be immutable. Well, if we try that,
what we'll notice here is we get this error that says
the late and init modifier is only allowed on mutable properties. And well, if we think
about that, the reason why is because we have to
mutate it as soon as, whenever we wanna change it. So it has to be a mutable variable. Otherwise it would be defined immediately. So as soon as it's defined,
if this were this way, of course, we're gonna get an error. But if we were to define this as NYC, it would work just fine. But since we are using late
init, it has to be a var. And that's how you can define a late init and use it in Kotlin. Let's say that you wanted to have classes nested with init themselves,
Kotlin supports that. So let's say you have
a class called vehicle and the class vehicle has a brand and the brand is gonna be string and we'll just default that
to unknown at this time. Now, perhaps a vehicle also has a type of a steering wheel class
for whatever reason. So we have a steering wheel. So we actually put this class
inside of the vehicle class. And then I could say
something like this file name, or we made some var or
whatever we wanted to do. And we could say the steering
wheel and inside of here, we could even provide a
function of some sorts. Let's say, fun info and
then we could just say, could be a print line of some
sorts of all inline function. And we would say name, we print the name. Now, if we wanted to use this class, we could actually very
easily come up here and say, val steering SW for steering wheel, say vehicle.steering wheel,
create a new instance of it. And then I could say sw.info. And if we were to run this,
we will see that it prints the steering wheel, which is we see here. Now we can also, we're not
limited to just one class, we can have multiple different
classes inside of here. So we could have a class
and we could call this one a transmission. And perhaps we just, for whatever reason, we're building some automotive software and we're going to have a var type and it could be run a
default, this to automatic. And then of course, maybe
we might have a function called shift and we'll
just have it print off something like that say, print, print ln. And it will say the vehicle has shifted. And what now we can do is of course we have the steering wheel
where you don't have an instance of the transmission, we
would say val transmission equals vehicle.transmission. Of course, in that we'd
say transmission.shift. And of course, we're gonna
see when we run this, we'll see the steering
wheel and then we'll see the vehicle has shifted. So now each time if we were
to run this multiple times, we'd see the vehicle
shifted twice, very simple. So we can actually nest
classes inside of here. Now notice we haven't done
anything with the brand here. So we could also have some information about the info function here
and this one is just print off the brand here of the vehicle. And the brand of the vehicle
will kind of print off there. And let's actually set this
to a var 'cause we know we're probably gonna change that as well. So maybe we wanna do something
like this val vehicle equals vehicle and move
on to change the vehicle to brand equals Fiat vehicle.info. So we'll print it, we'll see Fiat and then we'll see the steering wheel and the vehicle shifted and
the vehicle has shifted. So that's how we can nest
classes inside of Kotlin. And it's useful for when
things are somewhat related and you wanna kind of keep a
class hierarchy accordingly. So he might have a vehicle. And inside of that,
there's a steering wheel. So as we're creating
it, we can kind of see that these things are related. So there's many different related domains that you're gonna encounter in software. This is one way to organize
them with nested classes. Kotlin is nested classes
are easy to create. Inside of another class,
you can have another class to create different types of hierarchies. However, there are times
when you would like to access instance variables and
methods and so forth inside of an nesting class. So let's assume the steering wheel, we wanted to print the brand name. So we would say brand of
car is the brand of the car, a vehicle with a boom name. We'll call it the steering
wheel and we'll call it with leather, say leather with
a leather steering wheel. Now, notice we have an error
here, brand is not accessible. The reason for that is by
default nesting classes do not have access to the member variables of the outer class. If we want to do that, we need to specify that this is an inner class of vehicle. So vehicle has an inner
class and the inner class can now access these variables. So, you can see brand is
the brand new vehicle. So let's go ahead and get rid of this. And then we can say, Fiat
is the brand, vehicle info. Now we see here, the steering
wheel is the constructor of steering wheel can only
be called with the receiver of the containing class. Now, what that means is
because steering wheel has access to the member variables, it's gonna have carries a
reference to the outer class. Meaning that we can't use the
actual class name vehicle, we actually have to use the instance because string was gonna
carry a reference to it. So we have an instance of
vehicle here is called vehicle. So I'm just gonna go ahead and delete this 'cause it's the capital
V, which is the class name when we go to the lower case V. Now, if we wanna just
change this and call my car, we can do that, so it's called my car. And now I can say my car.steering wheel and then we're gonna actually
see, it's gonna create an instance of the steering wheel class. We're gonna see steering
wheel.info, which is right here. Now, if we go to info,
we'll see it's print off the brand of the vehicle and
let's go ahead and run this. And what we will see now is
that when it runs, it says Fiat, Fiat is the brand of vehicle
with leather steering wheel. Now, since the transmission
is still a nested class, we cannot call my car.transmission. So the reason for that
is it's a nested class. If we wanted to create a transmission, we would have to say vehicle.transmission. Now we wanted to make this a inner class, of course, we'd slap on the word enter. And now of course, this
is not going to work. we have to use an instance
because the transmission is going to have a reference
to its outer class. So in that case, when we create
an instance of transmission, it's gonna keep a reference to
the instance of my car here. And that's how you create an inner class, which can reference an
outer class in Kotlin. Often during development,
you'll need to create some type of construct that shows you what different types
you have to work with. And for example, we might
wanna have something different account types. Maybe we have various different
users who have account types and they look something like this. So we have one that's
a bronze account type, we have a silver account type. There's a gold and there's a platinum. Very often you'll see these,
for example, the credit cards or different types of gym memberships or something like that. And if you're modeling
something like this, you'll wanna create a user defined type. And that's typically done with an enum. And to define an enum, you will
use the keywords enum class. And then for example, I'm gonna
call this one account type. And then by default, inside of enums, if you give the names of the types, which is your user defined
type, you uppercase them. So we'll say bronze, and then
they're separated by commas. So silver, gold, platinum. And then if I wanna use the account types, which we'll get rid of this now, I can just say account type.gold. And now I have an instance of gold, so I can start representing
something like that. So if I were to just to print line this, and these are very useful
inside of various different if statements and conditional
clauses and so forth, so we can see gold there. Now, this is something in Kotlin. Anytime you're interacting
with an application, a lot of times you'll be
talking to API and perhaps you'll get a value down a that's
gold like that from an API. And so you need to create
an account type off of that. And to do that, you can use
the account type that value of, and then perhaps you would say, so let's say we had a
string that was given to us from an input somewhere, maybe an API, and we'll call this account type from API. And then we'll just call it gold. And if we wanna in turn
this gold right here into an actual account type,
then we'll say account type, we'll see value of, and we'll
say account type from API. Now, if we've run this,
we're gonna run this here and we'll see that well, that didn't work. There's no enum constant by
the name of account.gold, but we do see one here. So what's going on here? Well, the reason is, is because
this is an uppercase value. So we can just say here to uppercase. And if we run this again,
we'll see that it runs a compile is just fine. That gives us our account types. So now we can actually say print line and we're gonna actually say account type, and it'll say gold as we see here. So now we actually have an
instance of gold, which is nice. So we can actually account type, does this actually equal
and account type of gold? So this should return
true, which we get running and say, it's true. So now we can actually enter work with these different types of enums. Now, a lot of times this is very useful inside of conditionals and different types of things like that. So you might have your user class, which if we go back over here
to our typical user class that we have, we might have
a user that's associated with account type. So we might have a var account type, and that would be an
account type of whatever, and we might wanna default
it to account type.bronze. Maybe that's what it defaults to, or instead of doing it here,
we could actually just do it, put this all up inside of the constructor and have it use a default value. So account type, and we don't need that and say account type
equals, but we do need that. Excuse me, account type
people's the bronze. And then if we decide to override it, for example, we could
say val user equals user, and then we don't wanna say Donn Felker. And then I don't have it
provide an account type. But if I would, if I'd like to, I can say, well, he's actually going
to be a platinum account, for whatever reason, he's a good customer
or something like that. And now I can actually
start working with this. And then in my application,
I can say user.account type, I can then do some
different types of things with conditionals inside of that and then start working with it. And it gives us some user-defined types that are basically strongly typed. We're not working with strings
doing string comparisons, and we can work with them accordingly. That's how you can create and
work with very basic enums and define them in Kotlin. Sometimes it's useful to provide values associated with an enum. For example, if this was
used for gym membership, let's assume that if you're
a bronze account type, you're going to then
get, let's say 10% off. If you're silver, you're
gonna get 15% off. If you're gold, you're gonna get 20% off. And if you are a platinum
customer, you're gonna 25% off, maybe bronze is a single
member, this is two, this is 10. This is more than 10. So start getting more
discounts for more members on your plan or whatever. So how would you define this? Now, what you need to do
is provide a constructor for your enum. And so what you would do is
you'd say val discount percent, and this can be any number of things, but we'll make this an int, it
could be floats or whatever. Now you'll notice we have
a bunch of errors here because the enum has
no default constructor. So we need to use the parameters. But if we were to provide
a default constructor, maybe we said 10. Now all of these would work
and they would default to 10, but let's not do that. Let's make the compiler
work for us by defining that we actually need to
have a particular value parse d into each individual enum. So here, we're gonna say 10,
this one's going to be 15. Then this one will be 20
and this one will be 25. So there we go, pretty simple there. And we can go ahead and
delete all this stuff out. And now, if we were
actually to print line this to the screen, we would
see that we can still do account type.gold, and we'd
still be able to print gold. And then we can actually
say, discount percent. We can actually print
the discount percent. So for your to run this, we would see that we have gold and 20. Now you're not just limited
to a particular number of parameters, you can
do another one here. So we can say val, say
number of subscriptions and this could be an integer value. Now you see here again, we're
having the same problem. So maybe this is the minimum
number here to reach this one, this one you have to have a five, this one you have to have 10 people. And this one you have to
have at least 15 people. And these are perhaps the minimums. And now again, I could then say, all right, well, what does this one five for number of subscriptions? And I can start providing
a kind of a known simple little data type structure, a custom data structure here that says, hey, this is enum class,
it's an account type. And it's gonna be bronze,
silver, or gold, et cetera. And it's gonna have these
discount percentages and subscription percentages
or a number of subscriptions that are required accordingly. And I can start to
define that here in code, so it's strongly type. And of course, as usual I could actually, perhaps maybe even say I only know this because it comes down
from an API and I can say account type.value of, and
again, I'm gonna use gold to uppercase and I can even
do something like this. Maybe it's regular case like that. And would we be able to see
this, let's take a look here and we can actually just say value. Actually, this is do instead
of gold, let's do platinum. And we'll run this again and we can see that we have platinum. And now if we actually do
something like this, print line, I should just copy this twice,
it will say discount percent and number of subscriptions. So basically what we've
done at this point in time, as we've gotten a string here, we have acquired the account type enum and then we're able to
print the values off that. So let's say that the value
platinum came as a value from an API or a database call, we could then create this type at runtime. It's all right, well, they
are platinum account type and it's a 25% discount and a minimum number
of subscriptions is 15. So that's how you can use
constructor parameters with an enum in Kotlin. Let's use the discount example again, and let's assume that you
need to provide a discount for bronze or silver, gold or platinum, but you had to be calculated. So what you could do is you could define an abstract function and you
could say, discount percent, let me say calculate discount percent. And it's gonna return a,
it's gonna be a function and it's gonna return and integer. Now what you're gonna see here
is I spelled abstract wrong. And now, as soon as we get that fixed, we have a bunch of errors
here saying class bronze is not abstract and does not
implement the abstract member, calculate discount percent as
defined inside of this type. And the same thing happens here for silver and gold and platinum. So what we can do is we can
add some curly braces here, and then we can implement that interface. And actually we'll see, assuming implement that abstract class,
the abstract function. And we can either just do it inline here, or since this is gonna be... If you're gonna perform some
type of complex calculation, you could do that in here
and you don't perform your logic and so forth
and then finally return whatever five or whatever,
whatever you've calculated, or if you just know what's
going to be very simple, you can also inline it. So we might be able to just say five here. And the same thing would happen here. Again, I would need to
implement that member here, and then I could say 10. And what I'm gonna do
is just copy this over 'cause this is easy. That way, I have to
space that over what 15. And again, these are different percentages than I had last time. But for the example here
that are all the same here. So calculate that accordingly. And it looks like it's
expecting a semi-colon at the end of this after we've
kind of built all that up. Now, what I can say is if we
use that same type of example, again, maybe we have
something coming from the API and it's just all spelled
kind of really weird with weird casing. We wouldn't wanna say
we have the account type and we got that from the
account type dot value of, and then we would take this from API, go to uppercase, now we have that type. And then we could say print line account type.calculate discount percent. And if it's gonna be gold,
which we could run it, we should see that we're
gonna get 15, which we do. If we change this to numb,
which we're just doing weird casing here to mess
with it and platinum is 20. So that's how you can
implement an abstract method inside of an enum. This will be very useful,
again, if you are not sure what you are going to return. Of course, it's gonna end and you need a lot larger calculation, you
can use a larger block here. So you could do a bunch of work here, and then you're gonna return
whatever that value is. If it's, for example, maybe
it's gonna be a var percent and you wanna start off at
zero, no discount percent, and you get to return to percent here. And perhaps you're gonna
kind of do calculations inside of here based upon maybe some other constructor parameters that you have inside of the account type,
which we don't have any here. But if you did, you can perform
all those calculations here. And that's how you can
use abstract function inside of a Kotlin enum. You can also iterate over all of the types inside of an enum very easily by using the account type.values. So you can use the values method, which has built in for you. You don't have to define
that, it's just part of the enum built in for you. And so let's say it's for, say type, let's actually say account
type and these types here, then I could say print line, and then I could say account type. And if I were to run this,
then what I would see is that when I run it, we're
gonna see all the types here print on new line, bronze,
silver, gold, and platinum, and the values method returns in array of those account types. Now, because it's an array
it's so it's a collection that's built into Kotlin,
we also get to have access to all those nice little helper
functions such as Foreach. So I could do the same thing
here and I could use print line I could use the kind of built IT. It's kind of one line
all of this together, instead of having three lines of code that does the same exact thing. So you can iterate over each one of those. And that's how you can
integrate over all the values inside of an array, really of
enum as an array, very easily. To add a static method to an enum, what you wanna use as a companion object. But first you wanna
terminate the different enums by providing a semi-colon at the end, then you'll wanna say a companion object. And for example, here, we
could actually have a function that says get account type by name. We can just parse in the
name and it's a string, and this could just be valid. We could just use the value
of named.to uppercase. And you may be wondering what this is for. It's a kind of a nice
little syntactic sugar to say account type by
get account type by name. And I could just parse it and gold. And so I can say account type, and now I could actually
print this to the screen and say account type. And then if we hit run, we should see that we're going to
get gold on the screen, which we do down here on the
bottom in the output window, I could change just to platinum,
which is different casing or whatever. And then of course, for whatever reason, if I just spelled wrong, it's going to blow up
because we don't have a value with a constant of this type, which is what we would
probably want to have happen if you're trying to use that
in your production code. Now, just an important note here is that Kotlin, doesn't have
a concept of a static method. This is just Kotlin's
way of kind of providing the same functionality out of the box. And that's how you can create
a companion object method. It kind of acts like a static method inside of a Kotlin enum by
using a companion object. Kotlin doesn't have a concept
of a switch statement. Kotlin's switch statement
is known as a when. So let's go ahead and create
a user to work with real fast. And this user will have
a first name of Don and the last name of Felker. Now the when statement
looks something like this. You say when and then
you provide the value that you're going to evaluate
here as the condition. Say, user.first name say, hey,
when the user's first name is equal to something and each line here will be a condition that we're checking. So when the user's first name is Don, then we want to go ahead and
perform some type of action and we'll put that inside
of some curly braces here, and we'll say, print ln, this is Don. And then we can run this. Now, if we run this here, we'll run and we'll see in the output
window that we say, this is Don. Now, if for some reason we
changed this username to Dan, of course this evaluation's
not going to run, and we're not gonna see
anything printed out to the screen. So what we can do is also
provide a default brand, which is known as an
else inside of the when and we can provide the
brackets in here too. Print ln, this is not to Don. Now, if we run this again
because user's name is Dan, which I'm sure there's
a Dan Felker somewhere, we'll see this is not Don, so the else branch has now executed here. Now, one of the things,
and you can put multiple different things inside of here. So we could do a print line, foo var, or do some type of logic or whatever. These are just basically blocks of code that you can do stuff with. Now, one of the challenges
that you're going to run into when you first start
developing with Kotlin is you're not gonna understand how to write a when statement. At least that was my experience. So thankfully the IDE
helps you out a lot here. So you may be familiar with
writing something like this, user.first name, equal equals Don. And then you may have this
print line statement here, and then you're gonna have an else. And this is very common
in all C style languages, which you're probably used
to seeing many times now. So if we comment this out,
we'll see here that Dan Felker. So we should see, this is
not Don, let's run this now. And we'll see, this is not Don. If I change this back
to Donn Felker up top, we'll see that this Don, so
that condition will be run. Now again, I know that
I probably wanna use a when statements very
Kotlin idiomatic to do so. Now you see as I put my cursor here, we get this little yellow
light bulb alt enter or hit this and say replace if with when and automatically it
rewrote the code for us to a little bit different here. Now this doesn't look like
the one that's down here, yet it does the exact same thing. And what this is basically saying is, hey, is this username
first name equals Don. Now it's going to just say print line. Now notice how there's no
brackets here like I have here. So let's undo what we just did before. If I have multiple statements
in here and I said, this is cool. And then I come back up here and say replace the if with the when, the IDE will automatically
provide the brackets for it. So if it's a single line statement, what will end up happening
is when we replace the if with the when, it will then turn it into a
single line in line statement. So it's easier to read, but it's also optimized
it here because it says, hey, we don't have to evaluate
the wind for the else here. So we're just gonna throw
it in this item here, which also means we could
just say, this could be Dan. Now at this point, it's
getting kind of redundant. So we say, this is Dan. And if we were to run this,
now let's run real fast. You're gonna see that it's
gonna say this is Don. Now, if I put in Dan, say, this is Dan. And then if I put in, this is Dana, it's not gonna work 'cause this is Dana. So then I can rewrite this in my opinion, it's a little bit cleaner. I can actually just say,
get rid of this stuff here. And what this will do is it'll valuate when I can put this inside
the, the user.first name. So what this is gonna do is only evaluate whatever's inside of
the first name variable. So what the saying is user's
first name when it equals Don, go ahead and print this out here. When that equals Dan, print this out here. Now, when would you wanna not use that? That's a very good example. So you could actually say,
well, when the user's first name is Don, then we wanna print this as Don. But what if we only wanna
print it when it's Donn Felker. So user.last name equals, excuse
me, we wanna go over here. Username equals Donn and
user.last name equals Felker. Then at that point, we
wanna say print this as Don, otherwise we don't. So let's go back here
and let's change this. Again, this go to Dan, let's
go to Donn Felker and run this. What you'll see here is
it's gonna say this is Don, but what happened if we added just Felker, I misspelled whatever, and run it again. This is not going to run, it's
gonna say this is not Don, because what this one statement is doing is using this entire
line as an evaluation. And again, once it starts
getting kind of long and goes after the initial line break, I prefer to put it inside
of a print line here. So you kind of start evaluating multiple different conditionals inside of here using this when statement, and
then of course you can have a fall through as the else at the end. You can mix having blocks
of code inside of here, or you can kind of have
one line evaluations and statements here,
which will then evaluate these expressions and
print onto the screen. You can not put multiple
ones here on the same line, as soon as you need to do multiple things, you will need to provide a block for that. So that's pretty easy to do. You can just go ahead and
provide that print all those, print ln and accordingly, and you can put some other
stuff inside of here, print ln. This is ABC, whatever. And then that's how you can go ahead and use a basic when
statement inside of Kotlin. And of course, this can be used with different types of
things, such as primitives can be used to check
different instance types. This is just a conditional
very much like an if. And again, if you don't know how to write the when statement, you can always go back to the if statement,
write your if statement, and then go ahead and use the
helper inside of IntelliJ, Android Studio or any
other Java IDE that's built by JetBrains to have actually have it. Go ahead and replace your if statement with a when statement. There are times in a when statement when it's used as an expression that it needs to be exhaustive. What that means is ln
statement must be provided. So let's assume that we
have at when statement based upon an account type. So if we have an account
type and we'll just say account type.gold, and we can
say a when statement here. So when an account type,
and then we say gold, what we can actually do
here is we can actually have some type of code
here and we can say, return gold member. Maybe we wanna return certain strings based upon whatever their status is. So you can see here, we
have an error and it says, hey, this is by default,
a unit and returns a unit, but we're telling you to return a string. So what we can actually do
is have when return of value. So we actually, val equals message. So I wanna say a message
off of this when statement. And what you'll see
here is it's telling us, hey, well now when the when
expression is used like this, it must be exhaustive. So we have to actually handle
all of the different scenarios because this val must be
populated with something. It has to be something. And the compiler knows by default. Well, account type has different values, it has bronze and silver
and gold and platinum. If you parse in, if this is platinum, it doesn't know what to return. So it's telling you,
hey, you have to return all of these things inside of here. And what it's saying is here, you can do this in one of two ways, you can add a necessary
bronze, silver, or platinums, or add an else branch, so
there's two ways to do this. We could say, hey, a gold
member, or we can say, what would we say, else
and we could return a regular member, something like that. We'll see here we have, it
looks like it's returning, we don't need to add the
return statement there 'cause it's actually
used as an expression, so we don't need to return anymore. And then what we can do here
is say, print line message. And when we print this,
well, you're gonna see that they are a gold member. Now, if I were to type this as platinum, we know that this is incorrect
and it says regular member. Now that's not correct here. So maybe what we wanna do is
if we say, if they are gold, then anything else like that, where maybe platinum is a highest level, we'll say platinum member. Platinum. And then inside of the classroom, we can say not eligible
for special access, whatever that might mean. And if that's basically
saying this is maybe a string, that's going to be
displayed to the screen. When they're platinum, then use this, otherwise for all other instances,
if they're gold, silver, or bronze, then we want them to only see not eligible for special access. Well, here they're a platinum member. So we can actually say
platinum member access allowed. And then when we rerun it, we'll see platinum member access allowed. And then we'll come up
here and changes to silver and we're rerun it. And it says, hey, you're not
eligible for special access. Now that's when we could do it. One way we could do it now,
we could also change this and provide an implementation
for each individual one. Now it doesn't matter, we
could type bronze here, we could type the different ones here. This would, of course, maybe
there's a bronze access, there's bronze member access. And so we have this, we'll
change this to silver. And notice we still have
the squiggly align up here, it's still complaining that
we don't have everything that we need and then we
can change this to gold. As soon as I provide the gold, I've provided all of these values, gold member access allowed. Now the beautiful part
about this is regardless of whatever I've typed inside of here. And I look this telling us
we can inline this here, we can inline the message,
which we don't really wanna do, 'cause we're assuming this
comes from an API or database. The beautiful part about all this. Once we run it, we'll see that
we get the correct bronze, silver, gold is that, and just, excuse me, I'm gonna reorganize here because it just makes more
sense and matches the code, is it when we have account types, they're gonna have a value, which means later on in our application, if we decide to, for whatever reason, our business needs change
and we add a new level Onyx. Now our application will not compile. The compiler is gonna check this and say, hey, this when statement
is not exhaustive. So if this account type
is in another class file or another file or application, which it's most likely is going to be, and we've added Onyx because
it's coming down from the API. Cool, we have Onyx now,
we're gonna to add that and we're to try to
compile our application and the Kotlin compiler's
gonna say no, no, no, no, no, you can't do this because
you have to make sure that when expression is exhaustive. Now of course, that's, of
course can be gotten around by if the else is here, then
say unknown member type, or whatever and you can
have that and will be, you can add it in there. So if you add another
one, you can say pink and whatever that would
be, maybe it's special, cancer awareness or something. It pops in there, then you
can have that inside there. The different types that you have in here, again, this doesn't matter if the bronze is the bomb or whatever, the
expression will be evaluated and the message will be
returned at that point. And that's how you can
work with an exhaustive versus non-exhaustive when statement. And again, the non-exhaustive
one is going to be perhaps if you just want to
perform some type of evaluation. So this would be non-exhaustive
one account type. And I could say gold. And I would say, maybe just
print something to the screen. This is gold. Now this is non-exhaustive here. We're not required to
provide an else statement. Because this is using the expression, it's gonna be returning a value. We have to provide an exhaustive approach. We have to exhaust all the options. So we need to provide all of the options or also provide an else at the bottom so we know what to do there. And again, there's no Onyx here. So it starts complaining and so forth. And if we delete the Onyx and we had to go to platinum there, we fix
our compiler problems. And that's how we work with the exhaustive versus non-exhaustive
when statements Kotlin. In programming, it's very often
that you'll have in a class, this you simply to hold data. And so what we might have
is a class called person and as string and a last name of string, and it doesn't maybe even have a body that's just has no properties
or any of them be no functions or anything like that. So this is a person and we may
define person one as P one. And we would say person, and we'll save their first name is Don and their last name is Felker. And I'm gonna create two of
these, so P one and P two. Now, if I were to actually say, are these equal to each
other, you might think of course, that these
are equal to each other. They have the same contents, they must be equal to each
other, so let's run that here. And what we're going to
see is that it's false, these do not equal to each other. And if we are to print these
out, we're going to see P one and P two actually have
different little codes here at the end, which we can get
to that in at a later time, basically showing that,
look, both of these objects are actually different. They're different is
not actually comparing value equality inside of them. Now, thankfully, the
language designers at Kotlin have thought about this and said, well, there are times when we
just wanna carry data around. And so if we have a class
like that, you can then use what's known as a data class. And to use a data class,
you'll just slap the word data at the beginning and then
you also have to have at least one parameter and all parameters must either be val or var. And so we've added that here
and now we can actually say print line P one equals equals P two. And if we run this, we'll
see that both of these values are the same. And what will happen behind the scenes is it's actually comparing the values. Now there's a couple of things
that are making this happen. The Kotlin compiler is actually overriding and creating the equals operator
and the hash code operator, as well as the two string and
a couple of other functions, which we'll talk about soon. So if we're gonna take a look
at the equals and hashcode is what's kind of helping
us with the equality here. However, with the print
line, we can actually, as we remember print line will
call the two string method by default. So we can say P one and
then what will happen if let's actually go
back a little bit here and take this off. If we run this just as
this is as a regular class, we'll see that we have,
if we do a print line, we'll see person at 49476842,
so it's not too helpful. However, if this is a data
class, what you'll notice here is that the data class
has a two string method, which has implemented for us, which is a very nice format here, which has person first name, last name. And let's say we add
the age inside of here. So val age int, and we'll
put some numbers here, 30, and let's just do the same thing 30, and we run it again. You'll see that the print line method also outputs that parameter. So it'll actually output
each of the parameters as a value in the two string method. So this is all generated for us. The equals the hash
code and the two string. And this is how you
can create a data class inside of Kotlin. Now you can also have
functions inside of here. So you might be able to say full name. And this is very similar
to our other user object, which we've seen before. This will return the string, and this will be returned like this. We'll say return, first
name and then last name. And we can actually come up
here and print the full name. And if we run it, we will see Donn Felker and of course we can have
do other things such as this such as name, length. If we wanna have a method
that perhaps returns an int, and all that does is full name.length. And we can do the same thing up here, full name or name length, and that will provide a
full length of the name and characters at the
character council at 11. And that's how you can
create a very simple class in Kotlin, very simple
data class in Kotlin and how it can perform
data equality and so forth. If you would like your data
class to be constructor list or seem like it's constructor list, you will need to provide default
values for each of those. So you could say first unknown here, and that was gonna go to
first unknown known here, last unknown. These are just values I'm typing in there. They could be anything, an age we could just default to zero. Then of course, it's getting kind of long, so it's gonna put these
on a separate lines. So now we have our class
kind of broken apart into separate lines. And then if I like to
create a P three up here, I could very easily do. So
Val P three equals person, and I have to provide any values because they're all default. Now, again, I can't have
another value in here. Val is just Fu, which is a
string it's not provided. We'll see, we get a bunch of
errors here, even up here, because there's no value unless
I defaulted to something. But at that point, these are all vals. And then of course, if
we print ln, the P three, delete these, you're
going to see that we have the default values that are output. So first unknown, last
known, known, and age zero. And that's how you can
simulate having a parameter list constructor in a data class. Kotlin data classes by
default will generate what are known as component methods. So if P1, we'll see component,
we have component one, which is a string, and this is going to
be the first name P one dot component two, is going
to be for the second item in the parameter list, which is a string. So in the order in which they
occur is what the components will occur. And so this would be val
first name, val last name. What do you call that? And then as you can see here, we do not have any more
components because that's all. So P1.component, component
one, component two. However, if we come down here
and add another item here, so Val age, enter int, and
let's provide a value up here. 30. Now, if we insert P one dot component. We see we have another one. And this one is an integer
type because this is the third parameter up here. This is the Int. So it's the third component
in the parameter list. And so we say Val age
equals component three. And these are generated
for you behind the scenes by the Kotlin compiler. So if we were to print line
all of these things out there, we could say first name,
and we say print line, last name and print lane. We can not do age. And if were to run this,
we will see Donn Felker, 30 being printed out to the screen. Now, of course, we could just replace this with P one dot component one and P one dot component two. And we just copy this here, save time. It would do component
three and we can delete these other ones here
and delete all these. And if we run this,
we'll see the same thing we just saw before: Donn Felker, 30. So component one, component
two, component three. Now, if I switch this around, so I'm gonna put 30 here
and see if I'm gonna get a competitor error at
first, so I'll say 30. I need to switch the
parameter order down here. I can actually do this. Then component one, two, and
three are gonna be different. Component one will be Don,
component two will be 30, components three will be Felker. Let's put it back to how it was before with the age at the end. And one more thing that we can
ask you actually do here too, is we can actually specify
these in correct order. So let's do this. Let's just say age equals
30, last name equals Felker. First name equals Don. And actually let's even flip these two. Let's flip these two right here. So we're completely out
of order at this point, but however we're using
named parameters and Kotlin is gonna figure that out
for us in a way anyway, because this is the way
that they're defined. So these are the way that the components are going to be adjusted. Not in the order in
which they're parse d in, but in a way that they're
defined in the data class. So first name is component one. Last name is component two
and age is component three. So even if I use name
parameters in a different order, Kotlin will figure that out
behind the scenes and correctly render them in the correct order
with the correct component. As we can see here,
component one is Donn Felker. Here is component two and
component three is 30. And that's how you can
work with the components that are generated inside of data class. And I do have one more
use and that's going to be de-structuring, which is coming soon. One of the main reasons for the
component values that we see inside of data classes,
such as component one, component two, component
three is for de-structuring. So component one,
references the first name, which is the first parameter as defined inside of the data class. Component two will be the
second parameter here. And component three will
be the third one here. This is used for de-structuring. So we can say val, and
we can say first name. And then we say last
name and we can say age. And I'm gonna say equals person. So as we've defined the
person here on line two, what we're doing here is
we're actually defining these three variables
and we're de-structuring the person object. And what's happening behind
the scenes is Kotlin is using component one, component
two and component three to shove it into these variables here. So now let's print line each one of these to see what they look like. I'll say first name print line, last name, it will say print line, age. And if we run this, what we're going to see here
is that we see Donn Felker 30. So this is great. Now this isn't based upon
the name of these variables. So I could actually switch these around. I could say last name, age, first name. Now watch. If I run this, it's not gonna
be Donn Delker, 30 anymore. It's not gonna print these in that way. So it's gonna print 30 Donn Felker. Now, the reason is it's based
upon the de-structuring. So this is component one. This is actually gonna be the first name. So last name right here,
which we print right here. This is the second one,
that's Don, that's correct. Then we have first name, which was actually using
component three's location. Component three, so we're
just gonna see first name, component three's location, which is age, which is why we see 30 printed first here. And the last thing that was printed, I have listed as the
name of the age variable, is showing Felker because that's actually the second component here. So this doesn't matter what
these variable names are. So in essence, I could
actually just rename these two component one, component two, which I would have, these names
don't really mean anything. And I would never do this in production, but this is just an example
that it doesn't matter the name of the actual values that you're destructuring into. It'll still print those values. So let's go ahead and
move this component one, component two, component three. Let's print them in a proper order again. So there was Donn Felker 30. So again, I could just call
this F name for first name. I could call this L name for last name. It could be, you know, whatever casing. L name, and then we could call it go, and this could be person age. It doesn't matter what, as
long as whatever is in this third component is this
is the third component is gonna be shoved into
this variable here. And that's how you can
de structure a data class into values here, just in one quick line, because if you were to
do it the other way, you might have to do something like this. First name equals person
dot first name Val, last name equals person dot last name, and there's nothing wrong with doing this. It's just, you can also use destructuring to your advantage very easily
by turning this three lines of code into one line of code. And that's de-structuring
data classes in Kotlin. It's very often in development
that you'll need to create a copy of some data. So perhaps you need to
create a copy of this person and you'll want to create a sibling. And the sibling will, then
you can easily do that with the data class with
the built-in copy method. And the copy method will
automatically create a copy of everything inside of the class and give you a new instance. So if we were to create, to
print this to the screen here, we would see person and then
if we were to come down here, we could see sibling and we would run it. We would see that the two
classes had the exact same data. Now it's very often, you
may only wanna change a couple of the attributes
and you can do that with the named parameters. And so I could say something like Sam. Change the first name to Sam. And if I were to run that here, we'd see the rest of
the data stays the same, except for the first name changes to Sam. And maybe Sam is perhaps an older sibling and Sam's age is 44. And we were to run that. And now we could see Sam Felker age 44. Now this is an easy way to do this. This is very useful if you
have a very large object graph. So perhaps you have a
person, or maybe you have a, perhaps a data class with a order. And that order has an
amount which is an int. And then the order belongs
to an action needs to be, have a Val or a VAR amount, and then actually belongs to a person which could be the customer. And that will be a person instance. So if you were to do this, you
could actually come up here, let's go ahead and create this order. So we say Val order. And this order would be, let
me say the amount is equal to 100 and the person equals let's say it equals the sibling. Excuse me, the customer is the sibling. And of course, if we print line this, we're going to see that the
order has the amount of 100. And then inside of there, the customer is that person
instance with the age of 44. So let's say I wanted to copy that order. And so I say new order, and I know let's say the
customer is the same, but I just wanna change the amount. I can say order, and I'd say
the amount is 200 this time, perhaps they purchased
a different product, but it's gonna be the same customer. I could easily do that. Oops, excuse me. We'll say order dot copy. And the amount will be 200. And once we get the 200 here, we can print line that for the new order. And then once this runs, we'll see that we have another order here for the amount of 200, with
the same exact customer. Now, this could also be, you know, let's take this something
a new route here. So let's say Val new order two,
I could say order dot copy. And perhaps I want it
to create another order of the same exact thing but this time I have a different customer. And this time the customer
would be the person. Now I can say print line and we can see what this would look like. And it would say new order
two, and we'd run it here. We can actually see that the
order amount is still 100 but this time the person
has actually changed. And that's how you copy a
data class in Kotlin, period. It's important to note that
this is not a full deep copy if you're using lists and so forth, a lot of these items
will be shallow copies. So be careful copying lists this way. You can build your own data classes, but there are a couple of
useful data classes built into the standard library. One is known as a pair and
it's just a pair of values. So you could have foo and
then you could have bar, and these are just a pair of
values and they're accessed via the first and second parameter values. So you can have a pair of value. So you don't have to create a, if you have two values
you need to parse around, you don't feel like creating
a custom class for it, you can use a pair. And of course, when you
print line these to screen, you're going to see that
we have the following. So we have the first just
foo and the second is bar. Now there's also another
way another syntax you can use for this. And you'll see convert to two here. And this is a syntax using the two syntax. So basically we have foo
is going to match to bar. It's a pair. So this is another way to
create a pair is foo to bar. It's a syntactic sugar
over the application. And this is inside of the
Kotlin standard library. And you can look at the
implementation here, creates a (indistinct)
pair from this and that. So this and that is
basically the two values. So you don't have to worry
about the implementation, but you can either use a pair like this or if you want, you can go ahead and use the regular pair method like this. We have a small error
and there's our pair. Now there's another one here. If you have three values,
you can call it a triple. And a triple is the same type of thing. So we can say foo bar is or whatever, and we have three values. And the same thing happens here. You can say triple.first.second.third. And these are all very similar. Now they don't have to be strength. They can be whatever you want. If you want them to be an
integer and you want this one to be Boolean, you could do that. And this is up to you, how
you would like to have them. And then of course, as we see here, the triple dot second is gonna be Boolean as we see right here. We see the third is gonna be integer, and then we can see we can also copy that 'cause it's a built-in data class, so we can copy these and
work with them as they would with regular data classes. So these are the two built
in data classes right now that are inside of the
Kotlin standard library. Of course, more to come, if you're watching this in the future. It's pair and triple, they're
both in the standard library. Adding a protected modifier to
a variable in Kotlin is easy. All you have to do is
inside of your class, add the protected modifier
to your variable name prior to the declaration. So here I've added
protected two favorite food. Now let's go ahead and
remove that real quick and see what the cause of that happens is. Back in the main file,
I've created a new instance of the person class, and this person class has a favorite food property on it. So I could set perhaps watermelon. Now this works just fine, works as a regular setter and getter. I have a property that
we're used to working with. However, if I were to add
the protected keyword here, this now modifies this variable. So it's only visible from
within the current class, in any of the classes that
it get inherit from it. So if we come back here, we
now see in the main class, I no longer have access to set this value. And if I try via intellisense
or code completion to see favorite food, we're
gonna see that it's not there. And the reason for that is
because it's now protected. So only this class and any
of the classes that inherit from it are going to see it. So let's see that in action here. And I've actually already created
another class called chef. And remember, I have had
to add the open keyword so I can extend the person class. I've created person and chef. So basically a chef just extends person. Now, maybe if I have a chef
class, for whatever reason, the chef can have a favorite food. So I'll say fav food, and
we're just gonna say string. And so when I create a chef now, I have to provide a favorite food. And then what I can do is
I can actually set that. I'm gonna create an initializer block, which will be called as
soon as we create the class. And then I can say
actually, favorite food. See there's favorite food. And if I go to the
declaration of it's actually gonna take me right up here to line five. Now back here on line
15, I say favorite food equals my favorite food
that I just parse d in. So that's interesting. I can access it from within side of chef. Now, if say for whatever reason, I don't want to allow
regular persons to modify the favorite food variable. I only want it to allow the chef to do it. So I've not really
restricted anything up here. I could still come here
and say favorite food and I can still change here, but maybe I don't want to
have a public API per se, that allows me or any callers to modify. So I don't ever want the person variable. So I never want the person not
favorite food to be called. Now, if I were to call a
chef, so, or create a chef, I would say, perhaps something
like this, and we can say, Bob and Bob is 33 and Bob's
favorite food is ribs. Then we can see here that
now I can actually set the favorite food here, because this is being set right here, which in turn is being set
inside the initializer block. But let's go ahead and assume
that I want them to only allow chef to change it. So I create a function, as
I say set favorite food, and they may a parse in the food. And then what I would do
is this a favorite food equals food. Now we have an interesting error here. We're getting a squiggly line. And if we put our cursor over it, we'll see that we get an error
of an accidental override. And what this is basically
telling us is that, hey, Kotlin actually does us a favor here. When we declare a property
up here automatically what's being created behind the scenes for other JVM based languages
such as Java and so forth, are variables like this. Excuse me, methods like this. We have a set favorite food, which is going to allow
us to parse in a value, which is a setter, which can be string. And then there's also a
getter, so an accessor. And these two methods
are created simply for, it's gonna return a string, interoperability with other JVM languages. Anytime you've worked in
traditional Java environments, anytime you declare a variable, you need to provide a
mutator and an accessor. So a setter and a getter. And this is typically the
method in which you do it. You prefix the set,
whatever variable name is and proper, of course, camel case. And then of course, if there is a getter, which are usually is in this
case, you wanna get the value, you have the getter in the same format. So prefix it with the word get. So set and get, these are
generated automatically for you behind the scenes. So Kotlin's saying, "Hey,
we can't really do that. "You shouldn't do this. "We're already gonna generate
this for you, so no dice." So I'm just gonna go and
rename and set my favorite food just for an example. So now if I go back to my main class, what I can actually do is
that the chef can actually set my favorite food. And maybe I wanna change that to celery. I don't know who likes celery that much, but that's beyond the scope of this. So we have a favorite
food that set the celery. But now if we try to change the
food to set my favorite food on the person, well, that
method of course does not exist in the person class. So that's interesting thing. But what we can do is
we can actually print with my favorite food from the superclass. And that's very easy to do. Say print line. And inside that print line, we'll go ahead and say the favorite food. So that's pretty simple. Now, if we were actually gonna call this from the person class, P
dot print my favorite food, that'll work. And if we're gonna do it down here. C dot, let's actually do it two times, C dot print my favorite food
and then same thing down here, print my favorite food. Because again, I am extending
the person class over here. So the chef class is
extending the person class. I then can call any of the
methods that are available to it. So that are not private based. And I'm calling print my favorite food. Now for the person class, this is gonna show up as
unknown because we are not able to set the favorite food of that person unless we're using a chef instance. So the favorite food is just
going to show up as unknown for the chef we're actually
gonna see it twice. So we'll say print my
favorite food, it'll say ribs. We're gonna change it to celery. And then once we print it
again, it'll show as celery. So this is us enabling to work
with a protected variable. So if there's anything that
you would like to protect from any outside callers,
you wanna hold some state inside of your class, but
maybe you still wanted to be available to other
children classes via inheritance. Then you can go ahead and
provide a protected variable and you can do that. And of course this works
with objects and value types, et cetera. Now, of course, if we were to run this, here's what we would see. We would see unknown, which is the person knowing see ribs and we'd see celery. And that's how you can work
with protected variables in Kotlin. You can also make a
method protected as well. So we're going to use the same
example here that was used in the variable protected modifier lesson. And so here we have the print,
my favorite food method, which can be used back
inside of this main class. So again, like I say
p.print my favorite food. And when you say c.print my favorite food, and that's going to print
the actual favorite food, which is called from the person class. Now, for whatever reason
inside of your application, you may determine that
you do not want this to be part of your public API. And what I mean by public
API is a part of your class that can be called anywhere
that's perhaps public, and this would be a public method. So we can actually access it publicly. Let's say for whatever reason you want it to only access this within
the current instance of person or any of the children classes such as chef or whatever. To do that all you're gonna wanna do is slap on the protected keyword. And now this modifier was now applied the per print my favorite
food to only be available from within this class and
from within this class. So let's go back to the
main file and take a look what happened. Now we can see that the print
my favorite food is protected. So what does that mean? Let's take a look here
and the auto-complete, we don't see print my
favorite food anymore. However, if we were to go
perhaps into the info class here, we can say, print my favorite food. We could still call it from
within the person class. So give us a little flexibility here. And also any of perhaps other classes, perhaps that we wanted
to set my favorite food and then print it for
the console or whatever, we can call it from within any of the other children classes as well. So now let's say that's what we wanna do, we only wanna print it when
we set the favorite food for whatever reason. So we can say, print my favorite food. Now we can come back here. And of course, if we cannot
print or have anything to do with the favorite food
from a person perspective, because maybe that's
just part of the given, the way you're designing application, but we can actually say
set my favorite food. So we know that the person's
food was ribs before, but for whatever reason,
we wanna change it now and change it to be a potato. And so if we were to run this
now, what we should see happen is it's gonna be set from ribs to potato, and then it's gonna print
potato to the screen. Let me see print potato to the screen. And the reason why is
because inside of the set my favorite food method, we're setting the favorite
food to a different food and then we're calling
print my favorite food, which is protected inside of here. Now, of course we could,
maybe there's no need for this to be inside of here. And maybe the food does not
even need to be inside of here, which when you start
thinking about it perhaps, the person, we don't need to
know anything about the person having a favorite food. So maybe we just need
to move both of these into this class down here. And this would both work here. So we're gonna see favorite food now, is not going to have a problem here. So what is it saying can vary by... Variable cannot be initialized
before declaration. So what we can then do here. So we've trying to initialize it before, it's really been declared, there's a couple of
things we could do here. So we just kind of go
ahead and remove this and we could actually remove this as well, because let's assume that every
chef needs a favorite food. So to get access to this
and since we're mutating it, we're gonna change this to a variable. So throw the var word on
there and now we can print it and now we can also mutate it as well. And if we go back to our main class now, we've kind of cleaned
up our API a little bit. So we definitely do have our person class and we do have the chef class, and this will still run accordingly because the person is very
kind of a just a shell of a class. And of course we print and we see potato. And what I mean by that
is the person class just contains the name of the age. And it's just a very basic
information about a person and we've extended it using inheritance. And we've created a chef
and added a favorite food for that chef. And this chef males will
have additional methods like cook their favorite
meal or anything like that, or prepare foods. And as we start building
an application out, we can start separating
it into different classes, yet also having values
in here that are perhaps more protected and the
modifiers are in way such that they can only
be seen by children. So, which kind of brings up a good point that if you had a chef class,
well, you could also have a class, you have a sous chef class. And this one would take in
taking the name and a string and an age and int and
a favorite food perhaps. And this one would inherit from chef. And so we have name, age, favorite food. There we go. We just gonna renew this
to fave food or whatever, we'll call this the fave food. And then of course we
could have inside of here, something else, we have some other classes and stuff inside here,
or you can actually, if you would like to, you can
say inside of your inner block whatever reason you
wanted to print something when it was created, you
could do that side of here. And so for my favorite
food is not accessible to the outside world,
it's not a public API. So I can't call c.print my
favorite food, which is a chef. I can't call it anywhere in
public because it's protected. However, any of the children classes can then still call this. So here person is just giving us some root level inheritance. Now we're kind of created chef class has some stuff around it. And then we've got a sous chef class that maybe has some other
things inside of here. And you might wanna have
something like prep foods methods and stuff like that or something
that a sous chef would do. And that's how you can create a method, which has a visibility
modifier of protected. How do we use the internal
modifier on a class? And what does it use for? It's a great question. So the vehicle class that we have here is a very simple class that
just takes a color of a vehicle, and then we can print it out. Now, of course, this class would
have a lot more information about a particular vehicle, et cetera. However, let's go ahead and
assume that this vehicle has some axles. So we knew another class. So we're gonna create a class called axle and we're gonna have a count. And this count is gonna be,
actually let's call it number. And it's gonna be entered your value and that's basically the number of wheels. So we'll say number of wheels on the axle. And then of course it would be
a whole bunch of other stuff inside of this axle class
that would do things for us, but we're gonna leave
that out for brevity here. Now we know that the vehicle is gonna have a number of axles. So let's go and say axles,
and then we're gonna have it as an array of axle. Now, of course, we don't know what that is given this period in time. So we're gonna actually go
ahead and apply the late init modifier saying, Kotlin, I
don't know what it is right now at compile time. I'm gonna initialize it later. Don't worry about it,
I'll take care of it. So this all makes sense. We have a public class, we have axles, and then we might even
have something else, like another class. And this one's gonna be a
truck because perhaps we know that the truck is going to be, of course, it's going to be a vehicle and we need to parse it in the color. And then for whatever reason,
we already know that a truck, this truck that we're
building or all trucks in our application are
gonna have some axles. So we say axles equal, we'll say array of, and allows us to create an
array and I could say axle. And perhaps this first access to wheels, and this next axle has four wheels, basically, meaning we have two axles, one with two wheels in the
front, four on the back. So this all makes sense, this is great. If we go back to our main class, we can easily create a
vehicle as I've done here. I can actually say vehicle that
axles, I can see the axles. I can say, val truck equals
truck and I can parse in blue. And the truck's gonna have
axles that can have access to, but let's go ahead and
assume for whatever reason that we do not want anybody
outside of our current module to know about this axle class. Maybe we only want to expose
things about vehicles, but internally for
organizational purposes, we want to be able to have a
class that represents an axle. Maybe it has a bunch of utility methods that just helps us do things
inside of our application, but we don't want other people
to know about this class or even be able to use it, we just kind of wanna
keep it internal to us. Like this is our class it's
for us to do work with. We don't want anyone else using it. There is a way to do
that and you can apply the internal modifier to the class. Now notice we got a bunch
of errors automatically right out of the gate. So right here in axles
that says public property exposes an internal type argument axle, which means bill over here in main, if I were to type t.axles, well, we would have the
ability to have this. So now this can be interesting if we don't want that to happen. So what I can do is I can actually say, hey, you know what, I
don't want axles to be... Basically Kotlin saying, hey,
look, you can not expose axles because axles' internal to this module. Meaning that you can
use it in this module. We'll compile everything together inside of perhaps you're building a library. We'll compile everything together, but we're only going to
expose vehicle and truck because those ones are public. However, you said, here's an axle class, it's internal, so don't expose it, but you're trying to expose
it here, so don't do that. So in this case, what I really
need to do is actually say, put private axles. Now, as we see here, oh,
now we have another problem. Okay, can I access because it's private. So what we can do is then
we say, you know what, let's change this to protected
'cause we have a child class. Now, if protected, then
what do we have here? Protected exposes its
internal type argument. So now we have a whole different situation of maybe we don't want
to expose these axles. So you have to start
rethinking your API design at this point in time
if you want your axles available inside of
these other types here. And so maybe you don't wanna do that, maybe you wanna put it as
private just like this. So now you have your
axles that are private. And then perhaps you want
to say the number of axles in the wheels and you just
wanna expose this as a function. So you could do that. So you say, add axle and you
say a number of wheels int. And then what you could do is you could do something like this. And all this would do
is, would say axles dot. You know, we just turn
this into a list actually to make it a little bit
easier to work with, let's say axles dot and
we need to make this actually a mutable list so
we can actually change it otherwise we have a read only list. Add, and then we'll say axle, and then we'll parse in
the number of wheels. And there we go. Now we can actually have that. And so if we know that we need
this, we can say, add axle. I see two, add axle four. Now what this is allowing us
to do is have this axle class inside of our module, but not allow it outside of the public API. So if we are over here, now
we could say t.add axle. So I could still kind
of work with the axles, but it's hidden behind an API here. So I said add axle on a vehicle. So the vehicle can also add an axle. But if I were to try to
do anything with that axle such as return, and maybe I want to say, all right, well let's return the axle, someone might think so. So we say, get axle, so you get axle info. And what we're gonna do is
we return all the axles, we'll turn a list of axle. Well, as you can already
see, we have a problem. Public function exposes
internal return type axle. So even if I wanted this get axle info, this is not gonna work
because Kotlin would say, look, this is internal. You're trying to expose
us as a public API. We're not gonna allow that to happen. So perhaps I need to just
expose some additional information, I exposed the strings and then maybe for whatever
reason I iterate over them or whatever. Now, if I do need my class
to be accessible outside, then internal is not gonna work. However, it's very useful. If you have a particular
function class where you need to encapsulate behavior, but you don't wanna expose this behavior and all of its intricacies
to the outside public and you don't want anyone
to be able to call it. You want that to be perhaps
any of the interaction with that internal class to
happen through its public API, such as we're doing
here in the main class. If I wanna add a axle to a vehicle, I can number of wheels
three or four or whatever. And maybe this method perhaps
does a bunch of validation and a bunch of checking
before it actually creates this axle class, or maybe it has to do a
whole bunch of other things that if at your application maybe goes out and checks to see if
are any access available at the manufacturer, can we
even add an axle right now? What is an axle, et cetera. So there's a whole bunch
of things you can do, but it allows you to lock
down your API internally. And so you kind of wanna play with it and see what works best for you, but it's very useful
for hiding bits of code and functionality and logic
inside of your application, but still providing you with the ability to be organized inside of your module. To create an abstract class in Kotlin, all you're gonna do is
slap the abstract keyword right on the class itself. Now you have an abstract class. Let's go ahead and delete that though. And back at the main file
and see how this impacts it. By default classes are open. So we can go ahead and create
instances of them in Kotlin. So now I have a vehicle,
I wanna provide the color, which would be red and I can
create many different vehicles. So I create, we'll call this
one A, this will be blue and I can create many more, et cetera. However, if I want this to be abstract, all I have to do is add
the abstract keyword, which is a modifier to the class. Now I am not allowed to create an instance of an abstract class. Now you may be wondering why
would you not wanna create an instance of an abstract class? If you think about it, when
we're designing type systems, we have various different types
we're trying to represent. In this case, we might be
building an application which works with vehicles, but we do not want
users of our application to create just a vehicle instance, we want them to create actual
implementations of a car and a truck and so forth. And so a lot of these vehicles
will have some similar things to them, for example,
they may all have a color. They all may have a number of wheels. So we say val number of wheels, they may all have various
different things and doors and so forth that are all similar. So we could say number of doors as well. So val number of doors. Now, for whatever reason, this vehicle may actually
drive a certain way. So it may need to shift
gears a certain way. So it may be an automatic,
it might be a manual, different type of vehicle. And we may not know what that is. So we may wanna actually
have, if we, for example, wants to provide a method to
tell the vehicle how to drive. And so that function might
be just be called drive. Now this perhaps does something. Well, now depending upon the vehicle, drive can mean one thing or another. For example, in a car
that's not a automatic, we just step on the accelerator as long as we're in the
drive position, it drives. However, if you're in a manual
transmission environment where you have to shift gears manually, well, drive is gonna
require a few more steps. So we cannot abstract all this information into just this class
here and too abstract. So this class we might
wanna actually say abstract, abstract function drive. Now I'm not gonna provide
an implementation here because the implementation is gonna change based upon each different
implementation of the class. Now, one thing that may be
the same as everything else is a function called open door. And this function just
simply opens the door. And all that does is open the door. Now it's gonna be the same, perhaps for every single vehicle you have, you pull on the handle and the door opens. Now, of course, this is
different if in real world, we're gonna have electric
cars and electric doors and electronic doors and manual doors. But for the brevity and situation here, perhaps just assume that every
door is opened the same way. And so we can leave that
inside of our class. Here's an abstract class. We can start having other
different things inside of here, such as function called stop. What would a stop do,
it just stops the car and maybe even better say, turn off. Every car can be turned off,
vehicle we could turn it off. Let's just turn off the ignition. And if we only wanna be real specific, say, turn off ignition. And perhaps this is gonna
turn off the ignition, whatever that implementation looks like is what it looks like. However, we now have an abstract class that does a couple of things
for us that we don't have to re-implement everywhere else. And this is the vehicle class, this is how we can define natural class, but we can not implement it. So we actually have to
implement it in another class somehow, but so we can have
a function that's abstract, which means, hey,
whoever's going to extend this vehicle class at that
point has to also implement this drive method
because it's gonna differ between different classes. However, they can go ahead
and use the open door and turn off ignition methods. And that's how we can develop
a very simple abstract class and define it in Kotlin. To implement a abstract class in Kotlin is actually pretty easy. So let's say that you
wanna have a car class that extends this vehicle class. So we'd have class car. And for whatever reason, when you say we're also gonna have a color,
which is gonna be string or have number of wheels
as integer and number, this needs to be bow number of doors. And then what you can do as
you're just gonna go ahead and extend the vehicle class. And vehicle of course
is gonna need that color and number of wheels and number of doors, because those are constructed parameters. However, notice we have
a red squiggly here. And the reason why we
have that is the car class is not an abstract and does not implement the abstract base class member drive. So what this is saying
is, look, maybe you can do one of two things here. You can have an abstract class
extend an abstract class. So you could map this one here and you can maybe even call this a two, let's call this a two door car. So call us a two door car,
and I'm gonna go ahead and break this into a new line here so it's easier to read. And so instead of parsing
in the doors here, I already know that this is
gonna be my two door sports car for whatever reason. And now I have an abstract class here and this one might have a function here, an abstract function, and so I'm gonna call it drive fast. Of course, I don't know
what dry fast means 'cause in this two door
car, it could be a Porsche, it could be a Lamborghini, it
could be any number of cars, but I know that this two door
car is going to drive fast and how it does that depends
on the implementation. Maybe it's an electric car. So that's one way I could do it. I could also just go
ahead and implement a car if I just know that I want
perhaps a simple TownCar or this is called a hatchback,
even simpler, hatchback car, so you know it has
little hatch in the back. And then I say color of course
is string number of wheels gonna compute any variable,
number of doors it's gonna be, who knows, same thing here,
I'm gonna put in a new line. This one's gonna implement vehicle and it's gonna be the
color number of wheels, number of doors, et cetera. Now, again, I'm going to get this because I'm gonna get this error saying that we have not
implemented the drive function. So what I can do is implement that member. And basically what we're telling here is, hey Kotlin, I have now
implemented the hatchback car. It is of type vehicle and
this one is going to drive a certain way. And so when the drive method is called, it will then call this
implementation right here. Now I can have many different
implementations of this car. So I'm gonna actually copy
this and paste it right above. And then I'm gonna call this one, perhaps, call this a TownCar. I'll call it a TownCar and
same thing we have drive. So we have different implementations here. And this TownCar might
have something different where it has a very smooth ride. And this one is a very basic smooth, and this one is very basic. It's very just hatchback car. So we're gonna have many
different implementations here, however, in order to
implement an abstract class, you also have to implement
the abstract functions here. So, which is interesting, let's
go with this two door car. Let's create some space
here and let's implement this two door car. So we've already seen
how we can do it once. But now we have an
abstract class that extends an abstract class. What does that look like? And so let's go ahead and
implement something here. So we'll call this Lambo
and of course it will, perhaps we just are gonna
assume that every Lambo is gonna have, so it's
gonna be extended vehicle. I'm not even gonna provide this things inside of the class
Lambbo 'cause every Lambo that we're going to create is gonna be red and it's gonna have four wheels and it's gonna have two doors. There we go, simple enough. Now we have this little error here saying, hey, look, drive has not been implemented. And actually we know it's
gonna be a two-door car, so let's change this to two door and we can get rid of this right here. And now we have Lambo says, look, drive fast has not been implemented. So let's go ahead and
implement drive fast. Okay, look, we have two things here. Well, let's just go ahead
and put and drive fast 'cause that's what it
was complaining about. And we'll say, and whatever
this is going to look like, is what it's gonna look like. Depends on your implementation. Now we also have another
error here saying, hey, the drive has not been implemented. And why is that? Well, because drive fast method came from this abstract
class and the drive method comes from the vehicle. So because two-door car extends vehicle and our Lambo class extends to door, we basically getting the entire object that we need to implement here. So as you can see, this can be beneficial, depends on how you are
developing your classes. However, it can also be very
difficult if you decide, let's say you have 20
implementations of vehicle and the subclasses are majors 100 and you decide to add one abstract method to the top where the
people must implement it. Well, now it's gonna repl
throughout your code base and you're gonna have to implement that in each of your child classes. So it's something to think about that you'll encounter from time to time. Sometimes that's necessary, sometimes that's what
you expect to happen. Maybe you need to implement
some type of method, like a safety check to ensure
that all of the vehicles have meet a particular safety rating and you have to implement
that in each class and that's what you need and that's okay. However, other times you're gonna realize, well, maybe I don't need
that method to be abstract, and that's gonna be on
a case by case basis. Now over here, of course, we can go ahead and start implementing all of these. So if I wanna say var or val Lambo that I can just say equals Lambo. Now I've got a Lambo. And remember, I didn't have to
provide any variables there. Well, why didn't I,
because I already knew, could I said, hey, all of
our Lambos are gonna be red and are four wheels. And since this is a two door car, we already know it has two doors. I don't have to provide anything, I already have a Lambo now. And so if we wanted to
provide a method up here, we could say what kind they are. Another thing you can
also do is let's go ahead and create a couple of
these other cars here. Var two door equals let's say TownCar. Equals TownCar. Now of course, what are we missing? We're missing the color,
so let's call brown. It's gonna have four
wheels and four doors. And now I have my two cars. And again, I could have multiple
other ones inside of here. I've got my Lambo, I've got my TownCar, I've got my hatchback. I've got all these
different types of cars. So one thing I could do here,
so we have a hatchback too. So, we have a hatchback car. This one's gonna be silver. It's gonna have four wheels and two doors. So this point, you might realized, well, some hatchbacks
have for some have two. So that's why we need the,
have the number of doors in a way that we can configure them. So this is how you can
implement an abstract class and you can also have an abstract class extend another abstract
class implement that. And then you can see
how the abstract methods and members are required
inside of each individual class itself and you're required
to implement them. So for example, our Lambo here extended from the two-door car, which
extended the vehicle car. So we had to implement the drive method and we also had to implement
the dry fast method in order to fulfill the
requirements of the abstract class. And basically what the abstract
method is saying is like, look, we don't know what drive does, but we know that every
person or every class that implements this
vehicle class has to provide an implementation of drive and the same thing down here. And we didn't have to
implement it down here is because this is an abstract class. And that means like, hey,
there's nobody who can create an instance of two-door car. But if you are going to use two-door car, then we do know that you
have a drive fast method for whatever reason. And whenever you implement that, you have to also implement whatever else vehicle is asking here. So if I were to throw another
abstract method on here, so abstract fun stop. Now notice one thing, Lambo
is gonna give us an error. And if we scroll down also, so is TownCar, and so it was hatchback. Well, why is that? Well, simply because stop is
implemented everywhere else. So I would have to go into
TownCar, implement stop. I would have to go into
hatchback, implement stop. Now here's an interesting thing, maybe I know that all of my two-door cars, the only way to stop them
is to slam on the e-brake. And so what I can do
is I can implement stop inside of my abstract class here. I'd say pull e-brake, which
is the emergency brake. That's how we stopped these two door cars, just as an example. So in this case, Lambo
doesn't have to implement the stop method because
that's just handled in the two-door car level. So I've actually can
override an abstract method inside of an abstract class
of the parent abstract class. So again, this stop is
actually from up here inside of this stop. So you can go here and you
can right click on that and you can say navigate, excuse me, not navigate. You can go to command U on Mac, it'll take you to the
implementation of where this is at. And I think it's control+U for windows. So this abstract class has
implemented one member, but it's still parsing this
member down further below. So we're still saying,
hey, drive needs to happen. And what we can also
say is, you know what, well maybe any time a
two-door car is driving. Well, we also know that
we are going to drive, we're just actually just gonna drive fast. So anytime you drive two
door cars, only drive fast. So notice one thing, I can
actually get rid of this down here, because the Lambo
now is using the drive method. If anyone calls drive, it's
just gonna call drive fast, which calls this drive fast method. So let's really see this in action. Let's go over here into our main file. And inside of our drive fast, let's go ahead and just do
a print line, driving fast. So if we have a two door
car, it's gonna drive fast and let's do a another two-door car here and let's create another one here called, let's do another one. Let's call this one Honda. And maybe you were saying,
hey, you might like this really fast Honda or let's do Acura. And this one's gonna be a two door car, a two door car. It's gonna be blue and it's
gonna have four wheels, there we go. However, we do need to
implement this Acura drive fast method. So we're gonna say print line zoom zoom. And what am I getting at with this? What we're gonna see here is when we call the drive method here on a two-door car, it's gonna delegate to the drive fast. Now the drive fast has no implementation, we don't know what that does yet. However, that's gonna delegate down into the actual implementation
of the abstract class. So the two door car has
a drive fast method. So anytime we call drive,
it'll call drive fast, which then calls the drive fast method, which is implemented
in the abstract class. So let's go here and
let's go ahead and create. We'll have our Lambo move that down here. We can get rid of these two for now. And let me say val Acura equals Acura. And now what we can do is we
can say something like this. Let me say Lambo drive and Acura drive. Now they're both gonna drive, they both drive the same, whatever. They have the same abstract class, they have the same interface
so I can work with them. They're both vehicles. Now we see it on here, one is driving fast and one is going, zoom, zoom. Now an interesting thing
that you can do here is you can start creating all different types of implementations. Now I can still we'll call drive fast. That's not restricted, I can
still say Acura.drive fast. and I can still do this and
everything will still work and we'll still get the same results. So driving fast, driving fast zoom, zoom. 'Cause remember, drive, if
you go to implementation command B, it takes us
to the two-door car, which is just going to delegate
down to the drive fast. Now implement the drive method. That drive method was from
the original vehicle class. This is an accidental import. And so that's how you can
create an implementation and instance of implement
an abstract class and have it also extend other
abstract classes as well. One of the more interesting
things about abstract classes is the word abstract. Anytime you think of an abstract class, think about the definition
of the word abstract. What does it mean? It means to basically pull
something away from it, to abstract the details away. In software when you use abstract classes, especially in Kotlin, it
means you can kind of use this as an abstraction of source. So let's assume that we've created these four different cars. All of them all inherit
from a base abstract class called vehicle. So we have vehicle, we
have a two-door car, which extends vehicle. We have a Lambo which
extends a two-door car and an Acura, which
takes is a two-door car, a TownCar and hatchback. And each one of these have a drive method. So the hatchback says driving a hatchback, drive for TownCars is so smooth, the drive method for
Acura says zoom, zoom, and the drive method for
Lambo says driving fast. Notice you say drive fast
and why don't I implement the drive because that's implemented in its superclass up here. And anytime someone calls drive, we just call the dry fast method because we're just going
to assume two-door cars just drive fast for whatever reason. So we have all of our cars here. Now, if we wanted to call
drive on each one of these, what we could do is we
could say lamo.drive, we have call acura.drive. Now this is great and all, or
we could even say drive fast if we know we want it to just drive fast. However, there are a lot
of instances in power when we know that we wanna
rely on the abstraction. So we'll say drive vehicle,
let's create a method called drive vehicle, which
just tells the vehicle to drive. Vehicle. And then what we're gonna do is we're gonna take in a vehicle and inside of here, what we're gonna do is say, vehicle.drive. Now this vehicle is an abstraction. We're not creating an instance
of it, we're just saying, hey, this method is gonna
take an instance of a vehicle. We don't care what kind it is, we just want a vehicle and
we're gonna tell it to drive. So what we can do here is
then you could say, Lambo, you say drive vehicle
Lambo parsing instance. So I'm gonna just
duplicate this a few times and I'll say Acura and then
I'm going to say hatchback and TownCar. Now, when I run, this what's
gonna happen is drive vehicle is gonna call in. So we're gonna say Lambo,
we're gonna get an instance of a Lambo. It's in the call this drive
method, which is gonna say, all right, who's implementing
this drive method. Well, a two-door cars
implementing the drive method because well, who is implementing, okay, it's going to call drive 'cause Lambo is a two door car and say, oh well, I'm
gonna implement drive fast, which is this method. Well, who's implementing dry fast. And to say one Acura and Lambo are. So that case is gonna go
here and call driving fast. So if we were to run
this, what we're gonna see really easy is we're gonna
see each one of these methods, method invocations are gonna dive down and call the appropriate methods. So for a Lambo, it's driving fast, for an Acura, it's zoom zoom, for a hatchback, would
say driving a hatchback and for TownCar, it's so smooth. And so abstract class thing, again, think of the word abstract,
allows you to abstract the details behind some type
of, almost like a contract. So this is the contract
of all the vehicles are going to have. They're gonna have color,
they have number of wheels and have number of doors. There have a drive and a stop, which is different for each vehicle. And then it's those
child classes implement can either decide to
override and implement things or if it's an abstract class, it can leave it as is and
let the implementers downline implement it themselves, such
as TownCar is doing here, what it needs to implement drive. So again, you could have an addition. You could extend two-door car
to be a slow two-door car. And instead of it being dry fast, you could override that
to, I could drive slow. So, and then again, up
in your main file here, you could still just use
this method to call drive and that class would
then react accordingly and you can start performing
all different types of operations with
different types of vehicles, but have them have the same
type of type signatures. And that's how you can use
abstract classes as abstractions on a very simple manner. So when do you want to use an interface? Let's take the simple example of you going to eat at arraystaurant. So here you are, you're getting ready to go eat at the restaurant and the waiter brings you out a menu. Now the menu I want you to
think of as an interface. And what is an interface? Well, an interface is a contract saying, what are the things
that we can do for you? So here at the restaurant,
this is an Italian restaurant. They say, well, here we
can make you spaghetti and meatballs, we can make you bakesidi. We can make you lasagna, we can
make linguine and clam sauce and shrimp and garlic and
all kinds of stuff like that. And you determine the one
thing I would really like is spaghetti and meatballs. And so what you're using is this contract. You're reading the contract saying, hey, what are the available
options, which this menu is. It's the list of available
options of what this restaurant over here can produce. And this is the restaurant. And the menu is the interface between you and the restaurant. So the restaurant says,
this is what we provide, this is our contract of
goods that we can do for you or things we can build. And then what you do is you communicate through this interface
here, this menu and say, hey, I want to order this. And so you tell the
waiter, hey, I wanna order this spaghetti and meatballs. So this order gets go
over here to the kitchen. And then what they end
up doing is they end up making your spaghetti and meatballs and they end up making it
blah, blah, blah, blah, blah. And here's all the noodles
and it kind of looks like a smiley face now. Anyway, there's some
spaghetti and meatballs. And then what ends up happening from there is that gets directly returned back to you and then you get to enjoy it. And you are now a happy camper
because you are enjoying your spaghetti and meatballs,
so it's a pretty good thing. All right, there we go. So your spaghetti meatballs,
now this is the contract. Now here's the thing, let's
assume that this restaurant has many different locations. So this locations, you
could have another location, which is over here. It maybe some different town, but they're owned by the same company. But when you go to this restaurant
to this chain restaurant, you're like, you realize
that, hey, you know what, I really like the spaghetti and meatballs. And so you go to the blue one here, which is the same company. They have the same menu,
so they bring out you the menu again and say, all
right, here's all the things that we have to offer. And these things of course are gonna be your spaghetti meatballs and your bakesid and your linguine and clam
sauce and et cetera, et cetera, et cetera, et cetera. And of course you say immediately, well, I like the spaghetti
meatballs from this place. Again, it's owned by the same company. So again, you're gonna make an order again say, I want spaghetti and meatballs. The order goes over to here,
spaghetti and meatballs. And then what you don't
know behind the scenes is, well, this is just the contract, like you don't care how the
spaghetti meatballs are done, you just want the spaghetti and meatballs. And so what ends up happening is maybe because of the way that
the business is built or the software is built. In this instance, when
I'm running over here, instead of building and
making the spaghetti and meatballs myself, they
actually kind of just go over here and say, hey folks,
we need some spaghetti and meatballs. And maybe they've already pre-packaged those spaghetti and meatballs before. And they actually have already delivered a box of those spaghetti and meatballs. And the spaghetti and meatballs
are already pre-packaged and ready to go. And then as you can see here, they got all these
little meat balls in here and you're ready to eat them, or they're ready to warm them up. And so at this point in time, you, again, you've just ordered the say, I
want spaghetti and meatballs. The restaurants are also
adhering to this contract and saying, okay, spaghetti
and meatballs came in, cool. If you're at this
restaurant, we make it fresh because we're the main restaurant. If you're at our other location,
we've already made a bunch and kind of pre-packaged it. So this location can kind of get it done because we have a special way of making it that only two people know how to do. And so now over at this location, what they do is they come
over here and they say, all right, cool. Well, let's go ahead and take
some spaghetti meatballs here and we'll head and put it on the plate and we're gonna warm it
up and we'll do whatever we wanna do to it. And here's the spaghetti
and meatballs or whatever. And then at that point,
we're gonna go ahead. And once that's done, we're gonna go ahead and deliver it back to you and boom, there you go right into your stomach. So now you have spaghetti
and meat balls over here. The key thing is here
from a user perspective, this is the user who is
perhaps consuming a library, your code or whatever,
this is your interface. Your interface says, hey,
here's the things we can do. And then the implementations
are these restaurants. These are who implemented the interface. This restaurant A
implemented the interface and restaurant B implemented
the same interface. They both said, hey, we
can both make you spaghetti and meatballs. Now how they do that, you
really don't care about. The caller of that from
the interface perspective doesn't care about it. The caller says, hey, I want
spaghetti and meatballs. At the end of the day, you
know that you've just been delivered the spaghetti and
meatballs that you want, you don't care how it's done. Now behind the scenes, the implementation could be different. For example, this is code. This code might call into a
completely different module to generate something or this
code might generated itself. It all really depends
on how you're doing it. But what it allows you to do
from a developer perspective is say, hey, I have this
known interface here. And so anyone who interacts
with us just needs to parse us an instance at this interface
and call this interface and whoever implements it over here will do whatever they
need to do to return back what they want. And so you can have one,
you can have multiple different implementations
of these over here. You could have, we have two colors here, but you can keep going. And there could be all different
types of implementations of this interface and they
could be all over the place. They could be ones up here,
there could be hundreds of different of the implementations, but they all, every single one of them adhere to the same
interface, the same contract. So it doesn't matter if
you're ordering spaghetti and meatballs from this
location or this location or any of these locations, they all know how to return back to you
the spaghetti and meatballs that you want, all done
through this interface. Now, if you take this a step
further and we can go ahead and kind of clear this thing out, again, you have yourself over here and now this is we're going
to a futuristic restaurant. And this futuristic restaurant now has just a digital ordering pad. And you come in here and you
get to press on some buttons. So I want, spaghetti and
meatballs or baked ZD or whatever. This is just an interface too, think about it like a graphical interface. This is an interface
that's been implemented. Now, depends on where you're going. You don't really care
who's implemented it, you just wanna know that you're gonna get either your spaghetti and meatballs, or you're gonna get your baked ZD. And you really just don't
care where all these different types of things are coming
from because to you, you interacting with whoever
agreed to that contract. And then at that point in time. So for example, let's even
take this a step further. You could have two
different implementations of that interface. So this is gonna number
one will be up here and number two will be down here. And as you order this item here, right in this little section,
so I wanna order this. And at this point in time,
you're gonna kind of go down this path right here. Well, cool, that means I'm gonna go ahead and this automatically then says, okay, let's spaghetti and meatballs. We're gonna go to this module and generate whatever we need to generate. And for whatever reason, you're going down this path over here, well, all that's just kind
of, we have custom codes for everything. So everything is just
kind of done right here in this whole section. So what that allows you to
do, the interface allows you to hide the implementation
behind a particular interface. It's the contract that this is the things that we can do for you. So anytime you want something to adhere to the same contract, you can do that. And that's exactly what has happened over inside of the examples here. In this case, we're using
mammals so we can have a mammal and it can be a cow, it
can be a human or whatever. We noticed that it walks,
it can speak, whatever. All we know is it with a mammal interface, if I tell a man what
to walk, it will walk. Now I don't care how it walks. Does it walk on four feet,
two feet, 12 feet, 15, I don't know, don't care,
I just know that it walks. And so when you have an
interface, it means that, hey, these are the things we can do. To implement an interface in Kotlin, you'll type the word interface and then the name of the interface. I'm going to call this one discountable as in something might
be able to be discounted such as a physical product, or
maybe even a digital product, but I just want it to be discountable. And instead of there, I can
declare values or excuse me, functions that I would want
the implementer to implement. So here I'm going to implement a function called discount percent, which will then return the percent amount that whatever the discounted
item is going to be will be discountable. So now I have created an interface that is called discountable. It has one function, I
create another function that could do something else. Perhaps I'd call it a foo
and maybe it doesn't have a return type, it doesn't have to be, it might be something such
as calculate something or you could just do whatever you wanna do inside of your function. So it doesn't have to have a return type and return types can be anything you want. And that's how you create
an interface in Kotlin. To implement an interface in Kotlin, you'll want to actually create a class. So you say class, it might
wanna have a physical product. So we'll just go ahead
and say generic toy. And this class is going
to extend and implement the discountable interface. So here we have the red
squiggly and we can click on this button and say, implement members and it'll ask us which ones. We could select discount percent. And then what we have to
do is we have to implement whatever that value is. And of course, if we do not implement it, we can leave the default
to do here, which will then if we look at the
implementation, will go ahead and throw a null implemented exception. So if we run it, we'll
kind of get something in our log saying that this
has not been implemented for later, but this is how you
can implement the interface. Now, if you do add
additional member on here, so let's just call this
foo for whatever reason, we're gonna get a compilation error again. So we won't be able to compile
until we implement that on anyone else who has
implemented discountable. So we may also have another
class called digital product and digital product is also
going to implement discountable because perhaps you can
discount that as well. And you will also need to
implement both of these values on to both of these as well. So they'll both be discountable. And that's how you implement
an interface in Kotlin. And some examples of this
might be you build an interface for your file system. So if you build a file
file system interface, you might have something like this. Interface, we'll call it fs for five. It's very comical file
system and I think nobody even has one called fs. And we might have a method called reader, which is going to read
back and give us back a list of string is called files. We could have function read file, and it's gonna return us back a string. And we can have a whole
bunch of other things inside of here. Now, what we could do is, I'm actually just gonna rename this so it's a little bit more, make some more sense of file system. Then what we could do is we
could have an implementation of the file system, and we might wanna call
that a real file system, or you know what, let's just
call it a fat 32 file system, which is a real file system. And it's gonna implement file system. And then what we'd have here implement, oops, implement these members. And then we have to do something with each one of these members here. So we would read the
file in a fat 32 method, and we would read the
directory in a fat 32 method. And then there's other file systems here. So let's just go to return an empty list, just so we don't get a compiler error and let's just go ahead
and return an empty string. And of course, we would
read a file that the file would probably have a path of whatever, and we're leaving it off
for brevity at this point. Then if we wanna have it ext file system. So class ext files, we've
gotta have a file system here. Same thing, we need to
implement those members, and we need to actually
implement the other one too. And it's gonna leave us to do there so we don't have the error. Now, this is great because
we have the ext file system. We have the fat 32 file
system, and we know that anytime we're working
with a file system, we need to do something. Now, lastly, we may have
a very interesting one, we have right here. So I have class memory file system. Why would we want this? Now, this is very interesting here, because we might just have
something that allows us to, let's go ahead and
implement these members. And we might be able to
have an implementation here of memory file system, where we take in and a constructor here. Watch this, we say read directory. We take in files. So we've taken a list of strings. And look what we're gonna do here, and we take in the file content, B string, and now all I'm gonna do
here is just return files. That needs to be a val,
so we can actually work with the inside of the class and the same thing down here,
return the file contents. So the interesting thing
here is if we go back to our main application. If for whatever reason inside
of our main function here, we wanted... If we were to work with
just the file system, so it will say file system. And we were broke with
the fat 32 file system. And then we're gonna do
something with the file system, blah, blah, blah. We could actually say, I'm
gonna read this directory and it's gonna read the directory. Now this would be very pertinent
to file 32 fast system. Well, what if we wanted our application to run on the ext file system? Well, now we're gonna
have to do like some, if the FL statements to
check what file system we're on or whatever, or
what could be provided to us during real time is actually a interface. So we might actually
say something like this. Val file system, file system right here. Remember, this is the interface here, and we can say equals fat 32 file system. Now that's still works and
we can still kind of do the same kind of thing we did before. And if we were using something
like dependency injection, we could actually get
this from a constructor, which is beyond the scope of this, but we get from a constructor or a setter or something like that,
some variable, some type, in which, we could set up pretty easily. Now the real power from this comes in when we determined later on. So let's actually do that now. We actually just create a method here, call get file system to function. Get file system is going
to return us a file system, turned fat 32 files, okay, there we go. And all we can do here is
just say this file system. Create file system. Now at that point in time, we can do this. Now, the interesting thing
here is if this was inside of a dependency injection or whatever, we could use the in-memory version. So if I'm writing a test,
maybe I've just moved this code into like a helper function somewhere. And then what I do is instead of this one, I say return memory file system. And then what I do is I
know list of no path to file another paths, so remember
this path of file pars, file content go here. So what I'm doing is I'm actually creating a in-memory file system, kind
of faking the whole thing. And then inside of my
application, I could say, filed.read directory. And then what I can do is actually give a known list of files here. And so now I've actually
and I've enabled myself to have different implementations. This is the contract. Remember the contract says,
hey, I'm gonna redirect and I know how to read a file. And if I read a file, I'm
gonna get back a string and if I read a directory and
to get back a list of strings. That's all I know how to do,
I don't care how it's done, but I'm a fat 32 system
it's done differently than an ext file system. And you know what, I may also
build a memory file system, which allows me to provide the values to the actual file
system so I can actually basically do this in memory
or for testing, et cetera. So interfaces allow you to
separate and think of them almost like shims. They allow you to kind of really
decouple your application. And so they're just a way
for you to communicate. It's a known contract between
you and your application and what it can do. When creating interfaces, it's
very often that you'll do it in an anonymous fashion. Here, I have set up an interface
called on click listener. This is something you've probably seen in various other UI toolkits. And this on click listener
has an on click method that is called when a
particular view was clicked. And so we have a class here called view. This could be such as like
a button, a text view, an image view, some type of
map or anything like that. And it has a listener that's
going to be assigned to it. Now, we're just gonna very
simply here use the late init modifier to say at some point,
this will be called and set. And we're not doing
any null checking here, so this is not production quality, but this illustrates the example. So anytime the view is
clicked, it'll then instantiate the click listener and
then whatever happens inside the click listener will happen. So that's the interface
here, on click listener with the on click method. So we have a few other
classes that extend view such as button image and map. So let's assume we create a button here. If I wanted to set the click listener, I could set it to an instance
of the click listener if I had implemented it
in an instance of it, in which I could do my own
custom one, like this class, my listener. And that would be on click
listener and then be like this. And then perhaps I wanna say
print line and I say clicked. And I can do that very easily
by just saying new listener to list my listener. And there we go, now that
click listener will work. And if I call button.click, the listener will now get invoked. So if we run this here,
we should see clicked in the output window, we do see clicked. Now this is not an
anonymous implementation, this is a concrete implementation. So let's go ahead and get rid of this. This is not what we want here. There's a way we can do
it directly in line here. So what we're going to do is
we use this word called object. We're basically creating an
object right here in place, and we'll say click listener, and then open and close brackets. And then you'll see object is highlight and you hit alt+enter for Mac
and implement the members. And it will just allow you
to implement that member, which is on click and then we
can just do print line here. Let's say this was clicked. And of course, now if we run this, what will happen is this basically created an inline object here. A we've done it in line object,
which is an implementation of the on click listener directly in line. And it was called here and
directly set up accordingly. So that's how you can create
a new instance of that. Now, sometimes you'll also see methods perhaps that allow you to set listener. So to say, set my listener,
we'll just call it that. And this is gonna have a click listener, and this click listener is going to be this dot click listener
equals click listener. And so what it basically
says, this one is gonna be set equal to this one up here. And so that's another way
you could do that there too. So inside of here, we say
button.set click listener, which is a method now. And so I can actually just
cut and paste this out of here in the same thing. I'm gonna use an object, so object: the name of the interface, open and close brackets and then implement the required methods in the middle. So the abstract methods
and this place on click and an instance of on click
list will be anonymously created and set into this value here. So when the click button is called, it will then say this was clicked. And just to show some differences here, let's say this was clicked ABC. And if we run this again,
when it gets to button.click, we'll see this was clicked ABC. Now the same thing happens
here for the map and the image. So you could then eat it
very easily and say map. So val map equals map and we
would say map.click listener equals, and you could do the
object thing here as well. Is that on click listener and
then open and close brackets. And there you go. And then you can do
something inside of there. And then of course, if
the map was clicked. Now, what this allows you to do is have the very common interface,
a contract of, one of you is clicked, it'll just invoke a function. And then you get to decide what
to do when a map was clicked or what to do when this
particular button is clicked. And so this is a very common
pattern you'll have seen inside of various UI platforms. So that's how you can create
an anonymous interface implementation in Kotlin. You can easily create a raise in Kotlin. So let's do that real quick. We'll create a variable called items, and you can say array
of and then you parse in some primitive. So we can say one, two, three, four, five, and then we can do
something with that array. So now we have an array of integers. So we can say items.foreach. And what we're using is
the extinction function on the array class. There is a foreach extension method built inside of the arrays
instead of the Kotlin standard library. And so what that allows
us to do is parse in a lambda expression, and we can actually just
do a print line with value. So we can say for each and
print line will then provide the, take a value and
print it to the screen. IT is the default name of the item inside of the lambda expression. If we want to change it
to something different, we could change it to maybe the word value and we would do so like this. And then at this point,
we could say value. Now that doesn't really provide us any additional benefit here. If I hit alt enter, we can
see that we can replace the explicit parameter
value with the name it, and this just kind of cleans
up the code a little bit. Now, once we run this, what we will see inside of the output window
down here on the bottom is all the integers have been printed out. Now, an additional cool thing
with some of the built-in array classes of Kotlin is we
can also have an int array. So by default, we'll
specify how we would like some integers, we can
also have a double array. And notice this is going
to create a problem here because these are not doubled. But if we were to turn them into doubles by providing a.zero. So what is actually a valid double value, we can then at that point,
start using the double array of call. There's multiple other
ones in here as well. So we have the array of double
array of, float array of, longer range of, of
course, the int array of, char array of, shorter arrays, by arrays and Boolean arrays. Now you'll notice we don't
see a string array in here. So what do we do there? So that's actually pretty easy. We can actually just do array of, I know we have to provide
some values in here. So Donn Felker via a
simple array of two items. And if we were to run this
again, it would just say, Donn Felker, one string on each line. Now that's not all we can do with some of the arrays as well. We can also get the array of
objects, so we'd say array of, and what I can do here is
since we have our user class, which if we remember that
it just has a first name and a last name. What we'll do here is we'll have user and with page, say Donn Felker. And then we might have
another one called Jane Doe. And now we have two users who
are part of the user array. Let's actually call this users. And then at that point, we
can actually do the same thing that we saw before and then we can iterate each over one of them
and we'll call print line and we'll call this IT. 'Cause remember IT at this
point is just a user itself. Up here it's going to be a string. So the compiler is going to know that. If we run this, now we're going
to see the two string value of the user class. So let's go take a look
at the user class again. And the user class, I've
added a quick two string implementation, I've
just created an override so two string, and we can go
ahead and return something. Now its full name and it just
returns this string up here. And that's how we can work
with strings and primitives and objects and easily
create a raise of them and alter them and so forth. Now let's assume that we, for some reason, we needed to update
the users and we needed to add one to it. So what we could do is
to say val updated users, and we could say users.plus, and they're gonna add new user to it. And we say, Jon Doe here,
and what this will do is if we take a look at
the plus implementation, what does it do behind,
underneath the hood. It's just an extension
function that creates a copy of the array with a one larger index, adds the item at that given
index and then returns that new array. So we have to make sure
we're using that new array. And so of course we could
do updated users.foreach and it's a print line. And then what we could do
inside of here is just say IT. And now actually see if we run this again, we'll see additional
person down here, Jon Doe has now been added to the second array, which is these three down at the bottom. There's other things you can also do with those user objects. So you can actually say users
dot there's a little extension function on here. So you say reverse, this will
give you the reverse array. There's a whole bunch of other
collection classes you can do and which we'll talk about in
the future here and so forth, but you can also get the size. You can get a particular value added. So if I wanna get the
value at item number zero, the location, and I can say
print ln and I can say item, that's going to be at that point item zero is going to be Donn Felker. And then if we run it, you'll see that it's just Donn Felker's printed down here at the bottom. Now you'll see that we
have a squiggly here because we can actually
use the indexing operator. So we'll place that and
then we can actually do the same thing here that
does the same exact method. We can set a value. So if I wanna set the index of zero to be a different user,
so I guess a foo bar. And then if I were to
go ahead and get that, and if we look at the
implementation of set, you'll see it's just gonna return a unit. So we don't wanna do
anything with this anymore. So now at this point,
we'd want to actually get the value again. So we'd say val item equals
users dot and we'll just use the indexing operator instead of the get. If we were to run this,
we'll see here that the item that's printed on the bottom is foo bar because the first item
in the array was updated. Now, again, we see a squiggly here. We can update this with an
indexing operator again. So we're gonna say, hey the first user is actually gonna be equal to
this, go get that first user and print it at that point in time. So there's all different
kinds of stuff that you can do with arrays. You can add things, you can
copies, you can reverse them, reverse them, slice them. There's a tremendous amount of
things you can do with this, these collections, such as the
other collections in Kotlin, which we will cover. And that's how you can work with arrays and build them quickly in Kotlin. To create a list inside
of Kotlin is pretty easy. You can just say let's
create a variable here and we can say val item equals list of, and then you can provide a list of, perhaps a list of primitives
like we have here. So we have a list of integers. This is very similar to how it's done with the array of operator for arrays. So if we want to loop
over each one of these and print them out to the screen, we could say something like
this, say items foreach. And then once we compile
this and print it and run it, it will then print to the screen as we see in the output down here, one, two, three, four, and five. So we have a list here. Now, this is important to note here that this is actually an immutable list. So if you notice here, I don't
have the add method here. I can't add an item to the list at all. So if we look at the
implementation of list of, we'll see the here, this is gonna return a new read only list. So that means that we
can not add items to it. The same thing goes for, if
you were to create a list of perhaps a list of users. Let's say list of, and
then we can actually create a list of users. So I'm just gonna do a new
line here, say Donn Felker, and then we can have
another one would say, user would be perhaps Jane
Doe and then so forth. We could have a number
of them and so forth. And of course we could also
iterate over these as well, pretty easily. So it would say users dot
foreach and inside of this, we'd say print line and
inside the print line, we could just say, if
we could print the user. And that's gonna call the two
string method on the user, just like it did when we
did the array version. So we'd see a Jane Doe and Jon Doe, and actually did print
here and do print line to it all put it on the same line. Print line will add each
item onto a new line as we see here. So again, these are immutable lists. If we want to add an item to a list, we're not gonna be able to do that. So we're have to create a mutable list, which will be in the next lesson. So here we can also get access to items. So maybe we wanna do the first item. We could say users dot
first, and it's gonna give us the first item. And notice the first method. This is an extension method
on top of a generic list. So we have a first item. So if for some reason there was a default, if we were to print this here,
let's actually do print line, just to show you what this looks like. We should see Donn Felker again, printed, and they're ready to go. Now there's also the opportunity
here for us to perhaps we need to create a empty
list for whatever reason, maybe we have a null value
or something like that. So let's say that we have a name. So let's say val name
could be a nullable string equals whatever. And for whatever reason, the code is null. And then we wanna create a
new list and we can say stuff. We could say, it's going to be. If the name is a null. So if we wanna do something like this, we put it around the if statement. if the name is null, then
we could return empty list. And that would just
return us an empty list. Otherwise we can then
return a list of name. And what that's gonna do is
automatically the compiler is going to infer, Oh, since
Donn is doing a list of names, it's gonna be a list of strings, otherwise this is gonna be
an empty list of strings. Now notice here, I was
getting a compiler error 'cause it doesn't know
enough about the type here. So if I wanted to specify
the type for empty list, I should say string their work. And we could say list of name. Now, at this point in time, you can see that this is grayed out
because the compiler can infer the type. So I can go ahead and remove
that explicit type argument. Now empty list, all it
is again is just some, it's just a function just
gives us an implementation of what's known as an empty list. It doesn't have anything in it. There's basically the size. If you look at it, the size is zero. It is empty, is hard-coded to true. So this is just kind of
a general helper method inside of our application. So if we were to see if this was empty, we could actually say stuff.is empty, that would return true. We'll see a bunch of different
other methods on here as well we can see for lifts is empty. Again, we can't add anything to it because it's an immutable list. So that's one way we can
work with an empty list. Now we can also grab
the last item of a list. So we could say users.last, and that will give us the
last item of the list. At that point in time, we can
also print that to the screen, which is gonna go ahead and show Jane Doe at that point in time. And then we've got Jane Doe. So there's a bunch of
different operators on the list you can look at it. So you can just say users
and you can use IntelliSense to in code completion to check it out. You can do last, you can
do last of the prodicate, all different types of stuff, which we will cover in the future. And you can also get the
first value or of null if it's nothing's there,
so we'll see here, returns the first element or
null if the list is empty. So there's a bunch of different things that you can check for there as well. And that's how you easily can
create a list inside of Kotlin with either primitives or you
can create it with objects. In another lesson I
showed you how to create a immutable list using the list of method. The list of method allows
you to create a list of some primitives or objects or anything of that you desire. If you'd like to create a
list in which you can change, because let's take a look at this items, does not have an add must. We can't add or change
or remove or do anything of that nature with an immutable list. It's just there, it can't be changed. If we need to create a
mutable list we can do so with the mutable list of, and we use mutable list
of, we can provide a type if we'd like to such as we can do with the list of operators. So
if I know it's gonna be int, that I can do that,
and I can say one, two, three, four, five. But then what will happen is
type inference will kick in and the IntelliJ IDE
would let us know saying, hey, we don't really need
that explicit type argument, so let's go ahead and remove it. So at this point in time,
based upon the parameters of the mutable list
method, Kotlin can infer the types for this list. So now I have a mutable list of items. Now this work the same
way if I have a list here. So if we just do list and we leave that, we'll notice that both of these things are going to compile just
fine and do the same thing. We have the same method for
each, we can print those. The differences here though is
that we can add an item here, so I can add the number six now. And then if I copy this
and I kind of reiterate over this again, we'll see
that I have six in here. So let's go ahead and print
a line here so we can see that there's something
different and let's run this. And what we'll see
here, we print one, two, three, four, five, and then
we're gonna add an item here on number six, and then
I'm gonna run it again and say one, two, three, four, five, six. So we can also do something else and say, we can say items.remove, and we could say a whole
bunch of things here. We can remove if we meet
a particular condition, we could remove all the items. We could remove a particular item. So I could remove element number zero, and we can print this off again. So what would happen here
if we were to run this. So now we have a list
where we have to make this a little more clear, let's
add this in here as well, and we'll run it again. And we'll see here that we
have now one, two, three, four, five, one, two,
three, four, five, six, one, two, three, four, five, six. So, why didn't we get
anything out of here, because we're asking it to
remove a particular item. Now it's not finding
the item zero in there, so let's tell it to remove number three. So let's run that again. And then what we will see
down here in the bottom window is it was actually
found item number three. So it didn't find one, two or three. It wasn't the index base,
it was actually the item in the list that it was removed. So that was able to be
removed out of there. Now there's a whole
bunch of other things now we can replace items inside of here. We're gonna replace all, we
can add a whole bunch of them. So if we take a look at the
implementation of add all, we can take in our collection
and add another one inside of here so we can
add a whole bunch of them. So if I had another list,
we can add that list to it. We can remove something out of position. So let's say we wanna remove
this add position zero, let's run this and see what happens. Now, notice how this has index. So now we have two,
three, four, five, six. I'm just gonna delete this
stuff here, just for brevity. And we'll run this
again, and we'll see that we have two, three, four, five is printed. Now, if I were to change
changes to remove, just to reiterate here, and we could say, I wanna remove a particular element. I wanna remove element,
that is the value of three. Now we're gonna see one,
four, five is the output down here in the bottom. So remove at removes it with an index. So instead of the index of the list, we can find something to remove. However, if we wanna move an
actual item given its value, we would say remove element. Now we can also say we can do
something with a mutable list of objects as well. So we're gonna say user Donn Felker, which is something we have done before with the other types of lists and we always have a user called Jane Doe. So now we have two things in here. And if we try to remove
here and we use the, I'm using an index here,
because it's the number three, even though it's saying,
hey, you're wanting to remove something here, removing int is narrow. You remove at index
instead because it realizes the compiler that I'm
trying to use a number here. So it's trying to help me out saying, hey, you sure you don't want
to remove a particular index. Now what I could do, let me just go ahead and comment this out for a second. Inside of here is once we let me run this, so I'll show you what this looks like. We're gonna get basically
two users printed. If I wanted to pull this
out into its own variable, I could do that and say,
pull it into a variable. And I could say, Don. Now what I could do inside
of here is I could say, remove Donn from the list. Now let's print this list
and see what happens. What do you think was
gonna see here where see, we just have one item in the
list because what happens is we have these items. So let's actually do two things here. Let's do this or print each one of there. And then inside of here,
I'm gonna do a print line and we'll just print out
a little break there. And we're gonna see as both
items are printed first, which we see here. And then we print this
nice little line break. And then we say, hey, I want
to remove Donn from the list. Now, remember remove, remove the element, it's gonna tell the element
that we'd like to remove. Remove at is gonna tell us
we want to remove an index. So I can say, hey, if I move index one, it's gonna remove Jane Doe. Now, if I run this here,
we're gonna see Donn Felker and Jane Doe and then we're
going to remove Jane Doe and it's just gonna say Donn Felker. Now there's a whole bunch
again, this is a mutable list, so I can continue to add and
remove items accordingly. And the differences it's
beautiful exchanging the other one is immutable. So if we want to do different
types of things with it, you can inspect the various
different operations using the code completion here. We can remove it out, we
replaced things we can set certain values of I would
like to set the first value to equal a different user. I could say different user
is now going to be Jon Doe for whatever reason. So let's go ahead and change
that and get this down here. Now, what we're gonna
see at this point in time is I want to set item one to Jon Doe. So the first time through, we're gonna see Donn Felker printed, and then the next time
through, we're gonna change the first, the item index one to Jon Doe. So we'll see Donn Felker, Jane Doe. We change it and it's
Donn Felker, Jon Doe. Now knows we have a little
bit of squiggly here, alt enter. We'll say we can replace the set call with an indexing operator. So using the indexing
operator, we can say, actually take the first item in the index and replace it with this one. So it's the same exact code
this does the exact same thing as this. Now we can also access these
items via the index as well. So I might say print, say print line, and I wanna do items zero. And that's gonna print the first time. Now it's gonna be the same
exact thing as if I were to do a items.get, and I
could provide the index. Now notice again, we're
gonna get the squiggly and what IntelliJ is telling us here is, hey, we can actually use
the indexing operator. So go ahead and do that. Now, that's how you create
a very simple mutable list inside of Kotlin. You can create a mutable
list of primitives, could be strings or intergers, or could even be something
like an object itself. All right, I'm gonna
show you how to filter a list of primitive values here. I am filtering a list of strings. This could be integers
doubles, longs, Boolean values, anything of that nature, but I've created a list using the list of, so this is an immutable
list, a list of names. That's why we have Don, Bob, Jane, Jenny, Tushar, and Cavita, let's assume that we
wanted to filter this list. And we only wanted to include,
we wanna include everybody but Donn this time. And so we would say name.filter,
and then we can parse in basically a lambda expression. So there's a couple of things we could do, we could do open closed parentheses, and we need some type of predicate. If we look at the implementation of this inside of the Kotlin
collection standard library, we'll see that we had the filter method and we needed a predicate. And this predicate is
requiring a lambda expression. That's going to return a Boolean value, basically return the list
containing only the elements matching the predicate. So if it's true, it will
be included in the list. If it's false, it will not. So what does that mean? Anything inside this filter we parse in which I'm gonna use the
lambda expression version. If this equates to true
what happens inside of here, it will be included. So it's pretty easy using the
default iterator value of it. I say it does not equal
Donn it'll be included. So what this means is I want
to look through all the names and filter it and check to
see if it doesn't equal Don, if it doesn't equal Donn then include it. So how can we tell if that worked, let's print this to the
screen and we'll say filtered, and once we run it, you're
gonna see that down here in the output window,
we'll see Bob Jane, Jenny Tushar and Cavita. So Donn was not included. Now we could also flip
that around and say, we also only want to include
the names if it includes Don. So let's do that. Say filtered it does equal Don. So here we're gonna get
back in array with one value in it which is gonna equal
Don, which makes sense. Makes sense, it's good enough. But sometimes, maybe
inside of your application, again, this is a predicate,
a whole number of things could happen. This could be a bunch of
code that's doing something inside of here. But let's assume that you
wanna check to see if anyone has a letter A and I
only wanna see if someone has a letter A. So what we might do is
say it.two lowercase because I wanna check for
lower upper case.contains a character and I wanna see
if it contains the letter A. If it contains a letter
A, I want it to go ahead and return it, so let's
see what happens here. If we run this, we'll
see we get returned Jane, Tushar and Kavita. That's right, 'cause Don, Bob and Jenny do not have the letter A inside of there. So you can start filtering based upon all different types of things. And the same thing will work
if you had a list of ages such as this. And then you wanted to
actually filter them. So you'd say val, over
18, or you say adults, which can be over 18,
and you'd say ages.filter it.greater than or equal to 18, print line that, and we
could see the adults. And if we run this, we'll see that we have 23, 33, 19 and 99, which is
true because if you're 12 or year nine or 17, you are not an adult. One additional thing
you could do is actually create a function called is adult. And perhaps this function
has a whole bunch of things inside of here. So we wanna say age,
let's just call it value. Let's be int. And then inside of here, say if the value, so we'll say return value
greater than or equal to 18. Again, this is gonna be Boolean. So we need to return a value. So this function will return
if this person is an adult, but now let's assume for whatever reason, you have to put a bunch
of if statements in here. Maybe in one country and adult is 14 and the next country it's 21
and the next country it's 18. So you might need to
have many different lines and logic inside of here, but it's gonna return a Boolean value. Doing that inside of this
filter is gonna get real messy. And so what you can do
is that you actually pars a function reference into the filter. And you can do this with
a lot of the methods inside of Kotlin. So here's the age filtering tool, it's like colon, colon is adult. And what this will do is we'll take this as a function reference
and say, as soon as I say, age is.filter, it'll
basically parse in the values one by one into the is adult function. And then we'll run it through everything that's inside of here. And then whatever the
result is, if it's true, it'll be included in the filter. Otherwise it will be excluded. So if we run this again, we
should get the same result as before, so we see 23, 19, 99. Now we could change this
and so we could change, if we wanna say is adult, we say his child and we probably need to
fix the spelling of that. And then we can say less than or equal 18. And if we rerun this again,
of course this variable name is wrong, it's not adult,
that should be children. And run it again. Well now see 12, nine and 17
for the age of the filter. So that's how you can easily
filter a list and array and so forth in Kotlin. Let's assume you have a list of data and you wanna find something
in that list of data. This could be primitive as we have here such as a list of
strings, or you could have a list of objects, anything of that nature that you want to filter. Not filter, but just find a
particular item in the list, how can you go about doing that? Well, of course you could use a for-loop. However, there's also a built in utilities in the Kotlin standard library. So Lauren have is I'm gonna
create a result variable. And then what I wanna do
is I wanna actually find the first instance, excuse me, names.find, I wanna find a first instance of Don. So let's go ahead and
say it equals equals Don. And then I wanna go ahead and print out if I actually found it. So print ln and a result. I we'll go ahead and run that. And then what we're gonna
see here is we have Don that was returned. And that's what we
expect Donn was in there. Now however, there is a
kind of a gotcha here. So let's just throw something in here we know doesn't exist
and let's run it again. So I'm gonna use the word foobar. Now we have the word foobar, of course we're gonna get back null. Now we kind of had some help
from Kotlin to find this here. So we had find it was
gonna find the first one. And if actually look at
this, what it's gonna do is it returns the first element
matching the given predicate or null if no element is found. So fine, kind of helps you,
but now you have to start worrying about null values at all times. So if you're gonna do
a null value, you say, so if you wanna compare
something results equal equals. When you say result dot length. Well, now you're gonna get a problem here because it's saying
this is a null variable. So you need to check to
see if it's null first. You may have heard too,
that Kotlin will protect you from null pointer exceptions,
and doesn't allow you to do nulls. Well, that's false, as you can
see here, we can use a null. However, I prefer to
use things like first. Now first will actually
give me the actual value first of Don. And so if we run this here,
I'm always telling me like, I'm always going to get a variable back. Now, if I run this, I'll
say result dot length. It'll be four 'cause Donn
has four characters in it. Now the real interesting
part comes in here. It equals equals foobar
which we know foobar does not exist in this list. But if we run it again,
what we're gonna see is we're gonna get an
exception from Kotlin and that's the no such element exception. And if we look at the
implementation of first here, it says it returns the
first element matching, or it'll throw an exception. So this is something
you need to be aware of. And I prefer to use this method
because now I actually have a string that's gonna be
returned if it's found. Now, but what if I wanna find
the first or the no value? I feel that's a decision I need to make because I need to be able to
read the code and know that, hey, I'm gonna get the
first one or null value because it gives me
context into understanding what's happening in my code. As regards to find, it does make sense, but it's not as obvious,
it's not in your face. So first has also one
called first or null. So I'm gonna take first or null. And now again, we get back a string here that's gonna be empty. So I'm just gonna go ahead
and take that off there, just 'cause it's not gonna
work and we'll print it and we'll see it says null. Now of course I could do
the null operator check. Let's give me the null
safety check.length. Otherwise I could just go
ahead and do something else, do that or that should work. I never going to get back null because there's no values there. So I can find the first instance. So what this also means is
if I have another instance of Donn in here, well actually
let me say this to say, Donovan, let's see, we
have Donovan in here. And I wanna see the first instance of, watch the always say first.contains. So we'll say dot two
lowercase.to contains Don. So I just wanna find the
first one that it's gonna find in that list. So what is it gonna find? Well, that's not gonna find anything. So let's go back to the result here, you didn't get rid of the length. First it dot two lowercase contains Don and that's not gonna work,
so the capital D there. We'll run that and now we'll get back Don. But we had we actually
have two instances in here. So what if I wanted to
return the last instance, we can do last. So there's the last operator
we can find the last instance of a list Donovan. So there's many different
things we can do inside of here. We can find names.index of
and what we're gonna see here, index of first index of last. So I could get the index of the first. And what is that gonna return? That should return us a zero. And if I do index of
last, I can do index last. It'll show me the last value here. Of course, if I were to throw
something in here and X, Y, Z, what do we think would happen here? We're getting negative
one, so it's not found. So if you look at the
implementation of that or negative one, if the
list does not contain such an element. And this is all within the
Kotlin standard library. So there's a number of
things that we can do to find values inside of here. And just like we had the last before, we also have last or null. So we can also take last or no as well, so we'll just change this back to Don and we'll see the last or null
returns us back with Donovan. And then we have Donn X,
which there's nothing in there with Donn X in there, it will return null. So we've got a null value
at that point in time. Now, the great thing about
it is I can read this code six months down the line and understand that I'm sheltering the names list, and I wanna find the last
value or give me null. And then at that point, I know exactly what to expect at this point. I'm either gonna have the
value or I'm gonna have null, and I need to plan for that accordingly. Now, thankfully Kotlin
is not going to allow me to just print the length of this because this is a nullable type. And I need to basically
say, hey, what happens if this is null and I'll just provide the null safety operator, which says, hey, if this is null, just return null, otherwise give me the length,
that's what that means. You can filter to include certain things and you can also filter
to exclude certain things. So for example, if we
wanted to have the result to include everything from the list, except something, we could have
used a inequality expression inside of our predicate,
or we can just use a names.not filter not. And then we can say
it.contained the letter A. And what this will do, it
will print line the result. And this will go ahead
and include everything that doesn't have a letter A. So in this case, we're taking the list and we're filtering it
to not include anybody who has a letter A in their name, which is Don, Bob and Jenny. And that's how you can use the filter not, which is basically the opposite of filter. So if we include filter here, we're now going to see the
folks who have the letter A in their name, which is Jane,
Tushar, Cavita and Donovan. So, yeah, filter and filter not, which are basically the
exact opposite of each other, or you can also just use inequalities inside of your expression. It just really depends on
what makes sense for you and your code readability. I prefer to actually use
the different variations, so filter and filter not
because it's very readable and I have to do less
thinking and cognitive load, cognitive load for me
in regular development. And in six months from now, I can read it as just kind of regular English. And that's how you can
filter and not filter. You can easily take one list
and then take some values out of it and drop it
into another mutable list with the filter to commands. So we can save names dot filter to, and then you have provide the destination, which is gonna be where you
want it to be applied to. And this has to be a mutable list, which is the approved list here. So let's take the use case
of you have an approved list of people that are going
to be on the guest list for a particular event and
you want to randomly choose some more people out of this other list. And perhaps you wanna say anyone who has the letter A in their name, we're gonna randomly choose them. Not really fair randomized
assessment, but it does work. So we'll say print line,
and then we'll say approved. And what this is gonna
do is gonna take anyone who has not taken just kind of
say anyone who has a name A, and we're gonna append
it to this list up here. So if we run this here, we'll
see Danielle, Paul, Jane, Tushar, Cavita and Donovan. And so that's how we
can take one value here, which is a list of names and say, hey, if anyone in this list has letter A or whatever this predicate
is, again, it's true or false. It could be a very complex, if else switch with a bunch
of things happening behind it. If this returns true, then include it and drop it and add it
to this mutable list, otherwise don't. Now we can also do the opposite too, filter, not to. And what this does is say,
all right, if you don't have a letter A in your name,
well, now we're gonna move you into this list, it's the exact opposite. So we have the inclusion and exclusion. So now it's Danielle and Paul, they have already existed in the list, but now we want anyone who
doesn't have a letter A in their name, so Danielle,
Paul, Don, Bob, and Jenny. So we're gonna take anyone
who doesn't have letter A in this list and apply it to this list. And so that's how you can
easily filter from one list into another list. Now, again, the list has to be mutable. So if you were to change
this to just a regular list, which is an immutable list,
this is not going to work. As you can see here, the error states that a required collection
must be mutable. So we need to change this
to a mutable list of, and we can get rid of the
type parameters there. Again, if you want it to
provide them, you could, but we don't need to because
it's already being inferred. And that's how you can move
one things from one list to another using filter
to and filter not to. Sometimes in code, you'll find yourself having a list of arrays or
basically a list of lists and an array of arrays. So let's start with the top example here. We have an array of think
of a bunch of people bringing a bunch of their fruits together, and we're gonna have a party and everyone's gonna
bring all their fruits, and we're gonna combine
all of our fruits together. And I need to see which
fruits all of us have and we're building an
application for this. So mine, I have apples and grapes, theirs they have oranges,
pears, and strawberries, and there's the other
people were bringing Kiwi and watermelon. So I need to see exactly what I have. And so I am going to combine
all of those together, all of these arrays,
because maybe they've come into my application at
different periods of time. And so I have the three different arrays, and now I have an array of arrays. So basically I've
created a list of arrays, excuse me, and list of lists. So I have a list of mine, a
list of theirs and list of. And now if I were to print
this, it would show an array. It's gonna be a list
with three items in it. And this one, the first
item would have two items in that list, the second one
have three items on that list and the last one would have
two items in that list. Now there's actually a
method called flatten. And what this will do is
it'll take all these values and flatten them out and
basically put them into one list. So I'm gonna go in calm
this out so we can run this. And you'll see here that down
we have, when we print this, these brackets indicate a list. So this is one of the lists, this is the next list right here. And this is the last
list here with the kiwi and the watermelon. And of course through
the whole thing is inside of one set of brackets here, which represents that the whole thing is inside of a list itself,
so it's a list of lists, kind of a mouthful. Now, when I call the flatten method, what it does is it just
takes all those values and flattens it into one list itself. So I'm able to combine all of these lists if they're inside of one master
list and flatten them out. Now you can do the same
exact thing with arrays. It's exact same thing, we'll
have an array of arrays here. And so we have an array
of the same exact things. Instead of using lists,
I'm just using arrays. And I'm gonna call it the
same exact method on it. And so I'm calling flatten
and both of these actually do the exact same thing. This one is gonna be iterating on a list. And this one is going to
be iterating on an array. So you can see they are different
there's different codes. So see line 19 inside the arrays file. And then we have line 69
of the itterables file. So there is different code, but
it does the exact same thing in our use case here. And so we're going to take
arrays and if we run these here, what we'll see as a top two
lines will be for our lists. So we have a list up
here and the next one, this is the array of arrays,
and it's printing out the, hey, there's a bunch of strings in there and then it kind of stopped printing and then it flattens them
out into one large array. So that's how you can go ahead and flatten an array of arrays or lists
of lists inside of Kotlin. Let's assume you have multiple lists that you'd need to combine in Kotlin. These are all immutable
lists, so you can not mutate any of them, but you
would like to combine them and have a list at the end
that contains all the items. It's fairly simple. All I need to do is you have a variable and you actually don't need a variable if you're just parsing this, you can just say mine.plus theirs. This is gonna return a new instance and they say .plus others. And this is gonna turn all of the values of concatenated together. So if we were to run this,
you'll now see that the result contains apples, grapes,
oranges, pears, strawberries, Kiwi, and watermelons. So we've combined all
of these immutable lists into one final immutable list. Furthermore, you can do
something interesting. You can, if you have this
list and you want to remove a whole bunch of items
from it, you could do this. You could say minus, and you
could remove a bunch of items. So maybe you wanna remove
theirs from that list. And what you're gonna
see here is our result. Now is gonna take out
theirs, we're going to remove the oranges, pears, and strawberries, which on the second line
item here, we've removed the oranges, pears, and strawberries. Now the minus operator
does allow you to do a whole bunch of stuff. You can parse in an itterable, you can parse in some sequence, you can parse in an array elements. And you can also parse in
a single element itself. So if you just want to remove one item and you just wanna remove
Kiwi and you were to run this, now you would see that the end that Kiwi would then be removed. So you can parse in a list or an exact element to be removed. So that's how you can combine
concatenate lists together inside of Kotlin even
if they are immutable. One of the most powerful
operators in Kotlin on an a collection is going
to be the map operator. So we have a list of names here. Let's assume that we wanted to
take the first three letters of each name, make them uppercase
and make the abbreviation for the person's name. It's a pretty rudimentary
example, but let's go ahead and try to do that. We could use a bunch of
different loops to do that, or we can use the map operator. So when I say items.map. And what this allows us to do is parse in the lambda function. And what we can do here
is start doing something. So it is going to be
each name in the list. And then we could say sub string, we're gonna say zero to three. So we'll get the first
three letters.two uppercase. Now, what this is going to
do, map is going to return a brand new list, so, it
returns a list containing the result of applying the
given transformation function to each element in the
original collection. Okay, what does that mean? What that means is for each
item inside of this list, when we call map, it's
gonna execute a function. That's basically gonna give us that value, and then whatever we do with this value, it's going to return this back. It's gonna return whatever
the result of this is back. So let's print it off and
see what happens here. All this sub string is doing is taking the first three letters of the
name and making it uppercase. And so as you can see,
the result is Don, Jan, Carl, Pitt and Clar for the three letters of each of these names up here. Now I could just completely
ignore it and just say, foo. Now watch, if I run
this, we're gonna see foo over and over and over. The reason why, this map,
this function is executed every single time. So it's executed for Donn
Felker, it gets foo for Jane Doe. It gets foo for Karthik,
it gets foo for Peter, it gets foo for Clark, it
gets foo it just executes each individual time. So here, I'm just returning these values. So at this point in time, I'm gonna get those first three letters. Now we can take this a step
further and we could say, all right, well, we can break
this into multiple lines and say, let's say I
wanted to get the initials off of everybody. Now there's multiple ways
that I could go doing this. Now, what I could do, very kind of, this is very naive approach, but it works. I could say it.split, and I
wanna split this on the space. And now I have some words. And then what I can say is
I wanna return basically the words with the first word and then I wanna sub string that one. And that's gonna start from zero to one, basically take the first
letter of the first word, plus words.sub string is
gonna be the second word, .sub string. And you take the first
letter of that one as well. And so we're gonna see here type mismatch. It says, found string required unit. So I actually need to just do this. And you may notice like,
why am I not returning it? Because what happens inside
of the map is the last value is what's returned inside of this map, which is why it's pointing
with the up arrow. It says map inside the IDE. So this is a little hint, it says, hey, this is the return
value of the map operation. So now, if I were run
this, we're gonna see that it's just going to return
back the initials, DF, JD, KM, PP, and CK. Now this is very, this is
not production quality code. If the user has multiple
names and multiple spaces in their name, this is gonna fail. If they only have one
name, this is gonna fail. So this is not production quality, but this just illustrates
something that you can do inside of the map operation. So let's go ahead and you
rewind this a little bit here and go back to where we were
before with the abbreviations. So now I have the abbreviations. And just to run it again,
you'll see what we have. You have Donn Jan, Car, Pitt, Clar. Now, what I might want to
do at this point in time is actually go ahead and I
can perform form another map. I can map over an existing result. I'm just gonna move this over here. So whatever the result of
this, which is Don, Jan, Car, Pit and Cla, I'm going to go ahead and do an operation on that. And I can map on top of that. So I could say it.length
and we'll return that. Now what you're gonna get
back is a list of integers. And each one's gonna say three 'cause each of the
abbreviations was three long. So that kind of makes sense. Now, what I could also do
is something like this. I could decide that maybe
I want to sum something, I could sum by a particular
value, say it.length and run that and we're gonna
see 15, so some by that. But what does that mean? It means we've taken
each of the values here and then sum them up into 15. Now I can also decide if
I don't wanna do a sum, maybe I wanted to do a filter. So I remember this is applying
to the result of this map. So I can say filter I'll
know when I wanna get everybody's first three
letters of their name, and then if their first
three letters of their name have the letter O in it,
so it's it.contained, it's all we'll do to
lowercase just in case, actually two uppercase. Actually it's already uppercase. So I say, it.contains. Oh, if we run this, we should
only get one value back. And that's gonna be Don,
D-O-N because I've now grabbed the abbreviation and then
I've now filtered it. So I can start stacking these
things on top of each other. Now, if I, for some reason,
would like to reverse these things, reversed. If we run that, you're just gonna see Don. So let's say contains and so go ahead and flip this filter not. So I don't want it if
it contains a letter L which should give me all the other ones. We're gonna see CLA, PET, KR and JN. And now that's because
the array was reversed, I've reversed this array. And I can continue to
stack different operations on top of this. So this allows it to be very functional. And so I don't have to
perform all of my logic directly within one particular map, I can map from one thing to
another thing to another thing. And this allows you to
have functions that return, perhaps a list of something. And then inside that list, you
might perform an operation. And then you might map over that operation and map over something else
and allows you to transform your data in real time,
basically as you're typing it, it's much more declarative
and easy to read. So that's how you can
perform a map operation with just regular primitive types. Now, of course, you can do this
with other classes as well. So let's say we have
a class called person, and this person has a
name, which is a string, let's do something simple like that. And we'll say people, and
we'll say, list of person, Donn and then we say person Jane. Now I have all my people. Well, I can also map right
over these people too. We can say people result. And what this will be
is I'll say people.map, and now I'm gonna have
this IT value as a person. So I can say person.name, I
can do something like that. So I can just get everybody's name. Now, if I were to print
this to the screen, I would say people result
with this gonna be, is a list of strings,
it's just gonna give you everybody's name. So this would be Don, Jane, Bob, Cavita. So I'm just iterating over, I'm
just getting a list of that. So it allows me to transform that data, which makes it very, very nice. And of course I can map on top of that, which might be it.length. So I'm gonna get the
length of everybody's name, and maybe that would be a useful
function for me somewhere. I could put this inside
of a function and say, all right, give me the
length of everybody's names. And it would give back the
length of everyone's names accordingly. Now, maybe you would want to
transform a little bit further and you could say, all
right, what's the length of their name and perhaps also
I would like to show their... I want it to include their
name as well with that. And then you could create
various different mappings and return arrays or raise
arrays list of lists, et cetera. The possibilities are endless. The map and flat map
operator can sometimes be the source of a lot of confusion and determining which
one to use can always be kind of something that will
confuse you throughout time. Very often that I've had
to look the documentation and play around with a
few examples to understand what I'd normally need to use. Now, working with collections most often, you wanna use the map operator because you just wanna return values and perform transformations
on those values across all of the items in a list or an iterable of some fashion. However, let's determine
what the differences are. Here we have a class in
line 23 and it represents a shopping bag. And in each shopping bag,
we have a number of items that go into a bag. And when you go grocery
shopping, you usually have a number of bags. So one bag has grapes,
apples, and oranges, the next bag has milk, eggs, and pasta and the last bag has some
bread, naan and cake in it. And we have basically
a list of grocery bags, and those are gonna be our
grocery bags that we have from the grocery store. Now we may have also went
and purchased some clothing. And so we went to a few
retail shops and those bags, we have three of those as well. We have a shirt, pants and
trouser, let's say trousers. So we have shirts and trousers,
we have socks and shoes, and then another bag
has a jacket, a sweater, and a scarf in it. So we're gonna use these two lists. So basically these two groupings of bags. So we have a list of three shopping bags. And up here, we have a list
of three shopping bags, and we're gonna perform a flat map on it and then print the result. And then we're gonna perform
a map on the retail bags to see what the differences are. So here we can take grocery bags.flat map. And then what we do is for
the transformation function, we just tell it to, hey,
return all of the items. And so if we look at the
declaration of flat map, it says returns a single
list of all the elements yielded from the results
of the transform function being invoked on each element
of the original collection. That's confusing. So basically what it's saying is like, look, what's gonna flatten
out, if this is a list, it's gonna flatten it all out. Now, if we look at the map, we've already looked at this before. It says returns a list
containing the results of applying to give and
transformation function to each element in the
original collection. So that makes sense. All this function is
really doing is saying, hey, map over all of the
bags and then we're saying, and give me all the items. Okay, so let's do something here. Let's just run this to
see what the result is. If we run this, you'll see down here, the flat map actually says, hey, I've grabbed a
win inside of each bag, I've grabbed the list of
things inside of the bag. I've grabbed the list of
groceries that are in each bag. And I got back each list of groceries. And then basically what it is. I flattened them into one single list. So it's flattened them
all into one single list. In this case, flat map, when
you have a bag of groceries and each bag of groceries
has many items in it, it's like taking everything
out and putting it on the counter. So you're flat mapping. You flat mapped everything
out on the counter, it's all right there in front of you. Now, the retail bags and
we're using the map operator. What we're basically saying is, well, I have a bunch of items in the bag, and I may have taken all these items out, but I'm still leaving
them in little groups. I have a group number one,
which contains the shirts, pants and trousers, and
the group, number two, the socks and shoes, and
in group number three, which is a jacket sweater and scarf. But I don't have the bag
anymore because I've taken them out of bags. And you may be wondering, what do you mean I've taken them out of bags. So let's do this real quick. Let's say print retail bag. If I run this again, we're gonna see a couple of print lines here. So here we go, we have
three shopping bags. So if I just print out
the bags themselves, that's like putting three
shopping bags on the counter. Now, if I were to take
those items out and put them on the counter in the same
groups that they were already in, that's what retail bags is doing. It's just removing the bag, but I'm still grouping
everything together. Now, flat map basically says, hey, look, I've just taken
out all the groceries and just put them all on the counter. They're all right here in front of me, I just kind of put them out
one right next to each other. There's no groupings, we're just one, one after the other sequential ordering. So the best way you usually think of this is if you have a list
of a list of something to a list of lists, then
at that point in time, you need to start thinking about, all right, maybe I need to use flat map if I need to kind of get
into each individual item and combine them into a
larger lists that's congruent. However, if I just need
to operate on each item in a particular group, then
perhaps I just need to use map. So lists of lists think
flat map, everything else I default back to map. And then of course, if
you ever get confused, hop into the documentation,
I hope that helps. Sometimes you wanna have a list of data, but you don't want items to repeat. And so you want the data to
be unique inside of that list. That's known as a set. And so what we can do is
create a set of values. Like I say, set of, and
this is a helper function inside of the Kotlin standard library and the Kotlin collections library. And I'm a create a set of names. So I would say Don, and let's
say John, and then Felicia, there we go and I can print this out. Now what this will do, it should be names. When I print this, it'll
look almost similar to just like a list that
we have Don, John, Felicia, that works well. But the benefit of a set is
if we look at the definition here of the set of helper method, returns a new read only set
what the given elements. Element of the set are
iterated in the order they are specified. And the return set is
realizable, so we can realize it. But the interesting thing is here is it does not allow duplicate elements. If I wanna put Donn in
there twice and I run this, you'll see that Donn is not in there. Let's see if I tried to
put Felicia twice or John, so, let's do John again,
we see this not included. So we cannot put that inside of there. So with a set, you can't have
duplicate items in there. And so that's how we can create a set that's basically a read only set. If we wanna create a mutable
set, you would actually, which through the set you can change. And again, you could
specify this differently. This is also gonna be known
as basically a set of string. That's the way you could do this. Instead of string, of course,
this is not the helper method doesn't work this way,
but that's what this is, is a set of string. If you wanna create a mutable set of, you would do like this and
we don't need to provide the type parameters
because they are inferred. If you wanna add something,
you'd just say names.add, use the add method. And here, we're gonna
go ahead and say, Jenny. Yeah, go ahead and print line names. And when we print that again,
we should now see Don, John, Felicia and Jenny. Now what happens here
if we try to add Donn in and let's go ahead and print it again. Now Donn already exists in there. So if we run this and
see what happens now, we'll see that Donn is not added again. So the set is remaining unique. Each item in the set is unique. So the supplies for, if you're
gonna have your strings, your inner integers, et cetera, each item in a set is unique. Now let's take this a
little bit further here. We've kind of understand what we can do for duplicate names here. If Donn is up here twice,
it's not gonna show, but now what if we have a class? So we have an object. So an object should not have
duplicate values in there for a set. Now, however, if I've
added a person class here that just has a name, and
then I add two people to this, two persons to this people set, we should technically only
see one Donn in there. Now, when we run this, we
actually see two persons. And notice this little
number here at the end. I'm not gonna get into
the details of what it is, but this basically is saying, look, these are different objects. And so the equality checks inside of here are not being done. Now, a way you can implement
these equality checks inside of your classes with the equals and overriding all the proper
methods to ensure equality instead of your classes. However, if these are just
classes that are going to hold data such as this one is, then what you can do is
you can just turn this into a data class. And use this as a data class
it'll work the same way, but all these equality checks
are already done for you. It's comparing all the fields, et cetera to see if they're exactly the same. So now if we run this, I'm
basically adding two Dons to the set of the same data class and say, nope, no, no, it's not gonna allow it. So I could come up here
and add another one. Now I'm really sure I don't
wanna add Donn three times. What will happen is it
will come back and say, nope, there's only one. And so then I can actually
maybe just change this one to, let's do this one is Janet, run here, we'll see there's Don,
there's Janet inside of there. So there's the set is
not allowing multiple different versions in here of
the different types of folks. Again, so you have a mutable set. So this is very much a very
kind of Kotlin specific idiom. You have immutable and mutable. By default everything's immutable. So you need to think about this, if you want something to be mutable, you need to tell Kotlin,
hey, this is a mutable list, this is a mutable map,
this is a mutable set. And if you need it to be unique, like a list of data to be unique, then you should be using a set. And if you're gonna be using classes, you wanna make sure
that the equality checks are implemented correctly
in your given class or you're using a data class. Loops are one of the most common
structures in programming. And for loops are gonna be
something you've probably used in other languages. If not, they're gonna be something you'll be very familiar with. So let's assume we have a list
of values and those values are maybe integers one, two,
three, four, five, six, seven. You can iterate over these
or perform various different operations of them pretty easily. The first thing you can do is
just iterate over the list. You can say for number and
values, print line number. And what this is, it says for every value that's inside of here, we're
gonna give it a variable called number, and we
wanna print that number. And if we run this, we'll
see one, two, three, four, five, six, seven is printed. So now I can call this something else. This doesn't have to be called number, this could be called a chicken,
I mean, just for whatever, just for purposes of demonstration, this can be anything you want it to be. It's gonna print the value. And this is each value that's in this list will be put inside of here. So the same thing goes for
these primitive values. It could be names, it could
be anything of that nature. And then again, well
that changes from chicken 'cause doesn't make sense, but
to name, we'll run it again and we'll see Don, Bob, Janus. That's each one of those
values in that list and that's how we can kind of
iterate quickly over a list using a four loop. Now there's also ways you can do it if you wanna do some type of counting. For example, if we wanted to
say for I in zero until 10, we could print a letter I. Now we're used to seeing
I as a counter variable in very many languages. And you'll see here, what's
happening is I is defined as an integer in zero until 10. So it says it starts to
zero up until we hit 10, We wanna do something. So each time it's just
gonna iterate and increment the variable value by one, so
we have zero through 10 here. Now there's also nothing you can do. So that's a very common
way to do some looping and you can set this
as a variable up here. So 'cause that val say upper limit is 10. And so you can say until upper limit. Or you could say here's
another way, you stop. So stop and then you can make
another one called start. So there's all different types
of ways that you can do this in your code. And it's up to you
depending on the situation. For I and start until stop. I mean, it doesn't really make sense 'cause then I have to go find
what start and stop means. But as it's just a demonstration
that you can run this and it'll still execute accordingly. So I'm gonna undo this here
and we're gonna go back to how it was before until 10. Now you can also perform stepping. So what this says is for I in zero to 10, I want you to step by two. What that means is basically count by two as we're iterating. So instead of incrementing
by one, increment by two. So therefore you see
two, four, six, eight. So if I were to jump this
up to, let's go to 100. We're gonna see it's gonna count by twos all the way up until 100. And there it has done here at the bottom. Now I can say step 10
and that's gonna iterate all the way up to 100. So we're gonna see starting
at zero all the way up until 100, and I've had to do step five, of course is just gonna
run it till it's five and then it'll iterate upwards. Now you can also do it the opposite way. So let's go down to the step,
let's get rid of step again. So we have item 100 down to zero. So this will be basically
a reverse for loop. So it's gonna count from 100 down to zero and it's gonna increment
by one by default. Now again, I could do step two here and that's gonna count down
by two or I could do step 10 and then we're gonna
have 100 all the way down until 10 to zero. So we can see here on the
100 all the way down to zero. It's all one more step by 10. Now the last thing you
could have possibly, let's take a simple class like, let's create a quick
data class called person. And this person of course
is gonna have a name, which is a string. And let's create a
couple of people up here. So we'll say val people
equals list of I'm gonna say person Don, person Jane, person Karthik. And then what we'll do inside of here, we'll say for I and people
we're gonna print the people. And so you can iterate
over objects as well. So here we're either writing
over each item in a list, just using a simple for
loop inside of Kotlin and that's how you can
use a for loop and Kotlin. A while loop in Kotlin
is very easy to create. So we'll say while X is less than 10, we wanna perform some type of operation. So here we'll say print line X. And then what we need to do
is increment the X value. So it continues to increment upwards as we are progressing through the loop. And if we run this, what
we'll see is while X is less than 10, we
perform some operation. Now a while loop is very
useful where you need to do something while a
particular condition is met. And so this could be maybe you
need to be running something in a loop the entire time and only do it when maybe perhaps a
certain button is pressed that needs X it out. Now, for example, if I forget
to set this X variable here and increment it and I run
this, watch the terminal window, I'm gonna stop this pretty quickly, but then we're gonna stop it now. But this is a very long scroll of zeroes. I mean I'm scrolling a lot
and it's barely moving. If I let that run, most
likely the IDE would have had out of memory exception or
stack overflow exception because it's continuing to print
line over and over and over and over and over and over and over and this loop is never going to stop. This is basically an
infinite loop at this point because there is no termination that's being provided by it. And when we increment this X value, we're actually incrementing
the X and eventually this expression will end up being false. And so as soon as it hits
false, the loop will exit. and then the next line of code will run. So say print line, let
me see this all done. If we run this now, see all
done is printed afterwards. And if we just let this
go for 100 or whatever, then it could be a problem. Now, a lot of times folks
will actually run these while loops in various different
programs in the backend and incline applications
while they're waiting for a file to process
while they're waiting for something to happen. Perhaps they put asleep command in here for the thread to sleep. And again, there's pros
and cons of all this. I'm not telling you and
advocating to you sleep inside of your code, that's
usually a code smell. However you'll wanna
understand when is a good time to use a while loop. A lot of games, stuff like
that use a lot of while loops to keep things running in a game loop. So while loops you'll need to make sure that everything's going to
run inside of the while loop inside of between these two brackets, everything is gonna run while
this expression is true. And so this could equate
to be some type of value and perhaps this root loop
runs until a user presses a particular button. Maybe it's drawing something on the screen and you're determining
where to render things. A while loop is a perfect
opportunity to do that. I need something to happen
over and over and over until an exact particular
condition has been met. That's when you're gonna
wanna use while loop. And that's what you can
do is this can be a very, this could be a method call inside of here if you want it to be, this
could be a fun is okay too, or like keep going, how would
you want to try Boolean. And at this point you could
have some code in here. It says return, true or false, true or false in this point, we'll say true for whatever reason. And you could actually just
call it this right here. So we'll keep going. While keep going, blah, blah, blah. And this method in here could be going out and checking file system, checking an API, waiting for an input or checking the input or interrupts some form to say, "Hey, should I continue
to go," and so forth. So that's when you're gonna
wanna use a while loop in Kotlin and how to use it. Let's assume that you have a
list of people here in a list. Now you could, of course,
go ahead and iterate over each one of these
inside of people like this, for I and people. And you could print line
each one of these people inside of this list, and
this will work just fine. There's nothing wrong with this. In fact, many people
probably do this day to day and it works. However, there is a more
idiomatic way to do it in Kotlin and you can use the Kotlin
collections to view that. So you can say, people
dot, you can say for each, for each, for each here, and then what you can say is
print ln, and then you DIT. And that's basically ended
the same exact thing for you. It's gonna allow you to
iterate over each item inside of the array, but allows you to say, "Hey, for each item and this array, "do some particular function." Anytime I'm working with a list, a map, some type of collection
array or whatever it is, if it's a, some type of
collection inside of Kotlin, I'll always wanna prefer to use iteration on the actual list itself. It makes it much easier
unless I'm modifying the list and I need to do something with a list. And then I might use a
different type of loop structure around it. But for the most part, for
most of the operations, I'll have, I'll prefer to use
the actual, each operator. So I can iterate each over
each one of those things and so forth. So another thing that you could do, I mean, you can also,
as we've seen before, you can perhaps map these
things together and say map, I could say a sub string of
each one of these things, and it would be it.name.sub string. I'll actually just take each
person's name and then I can do for each and then I can
perform some operation on that. And I could say something
like it.to upper case. So I kind of start chaining all these different things together. And as soon as I get one or two
of these things in the line, I'd like to kind of drop them here. And then I might say for
each, and I can do a map on top of this. Actually, I can do another
map here if I want to. And then as soon as I have that list down and I wanna do something with it, I can then do four each over it. Now for each returns a unit,
which means it's just a unit. It doesn't return on our map, which is why I was not able
to add a map operator before. So if I wanted to do something
additionally to this, to transform these values, I would before map here
and say it.to uppercase and we can turn through this.reverse. And if we ran this, we
would see each person's name is upper case and reversed, but we did not print it,
which is the problem. And here, when we actually
print it to the screen, we'll see everything here
will be all the names reversed and upper cased in there. So again, if you're going to be using any of the Kotlin collections, I highly recommend using
the for each operator to iterate over a collection. It's much more idiomatic
and easier to read and actually less code. Very often in Kotlin you'll
receive multiple lists and you need to combine
those lists into a list that does not contain duplicate elements. Now you could iterate over
both lists and check to see if either one contains a value and if it does then not include it. So each item in the new list is unique. And that's usually what you're after. Is a list who has a unique values but really at the end of the
day what you're looking for is actually a set and a
set, if you look at it, is a generic unordered
collection of elements that does not support duplicate elements. And that's line 252 here,
this is in the collections class of the Kotlin standard library. So basically I set is
an order to collection that does not support duplicate elements. Now, thankfully, there are some utilities built into the Kotlin collections library that allow you to do this real easily. So here we have two
different lists of people. This one is Don, Jake, Janet, Cavita, and this list has Don, Janet,
Jumo, Cavita, Kevin and Kathy. For whatever reason,
there's in both lists, both people are present. Now, maybe this could be
because these could be groups that are on a popular site that you have, or maybe you're building an
event management platform. And so Donn and Janet
and Cavita are both going to these two events, but
there's some differences here. But you wanna find all the
unique people that are there. So this could happen for
one, two lists, 100 lists, and you need to find out
which ones are unique. Now, there is a built-in
operator to do that. So say val, unique values. And all we really have to
do is say people.union, more people. And what union will do is
a return a set containing all the distinct elements
from both collections. And it's gonna return a set of the values. So a set of whatever type it is, and this tastes we're using persons. So what it will do is
it'll look inside of here using the equality
operations on the data class, which it checks, you know,
the values, et cetera. And it'll check to make sure
that there's no duplicate. And so now, if we were to run
this, so we'd say print ln, unique values, we run this,
we'll see that we will only have Donn inside of here once,
Jake is only in here once, Janet's only here once to
Cavita, Jumo, Kevin and Kathleen. There is no duplicates inside of here. So even if you were to add
another one here, say a union something else, and you could say person, and again, you'd say maybe Donn again. So the third time that Donn
is gonna be inside of there, you'll see that it's still only added once because the union operator will return you the set containing all
the distinct elements in both collections. And that's how you can merge
multiple different lists and return unique elements
across all of them into one final set. When working with list of
data, it's very often to want to know the index of an item
as you're iterating over it. With Kotlin's built-in for each iterator, you don't have access to the index. You only have access to the
value, which is the person. However, there is an
overload that we can use for each indexed. This will allow us to use, get us the index of the current operation and the person at that given index. So for example, if we
were to print line this, we could see something like this: it's an index and we'd use
string interpolation here. And then we could say person, and if we were to run this
and actually we can use move the curly braces there
because the class will give us a good two string method. We'll see, when we print this out here, we have the index zero
and the person objects that's associated with the index zero. Now this is very useful
if you're doing operations in which you need the know the
index of a particular value inside of that data structure
for whatever reason, you now have the index
and you have the value that's associated to that
given index to perform whatever operation that you need. So if you need that, you'll wanna use the for each index operator. We're using the lambda
expression version here. You may also have seen us
with parentheses like this. This will also work. However, you'll notice that
IntelliJ or Android Studio, whatever you're using,
will give you a hint here that it does not need that. This is a lambda argument so it can be moved out of the parentheses. Basically just remove the parentheses and the lambda expression is parsed in via the Sam operations. And that's how you use for each indexed. Arranging Kotlin allows us
to specify a range of values. So here I'm specifying a range
of values from one to five saying if the I is in within
this range of one to five, then print the value yes,
otherwise, print the value no. So if we were to change
this to 12 and rerun this, we would see that the value of 12, because I is not within
this range of one to five. We can also say for J, this will give us a range very
much a for loop variable now. So I can say print ln. So JN zero to 10, the
range again is zero to 10. This is the range that
we're working within. And so we could say J
and when we print this, we're gonna see one through
10 printed down here. Now, again, this is, if you're
familiar with the four loops inside of Kotlin, you can
also provide stepping. So I might decide to
say, I wanna step by two, meaning that instead of
incrementing the value of one for the J counter variable,
it's gonna increment by two. So it's basically in this
range from zero to 10, I want you to execute something
if J is within that value, execute some code, count from zero to 10. And then I want you to make a step of two, which as we're gonna see down
here iterates from zero to two to four, six, eight, 10. And so every once in a
while, you will use ranges. And of course these values
can be variables, et cetera. So this is a very simple
implementation of how you can use ranges to count inside of Kotlin. A map is a very common data
structure in computer science. A lot of times this would
be called a map hash map or dictionary. And basically it's a key
value pair that you can store inside of a data structure. And this is a map data structure. To create one, you can use
the map of helper function and this will actually let
you create an immutable map of values. And what this means is we'll
have a map where the keys are strings and the values are strings. The keys over here are over here, which means NY maps to New York, NJ maps to New Jersey and
CA maps to California. And of course you can continue
mapping all these down there. Then if you wanna get that
value, it's pretty easy. You're gonna say, do say states.get, and you can parse in the value of NY. And then of course, we
would print line this and see what the result is. Now, as you're executing this, you'll see that New
York has been returned. However, if I were to type,
let's go FL for Florida and run it, we're gonna
see that's not in there and a null value is returned. That is because it will
return the corresponding value if it exists or null if
it's not present in the map. So it's very often that
sometimes you'll wanna say get or default if you
want a value to be known. So I don't wanna work
with a nullable type. So I'm gonna say unknown value. And then at that point,
when I run FL we'll get back unknown value that's printed out down to the screen down here. So that's one thing you can do. You can also use get or else. And or else is going to get
or else is gonna allow you to provide a function
where you can actually return some code, which could
be whatever, could be foo. This is function that you're gonna call that it's going to be invoked
when the default thing, nothing can be found here to match FL. So if I were to run this, we're going to see that
when we say, get her else, foo is being returned. Now back to the original
method here of FL, we saw that the squiggly
here inside of IntelliJ is saying that we can
improve this through the, basically the code improvements. And when we press the light
bulb here to get that, or over here, I'll be
able to just turn this into an indexing operator. So basically saying, hey,
state's just like an array index. Give me the value for the key of FL. And then of course it returns
back a nullable string, and we're gonna get back null here. A couple of other things
that are very useful inside of matches. You can check to see if a key is there, so you can say, is there a key there, returns a Boolean value. If the key is there it'll return true. We can also say, hey,
does this value exist? And we know the value NY
does not exist as the values, 'cause the values are here
in New York, New Jersey, California, those are the values. These are the keys. And so the values don't container. So we can do that. And so there's a whole
bunch of operations. And if you're interested
to see what you can do, because a map is part of the
collections library in Kotlin, so we can actually get all the entries. So, which is gonna be a set,
because remember, in a map, we can't have a duplicate. We can't have duplicate keys. So key has to be unique. So when get back to this result, if we print this, this
will be a set of strings. And we can see here,
there's all of our entries, which is gonna be, for all the, assuming it's gonna have
the keys and the values, all the entries. And then what we can do here
is also, we can see the keys. So if we wanna look at the keys, you can also inspect all of the keys, which will return back NY, NJ and CA, and of course you can inspect
the values across the board. So you can start working with
all of these different types of things in a map. Now, that's how you can kind
of set up a regular immutable map inside of Kotlin. Just like lists, which can have a mutable and immutable lists, you
can have immutable maps and mutable maps. And so if you were to create your map, so we can just call this map,
actually call this items. You can create a mutable map. And what a mutable map
will do is it'll allow you to add and remove items to the map. So what we could say here again, we could do the New York to New York. And again, this is just gonna create a map of basically state abbreviations
in the United States to the actual full spelled out version. And we'll just do the
same three that we have for the immutable version, which are New York,
New Jersey, California. And of course you could
have all of them in here if you want it to. Now, at this point in time,
if I wanted to remove an item, I would use the put command. And then inside of the put command, I would provide another key. And so I could say TX and then the value, what it would be, it would be Texas. Now, notice how we have
a little squiggly here. What this is telling us
is that the put method can be converted into an assignment call. So I'll hit Alt + Enter or
you can also just click on the little light bulb right here when you put my cursor here, there you go. And we can just say, it looks like that. I'm gonna leave it for put for now, just 'cause it reads a little bit better and we can print line so we
can see what this looks like. And if we run this, we'll
see down in the output window that we have one, two, three, four items. So we said New York, New
Jersey, California, and Texas. Now, if for some reason, I
wanted to, whatever reason, to remove an item from
the state of structure, I could say items remove
and all I have to do is provide the key here. And now to provide a key, I'm gonna say, all right, I'd like to remove New York. And then of course, I
wanna go ahead and validate that our items were removed. And if we go ahead and run that again, we'll see that we now have the first call, had all four items and the
next call had removed New York. Now something interesting will happen here inside of your code. If you already have this list, let's go ahead and remove
these we'll leave Texas. And we decide to put an item in here. And we decided to put New Jersey in, though we already have New
Jersey up here on line five. If we decide to put New
Jersey in, and we say Joisey, because sometimes that's
how they say it out here. And we print line. We're gonna notice an interesting
thing will happen here. And what happens is, is that New Jersey is actually
replaced inside of the race. So instead of New Jersey,
it's replaced with New Joisey. So perhaps that was a mistake in our code. Now it's perhaps created
a weird side effect where there's a bug. Now there's something you can do to kind of help prevent this. So if you thought that
New Jersey was there and you didn't mean to overwrite it, or you don't wanna override
it, you can say put if absent. And what put, so if we run this, we'll see is New Jersey
was not overwritten. Why, because put if absent
will only put the item there if we scroll up, we see
this, if the specified key is not already associated with the value, otherwise it'll return the current value. So if we look back at
the code here, again, it returned a value. So what does it return? Let's go ahead and check it out. So say print line and
what this will return is gonna be the item that
was currently existing already for that key. And so we're saying, hey,
put something in there if it's absent. Hey, I'm trying to put
something into this map that has the key NJ,
and I wanna use Joisey. And what it comes back is saying, Well, just letting you know,
we already have something in there, so we're gonna
go ahead and show you what that is, and we're
not going to overwrite it. So that's very useful, so
you don't overwrite anything. Now on the flip side, let's
say for whatever reason, we realized there are some,
you're allowing the user to edit something on a screen
and they are removing items and putting them et cetera. And you want them to allow
them to remove something, but only if the key and the value match. So if I remove this, if I
say remove, what will happen is New Jersey will be
removed from the list. And we can see that when we run this. But what if I wanna keep
New Jersey inside of there, but I only wanna remove it
if somebody accidentally kind of got wise was a wise guy and said, "Hey, the data is Joisey." So what this says is,
"Hey, only remove this key "if its value is Joisey." So let's go ahead and run
this and what we'll see down the last print line is that
New Jersey was not removed. So this key and the value stayed put, because while it did find
a key for New Jersey, it didn't match the value. So the value Joisey was not removed. Also sometimes during development
you'll need to retrieve an item, but sometimes it won't be there, but you do expect it to be there. And if it's not there, you
would like to add it to the map. There's actually something you can use and that's called get or put, and what that means returns
the value for the given key. If the key is not found the map, it calls a default value
function and puts its result into the map under the
given key and returns it. So if we go back here, so get or put we're gonna take this key,
and we're gonna say, UT, we'll say Utah, and notice
how it's giving us an error, because it actually needs
to be an expression. So it's a lambda
expression because perhaps this needs to be a method that
goes out and does something or a bunch of different things. So it gives us the ability to do that. So we're say Utah, and if we print this, what we're now going to see
is because we have not ever added Utah to this map, we put Texas in, and now we're gonna basically say, "Hey, map, get me Utah, but
if Utah does not exist for UT "for this key UT, go
ahead and insert it for me "and return it." So, which we could
basically do inside of here. So we say, val result equals this. And then we would say print
line and what the result would be would just be that, that value. So when we run this here, we're
gonna see Utah was returned because it's basically
saying, "Hey, give me the key. "Give me the value for the key UT." And behind the scenes,
the mutable map is saying, "I don't have this in here,
let me go ahead and insert it. "And now I've inserted it. "I'm going to return it back to you." So there's one little
line of code can save you a number of lines of
code if you need to add and remove things to a value. Now of course, this is
a map, it's mutable map. You can do all different
types of things with it. If you would like to clear
it at this point in time. And then we can say, so
here, we're gonna print all the items and we'll print it again. The items that clear is
gonna go ahead and clear all the items out of the map. So it's just a very, very
kind of just typical map, hash map, dictionary, object, data structure that you
used to working with. And that's how you can work very easily with a mutable map in Kotlin. Retrieving values from a map
is usually pretty simple. And you've seen this
probably in other lessons. You can retrieve the
value via the get method, what you could just say NY
here or this will return you your result as we can see here, or you can actually go ahead
and use the indexing operator to return the value. Now, there are a bunch of
other things that you can do because a mutable map inside of Kotlin is inside of the collections library. And so there's a bunch of
different types of collections. And the map is one of the most common that you're going to end up using. Now there's a bunch of different
utilities that you can use on a map, just like for a list and arrays and other common collection data types. So if we say items dot,
the best recommendation I can give to you is
to start looking around at all the different things it can do. Now, one of the things that
I am really happy about is actually the, any method. And any method basically says, "Hey, if there's any items
inside of this list." And this is very useful,
especially in common expressions where you're checking to see
if there's anything in there. And so if you would like
to see if there's any items in the map, then do something. So you wanna do X, Y, or Z. Otherwise, maybe you wanna prompt the user to add some values because
they can't continue. So any is a quick way to check
if there's anything in there. Now there's other things in there as well, which kind of is the opposite of that, which is none basically says returns true if the map has no entries. So is there nothing in there, okay, cool. Then at that point, it's
kind of the opposite of any. So this would then operate
if there are no entries and this operates if there are entries. So these are very useful little operators onsite of the map. This is also very common
stuff that you're gonna see instead of lists of stuff as well. Now, if you also would
like to return values outside of a map, again,
the most common way is going to be using the
items.get or get or default or get or put get, get
or else, get values. You can actually iterate
over the values themselves. You can also check to see if
it contains a particular key. So does it contain the
key and NE for Nebraska, this will return true because
we do have the key Nebraska inside of here. Now, one of the things
you have to be aware of is when you're using the
common methods inside of here is you may wanna say, "Hey, I wanna filter "a particular value." So let's say val result equals filter. And I wanna say, item.key equals NY. Now you may think, well, that's
gonna give me back my value. Well, that's incorrect, you
actually gonna get back a map. And we can see this simply
through the code help inside of IntelliJ. But let's just go ahead and print that out to see what that looks like. And when we look at the
output, we're gonna see that we actually get back an actual map and we can see it's a little
bit further if we actually just filter this just by saying contains. Let's go with the letter
N and we'll go ahead and get rid of this NY here. And what this is gonna do
is actually filter the map and give us a new map back
that only has any of the items in the map where the
key contains a letter N. So we have New York, New
Jersey, New Mexico, Nevada, Minnesota, and Nebraska. If we were to change this
to the letter F of course, we're only gonna get back Florida because as we see down here, because Florida is the only
one that has any of the keys with the letter F inside of it. Now that it's kind of a
very common filter method you're used to seeing, but
you can also use filter keys. And what that will do is
remove the requirement for you to type keys, because
it is the key right now. So now it is the key, we'll
run the same exact thing and it'll filter out the keys. And of course, we just get the letter N, you can see that here,
so we can say filter key. So you can start filtering
based upon the keys. If you only wanna filter the values, you can say filter values, same thing. Does it contain the letter
N and that's only gonna be a capital N, by the way. And so we're gonna see
back, we do have New Mexico, New Jersey, blah, blah, blah. If we wanted it to be lowercase, then we will kind of get all of our values that have a letter N which
might be additional ones like Minnesota and
California and so forth. They're all gonna be
included inside of there. So there are different
ways that you can work with these things. And of course, these are
basically collection operations. So if we wanted to filter those values, and then I wanted to
provide a map over it, and I wanted to do something to that map, and again, this is gonna
be a transform function that's applied to this. I could say at this point, I wanna take the first three letters of the value. And I just wanna go
ahead and map that value and give me the value. So if we did this and then
we did the substring of it, and we said, all right, we'll
take the first three letters. What would that look like? You may think, all right,
I'm gonna get a map back. No, you're actually gonna get a string because we're just actually
taking the value here. So you can take a look at
all of the different things. So we can perhaps filter
keys, filter not to, we could say map keys. So there's different things
you can do inside of here. So you can map the keys,
you can map the value. So if we take a look at this,
it's going to be it.value. And then what we're gonna
do here is we're gonna say sub string zero, comma three. And if we run this, what
we're gonna see back is now we still have our map
intact because we're using map of values instead of the map. So previously again, let's go
ahead and comment this out, just to see the difference
here, we use map, and then we say it.value.sub string. And we said, all right, we
wanna take zero to three. And when we ran this, we just
got back a list of strings that contain the first three
letters of all the states. And the reason for that is
the map is just a mapping. "Hey, this is a transform function "applied to the map entry." Which is basically each entry
inside of a mutable maps is called a map entry. And the map entry is a string. As we can see here, it's
a string and a string. So the key is a string,
the value is a string. And I'm just saying, "Hey,
I wanna take the value. "And whatever is returned
from this is what's going "to be returned from the map." So it's gonna be the first
three letters of the value, and it'll be shoved back in there. And now, if we kind of switch us around and come put this out,
there we go and we run this, now, of course, what then
that values is gonna do. It's gonna say, "Hey, well, let's go ahead "and take the values and allow
you to transform something "on the value itself." So returns a new map as we
see from the documentation with entries having the keys of this map and the values obtained by
applying the transform function. So in short, what that
allows you to do is transform the values, but maintain
your map structure. You can do the same thing
with things like map.keys. You could do some type of
transform function inside of here. We don't wanna say map.value. We would say map.key. I'm gonna say, just make it all lowercase, to lowercase. And now if we to run this, we'll see that the result that
we get back is now we have all of our keys have now
been turned into lowercase, but we still maintain our map
structure and our map data. So anytime you are working with a map and you wanna kind of transform
the data, iterate over it, you wanna go ahead and take
a look at some of the various different map operators. So you can do flat maps and
regular maps and map not nulls and so forth like that. Which are very useful. But this is one way that you
can find and filter values other than using the
traditional get and put methods inside of the immutable
map and maps themselves. One thing that you will eventually
run into in almost every programming endeavor is the
point in time in which you have to start working with null values. Now, by default, you wanna
try to work with as many non null values as possible, which by default is why Kotlin makes you specify nullable types. However, there are times when
you perhaps need to perform some type of map operation. Now, for example, this map
might have you do something and you have to call
into a certain function. So we have a function down
here below and it's called find value in web service. Now this doesn't really find
a value in a web service, but we are trying to
emulate that environment. Let's assume for whatever
reason that all of your items in your map, you need to
go up to a web service and you need to ask it for some value and you don't control the web service, but for whatever reason, that web service could
either return a null value, or it could return back
that particular entry, maybe it's been modified. You know, it could be something different or it's just gonna return
it back as a regular entry. So if we were to print this out here, what you might see at this
point in time is that we have a bunch of weird values in here. So it looks now we have
basically a map, which again, is just going to return us
back a list of certain values or whatever, we're
going over the map here, and we're saying, hey, give me
all the values of everything. So it's returning us back a list, but this list has a bunch
of null values in it. And we don't want these nulls because now when we have a null value, you can see that the entry
that's returned is a list of all these different entries, which makes it very difficult because now we have to worry about null checking and so forth. And if your method that you're sending in this result value into, so if you have another method
down the line that says, you wanna process results
and process values, and that method for whatever
reason takes in a result, and it takes in a non nullable type, which is what you should try to aim for, because nulls can create
a lot of problems for you. That's gonna create a problem
here because you're parsing in null values now you
have to do the checking, now you have to remove them. Well, thankfully, there's
a way around that. So you could say map not null. And what map not null will
do is if it encounters any situation where a
null value is returned, which is what it will do here, and basically what that happens
is any time one of the keys inside of our map starts with the letter N we're just gonna return a null, otherwise just return the entry. So if it encounters, you know,
New York, New Jersey, Nevada, Nebraska, anything like that,
it's gonna return a null, which is what we saw here. That's just, these are all
the null values that we see down inside that were
returned back and so forth. So we got them there, we
got there, we got it there. We got them all over the
place and we don't want that. We only want the values
that are not gonna be null, because perhaps we need
to do some processing. We don't have to worry
about nulls, et cetera. So you can use map not null. And if you run this,
now, what this will do is Kotlin will say, all
right, we're gonna iterate through the map, if we get a null, we're just not gonna include it. So here down on our result,
we could see that we do not have any null values whatsoever. These are just values that are not null. So if you ever need to
have a list map, whatever, and you don't want the
null values included in it, but you do need to perform
some type of processing over them, and a lot of
times you may not even have the ability to change that code because maybe it's inside
of a library somewhere and it returns these null values, you can use map not null
to ensure that the map that you're going to get back is not null. Now, of course, items might be missing that you were expecting to be there, but you'll have to handle
that as a case by case basis. And that's how you can work
with a map and make sure the items are not null. This also works for lists as well. Let's assume that you wanted
to generate a very large list for whatever reason to do
some testing or perhaps just to play around. What you can use is the
generate sequence method that's inside of the
Kotlin standard library, which returns the sequence. Now a sequence is a
completely different beast, and there's some other videos that talk about what sequences are
and why they're important, but just know now that you
can generate a sequence using this method. We wanna seed it with the value of one. And then for each iteration,
we want to do something, we're gonna perform some
type of function here, and that's gonna be the next
function that gets executed. And all we're doing is we're
taking that existing value, which is the integer value of one and incrementing it plus one. And then we say dot take,
and what take is going to do is re return to sequence containing those number of elements. And basically we want to
do this 50 million times, which is a lot. And so again, this is a shorthand version for making longer numbers. Now, of course, we could
have done this too, that would work, but
thankfully Kotlin allows us to have some readability
with the underscores. So we wanna do this 50 million times, and then we wanna turn this into a list. So now we have a list of 50 million items that are all different, that
have different values in there. And then let's say that
for whatever reason, we wanted to do some operation. Now we could do this operation here, and now we have a very
large list to work with to see what's gonna happen. Now I'm gonna run this here, actually. And then what you're going to see is it's going to take awhile. And so this is currently running. We'll know when it's done,
when we see the print line is done on here. Now, it's very possible that
you could hear my computer fan start-up in a second because
this code is still running right now, it's still
filtering and averaging over 50 million items and
it's running on my laptop. And now I can hear my fans
turning on, and there it goes. It's now done, so it took
quite a while to generate that. And so we get back a double,
so we could say the result, and then we say print line
and we could say the result, and then we can see the results here. And again, if we were running it again, it would take quite a while to run this, but this is one way that you
can use the generate sequence built inside of the
Kotlin standard library to generate a very large
list to do some testing with. So again, this is generating
a with 50 million items in it, and then I'm iterating over it, basically filtering out the
items that I have a modulus of three, zero. So if it's divisible
by three, then go ahead and take those out and then I
wanna average those together. And then what happens is
I get the result back, which is a double, and that's printed out down here at the bottom. And that's how you can
generate very large sequences and very large lists inside of Kotlin so you can work and do
some testing, very useful. Let's assume you have
this chunk of code here. And if you run this code,
you'll notice that it takes anywhere from on my
machine, upwards of nine to about 20 seconds to run. And the reason why is because
we're generating a sequence, which is like a collection, and we're turning it into a list. And this line basically says,
hey, we have 50 million items in this list. And then what we're gonna do is kind of do some operations over it and
then we're gonna calculate the average of some of
the values in there, and then we're gonna
print and result and done. And so we can see that here. Now, one of the problems that
we have with a lot of code sometimes is we don't know
how long it takes to run. All I know here is like, wow,
that was a long time to run. So how could we measure this? Well, what we can do is
actually create a function called measure. And this function measure is gonna take in what's known as a lambda expression. And this lambda expression
kinda confusing. So it's called a block and
this is just a function. We're just parsing a function. And this function right
here has no parameters, that's why it's an empty, it's
like an empty parameter list. And this function is gonna return units. So this block right here,
right here, this block is going to have no parameters
and it's gonna return a unit. So it's basically a void,
so it's gonna be a void. So we can parse some type of function inside of this measure block here. But now we need to be
able to measure that. So we're gonna say, nano time. And then what we're gonna
do is say measure nano time. And we can actually
parse a block in there. Measure nano time, if
we take a look at it, it executes the given block
and return to the elapsed time in nanoseconds. This is built into the
Kotlin standard library and the timing file. And all it does is it looks
at the system nano time, and then it executes the block. And remember the block,
which is what we parsing, it's just a function. So I'll show you how to
parse a function in a second, we're gonna parse a function
in the measure nano time basically just grabs the nano
time, then tells the function, hey, execute, do whatever you need to do. Remember the function is returned to unit, which is basically the same
as void in most JVM languages. It's gonna return a void, and
then at that point in time, it just calculates how
long it took a nanoseconds for this function to execute, because again, that's
a blocking operation. We get the start time and
we calculate the end time and that's how long it
took and nanoseconds. So now we have nanoseconds, but we're not too familiar
with how it would work with nanosecond, so let's
go and get the milliseconds. And so we're gonna do time
unit.nanosecond.two millies and now we're gonna
parse in the nano time. That's how long it took. And then now that we
have the milliseconds, we can just do print line. And then what we can do is a print ln. We actually take the millies,
print ln and parse it in the millies and then
type in miliseconds. And what this will do is show us how long something took to execute. So how do we use this function right here? So what are we gonna do,
because this is a block we'll just scroll up here. And this is called a
block because it looks like a block of code and I'll
show you what I mean by that. Let's type in measure and see how we hit the code completion here. And it looks like these
little brackets are, we'd like to call that a little block. And so right now we're in the block. This is the piece of code
that's gonna get executed. So we can just provide these brackets here because is a lambda expression. And anything that's in
between these brackets is considered the block of codes. I'm just gonna go ahead and take all this and I'm gonna go ahead and
clean this up a little bit, and I'm just gonna drop it right in here. So now all this code
that's inside of here, this area right here,
this is the block of code. So remember it. So this right here is what
turns into this block. And then when we get onto the nano time, that's, what's executes right there. So anything between
these two brackets here. So top bracket, bottom
bracket is the block. And all this code that's inside of here is what's gonna get executed and measured. So let's go ahead and do that. So now what's gonna happen
is we'll still run this code as we normally would have, but what's gonna happen
is it's going to run and then it's gonna show us
how long it took the run. So if we run this now, okay, execute this. Again, this is gonna take
anywhere from five to eight to 10 seconds. And as it runs, it's
basically calculating it. Again, take a look here right now it's executing this code right here. It's already calculated the start time and it's calculating the end
time as soon as it's complete. And as soon as it's complete, we'll then see the millies
ms, there we go done. So we know that we are
done, we got the result, which is the average that was calculated and it took 18,244 seconds. So on my machine, this operation takes a little over 18 seconds. Now it's important to
note that measuring time and diagnostics for how valid
timing is a difficult task inside of JVM. So this is a very important note that this can not be treated
as the end all be all performance checking solution. The reason being is the
JVM has in deterministic garbage collection. So we could be in the
middle of running this and for whatever reason, the JVM decides, you know what, we needed higher time. It's to garbage collect. And that could happen right in
the middle of your execution and might throw this off. So I don't want you to
take the measure nano time as the end all be all solution. However, it is a very, very
useful tool to kind of get a good diagnostic feel of all right, how long is this taking,
why is this taking so long? And you can use this. So what I recommend is taking
this little measure thing, you can kind of copy and paste
it into maybe a scratch file somewhere or some type
of a tool where you say some code snippets and
use it any time you want to start measuring things, or you can put it in your
project under a utils and then decide if you
wanna use it or not. Now, of course, you
probably don't want this in your production code,
but it's very useful if for some reason you're like, wow, how long is this really taking? And then you can start determining, oh wow, this is the chart, you know? And then if you wanted to, you could say, all right, well, is it really,
is the measure actually, is it this piece right here? So then, what is that? So we actually, we'd probably
want to take this out here and say, wanna take this list
out and say list var list. So we do a late init var list, and that's gonna be a list of integers and now we can just do this. So then we can see how long that took. So if we were to run this now, we would see as it's generating
how long it's gonna to take. So to generate the 50,000
and then turn it to a list, as it's executing. Okay, so I'm gonna stop it here. Now what we see here is that
it took about nine seconds to generate that sequence. So then we could start measuring various different components here. All right, how long did it
take for this other stuff? So if I wanted to, I
can move this down here. Okay, that took nine seconds. How long does it take for this stuff here? Let's go ahead and run that. And then you can run this, et cetera. And then you're gonna be
able to see how long it takes for each one of these
things to be generated. Now, of course, there's a
bunch of variables in here. Okay, we're not late,
we're late in initializing. Is that impacting it? If you're looking for very,
very fine tuned ways to tune it, this is not gonna be a solution, this'll be just something
to help you kind of gauge whether or not something is slow and how to do it very easily. So here we can see that
the averaging itself takes about eight seconds. So generating the list
took a little over nine, almost 10 seconds, and this
takes almost nine seconds itself to calculate the average and print it. And that's how you can
use the measure nano time to create a measure block, which then you can use
inside of your application to measure chunks of code to see how long they take to execute. To understand sequences in Kotlin, it's important to
understand how list operate over list of data and how
the mapping operations occur to each of the list items. So we're gonna do is
create a list of strings, and then we're gonna filter that list. And it's not really gonna filter anything, it's just gonna go ahead and
process it and print line, and then return back true
basically saying all the items will be included. And then finally, we'll go
ahead and then print line it at the end of each one of those. Now for each is one of the
things we're doing here is actually showing that a
list is eagerly evaluated. So each time there's an
operation, a new list is created. So what that means is if I were
to perform this same filter operation twice, and I
were to say, hello here, what would happen is Kotlin
is then going to create a, it's gonna take the
list from the beginning. And then in the filter function, it's going to create another list. And then from that list, that
list will then be turned in and sent into this list right here. And when this one is done,
a new list to be created and sent into here. So basically we're having this
type of thing happen here, where these lists are being
created over and over and over, but there are new lists. So this is a list off of this one, this is a list off of this one. And then a new list is finally
created that we have a list down here. So Kotlin is lazily evaluating those. And the easy way to see that
is with this function here. So let's go ahead and run this
and what we're going to see here on the screen down here on the bottom is that we have the filter
was run for every single item in the list and it didn't
progress until the next function until all of the items
are processed in the list. The same thing here, and
then the same thing here. So as each item walked down, so this function did these ones, this function did these ones, and this function did these ones. So it operated one at a time. This one, then this one, then this one, and that's what's happening
behind the scenes. Now this can be home problematic
when you have a very large list or many processing
steps inside of your list, because if they are processing intensive, then if we're creating a new collection, each time eagerly evaluating it, we can just start
slowing down the process. So let's see what that kind of looks like if we were to use a sequence. Okay, so I've copied and pasted some code and I've removed the
additional filter up here. Now we now have down here
almost the same exact code. The only thing is difference
is we have the sequence of, and what the sequence of
is it returns a sequence, which is basically something
that can be iterated over. And a sequence is returned to
value through its iterator, the values are evaluated lazily. That's the key, values
are evaluated lazily while lists are evaluated eagerly. So we've created a sequence of this, basically everything
else is the exact same for both of these. Now, if we run this, we're
gonna see the difference here in the output. And so we have the line
that's separating down here, the top from the bottom. So up top we have new list was
created, new list is created. We can tell that because all
of the items were processed in sequential order before it moved on to the next map operation
or the next operation inside of the chain. However, instead of a sequence we can see that the filter executed
for the first value X and then X moved on to the next operation, which was for each, the same thing for Y. So as each item is coming
out of the sequence, it's processing through the entire chain until it hits down here,
hits the termination. And so each item is
processed sequentially, so as a sequence. So if you have a very
large list or you have a very high intensive chain of events that are being processed,
it's recommending that you use a sequence because it's
much more performing. Now, if you have 10 items
and you're only performing a couple of operations on
the list, should you use it, we don't know, what if
it's 100, we don't know. As the saying goes, what
gets measured gets managed. So how do we measure if
something is slow or not? Well, let's go ahead and delete this code and let's go ahead and generate some code that has a very high number of values. Now the code that I'm
showing you is actually from a gentleman named Benjamin. And if we go over to,
I found this blog post quite a while ago and it's
been the one I've relied on for years on the sequences. And he talks about all this stuff in here, and I'll provide a link to it. So I'm actually using the
same exact example he's using to show how performant
some of this stuff is, very, very useful. Thank you, Benjamin, just
want them to give you credit for writing this phenomenal blog article. And I'm basically explaining
a lot of what's covered in here, though he does
go into some more detail with some other information. Okay, so let's go ahead and get some code to actually show how to measure things. So I have some code in here and this code does a couple of things. We're generating a sequence as we can do, which is built into the
Kotlin standard library. So I'm generating a sequence, I'm gonna seed it with a value of one. And then for each iteration
again, generate sequence is in the Kotlin center
library and it basically says, you can give it a seed
value, and then we'll invoke the next function for every single time you want something from the sequence. And so I'm gonna tell it to
just increment that value and I want it to do it 15 million times. So I have a sequence of 50
million items and then I say, hey, turn that into a list. And so now I have this
list of 50 million items, which is a very large list
and it's gonna be processed on the laptop. So 50 million integers in the list. And then what I wanna do is filter it. So find anything that's
dividable by three, and then average that out. And of course, we're
gonna get a value back. So let's go ahead and grab that value. That's the result. And we can go ahead and
put everything back up on a single line here, 'cause
it kind of makes sense. And then we'll do is print the result, print line and we'll do it the result. And then what this measure
method will do here, which is what you've probably
been wondering about. It takes in a block and a
block is just a unit of code. And this measure function is
explained in another video, but basically shows how long
it takes this chunk of code to execute. And so this is what's parsed in. And so let's go ahead and execute this. And then what we're gonna see at the end is how long it took and
milliseconds to process this. This could take anywhere
from eight to 20 seconds, depends on my machine today. I've seen it kind of
range all over the place, depends on what's happening on my machine. And it's still running. We can tell this by the
stop icon is active, which means there's something running. The green little dot there
also helps us indicate that something's running. There we go, something came back. So we have the result, which
is 2.5 to whatever power, et cetera, very large number. And it took us 18,000 milliseconds, so basically about 18
seconds to run this process. So, okay, so we have a
list we're processing over the list and remember how we executed those things before. So let's go ahead and change this. And instead of making this a list, let's go ahead and make
this just a sequence. And so to do that, I'm just
gonna remove this list here. And instead of calling this a list, I'm gonna call this to change
the name to a sequence. So now this is a sequence. So it's almost the same exact code, I've just changed the
variable name and removed it from being a list. So let's rerun this to see
how fast it is to process a very large list and do
a couple of operations. Boom, we're already done. So 50 million items with a
sequence took 628 milliseconds. And if we did it with a list, it took us almost 20 seconds to do. So it's orders of operations, much more efficient to use
a sequence in this manner. At what point should you use a sequence, that's up to you to decide,
should you be using a sequence? If it's 10 items, depends
on how much processing you're doing on those 10 items. Are you doing 100 operations on it? Maybe it's important to
you the sequence then. What if you have 100 million
items and you're only doing two operations on it,
like we're doing here and only doing a filter and an average. Should you use a sequencer, probably so. But what if you have 10 operations, it's not really doing much calculation and it's only maybe 10
items in the collection. Should you use a list maybe, maybe not, it's up to you to decide. I wouldn't focus on making
sure that everything is performant, that's
premature optimization. However, you can use this nice
little handy measure block as we have here inside of your code, throw it inside of a
utils folder or whatever and then measure something. If you find piece of your code,
kind of being really slow, I highly recommend slapping
the measure block around it. It'll give you a good
rule of thumb of like, alright, is this taking a long
time or where is this slow? If you find you're processing
around a large list or list operations to
be slow, at that point, it might be time to take
something and make it a sequence, which it could be also very interesting. So let's assume that for whatever
reason, we have a method. And this method is called
get list of customers. And this list of customers
comes from a database. And it's gonna return a list
of data and this returns, let's call this integers for now. And then what we'll do
is I'm just gonna do this right here, it's gonna return
a list to that to list. We're just gonna pretend
that this is actually is from a database. So I'm gonna pretend it's in the library and I get this out of here. Okay, whatever. So now I'm gonna have my changes to list. Actually it will leave a sequence. Actually, I'm gonna change it back to list and I'll show you why in a second. So we're gonna say this is list and say, we'll say val list equals
get list of customers. So there we go. Now we have our list of customers. we're gonna perform some filtering
on it and it's gonna run. Now, as we know, this is
gonna take a long time because this list of customers here is a very, very large list. Now, again, we're
generating this on the fly, so let's go ahead and
pretend you don't see that. Let's assume that it just returns a list and we don't have control over that. But however, now that
we have this large list, however it's been processed
maybe on a background thread, but now we have it and
we need to process it. We now have control of them. So this is a very large list. Let's go ahead and run this again. So we have that list of customers, actually, we can go ahead and take, let's assume that this is out here 'cause we don't wanna
measure that component there. So we have our list of customers. That list of customers is
gonna take probably eight or 10 seconds to generate
because it has to turn it into a list. And most likely this block
of code will probably take another eight or 10
seconds to execute as well. So perhaps this is on a background thread. We can't control that,
that just is what it is. And we're maybe we're
gonna get that data back. But now once it's off
that background thread, we can control it, so for
example, it takes eight seconds. So at that point, how
could we speed this up? Well, because this is a
list at this point in time, everything's running slow. Now, thankfully there's
actually a nice method on the collection here called as sequence. And what this will do is will turn this, I'm gonna go ahead and break
these into new lines here. This will turn this list into a sequence. And so what this will
allow us to do is get some performance benefit
as treating this list as a sequence. And then from this point
on after this line, everything will be operated sequentially as a sequence would. So let's go ahead and rerun this. And as we're running
it here, we're waiting. And of course, it's gonna take a while because we're assuming this
is on a background thread. It's gonna take awhile
eight or 10 seconds. And then once we get a result back, boom. Now this code has been measured. Previously, it took them
as nine seconds to run. Now we turned it into a
sequence via the as sequence helper method. And we have transformed this
from a nine second operation into a 244 millisecond. 244 millisecond operation
orders of magnitude faster just by slapping on as sequence. Now, of course, you're not
going to get this benefit if your list is 10 items, you're not gonna see
that drastic improvement. It's gonna be very
negligible at that point. So follow the mantra of whatever
gets measured gets managed. If you find slow piece in your code, see what's taking a long time. If it's a (indistinct) list or
a map or something like that, perhaps you need to figure
out a way to turn it into a sequence so you can get
some performance benefit out of it. And of course, be sure to
check out the blog link in the show notes. So big hat tip to
Benjamin for demonstrating a perfect example. I tried to create my own, but his example was just
so succinct and great. Thank you Benjamin for doing that. What's the difference
between a list, a set, a map, and a sequence? Well, a list of data inside of Kotlin can be a list of anything. It can be a list of primitive values such as strings as we have here. So we have lists. We have some names, Donn
Tushar, Cavita Evelyn, Felicia. This could also be a list of integers. So we could have a list of
integers of one, two, 12, 23, 44, 66, et cetera. And they can be a list of integers. It could be a list of objects. So any time you need a
list of data to work with, you're gonna wanna use a list. Now, as you can see here, the list right now is strongly
typed to be a list of string. And again, if you need
it to be a mutable list, you would type mutable list
of which means this is a list that you could change. So the same thing goes for
almost any of the collections inside of Kotlin. If you want it to be mutable, you'll actually slot the word
mutable at the beginning. So when do you wanna use a set? Well, a set remember only
include unique items. So in this case, I've set a creative set
of these four items, Don, Tusha, Donn and Tusha. Now, when we actually print these out, if we were to print it, we would see that this set
actually only includes two items because a set does not
allow multiple duplicates of an item inside of there. So here, it's going to see that
Donn is then presented twice so it will not add the second one. And the same thing for Tusha
has been added another time. So it will not add a second one. And this result will only be
in the set be Donn and Tusha. So if you need your items to
be unique inside of basically you need a unique list,
you're gonna wanna use a set. So what about a map? So when would you use a map? Well, a map is basically a
mapping of one value to another, and I like to think of
it as a key-value pair. And that's what this is, is a
map right here with the helper is we have the key, which
is a string and the value, which is a string. Now this does not mean that
you have to always use string and string. So we can say map to, we
could actually say map of, and I'm going to say one to Don and then we say two to Tusha. And what this does, it creates
a map with the key being an integer and then the
value being a string. So you can create a map of
all different types of things. If you wanted to create it as an object, you could create it as
its own custom object. If you had say a data class, it'd be person and we maybe
had val name, it's a string. What you could do is you can
make this map, turn to person, Donn to, maybe he does Android. And we wanna have a map of person Tushar, which then maps to let's say he does J2EE. And at that point, we now
have a map where the key is a person object and
the value of the string. And of course it could be flipped around. There's no rhyme or reason
to what you would want. It's just a data structure that allows you to have a map and a key. So if you ever need to
have a map and a key, you're gonna wanna use a
map type data structure. All right, so when would
you wanna use a sequence? Now sequence is basically
going to be anytime you want something that's gonna
be more highly performant. And this could be, if you
have a very large list. So maybe 50 million items in a list or you have a bunch of
items you need to process. So you have 100 items in a
list and you need to process it through a bunch of different maps. You're gonna do list.map, and then you need to do another
map for whatever reason, a map, and then you're
gonna do it for a reason, you can do a flat map and then
you're gonna go from there and you're gonna do a filter,
or you can do an average, actually I'm not gonna work that way. But the new map won't work on a flat map because you have a list. And then so you have, you
know, some averages that are, or whatever you get inside
of here and you can filter and all different kinds of things. And let's say, assume all
of these different things had very complex calculations
that took a lot of time because lists and so forth
are evaluated eagerly. A new list is created each time. Then there's actually a
lesson that you can view that will show the performance
metrics of actually using a sequence overlords lists. And so if you need performance,
you'll wanna use a sequence. Now, you can also take a large list and turn it into a sequence here. You can also turn a map and
turn it into a sequence as well. So if you have a large map or a large list and you need to process it, sequentially, you can just slap as sequence on there and it will process accordingly. And then you can make things very quickly. And then if for whatever reason, after you're done processing the sequence, you would like to turn that
sequence into a list again, you can turn it into a list as well. Of course, you're gonna wanna measure the performance benefit. So sequence usually use for performants. So if you have large lists, large maps, or you're doing a lot
of mapping operations, you're doing a lot of
operations, you know, on the data structure itself,
then you wanna go ahead and turn it into a sequence
or use a sequence if possible. And of course you can always
use generate sequence. So that's the difference
between the list, set, map and sequence. Kotlin does not have a ternary operator. So if you're looking to
do something like this, let's assume we want to get
the length of the string, but we only wanna do it, if it's not null, perhaps we could say something like this. You might be used to doing like this. If name does not equal
null then we're gonna do name.length, otherwise wanna do zero. So you may wanna do something like that. You'll see here, we have
to use some wiggly things. It's not letting us do this. This is because the ternary
operator does not exist in Kotlin. So to do this, you need to use
a single line if statement. So if name's not equal to
null you can do name.length, else zero. Now this will give you the
length here so we can say print line length, and then
you can run the code here and you'll see that the
length is then returned. And for whatever reason, if this is null, we can return that. And then we'll get zero because
it's handled accordingly. Now we get this nice little use
here where we can replace it with the Elvis expression. If you know what that
is, you can use that. But if you are looking for a
ternary operator in Kotlin, it does not exist. So you wanna use a single
line If Statement instead. It's very often that you could be working inside of some code and you
may need to do something. For example, you may need to
get the length of the name of a particular value, maybe of a string and you wanna say name.length. Now what you'll notice here, because we're working
with a nullable string, that we cannot do this. Only safe calls can, this can happen. So we're gonna get back. We could change this to a safe call. This would give us a nullable integer. However, if we're
working in an environment where we do not wanna work with nulls, this is not going to work for us. So there's a couple of things we could do. If we can change the data type
to actually be non-nullable, that would be fantastic. However, sometimes data comes
back from other libraries, such as Java libraries,
where the nullable type is just going to be there and
we can't do anything about it. However, we may understand
at some point in time that we know for certain that this value is not going to be known. Now, this is something I
usually don't recommend, but there's a way to do that. And we can use the double
bang operator to tell Kotlin, look, I know what I'm doing. I know that a named value
is not gonna be null, which we can see directly from here. There's no way on nothing in line five. That's making this not null. We're making this null. So we're basically telling Kotlin, don't worry, I know what I'm doing. Trust me, just go ahead and treat this as something that's not null. And then what we'll have
happen is we'll go ahead and we can say print line
length, and then we can run it. And then what will happen is
we'll then have a int value, which is again, it's only
going to be a regular integer, so it's not gonna be nullable and then we'll get the length of four. Now, the real challenge comes in here when you're telling Kotlin, hey, trust me, I know this is not gonna be null, but for some reason it ends up null. And this can end up showing
up inside of your code multiple times. So if you find yourself
doing something like this all over the place, you
might wanna inspect your code to see if it's something is correct. Now on tests, you may have it a lot, but in this instance, perhaps I've said, hey, this should not be null. Maybe this code on line
six is buried somewhere in a method somewhere. But what happen is when Kotlin says, hey, go ahead and just treat
this as a nominal value and give me the length
on it, and it's null, it'll throw a Kotlin and
null pointer exception. So this will blow up your application. So you do have to be
careful in this regard to see if you want to
actually work with this. So again, if you know what you're doing and you wanna tell Kotlin about it, you can go ahead and say, hey, Kotlin, I know what I'm doing. Use the double bang operator. The same thing could go for a class. So if we have a person and
then we have a vowel name and that's gonna be a string, we could actually say something like this. And we can have a nullable person. Let's say person, and that's gonna be a
person that's nullable, again, maybe this comes back from an API and it could be empty or whatever. And so the person could be Don. At this point in time
I could say, if I wanna kind of get the values of print ln, I wanna print the person's name. Well, this is not gonna
work because again, person is nullable and Kotlin will say, hey, we're not sure if this is a safe call because well, person's nullable, and this could end up being
a null pointer exception. So I could handle it this way. And if I did handle it
this way, if we ran it, it would still work, we
would get the person's name. It would be Don. However, if for some reason
this was comment that out, and this was no we're now gonna go ahead and we run this again,
it's gonna show up as null. But again, maybe we don't want that, we want it to say, hey, trust
us, we know what we're doing. So you can use also the double
bang operator inside of here. Again, if the value is null
as it's happening here, you're going to get this
Kotlin null pointer exception. So if we were to just remove this, again, it's going to work as we expect. But usually my recommendation
is if you see yourself or wanting yourself to add
these values all over the place, you might wanna figure
out if there's a way where you can not use that or
use something like required, not null which is in another video and link in the show notes below. In some Kotlin code bases,
you may find yourself wanting to type the double
bang operator to force a nullable value to not
be nullable anymore. You're basically telling Kotlin, hey, trust me, I know what I'm doing. This can happen for regular
primitive nullable types, such as the strings integers,
et cetera, Boolean values, or it can also happen
for the nullable objects such as a person object that we have here. Now, there's a way around this. If you don't wanna type
the double bang operator, you can also use what is built in to the Kotlin standard library, which is called require not null, and require not null basically
will turn this value, which if you look at it
into a non nullable type, if it's null, though, it will throw in a legal argument section, otherwise returns the not null value. So if we run this here, what
we'll see is it'll continue to run this fine because we
have the length of the name. This has now been turned
into a regular string, not a nullable string. However, if for whatever reason,
this comes back from an API or database or something,
and that value is null and we process it. What will then happen is Kotlin will throw an illegal argument exception saying, hey, the required value is null. Now, to be honest, this
error message while helpful, this required value is
null, in a large code base, this is not very helpful. This can be kind of like,
okay, there was a null value somewhere that we shouldn't
have got, where's it at? Okay, let's look at the stack
trace, there's a stack trace. Let's go investigate it. Sometimes it helps to have
some additional information inside of there. And so you can do that,
there's actually an overload of this, which will allow you
to provide a lazy message, which will be the result
of that being called. And so it can be a function. And we're just gonna go
ahead and use the lambda. And I'm gonna say something like the name should not be null, but
it was, exclamation point. Now, if I run this, we'll
still get an illegal argument exception, but
we'll get the message the name should not be null but it was. And you could provide some
additional information inside of here, perhaps that
might help you debugging, if you were throwing in
legal argument exception, and you have a crash
handler on your application, if you're having an Android application, this information is gonna show up inside of your crash tracking utility. So you might be able to say, hey, here's the value that came in. You could throw additional
information inside of here that might help you diagnose what it is. Sometimes, maybe a backend
changed and they are sending a nullable value that was
not supposed to be nullable, and you're not expecting it
to be null and things blow up. And so you can just say require not null and that'll get you around it there. Now there's also, you
can do the same thing for objects as well, not
just primitive types. So we have this person here,
perhaps we don't wanna use a double bang operator here. We can go ahead and say require not null. And again, it's just
gonna take this object and make it to this not null type. And there we go. And if we run that everything
should run accordingly, except we're gonna get that
exception, so let's fix that. That's the exception from up here. So I would just say Don. And if we run this now, we'll
see that we get the Don, which is down here in the bottom, which is from this print line down here. So we can actually just go
ahead and get rid of that to make it easier. And then if for whatever
reason this person was null, same thing happens here. So you can say require not
null on an actual class, you'll get the same thing,
the required value was null. Again, just like the other version, it's the same exact thing. You can put some other our message here. So hi friends. Not that you would wanna
do this in production, but this basically oops, and that needs to be inside of a lambda. So that just needs to be there. And then it'll actually show
up in there, so hi friends. You can put any message
you want inside there. So person should not be null, here we go. And then once you run it and
you'll see that the person should not be null. And that's how you can
use a require not null inside of your application. So you may have some
code in your application that looks like this, you have
an object, that's a person. And for whatever reason, it
comes back from the API as null or it doesn't. And then inside of your code,
you're using the double bang operator to force the person
object to not be null. So of course this wouldn't work. You could do something like
this, this would also work. It's actually much cleaner in my opinion, this way than forcing it to be not null, because we could not run into situations. But as for whatever reason,
let's say you do have that or you're using require not null. And require not null will basically turn that nullable person
into a regular person. However, there's also check not null. And check not null basically
does the exact same thing as required not null. And if we go to the implementation here, we'll actually see that check not null, which is down here at the bottom
is basically the exact same as require not null. So let's go ahead and shrink this down. Let's see if we can get it
all into the same screen. Very close. So require not null is up here. And then we have check not
null, which is down here. And we could see it as a
contract for require not null checks the value, we get the lazy message. And if we look down here,
the code is exactly the same. So it's doing the same exact thing. It's basically checking
to see if it's not null and requiring it if it's not null. And if it does, if it's
there, it returns the value, otherwise it doesn't. So you can use either one
of these check not null or require not null, depends
on what you'd like to do. I have seen in multiple code
bases where a check not null will be somewhere
randomly in the code base, perhaps after an API call, just make sure that the users not null. And then at that point in time, the person might be a
var if that's the case. So if that's the case, it
might be var person equals check not null or person two. At that point in time, check not null. So you hadn't have a nullable version. So you can either use require
not null or check not null. Let's go ahead and assume
you have a list of names or some list of data that
has come back from a database API call or anything like that. Here we're just creating a list of names and we have a couple
of null values in here. Now it's very often that, for example, we now have a list front
that is a nullable string. So a list of nullable strings
and in our application, or maybe even a library
that does not allow a list of nullable strings, all the values have to be present. So what can you do here? Well, there's a couple
of things you could do. You could go through
each one of these things. And so we could say you
could filter them out. You can map them to a correct value. Or if you just want all of
the items that are not null, you could say val names that are not null. And you could say names
dot filter not null. It's a nice little help our method there. And then you can just go
ahead and print line that to the screen, name that are not null. Let's going and fix that
because it should say names. And if we go ahead and run this, what we'll see is we're
gonna have all of the names and we are not going
to have the null value. So very easy to filter out a list of all the non nullable types. So if you don't want nulls in your list and they are already there,
you can filter it out with the easy filter not null method. One of the keys to understanding Kotlin is understanding the
type hierarchy system. So this is a great diagram,
which is actually used over here from this gentleman who created it, which is Marcos Sandoval. So, thank you, Marcos. This is a great diagram that
illustrates the Kotlin type hierarchy system, which basically states that any non-billable type
is always going to extend to any type and any nullable type will extend the nullable any
type which we can see here. And then we have also
nothing in there, et cetera. We get into that later. So Boolean, string, a class,
you create unit number, everything extends any. So you can use any or
the nullable any type instead of your functions
as a parameter type. So this is very similar
to in the Java world of using object. Now you can actually test this by writing a simple little program And
we'll say Val age equals 32. And we could say something like this. If age is any, then we can actually say print is any. And that would tell us if this is any. Now this is how we can check
for types inside of Kotlin. This is type checking. We're checking to see if age
is of this particular type. Now I can change this to
say, hey if age is a string and we're gonna see here,
the incompatible types type string and into it already
knows the compiler knows at this point in time that
this is not going to work. So what we could say is I can
change this to the type any. I could say if age is a string, then it would print that off. And if we run this, we'll
see we get nothing back. So let's go ahead and add
an else statement here. Print ln.any. Now, if we say not any,
well, this is a string, so we're just gonna change
this to it is, it is not. And so is it page, which has
any, does it equal string? No, it's not. Okay, well, is it a int, let's
run that, see is it an int? We see it is, we can change
these to different values. So we'll say, is it a double? And we can run is it a double? It is not. So this allows us to start doing various different types of things. We could change this to
because again, string. We'll use Donn actually
is a string and we'll see that is not a double,
again it still compiles. Is it a int? No, it's not because it's an int. But if we were to change this to string, then we would see that we can
actually easily type check it. Now this also works for other,
your own custom classes too. So we could say a data
class say create an order. And maybe it has an
amount which is an int. And then maybe we have
just a regular class that is a person. And this person has a
name, which is a string. So we can create an object here, say, val, I'll be J and we would call this. We're gonna say, hey, this is
any, so we're kind of just, we're using the root object here. And we're saying that this is any. And we're gonna create this
person and his name's gonna be, let's call it Bob, Bob. And then what we can say, let
me get rid of this age thing. We'll say this object string. No, it's not, so it's not a string. And we see that it is not. However, it would be nice to
see what kind of object it is. And we can do that pretty
easily say print line, and we can just inspect the
object, object.javaClass.name. And this will give us the name
of the Java class that it is. So is this person a string? No, it's not, it's a person class. So let's change this back
to a, let's go do a 16.0 let's see what that is. So is it a string, it's
not, and we'll see, it looks like it is a double. And then we can also print
a, we could change this to, you can change this back to Bob. And of course, this is gonna say it is, but let's go ahead and
change this to int now. And if we run this, what
we're gonna see now, is it gonna say it is not and
the type is Java.line.string. Now we can change this around too. We can say, is this any. Now this is just gonna
execute for everything, it's just gonna say it is. So it is, we could say 12. We're just gonna say that it is. And we could say for the data class order, which is gonna have an
amount of 120 is object any, it is so any is the
root object of anything. So this allows you to do
some cool type checking inside of your application, because you may have an
application that returns, you working with some different types and for whatever reason,
it returns back in any which can happen for some various reasons. And so if, for example, let's
go ahead and in a string. So val value and it's gonna be a string and we'll use a one expression, one value. And when we say the value is
one, then we want it to return. Let's say return here two. And if we return two,
we're gonna return a hello. If we say three, then we're
gonna return something else, such as a true Boolean value. If we ask for four, then
we're gonna return a double. And so we'll say 16.01. And then of course we have
to provide an exhaustive else because this could end
up being a certain value. So we can get rid of that, there we go. And now at this point,
what we can do up here is we can just say, what we
can do is if we were to say, get stuff in order to parsing
them to number one there, what that's gonna do is any
is gonna report to it is. But let's go ahead and
change this to string. So if my method, maybe a
library I'm working with returns, an object or any,
or something like that, I can do the type checking
with the is key word. And so it's not, so it's an integer. What about number two? So whatever this method
returns is in any, it is. So now I can actually do this
and do different type checking and perform various different operations based upon the type checking
if I'm working with any type. Sometimes you have a
method that returns any, and you need to cast that
into a particular value. So maybe we know that we
need this to be the age and it's gonna be int. And so we need to cast
this into an integer. So we could say obj.as
int, and when we run this, because the OBJ is in any value. If we run it, we'll go ahead and see that it did execute correctly
and we forgot to print it. So we print this to the screen, we'll see that it was run as OBJ to 99. Now, for whatever reason, we were working with the string
and we wanted to use a name. So let's just call this
cast so it's easier. And this was a name and we'll
call this, this was a string. We're expecting it to be a string. And we want this to be cast to a string. And of course we want maybe
our method of assuming this is from like an API
or something like that or some library that
gives us back an any type, somewhat for some reason. We know that number two returns, hello, we can then cast that to a string. And then at this point
in time going forward, we now have a string. And what that gives us is
the ability to actually use different, for example, I have the length, I can do all different kinds
of stuff with this as well. If for let's even take this a step further and say that I had a data class, a person, and that person has a
name and that's a string. Then what I could do inside
of here if we say five, I would then return back
person with the name of Don. So this is gonna work because I have two, and I'm gonna return back to value of two, but I could also do something like this. I could say five. Now this of course is, if
we look at object, OBJ, what do we have on it, what is it? It's any type as you
can see here, it's any. So the things that we can do with it. If I know that when I parse a five in, I'm gonna get a person,
I can't say any.name. Name doesn't exist because
I'm working with an any type. So I need to cast that
to have the type them, which I can work with it. So five let's go and
cast that to a person. I need to make this person. And now I have a person object. And what that means is
now I can say cast it. And now I have the name and
I also have the copy method, which is built into data classes. And everything else that
data class is also have along with it or any other
class that you're working with. So now I can access the name. So I've basically turned this
any type right here, any, I've turned it into a person. So now when I work with
this person object, which is right here, I
can see that it's a person and I can use the type system accordingly. So if I run this here,
we'll see that we can run and we can get the object
turned into a person. And that's how you can
perform casting in Kotlin. Let's assume you have a
method called get stuff, and it can return in any type, which is the root of all types in Kotlin for non nullable types. So if you parse into
one, you get an integer, if you parse in two, you get a string, three, you get a Boolean value, four, you get a double and
anything else, you get a false. So sometimes we need to perform
various different casting. We need to cast it to a particular value. So I'm gonna create a new
variable called cast it. And what I'm going to
do is say OBJ as int. And so what I wanna do
is I wanna get this. I wanna cast this value that
I get back to an integer. And so when I try to do
this, I'm gonna run this. And what we're going to see is
we're gonna get an exception. And the exception is a
class cast exception, basically stating you can't cast... Basically a string can
not be cast to an integer. And that's because the value
we got back from get stuff, we parsed it two in two came
in executed this line of code. We returned the value of OBJ was hello. And we're trying to cast
the string as an integer and Java says, nope, no
dice, that can't happen. So we got a class cast exception to, how can we solve this? So
we can't cast it this way. So what we could do is we
could do some type checking. We could do if object and we
change this to as is into, we could get back the OBJ else, maybe willing to return to
zero, that that would work. We could run this, and this
would work accordingly. We'd get back zero because
object is not an integer. If it was, so if we came back
here and we changed to a one, which again, would return
us back in any value of one, we'd get back the one there. And actually who's go and change this to just what makes more
sense to change it the 99. And so when we parse in one
for the value of the get stuff method, we'll get back 99, which has hap executes
this line right here. And since it is because an
integer, we got this back, that makes sense. However, there's also another way that we can do this as well. Perhaps we're okay with working
with some nullable types. One thing we can do is we can
cast it and do a safe cast. And what the safecast
is saved as an integer. And what this basically says,
hey, I would like you to, as I'm gonna change this to two, done a second, we'll do that. I would like you to try to
cast this object right here, this object as an integer. And so when this piece of code executes, if it can execute it as an a can do it, it'll return the integer, otherwise it'll return a nullable type. So if we re run this right
now, we're gonna get back 99. That makes sense, because
if we parse in one, we get back 99. So let me change this to two, which is gonna give me back a string. Now, if I run this, what
we're gonna get back is null. And the reason for that is
because this line of code right here that executed said, hey, this object is actually null. And so the smart cast said,
hey, I recognize that was null. So I'm just gonna go ahead
and give you back a null value at that point in time, we can continue on and our application didn't crash. And that's how you can use a smart cast. So this could be anything for Boolean, can I parse that to a Boolean? And if we run that, is that going to work? Let's see, no that's null as
well, it's not true or false. So we can't do that. So anytime you need to cast
something and be safe about it with a nullable type,
you can use a smart cast, which is the word as with
a question mark after it. You've probably heard the
word generic tossed around inside of Kotlin various times. And the word generic just
means that we're using a class or an implementation in
a very generic manner. And so here we have an
interface which has a list, and then it has this
weird little parameter. Ignore the out for now. And it has this little E and
that E is the type of elements that are contained within the list. And so this is a collection
which extends collection, and it takes a type E. So what this allows us to do is have all the same operations,
like we can some contains, is it empty, we can use get index. It allows us for high levels of code reuse and allows us to specify different types. So we still have type safety. So I can do that a list of strings, or I could do, let's call
this list of strings, or I could say, val list of ints. And then I could basically
either say a list int and as a list of, and I can
say one, two, three, whatever. And I can have my list of integers. And now they're all both
going to have same operations. List of strings, I'm gonna
have the get operation, I'm gonna have a list of ints. I'm gonna have the same get operation. Now, a lot of these things
are already built for you. So a lot of these generic
operations and generic classes, such as a list and a map and a set, anything like that have
already been built for you. And so it's very easy to use them. So if you want to use a
map, you can specify a map and say map, and you can specify the types that you would like as well. So the key is gonna be
a string and the value will be an int. And then perhaps you wanna
create a map of that. And now you don't have to
provide these type parameters, because it will be inferred
if from the map creation, but here I'm just being
very explicit about it. So I might map Donn to 32
and I might map Tushar to 42 and so forth. But now I have a map and
that string is it's key and the value is its int. And this is all already built for us. So here's just using a map interface. And so there's a bunch of different maps that are implemented inside of the Kotlin collections library and all
different kinds of things. So you can see, so we'll go map.entries and we can look inside of there and see all the various different entries. We can do different, you
know, for different things, such as map.map, which is
kind of weird sounding, but it's part of the maps
function and allows us to transform items inside of there. But the key thing here is
that all of that common functionality is buried
inside of these generics. So you don't have to worry
about building a list, just custom for strings or
a custom list for integers. All that's buried behind
the generics themselves. And the good thing is
you can actually build your own generic classes,
which I'll show you in another video. However, these are the bald basic built-in different types of generics. And very often when you're
looking at the documentation, you're gonna notice these single letters. So E in this case is gonna be the type. Sometimes you'll see
multiple different types. So if we go look at a map, you're gonna see different types in here, you're gonna see K and V. Now the letters, they don't
have to be letters themselves, but they're using very
good terminology here. K for key and V for value in this case. And so up here, the E I think
is for element of the list. And so it was a very element. Now you could actually, if you're building your own generics, you can
actually provide a full on word for the different type,
if you would like to. But a lot of these
things are just that way. So that's what generics are. They allow you to encapsulate
common functionality across a large swath of
code and folks can end up using them for various different things. So for example, here's a mutable list that extends another one. And all that's doing is
saying, hey, we're gonna use all of the exact stuff
from within the list and everything from within
a mutable collection and we're gonna create mutable list. And by the way, here's how
we're going to override this remove function and
here's how we're gonna do all these other different types of things based upon this type of collection. So these are the generic
kind of collections classes. There's a whole swath of
things that are all generic inside of various different
apps that you're gonna use instead of different libraries. HTTP libraries will have you returning a different component. If you're using reactive
libraries, we'll be working with reactive streams of
different types of data and so forth. And those keys and values and
et cetera, are all performed and mapped inside of the various different generic classes themselves
built by the developers. And so these are all the built-in ones, and here's how you can kind of
understand what generics are at a very high level. Let's assume that you wanna
create your own generic class. So the first thing you need
to do is define a class and what we're gonna do is
create a class called even list. And even list is going to give us, we're gonna take in a list,
so here we'll say val. We'll say list, and
this'll be a list of type T and then inside of here,
we're gonna do something or we're gonna return so function. And I we'll say items. And then what we're gonna
do is we're gonna return a list out of this, and it's
gonna be the same type too. So notice how I'm not providing
anything inside of here. So what this class is going to do for us, this is generic class. And I'm basically saying,
look, this class is gonna hold a bunch of things and
they're gonna be of type T. Even lists, it can be of type
T, you don't know what it is. The constructor is going
to take a list into it, and it's gonna be of type T. So basically if it's a list of strings, then it'll be even list of strings. And then what will
happen is I wanna return the even items. And so I'm just gonna go ahead and return those even items. And to do that, I'm gonna
say return list.filtered. Now, I need to put val on here
so I can get access to it. Val.filtered index. And then what I'm going
to do is parse into lambda and it's gonna give me
an index and a value. And then from here, what I can do is say, index modulates two equal
equals zero, which means, hey, if it's divisible by two, if it's a second item inside of there, then if it's divisible by two,
so two zero, two, four, six, eight, 10, and then go
ahead and return the items. So only return the even
items in this list. So I have not actually
specified that I'm working with strings or integers or anything. And here I'm using this type T. So how would I use this
class, this is a generic class that only works with even items. And perhaps for whatever reason,
I want this to be something I can use all over my application, because I am maybe
building some type of game where I only work with even ones. And you know, of course
I could build another one called odd. So to do this, I could say
val result equals even list, not event list, even list. And I could specify a string
here, I want it to be string. I need to parse in, I'm gonna
parse in my list of strings. Now look, the IDE says, hey,
we can remove the explicit type arguments because it's
going to be inferred because you're parsing
in the list of strings. And we already know that
this list of strings is this type right here is a string. So we already know that
you're a string over here. So you don't really need to
tell us that it's a string. We kind of already
figured that out for you, so you don't need to
provide that information. So then what I can do is
I have my list of strings. So now I have an event list of strings. And if I wanted to print
out the actual even ones, I could say, result.items, which is gonna call this function here. It's gonna call the items, function. And wait, maybe we even
wanna call this even, be a little more explicit
here, we'll call it even items, which makes more sense, result.even items. And if we print this out,
what we're gonna see here, and we're only using
the strings right now, let's see Donn and Mary. Now the power of generics really comes in that you can reuse it
with different types. So let's reuse this again,
the same val results, let's say other result 'cause
we need another variable and this one's gonna be event list. And this time we're gonna
take in a list of ints not event, even list. So we're gonna take a list of integers in, and we're gonna print this out, I'm gonna say result.even items. And when we print this one out, we're now working with integers. And I used the wrong result. There we go. And if we use the other result,
which is the proper one, we'll now see, we were able to reuse this class's exact functionality. I didn't have to write it twice,
I'm now relying on a type, this called a type parameter, the type key to do some work for us. And if we can look at this
value, look, if we go over here and actually do something
with a value actually, we can see the value over
here is actually type T. So I don't know what that type is. I could say the value is of type blah. Now I could do some, I could
do as value as a string. So if I parse in an any into this, then we could do some type
of stuff inside of here. Now that's gonna limit
me on what I can do, but depends on my implementation
of my generic class if that's what I wanna do. So here now I have an
even class that will work exactly with what these and I could be. Again, we could even go a step further and we can go class person
and we could easily do name string, and then we
can create a list of people. So let's just do that,
val people equals list of, and then let's do person. And now if I have these people
and I wanna print it out, we're just gonna do this. We're gonna single line this right now. So I'm just gonna do even list and then I'm gonna parse in my people. And then I'm gonna say, I
wanna get my even items. I'm just gonna print that out. And then what we're gonna see here is even with my own custom
class, I'm getting that. And I can make a little bit
nicer and just say data class, 'cause data classes will
give you a nice little two string representation
of your class already as we can see here. Now the two string is
represented that two strings is built for you by
default and data classes. So I can print out and everything here. So now I'm only receiving the even number. So I'm receiving Donn and Mary,
but Bob has index number one and Felicia's index number three. So we're just gonna
completely skip over that. Remember 'cause these
are zero-based index. So remember that this is
zero, this is one, this is two and this is three inside of array index 'cause we're zero-based. So this is why we're
getting it even index. So the other cool thing about this too, is like, let's say that
you don't really like the type T. I prefer to use the word T
because it means type to me and it's a type system. I could also just say this right here. I could rename this type
and say type like that. And now it could use the same thing. I could even call this something else. If I wanted to call this, we
wanted to call this tiger. I call it tiger. It doesn't matter what I call it. This is just a tight parameter
that I'm gonna be using. And if I look over here,
now this might get confusing because I would never use the word tiger because tiger has an implicit
meaning of an animal. So I see that this value is of type tiger, but that doesn't make any sense. So it's usually good to
keep these tight parameters to be a very simple name
that represents something. So it could be type. It could be sometimes people use action. If they're building something
with actions and so forth. However, it's really easy just
to use simple single letter types, because then when
you're looking inside of perhaps your code and
you're looking at the value of something, you can look over and say, Oh, that's gonna be of type T. Oh, okay, I don't know if that is, that's a type or I need
to think about this more. So that's how you can build
a very simple generic class in your very first generic
class inside of Kotlin. Let's assume that you have a person and inside of your application,
for whatever reason, you can't allow anyone to
be under the age of 18. Perhaps it's an application
for allowing people into particular restaurants
or music events. So you wanna make sure
that no one gets in, unless they're over 18. However, this is an exceptional case. So you wanna check to see if
the person is over age 18. If they're basically
under the age of 18 now, that should never happen. Perhaps these values are
coming back from an API or somewhere else. And at this point you're
like, you know what, if I get someone back
less than the age of 18, this application just
needs to basically blow up. There's something that should happen. And to do that, you can throw
what's known as an exception and you use the throw keyword for that. And so we can throw an exception. And so we have an
exception here in Kotlin. We provide an error message. User is not old enough. All right, now, at this point,
if we run this application, what we're going to see is that
this person Donnie who's 13. Little Donnie can't
come in because the user is not old enough. Now the exception is built into
the Kotlin standard library and all it really is the type
alias for Java Lang exception. And what that means is
this kind of just created a little shortcut for you,
like a little link and said, hey, if you type exception, and this is a type in Kotlin
what you're actually meaning is this actual Java language exception. There's a bunch of
different exception types inside of Kotlin and
a million more of them in various libraries. But the common ones are gonna see you as just regular exception,
where you provide a message, a runtime exception and a legal argument, maybe provide an illegal argument. and a legal argument, maybe
provide an illegal argument. So it got into a weird
state that it doesn't know how to handle and it needs to get out. So that's an illegal state
exception index out of bounds. You know, if you're working
with an array or a list, an index out of bounds
unsupported operation, you're doing something
that's not built yet. Perhaps this could be
very useful for a feature you're building out, but
the function isn't done, but you need to make sure
that you do something in the function so it compiles. You could just throw it on
supported operation exception. And then if you accidentally
hit that in testing or in code, it'll blow up. And of course there's
various other exceptions inside of here, but this is how you can throw
an exception inside of Kotlin. And it can be any other exception here. So if we wanted it to be a
illegal argument exception, we could say the user is not
old enough, user is too young, and then we could run that. And then of course the
output would then show that we then got an
illegal argument exception. You wanna use the correct exception for whatever you would like,
for whatever you're wanting to do and you can throw that
correct exception to that time. And if you're using any
type of code and production, that's looking for these exceptions, you can start filtering
on the types of exceptions that were thrown, and that's how you throw
an exception and Kotlin. In Kotlin, and in many other JVM languages and most programming languages, you're not limited to the
default set of exceptions. You can actually create your own set that you would need as well. So let's assume that
you didn't want to use the illegal argument exception, but you wanted to create your own. So you can create a class and
we'll call it illegal age, or it's called invalidate age exception. And usually you'll add
the suffix exception to the end of an exception. So, you know what that type means. If I just typed in valid age, I don't know if this is an exception, if it's an object or what is it for, but if I say exception at the end, it's very easy to read for what it is. Now, this is a class I can
provide anything I like. So maybe I wanna provide
the age, which is an int. And then I'll also wanna
provide the message, which is a string. And then I'm gonna go
ahead and pars, make this. I can either make this just an exception, or you know what, since
this has has something to do with an argument, I'm
gonna extend illegal argument exception and then I need to
parse in either no parameters or the message. And I'm gonna go ahead
and create a message that's going to be based
upon the other stuff. And I'll say in a valid
age and then I'll go ahead and render the age variable. And then I'm gonna go ahead and then also append the message here. So now instead of using an
illegal argument exception, I can say invalid age exception, throw new invalid age exception. And of course it can give me an error here because I don't have the age parsed in. So I'm gonna say P.age,
which is the person's age. And now if I run this
and what we're gonna see in the output is that an
illegal age exception is thrown. So illegal age exception
and valid age, 13. User is not old enough, user is too young. And so I'm parsing this message in, and this is which means
invalid age exception is basically a child of
illegal argument exception. And so now I've created my own exception. You can create as many
exceptions as you would like. These can help you inside
of your application when things are perhaps not working and your applications is crashing, you can see exactly what
type of exceptions they are. Perhaps a particular
part of your application is really difficult. So you may wanna create a
couple of different exceptions and you track those
inside of your application or inside of your crash tool, or maybe you're building
a library and that library has very particular needs. And so you need to make
sure that you returning certain exceptions from your application and giving exceptional situations. So that's how you can do it. You just have to go ahead and take class and extend exception or
you can just even extend just regular exception and so forth, and then you can start building your own. So it depends on which
one you want to extend. You need to make sure
that you can extend it. So there is an exception,
you can also extend runtime exception as well. So if you wanna make your
own runtime exceptions, you can do that as well. So that's how you can
create your own exceptions in Kotlin. Okay, let's assume we have some code here. We have a person object, and
this side, this person object has a user with the name
Donnie and age of 13 and a method called check age. And if the age is less than
18, it throws an exception. And maybe this, let's assume
that this is in a different library somewhere, so we
can't even control it. So this is beyond the scope of our things. We have to call into this
library to do something. And for whatever reason, this
library throws an exception, even though we may or
may not agree with that on the way that the code is working level, sometimes that's just the
reality of the situation. I've worked with many libraries
in which I can't control or don't agree with how
they handle something and they throw an exception. I don't want the application to blow up. So what I can do is I can wrap this in what's known as a try-catch. And try-catch is going
to give us a ability to catch the exception. So basically what's gonna happen
I wanna show you something. what's gonna happen is
check age is gonna happen. And then that's gonna throw an exception so we can see the exception again. It's gonna throw this exception, then what's gonna happen is
basically gonna short circuit right here. I could have a whole bunch code. Most like I probably will
have a whole bunch of code down here and none of this
code is gonna execute. Why, because this thing right
here is doing an exception and I need to do something
with that exception. And so here I can actually
catch that exception and I can actually get
rid of that if I want to. Catch the exception and then
I can do something with it. Now I can decide to do print ln, caught the exception, et cetera. Now, when I run this, now the application is not going to crash, I got a typo there. And what we see is caught the exception. Again, we'll fix the
type, we'll run it again. We see caught the exception, but we still didn't get
all these values run. Now this is beneficial
because if we don't have this, let's go ahead and comment this out. What can end up happening is
our application could run. And then for whatever
reason, it just blows up. And maybe we don't want it to blow up. Again, as a library or something, we get an exception and we
wanna do something different. So we were like, oh wow, okay,
this person's age is not 13. Okay, maybe then we kind
of do some other type of code handling, set some variables and do anything like that. So that's how we can catch an
exception inside of Kotlin. Now inside of here, if this were, if we're gonna do print line, exception was caught
or say caught for now. If for some reason this person was 23. What we're gonna see now
is the code is going to run and check age is gonna run, that's fine. And then all of these
statements are gonna run. And this block of code right
here is gonna be skipped. It's completely skipped. So what ends up happening is Kotlin says, all right, I'm gonna try to
do all the stuff in here. And then if anything bad happens, I'm gonna go ahead and
basically short circuit down into this little branch down here. Now that's great. If no errors happen,
then all the code in here will successfully execute and
then it'll continue down here. So we'll say for an ln after try catch, and this is known as a try-catch block. Up here, so we have a try catch. And so we'll say you done,
done, done, done, done, done after try catch. And so that's how you can
work and catch exceptions. You can also catch very
particular exceptions. So maybe if we can see if
this is an illegal argument exception, and what we
need to do is change this again to something that will fail. So we'll set the age to
13, so it throws throw. And then we'll notice
here is it all of a sudden our application is bombing, but we still have a try-catch here. What happened? Well, the reason is because
what we're at throwing down here is an exception, but
what we're checking for is a much more granular
level of acception. Here, we're checking for
illegal argument exception. We're checking, hey, the exception, we only wanna catch
this if it's an illegal argument exception, otherwise
just let it blow up. So this is very useful if
we're trying to make sure that inside, maybe we have
that custom exception. Again, we have class invalid
name or valid age exception. And it's gonna parse in a message. And what it is, is it's an
illegal argument exception, and we parse in the message there. So instead of throwing
this regular exception, we're gonna throw it this one here, and we can see what happens here. And so, oh, looks like
it caught the illegal argument exception. Well, why did it catch your
legal argument exception if it's an invalid age exception, because invalid age exception is an illegal argument exception. Now, if we were to change this to invalid, let's say invalid class exceptions, which is just a different
exception from the Java.io. It's not gonna work because
it's not gonna catch it. It's saying invalid age exception. So a lot of times you may want
to catch multiple exceptions at once, so you need to climb up the hierarchy of classes here. So the exception is some
classes or form of code, throwable, excuse me,
that indicates, et cetera, the things are throwable. So what you can do is just rely on just the default exception. So we can just delete this cause it'll use Kotlin's type alias. And we can see here that the
exceptions kind of all root up towards the top. And if we use just a high
level exception like this, then what we can do is actually
provide a when statement. When EX, I can say is
illegal argument exception, we can say print ln legal argument. When is, we might wanna say
something like invalid age, then we wanna print something,
print ln, invalid age, and then anything else we just wanna say, print ln else, something else. Now, if we run this,
let me see invalid age. So if I were to remove this, let me say index out of bounds exceptions. So index out of bounds exception
or using an abbreviation. And what we see is invalid
age is still being thrown. So I'm gonna change this to
just throw something else, throw in legal argument, exception inside of the check age method. And then we're gonna see
that caught something else. So something else was caught down here. Now, a lot of times, what you wanna do is you wanna go ahead and
re throw that exception because you are not sure
what's happening here. So you wanna just say, hey, you know what, here is the example. We have the exceptions come in. And when the exception is an
illegal age exception, do this. When it's an index out
of bounds exception, do something else. Otherwise I don't know what to
do with it, just throw this. Maybe someone further up
the stack will catch it, but these are the only two exceptions I know how to handle inside
of this try-catch block. 'Cause maybe this will throw
an illegal age exception and maybe some other call down here that we haven't specified
yet will issue an index out of bounds exception. And then at that point you
can then start catching and handling multiple exceptions inside of your Kotlin
program or rethrow it with the stack trace accordingly, and then you'll get the whole stack trace all the way outside the call stack. So if it's further up, you'll
get the call stack as well. And that's how you can catch
multiple different exceptions in Kotlin. Let's assume that you have some code here and you want to surround this with a try so you can make sure that it doesn't fail. You can also, instead of using a catch, you can use a finally here and
you can say for ln finally. And perhaps let's have
something that was supposed to execute after this. So it would say print ln after check age. So we should not see check age execute because if the user's
age or the person's age is less than 18, then we're gonna throw an illegal argument exception. But this is the show
you that in a try catch or a try finally, the finally
block will always execute. So even if an exception occurs
inside of this tribe lock, if an exception occurs anywhere in here, the finally block will always execute. So let's go ahead and execute
that now, let's run that. And what you're gonna see is that finally, which is right here, finally is printed, but the exception is still happening, which is inside of check age. And then basically the
program execution is stopping. How do we know that the
program execution is stopping? It's pretty simple, the print line which is called after a try
is not executing at all. So what ends up happening is
this block is executing and now we're seeing the finely run here and finally will be run
regardless what happens inside of try it. Now, this is very useful,
let's say if you've opened a variable and it's perhaps
reading a file stream or maybe an audio store, or
audio or something like that, that's gonna take a lot of resources that you need to close to make
sure there's no memory leak. Then maybe you would say, you would have some type of
variable called file stream. And then inside of here, you might say, hey, regardless of what happens, if I'm getting an exception or I don't, I wanna close down that stream
so I can kind of clean up after myself and not have memory leaks. So it's a very common place
to clean up for anything that you need that's resource intensive or could possibly provide
any type of memory leaks. You can put it inside of
the finally area there. So if anything were to happen. Now, you can also combine a try finally with a try catch. So I could say catch
exception with exception, and of course we can do
this, so we can stack them. And this works, this is a
very common pattern here. So again, you might wanna
use finally to clean up after something that
you've, again, maybe opened an audio source, video source, something that could leak memory, but you might wanna do something
inside of this exception and say, print ln handled, or maybe you're gonna
handle it a certain way. So instead of let's, it's going to catch the
illegal argument exception. So let's go ahead and do illegal... Let's do a legal state exception. And if we run this, what we're gonna see is we're still going to get
the finally that's called, but we had the illegal
argument exception was thrown. So what ends up happening
is check age called, the exception was thrown immediately. This finally block realized
an exception was thrown. So finally it's printed and
then finally the program says, hey, here's why we executed
with this exception. Now, if we were to change this catch block to catch that illegal argument exception, the whole program execution changes. So we're gonna see that
it's gonna execute this, but it's gonna throw an exception. So we're not going to
see this line of code. Remember an illegal argument
exception was called or thrown. The catch block sees that
says, yes, I can handle that. It then prints out handled,
which is printed out handled here. And then as always, the finally
block will always be run. So we may need to clean up after
some memory intensive stuff we'll then go ahead and execute
the code inside of finally. And then after that, the
program execution continues because the program didn't really crash, we handled it right here. And at that point it's
executing after the try. So you can perform a try and a catch. And a finally you can stack
them all on top of each other in Kotlin and that's how you do it. The difference between
try, catch and try finally can be illustrated in
a very simple example. Here, we have a try block
inside the check age method. We check the variable
for the class person. And if they are under the age of 18, we throw in the legal argument exception with the value of boom. And if we run this,
what we're going to see is a few things happen. We see the word finally
is printed to the output and then we see the reason
why the application exited, which is the illegal argument
exception which was thrown. So this is interesting
because the print line was not executed, which we expect because it was short-circuited based upon the illegal argument exception was thrown. Every time this code is called, finally is guaranteed to be called. So finally it's going to be
called and we print finally to the screen. And of course, because
the application exited, we are not going to
see after the try here. Now, how does this differ from a catch? Well, a catch allows you to
catch and do something with it. Finally just executes
at all point in time. So let's just go ahead and replace this, comment this out right now. And let's go ahead and
actually, I'm gonna go down here and we'll comment this out. And now we'll say catch EX exception. So catch allows us to catch EX exception. And we can print line
the exception message, actually we're gonna print
caught just to be more clear. And once we execute this,
we'll see the word caught and we are then after the try it. So what this allows us to
do is this whole program has now executed though, except
for this line of code here, because it was short-circuited
via the check age because of the illegal argument
exception that was thrown. So the catch, you can actually catch either a particular exception,
or you can say if it's not a particular exception you can... Or if it is a particular exception, it can also not get short-circuited and still blow up the application. However, anytime using a finally, which it also can be combined,
a finally is executed all the time. So regardless if we have an
exception that is caught, we have finally this
exception was not caught. So it terminated the
program, finally was printed. However, if this is an
illegal argument exception, we're going to see that the
program execution continues on. We didn't crash the application. However, finally was still called. So regardless of what
happens inside of the try, finally is always called
while it's not guaranteed to always be called with the catch, because we could be filtering
it based upon the type of the exception. Defining a type alias can be very useful in various different scenarios. It allows you to alias one particular type to another name type. So let's assume we have a user class here and has a username and
perhaps an auth token, and perhaps the account
class also has an auth token that is needed to request
the account details. And for whatever reason,
there is a order class, and it also requires
an auth token as well. And it's a string. Now, eventually you
start finding yourself, having the word auth token all
over your entire application and realize that that could be a problem. Well, one thing you can
do is actually type alias auth token into its own
type, or maybe you can create your own type. So you could do that too. You could say something like
this data class auth token. And what it is that just contains a token that the string. And then you could start
replacing this with auth token, but then inside of any of your
providers or anything else that's inside of your system,
that's you wanna treat it like a string, well now you
have to treat it like an object and it's kind of a pain, but you would just love to be
able to call it auth token, because it would just make much more sense when you're reasoning
about your application. So I'm gonna go ahead and
put this back as string. So I wanna go and get rid of that. What you can do is actually
perform a type alias and here's how you can do that. What you can do is usually
I prefer to type these at the top of a file or in some type of common extensions area
inside of my application. So I might have a file
called extensions.kt. And I'll say type alias, and then it says, I wanna type alias the word account token, excuse me, auth token to equal string. And what that means now is I
can use the word auth token anywhere. So this is also a string,
this is also an auth token. But now it's much easier
for me to reason about because now if I'm going
to use my user class, user.auth token, I could see
that it's an auth token type. And then it actually gives
me a hint here saying, hey, that is actually a
string behind the scenes. And so I can say auth token. And then again, I still
have all the same operations that I would have on a string
like length and so forth. But now it allows me to
have much more meaning directly inside of my application. So if I'm creating a new order, a new order is going
to take an auth token. So instead of it taking a
string, it's an auth token. And so I could do one thing. I could actually provide the auth token and say user.auth token, that'll work, or if I just have a string,
because auth tokens are string, I'll say, my auth token. And this will also work too
because underneath the hood, Kotlin knows that auth token is a string. However, just for
usability and type systems inside of your application,
it makes it a lot easier sometimes with various
different values to use type aliasing so you can
actually get the actual types that you like without
actually having to create a brand new one, because
maybe that auth token is just a string, but you would just like to have its own type and you
can do that with type aliases. And it's very easy to
do just set type alias, what you wanna call the type alias equals, whatever the type is. One of the coolest things
about Kotlin is the ability to add functions to existing classes already inside of the standard library or other existing libraries. Now, for example, what I mean
by that is it would be great if a string class, which
is what this name is. If we were to specified it's string, but it's already through implicit nature, we already know it's a string. It'd be great, if the string class had an initials method on it. And that would allow me to automatically just say name.initials. And for that name, it would automatically print the initials, so it would print DF. Well, we can do that in Kotlin
and here's how we can do it. All right, so I've saved
the time of actually typing the code for you and I'll
walk you through it here. Now, what I've done is
created a new function on this line here, and
this function itself, we start off by saying the
type that we want to extend, here is string and then we wanna say, here is the actual name of the
method that we wanna create. And then of course, it's going to go ahead and it's going to return a string, which is we see here, returning string. And then at that point in time, we're gonna go ahead and go
through all of this here. So here we have the values, we're just going to split
it on an empty string. And then we're gonna
grab the first initial using the sub string. I'll grab the first character off the first part of the array of
the first item in the array. And then we're gonna
grab the first character off the second item in the array. And then at that point, we're
just gonna use some string interpolation and return them together. So now if I run this, what we're gonna see is we're gonna see DF is printed. So now any string that I
have, so if I say val equals, I could even have the something
like book that I'm reading, which is called, I read this
book called "The daily stoic", say daily stoic, I could say
print ln book dot initials. And what we would get back is DS. So of course, DS is printed
before the other name and then printed. So if we move this up here
would make a little more sense. So now we see DF and then DS is printed. So anything that has a
string in it is going to be easily created. Now this is the string is not
a class that we can control. This is not, if we look at string, this is part of the
Kotlin standard library. That's part of Java, we
can't do anything about that, but we're actually able to
kind of slap on some additional methods for it using extension methods. So now what I usually
prefer to do in this case, is I like to have a file and
I'll call this extensions. And that's where I like to
put my files, my extensions. And if I have a lot of them,
I'll call string extensions. I'll have integer
extensions, but in this case, I'll start off very simply
with one file called extension that doesn't need to
be inside of a package. It can be a top level
function as we're seeing here. Now, at this point, I can call this from
anywhere that I would like. So back of my main file,
initials still works. So anywhere in my application now, I'm gonna be able to
use this dot initials. Now the same thing can happen
over here in extensions. Let's say I wanted to extend
the integer class is adult, and this could return a Boolean value and all this is going to do... And we can actually single line this. And we'll say this now
notice how he said this, because this is in regards to
what type we are extending. So the extension function for
string, we said this.split. Here we had this is for the integer. So this greater than or equal to 18. So is adult. So I can single line this
one, this one's easy. Again, this could also
be if I wanted to do it the other way, which you may
be more familiar with seeing to make more sense. We can do it this way here. It says convert to expression body. So there we go as adult
and I can even probably get rid of that. And so I can now really
make this succinct. At this point, we are
looking at, I don't know, 30 characters total from
start to finish as adult. What this allows us to do
is say, val age equals 35, and I say, print line, age.is adult. And you see how the code completion found them automatically. And we see that true came back. So now I was able to
extend the integer class. I've extended the string
class and then furthermore, I could also do this with,
let's say another model. So I'm gonna create another file here. Call this one models. Inside the models form I'm
gonna have a data class. Data class. Actually let's do a regular class. The class person is gonna have
a val name string, a val age, and that's gonna be int. And then what I can do
is I might have my person and I'll say, hey, today,
I'm gonna be 89 years old. So Donn person age 89. Now the cool thing about
this is if I had my full name in here, I could do something like this. I say person dot... Well from here, I could say, initials, person.name.initials. Now the problem that we're
going to get here is that the name Donn, if we run this here, we're gonna get an exception
because the initials is splitting on a string on a space. As we see here, this is
what's happening here. Splitting on a space. And so there's actually
only one item in the array. Therefore, it's going to be blowing up. So that's not gonna work right now. But if for some reason I had
my name listed as Donn Felker, like that, I could then issue that and I would've then get
back my correct initials. I would see DF again printed. So let's now go ahead and
change this person class though. And let's kind of show how we can extend. Let's pretend that this
class had a first name and then had a last name. This is a very common example
that you're gonna run into. This person could be a user,
it could be anything else. And it's usually going to be a class that you do not control. So we'll say Felker,
we've made it my last name and then we'll see again, and
I've got a little bit older since the last time we spoke, I'm 90 now. And so a lot of times,
if I would want to print someone's name, I'm gonna say, of course we're gonna
do string interpolation, cause it makes more sense,
I'll say person.FirstName, and then I'll do a space and
then I'll do person.lastName. And that works great. The problem is I find myself
repeating myself over and over and over my application and I'm doing this all over the place. But I can't come in and
modify the person class because it was in a library somewhere. So what I could do is I could
be in my extension class, I'm in here, and I say fun person. I remember that person and I
can say full name, full name. And I'm gonna go ahead and
just inline this one as well. And all this is gonna be as this.firstName and I'm actually going to
and string interpolate this. So we'll do the first name
space, this dot last name, close that out. And now, instead of doing this, I have extended another person. So I'm gonna say person.fullName. I don't even need the
quotes there actually. I can just get rid of all that. Now it's person.fullName. And if we run this,
we'll see the same thing Donn Felker comes here. So if we say, you know, John
Smith first and last name, John Smith. So we've actually, again, we're assuming that this
person classes in some other library somewhere, we
don't have control over it, but we do wanna use it. And we wanna make it easier to use. We can create an extension
function called full name. We can create any type of other function that we wanna throw on to those types such as we're building
on top of the built-in primitive types that are
inside of the type system, such as string and int or
even custom types that maybe we have control over or we
don't have control over. And we would like to provide
extension functions to them. A lot of times you might
find an extension function, very useful in a particular
domain of your application, maybe in a couple of modules. And you might put that
extension function over there, but that's how you can create
extension functions in Kotlin. Let's say that you have a string in Kotlin and you wanna print it out. But what happens if this string was from an expensive operation,
what would you do then? You perhaps might wanna use a
lazy keyword inside of Kotlin. And so this allows lazy
evaluation inside of Kotlin. So what this will do is this
doesn't look any different than what we had before. Now, if I run this, what
we're gonna see here is that we have Donn's printed. Now however, something
interesting happens here, print line inside of here,
and I'm gonna say computed. And then what I'm gonna do is
I'm gonna print this twice. And so what we're gonna see
is two different things. We should see Donn printed twice, but we should also see computed, but we're only going to see
computed as printed once yet the second Donn is here as well. So I printed both the names as done here, but we only see computed showing up once. And the reason why is
because the first time the call to this name has been called, Kotlin will then go ahead and
evaluate and execute the code that's inside of here. And this could be a very
long running operation. And then at that point
Kotlin will remember the computed value and
then we'll return it each subsequent time that it's called. So we'll not actually execute it again. So if I run this, we'll
see computed and then Don will be printed three times. So a good way to emulate this is actually, if we just did a sleep function. And we'll do sleep, I'm
gonna sleep for three seconds and sleep is just part
of the Java library. So three, not 3000, there we go. This is simulating a
long running operation. Maybe we had to go off
into a background session or we had to do some type
of really complicated number crunching and to return this value. And this value here is as Don. So the first time it's gonna happen, we're gonna see print computed, and then it's gonna wait 3000 milliseconds or three seconds. And then we're gonna see
Donn printed most likely three times really fast. So let's run this here down
here in the output window, you're gonna see computed
it's waiting and then boom, all three of them were computed. So what ended up happening was
the first time this executed, all this was being executed
on this first line. So the first time this code was executed, it was executed right here. Now each subsequent call, which happens, which ended up happening here and here, it got the remembered value. So as soon as it came
out, this came out, said, all right, well, the new
calculated value is X. And then what ends up
happening is each time the subsequent calls are
made that calculated value is remembered and it's
not executed anymore. So on the first execution
when you're using by lazy, it'll execute this code
and then drop it into here. So that's how you can use by lazy to remember computed values so things are a little bit faster and so you can have a
little bit more performance. And if you want the value
remembered inside of your application in the future. In Kotlin, you can have
lazy property valuation, and you can also just have a lazy block in which you can state some
type of expensive operation needs to be lazy evaluated. And all you have to really do is provide some type of block in here. And you can either write
your code inside of here, and you can put your code here, or maybe you need to call an
actual method as I'm doing here to perform some expensive operation. And so what I have here is I have a method that returns an integer. And when it's the first
time it's executed, or every time it's executed,
this method is gonna call and print out this word computed. And then it's gonna sleep for one second and then it's gonna return
a randomized integer or somewhat random because
that's just the nature of the random class, but it's gonna randomize
based upon the current time milliseconds and et cetera. So it's gonna give us a
different random number each time. So what we wanna see here is what happens when we print this out. So all we're gonna do
is have a lazy result, which is an integer. We're gonna print it and
then we actually wanna see, there's actually a property on this and it's called is initialized. And so we're gonna run
this and what we'll see is that the first one
when we print the result, it says lazy value not initialized yet. And I said, is this
initialized and it's false. And so we have a couple
of other options on here, result.initialized or get
value or just value itself. So it's print this line here,
so print line result.value. And then what we're gonna
see here is we're of course gonna see non initialize,
it's gonna be false. And is gonna say computed
and then it returned us a number here based
upon the randomization. Now to verify that the
lazy evaluation is working and lazily evaluating meaning,
it's going to remember its future values, let's
execute this a few times to see what happens. If for some reason, lazy
is not doing its job, then we should see computed
printed three times and we should see every time
1000 milliseconds sleeps, so about a second in between
each one of these values. So if we run this, what
we'll see is again, we'll see the top two values
printed computed, boom. And then all three of these came out almost the exact same time, very quickly. And what that means is the
first time that this value was computed inside of
this lazy evaluation, Kotlin then remembered it. So for each subsequent time,
we received the same value. And then of course, because
we weren't sleeping, the milliseconds would
have been different here, which means we would got a
most likely a different number in one of these values. So we can tell here with
a high level of certainty that Kotlin is returning
basically a cached value of that which been cached for this lazy operation. So if we know that we want
that same value over and over, and we don't wanna call a
particular method or function or class, because it's very expensive, more than once we can
wrap it in a lazy block and it will be only
executed the first time. And then at that point,
all future invocations of that operation will then
return the cached value, which is cache via the
Kotlin lazy operator, which we can see here
creates a new lazy instance of the specified initialization function. So that's how you can use
the lazy block in Kotlin to help speed up your application and be a little more performant. When creating files
instead of your project, you want to think about organization and where to put these files. Files are usually organized
into various different packages inside of your project
and the project explorer on the left-hand side. And the SRC directory, you
can create a new package by going to new package, and
then you can enter a package. So one common one that you see a lot used, instead of example
projects is com.example. As we create com.example, we
see we have kind of what looks like a new folder with a dot in it, and that signifies it as a package. And let's say we wanted
to create another package inside of the example. We could say, we wanna create another one. We could say com.example and we see here that the
directory already exists. And why does it say directory? We'll get to that in a second. Let's say we wanna
create a models package, we wanna create a models package. And so we said com.example.models. And so it's kind of been pre pended here. Now, as we see these
dots and it kind of looks like a backwards domain name
and that's kind of what it is. Now the interesting part
about how these files are stored in the disc
is if we look at them inside of our file
explorer and I'm on a Mac, so I'm using finder,
you'll see, we have models, an example com. And if we go backwards, we
basically gonna come up here to our Castro column, our SRC folder, which kind of maps to
this folder over here, we see our IO our com and
see how we have io.Caster package over here. So com example models,
each one of the items that are separated between the
separators, which are dots, periods is actually a new folder. So let's say for whatever reason, I wanted to create a file
inside of the models project. What I would do is they
file new and inside of here, I might wanna create a person, it's called a customer class. And it's customer, you
notice how it automatically adds the value package at the top. And this is the package
declaration that lets you know what package is file is in. So here, this is inside of the
package com.example.models. And I can have my class
and maybe it would have a regular name and a
string for a customer. And those are all kinds of common things we might have here. And then we might have another customer or we might have another
value inside of there. And now let's go, we're over
here to our other file here. Let's say we have something else. And let's say we have in
cast or we have something known as a video file
and that's a video class, we'll say class video and it has a URL. That's a string that's
associated with that URL. And then inside of there,
we have some other things. But maybe it also has some
customers that have watched it. So we might have a function,
say something like fun. And it would be customers who viewed this and perhaps this goes to a database. And this database is gonna
go return a list of customers of customers. Now we're gonna see is we
have a list of customers and we see right here IntelliJ has found based upon us typing and code completion that a customer class have been found. Now, if I hit tab, which I'm going to do, automatically, what happens
is something is imported. This import is basically
saying, look, we wanna use a customer from a different package. And we're gonna use this from the com.example.model's package. Now of course I could just
hear, I could just hit return just to get the compiler to be happy. I could return them to the list. So we're returning a customer. So what we are using it
inside of this video class, on the side, we're using
something from different package over here. Now we could also have
additional packages. For example, I wanna
show you another trick and go over here. And let's say, for example, in models, we happened to come to the
file system and type in here and we'll say util. Now, if we go back over
here, look what happens. All of a sudden IntelliJ
recognize we have another folder in there, hey, there's actually
some other stuff in here. And a lot of times people
will actually create things in here that are perhaps view,
we call it model extension or something like that. Will create some extensions files in here for anything that's going to
be like an extension function or anything like that, create extensions. And this where they'll path
all of their extensions, et cetera, or so forth and it looks like it was misnamed there. So we'll say extensions.kt not a big deal. So we have multiple different
packages inside of here. And so you can have multiple packages inside of your projects. So, here we have a couple of them. You may import a different
package from another library altogether. So if we're inside of our
models here and we wanted to import something else
from a different project, as long as we have imported
inside of whatever build tool we're using, we can go
ahead and reference that just as we had referenced
to inside of our video file over here. Now, if for whatever reason you don't, you would just create a
file and we don't put it inside of a package. So let's call this and say,
I'm gonna call it a favorite. We'll call it a favorite class. And if you're gonna favorite something, say class favorite,
and maybe it has an IDE that you did for whatever reason. This class is in this right
here, it's in the SRC directory. It does not have at all,
it does not have a package. So the default package, it has no name. So at this point, it's
part of the default package of the application and the default package of the application is nothing,
there's no name for it. So here, you're just kind
of sitting at the top level of your application. This is not recommended to put files here at the top level here. It's always good to
have a place to put them inside of your application. So where should you put them, maybe you don't have a good idea. A lot of times if you're just
working with a sample app and you want someplace to put them, I recommend just using
com.example or org.example. And that puts them in kind
of like an example namespace, just so it gives you a little
bit a level of organization, or maybe if you have your
own website, like I do, what I will do is I'll put
them in my own package. And so you can see, I
have a Caster one there, but I also have a Donn Felker one. So I'll say Donn Felker,
which is my website. And I might say something like models. If it's something has to do with models, it has to do with IO, it might
have to do with input output, if it has to do with, I'm
building some calculation stuff or math or anything like that, I might have it inside of there. Or if I'm gonna build some services, I have com.Donn Felker.services. And now I have some packages in here. And if you notice com now has realized, hey, well, we have a couple
of packages inside of here that also are from the same
root TLD, which is the com, let me create a file here. And inside of here, we'll have something like a customer service, maybe
it does something special for customers, or let's just
say it's a tweet service for whatever reason I'm
connecting to Twitter. So there, we got a tweet service. And so this is just to
illustrate that you can have multiple different packages inside of here and the tweet services inside
of com.Donn Felker services. Example over here we have the models, we have models in here and
packages give you a way to organize your code
into logical groupings. So a lot of times you'll see a lot of code that's related, maybe it's
with file L IO together, database code will be together. You'll see services together
and various different projects will have a different way. So it depends on the
project you're working on. Some will be kind of grouped
together by function, some will be grouped together by feature, kind of depends on what it is. But packages allow you
to group your various different pieces of your
application and your classes into different locations. And again, if it's not part
of a particular package and it's at the root level,
perhaps just right here in the source directory, it's
part of the default package, which has no name at all. And usually this isn't
recommended when you're developing a real production application. And that's how you work with
packages inside of Kotlin. Kotlin has type inference. Let's talk about exactly what that means. Now at a real high level,
what that really means is that Kotlin can infer a
particular type of variable. Now Kotlin is a strongly typed language. That means that each variable
has a particular type. The competitor's gonna check those types and we have a lot of competitor things that the competitor does that
makes our life a lot easier. And one of those things is type checking. However, in traditional
languages, such as Java, you have to inform the compiler, here's the type that I'm going to use. And then you can start using the variable. Kotlin has type inference. So here we have a variable
or value by the name of name. And it contains the value of Don and we have another value here, which contains the value of age. And this could also be
a var, it doesn't matter if it's a val or a var,
Kotlin one for both. And the old we can see
here just through the helps inside of IntelliJ is that
Kotlin has already inferred that the name variable here is a string. And the reason how it's
doing that is it's looking on the right hand side of
the equal sign and saying, well, you have omitted declare a variable and you want me to call it a name? Okay, and then you wanna
assign the value Don and I already know the
value Donn is a string. And someone gonna put a
string inside of the name. So that means that most
likely that name is a string. So I'm gonna infer the name of the string. And the same thing goes for over here, you will want the value
33 and 33 is integer, and you wanna shove it into the name, excuse me, into the age variable. Well, I'm gonna assume
that that age variable is actually gonna be an integer. And as we can see here from the help that compiler is actually
getting this right. And this does a few things, it allows us to not have
to declare the variable. Now we could, of course, just say, hey, this is gonna be a string, but we're actually kind of
just writing a bunch of code that we don't really need to write. So we don't really don't have to do this. Now that's really nice thing
about us instead of Kotlin, with variables, we can
infer them automatically. Now there is a caveat here. If you remember late
initializations who have a var, and we could say maybe your favorite food. You could say something like this. Well, I'm going to do this
later and again, late init means, hey, Kotlin, don't
worry about this yet. I'm going to go ahead and
populate the food variable. I'm just not gonna do it yet, I'm gonna do it somewhere
later down the line, but don't worry I got this covered. I'll handle it. So that's what we're telling
Kotlin at this point in time. However, Kotlin says, well, I can infer the type of the variable
if you give it to me during the initialization,
which is what we're doing here, we're allowing the initialization
of the variable to occur and therefore type
inference can take place. So names like, okay,
that's gonna be a strain 'cause you're basically
setting it equal to a string. In age is gonna be
equivalent to an integer because well, you're
setting it equal to 33. But late in it food, I don't
know that's gonna be yet because somewhere further
down the line here in our application, who
knows if it's inside of an if statement,
inside of a catch block, who knows where it's at. But you're gonna set it
somewhere and I don't know what it is yet, so you have to tell me. So in this instance, I actually
have to define the type for the compiler to be happy. So I cannot infer a type with
a late initialized variable. Well, this also works too if you have... For example, let's say we wanna
use a name that's reversed. And we have a method which we
have down here at the bottom. And this method right here,
we'll just take in a string and we reverse it. Now, of course we could do that by hand, but this is just to illustrate the concept that you can have a function
that also return something. And we'll take that,
we'll take in the name. And what will happen as
Kotlin will then go ahead and take a look at this function here and say, all right, well,
you wanna use name reverse. Okay, well I'm gonna go
look at this function name, reversed and then I'm gonna see, oh, a name reversed actually
turns a string, oh, okay. So in that case, I'm just gonna go ahead and make reversed a string. So again, kind of as real
quick, just kind of Kotlin says, I'm gonna go look at this function here. I see it as a string and then, okay, cool. It looks like I'm gonna go ahead and make this reverse value a string. So pretty simple. And what is doing behind the
scenes, so you can do that. Kotlin will also do some inferring too, So, let's say we have two
prices, so price equals 10. Now this is gonna be an
integer and I've priced two. And whoops, so this might be price two and this could be val as well. So 20.01. And then I say, all right,
print line price one. I'd say print line price two. If we look at what the
competitor is telling us here is it, well, price is an integer and price two is a double. And then what happens if
we initialize another one? And we say, this is a total number. The total is gonna be
the price plus price two. Now what's gonna happen here. We're combining two different types. Well, this is a double
and this is an integer. Well, Kotlin is like, well,
what am I gonna do here? Well, Kotlin is smart
enough to know the compiler is well, you're getting
an integer and a double. So most likely you want the
precision of this to be a double because otherwise you
would lose some precision if we just stuck with an integer, meaning that we would
end up with, you know, what would this be, it
would be 30 most likely. So at this point in time,
total is gonna end up being a double, which we can
see here from the IDE help. So Kotlin is then doing type inference based upon a calculation
of an integer and a double. Now in other languages,
you might have to do particular types of casting to make sure that you don't lose any precision, because if you didn't do the casting, you might have precision problems, meaning you're losing decimals, et cetera. But Kotlin is smart enough to figure some of these things out here. So this is basically an essence,
what type of inference is. And so you can get inference
when you are initializing variables and then different
types of returned, et cetera, are going to be inferred, such
as the function name is here. And Kotlin will both go
basically look throughout the execution path as a code set, all right, what is being returned here? Okay, it's being set into
this other value up here. Okay, that sounds good. So it looks like reversed is that point is now going to be a
string and we can go ahead and infer that for you. For some reason, though,
if we are using again, a late init and we're gonna
go ahead and use a var and have our favorite food, we will have to actually declare
the type when we declare it because Kotlin is not
smart enough to know, hey, I don't know what this is gonna be. And because it's a
strongly typed language, we can't just kind of let this
type just kind of be floating around and nowhere and
we don't know what it is. So you do have to declare the type here, if it's gonna be late initialized. And that's how type
inference works in Kotlin. Okay, let's talk about lambda functions. Now, lambda in Kotlin is
defined as the following. And these can look very
confusing the first few times you see them. So if we're gonna build
our own lambda function so you can understand what they are. And basically functions
are functions that are, I'm not gonna get really
mathematical about you and high order functions
and everything like that, though I really advise you
to go read the documentation. Lambda functions are
basically little functions that you can create and
start parsing around inside of your application and allows you to be very
functional in nature. So enough of that let's
get into implementation. So a lambda is defined as
having a name of a lambda, and then you have its input types, which are gonna be the
parameters that are parsed in, and then it's gonna have a return value. And then inside of that,
you're gonna actually have some arguments that you get
to provide the names for, and you have to provide their types again, that's gonna be the input type over here, so we can go and change
this to input types so it makes more sense and put type. And then you're gonna have
the body of your actual Lambda function which does its work. Now, a lot of type inference
will happen with this, which we can get into in a second, but let's go and create a first one. And the first one we're
gonna call is the end result is what we wanna be able
to do is say greeter. We wanna be able to do this, and I wanna be able to parse in my name. And what I want to happen
is I want hello Don to come out somehow. So how are we gonna do that? So this is a very rudimentary example of what you could normally
write a regular function for, but this illustrates the lambda functions. And so what we would say is it's a val, it's a greeter and we do colon
and then we need to provide the input types. So remember, let's look
at our example again, we have greeter and inside
of the greeter function, we wanna be able to parse in the word Don. And so here, what we need to do is say, hey, we need to parse in an input type. That input type is gonna be a string. And then what do we expect the
return type of greeter to be, remember what we wanted
it to be Donn Felker is what we want it to come out of it. So we expect a return type to be a string. Now we need to start defining
what our lambda expression looks like, a lambda function. And so in here, we actually
are gonna have the parameters. So our first parameter here,
which is parsing is Donn. That's a string. Let's go ahead and give it a
name so we can work with it. So let's just call it name. And of course we know
it's gonna be a string. So again, we have to
provide it right here. That's the one of the arguments
and provide its input type. And now we actually provide the code body. And so now this is the actual body of code in which we can do some stuff. So here, what we can say is, hello and then we can use string
interpolation and we'll say name. And what ends up happening
is the last value that's inside of this lambda expression of this block of code is the return type. So whatever this type is, is
going to be the return type. So if this was multiple lines, which it could be what we can
put these on different lines and we can have multiple lines, whatever is the last one here I could say, val foo equals zero, it
doesn't really do anything. And then we get this nice
little lambda notation here. It says, hey, this is the
return value of our lambda. So whatever this type is
gonna be the return value. So it's a string. So if I said, hey, I want
the length of the string, we're gonna get a compiler
error because it's gonna say we are requiring a string. Again, this is the return
type of the lambda, but you're actually giving us an integer because this is the last line on here. We're not seeing return
anywhere, but by default, the last value is the value returned. So since we can single line
this, we'll do that now, it's pretty easy. And we'll get everything back on one line. And then what we can do is we can go ahead and execute greeter and we'll
go ahead and parse name Donn. And so it looks like a regular function. And we can see here when we did that, we created the new greeter here. We could see that it's
going to take in a string. Let's go ahead and run it. And then what we're gonna
see is that nothing happens down here in the output window. We're expecting something to
show up here, but nothing did. And the reason why is
because greeter is a function that takes a string and return to string, it doesn't do anything with it. So we actually have to
actually do something with it. So let's go and print that return value, which we could just do this
too just so you're aware. We could actually say greeting
equals greeter like that. And then we could say print line greeting. And then when we run this, what we're gonna see is the output finally at that function since say, hello Donn. So again, to kind of cover this here, we have the name of the lambda,
which is coming right here. The name of the lambda, we'll
do a little simple line here. So it's the name of the lambda
then we have the input type. Then we have the return
type and then we're having the arguments and their
corresponding input, the corresponding type was a string. And then this little section
over here is the code body, which comes right here,
that's the code body. So that's how all that's
mapping right here. So now let's take this a
little bit further though. Let's say for some reason
we wanted to be able to say, like we said, we have Donn Felker here, but instead of saying, hello, Donn. So we got that wrong. So there was a bug in our code. How are we gonna fix this?. This actually should
say, hello, Donn Felker. Okay, well, that's easy enough. We could just say hello, you know, we just put Donn Felker in
there and that would work. But what if we wanted to bill
accept two different names because in our database,
everything is separated out. So we need to have this
greeter and it's built to two values. So again, we need to come
into our expression and say, all right, it's gonna take
an input type of string and another input type of string. So it's gonna have two. Think of this, like the when
you write a regular function like this or a function, my function. And then inside of this
function, you have some stuff and it's gonna be name,
string foo whatever it is, last name, string. Well, notice how this, the
way I like to think about it is these parentheses right here. it almost like maps
directly to that print. So you kind of map there that prints, they maps there and basically says, hey, look, there's a string
here and there's a string here. And that's how I of visualize
what these parentheses up here are. So this is gonna take
in some type of string is one parameter. The next type of in that next
parameter is gonna be string. And the whole lambda expression
is still just gonna return a string. Now we get a problem over here. It says, hey, we're
expecting two parameters of type string and string. Remember we were taking
a two now, but over here, this is the list of arguments. Remember, this is where the
list of arguments map in. And if there's more than one, then we need to put a comma here, just like we would traditionally
in a regular function. So I'm gonna rename this as first name, 'cause I can name the same thing I want. And then I'm gonna say a last name string. And now I'm starting to run
out of a little bit room here so I could shrink this down a little bit. But what I have over here,
I need to change this to first name and then last name. And I'll do string interpolation again. And now we have a lambda
expression that's gonna take in to values. So you see right here, it
says, hey, we're waiting for the second value P two,
which can be a second parameter. So I'll take a Donn Felker, there we go. And I'm gonna go ahead and run this again and what we're gonna
and what we're gonna see on the greeting now is
it Donn Felker's returned because we're parsing in a string here, Donn and on our string for Felker. These basically, if you take
a look at how this maps, this one is mapping to this one, and this one is mapping to this one. That's how they're mapping right there. And we can see that the
first strings we're calling that one first name, the second string, we're calling that last name. And then at that point
was since we're inside of this lambda expression, we
have access to those variables and we do whatever we want with them. Now, if for whatever reason
we wanted to get kind of do some hokey pokiness stuff,
some hokey stuff in here, we could, we could actually
do some different things. So let's go a val modified first F name. Actually first name is spelled out. And what we could just say
his first name.two upper case for whatever reason. And I'm gonna say val modified last name. We can say, last name,
not owner exception. Last name, last name that two
lowercase for whatever reason. And then we can go ahead and replace this with the two modified
ones, modified first name, modified last name. And now if we run this,
now, what we're gonna see is that we have a lambda
expression with multiple lines. Hello, Donn Felker. So we've capitalized and
done different things here. Okay, that's cool. So we see that the,
again, the last line here is the one that's returning the type here and that's the type that's
going to be basically this lambda is saying, all right, look, we have a return type of
string and that's mapping to right here. So string, which is right
here is mapping to this and the last line of
the lambda expression, whatever its type is, it
needs to match this up here. So it's go and get rid of that. So let's just say for whatever
reason we wanted to say greeter needs to return an integer value and it could be the number of characters inside of this greeting. I don't know, for whatever reason. We've changed this
integer, which means now that this lambda expression
needs to return an integer, but down here, the last line
is what's going to be returned in rest last line is returning a string. So this is not gonna work. So what we can do is we
can just go over here and may just return the
length of this name. If we run this, now
what's gonna be returned. It's still gonna take
in a first and last name and it's gonna concatenate hello on there, but then we're gonna grab the link and we're gonna print the link. That's what greeter's gonna
do at this point in time. Okay, that sounds cool, but maybe that's not what we really want. So let's take this back to string. Now let's assume that we
really like what it does, but we just want greeter
to actually just to display the greeting, so we don't
wanna have to do this. We don't wanna have to print this. We just want, as soon as the call greeter to automatically just print
line hello, Donn Felker. So how can we do that? Well, this return value is what
the is going to be returned from the lambda expression. So we can change it to unit. Unit is basically like
void in Java, which means, hey, we're not gonna return anything, we're just gonna do something, but we're not gonna return any value. So here we're just gonna
go ahead and return unit, which has nothing. And then what we can do here
is we can say print line, inside of here and we can just do that. And then what's gonna
happen now when we run this, what we're gonna see is we're
gonna call the first name, the last name that'd be parsed in and then we're just gonna print line. There it goes, hello, Donn Felker. So we can do all different kinds of stuff. And there's gonna be multiple lines, could be hundreds of lines of, you know. Of course I don't recommend
that if you wanna make your code easily read and so forth. Because Kotlin has type inference, you can actually clean
this up quite a bit. We don't need a lot of this stuff in here. So what we can tell Kotlin is
because Kotlin can look inside of this lambda expression and say, well, we know we have one parameter, we know we have another parameter. Okay, so this first parameter is a string. We know the second parameter is a string. Okay, we can do that, Kotlin's
figuring that out for us. And then is looking at
this last line down here and Kotlin saying, well,
this lambda expression is not returning anything. So it's actually gonna
be unit so it can infer the return type and it can
infer the parameter list. So we can actually do
due to type inferences, just go ahead and get rid of that. So now we have a very succinct
or much more sustained lambda expression. So if we wanna do the simple
one, so we wanna do it just as simple one,
greet, we could've done something like this. We could have said name,
string and then it gets to this print line, hello name. And there, we have our
very simple little greet. So of course we have our greeter that does a bunch of different stuff,
modify some variables, which we'll move up
here just so is obvious. And then we have the very simple
version right here, greet. If we print these, of course,
we got the spelling wrong. And if we print these,
what we're gonna see here is we have both of these
printing out values. Now lambda expressions are
not limited to one or two or three different types. You can parse in many different types into your lambda expressions. If for some reason, when you're writing your lambda expressions,
and for some reason Kotlin cannot do perform type inference, you will have to provide these hints. And sometimes you'll run
into weird situations where based upon something
that you're doing, you're going to have to tell
Kotlin, hey, by the way, here's how we're gonna go
ahead and do all this stuff. It's gonna be unit or
whatever and so forth. So you'll have to provide all information. And this is the blueprint up here. This is the blueprint that
you just need to remember, that you're gonna have lambda name, then you're provided input type, the return type equals
whatever this lambda is. And it's gonna have your arguments, which is gonna have one to many of them. You have the body of it, et cetera. And that's understanding
lambda expressions. Using lambda expressions in your code can be very beneficial,
especially if you decide to take lambda expressions
as a function parameter. So it's assumed that you have a function and you want to make something repeat, actually it's call it line logger. And you want something
to kind of log various different lines over and over and over. And so what you could do is
you could take a message. There is a string. And then perhaps if you wanted some logs to very basically kind of look like this, you want a bunch of these kind
of lines separating your logs just for maybe 'cause you
have really chatty logs, you could do something like this. You could say repeat five
times and then you parse it on little lambda expression. It's a print line and then let's go ahead and put in one, two, three,
four, four, five, six, seven, eight of these lines. And let's do that twice
and then what we'll do is do print line our message. Now this works pretty well. So anytime we wanna use
this, we can say line logger and to say hello there. And then if we run this
down in our output window, what we're gonna see down
here is we're gonna see that hello there is printed out. Maybe this is useful to kind
of help break up your logs for a visual aspect or whatever. But sometimes that can
be not what we wanna do and there's a way we can
actually make this much more in tune with being able to
do anything we really want inside of here. What if we wanna provide not one message, but we wanna provide two messages. So let's go message two, two string. Now we're gonna come in here. we're gonna say print line message two. We are in this again, we get
our message one, message two. It looks like we're missing
message two up here. Of course, we forgot to add
that to the parameter list. Hello again and when
we run this this time, we're gonna see hello again. Now the problem is as
we continue to grow this and our requirements increase,
what we're gonna run into is a situation where we're
just becoming overloaded here. And what we really wanna
do is be able to have a way for us to print this kind
of this block at the top and the block in the bottom, and then whatever we want
right here in the middle. And so yeah, we could
build our own strengths. We'll just leave it at one string. We could build our own string
up here and I could do this. I can kind of maybe kind of
do this like a little weird. I could do this thing up here
where I do like a new line thing and I kind of got there, but then it starts getting really hacky as soon as I wanna get anything
else kinda done in there. But there's a way we can solve
this with lambda expressions. And so what we can do
is what we're gonna do is I'm gonna call this block. And this is again, this is
the name of the variable, it's called block. And then what type of it's gonna be, it's gonna be a lambda expression. It's not gonna have any parameters and it's just gonna go return a unit. So again, remember, let's go ahead and take a brief review here. We have the input type,
which is right here. That's gonna be, there's
no nothing input type, so it's no parameters. And then the lambda has a return type. And what does that return
type, the return type is gonna be unit, so it's
not gonna do anything. And again, the name of
it is just called block. And the reason why we're
calling it block here is because it can be a block of code. And so this is a block and
so I can get rid of this. Now, what I can do is
just invoke this block since this is basically just a function. I'm saying, hey, this is
a function I wanna do. And so what I can do now is I
can get rid of this code here and I can actually just do this. Now, of course you wondering
where did the parentheses go? So I could just do exactly this. And then I need to put
my code block in there. But what you're gonna see
here is this little squiggly and say, hey, you can go ahead
and move the lambda argument out of the parentheses. It's just a convention that
is allowed inside of Kotlin. So now I can just say
anything I want here. I say print line message one. I can say two, I can say three. I could do something like
this, I could run this. I could even put my own
loop inside of here. I could say repeat five times
I could do my own lambda express, you know, 'cause
that repeat thing takes on its own land expression. Repeat as part of the Kotlin
standard library, by the way. And if we run that
again, what we'll see now is we'll see message one,
two, three, four, five, repeated right here in the middle, all separated by our top and
our bottom parts of our line. So what this has allowed
us to do is create a much more extendable function. So we now have a function
called line logger that I can pars in whatever I want. I can do 20 different logs and
hearing do 10 different logs. And it'll help me be able
to find perhaps my logs instead of a logger. That's a very naive and simple example, but it shows you the power
of basically delegating the responsibility back to the caller. So I'm yielding this call. So right here, I'm gonna be
yielding whatever's happening here to execute what's gonna happen here. So what's, let's think
about this for a second. First thing that happens
is we called line logger, line lager said, hey, no problem, cool. I have a block. And then the first line
of code executes and says, all right, well, I'm gonna
print this thing five times. And then now after that, what I have now is I have
this little block of code. And this little block of code
right here, I need to run. Then that little block of
code is this stuff right here. It's whatever's in between
these two brackets up here and that's gonna run that block of code. And then after that's
done, it's gonna go ahead and return back down and it's
gonna run the next three, next five iterations of this repeat loop. So it's basically going
inside of here, running this, hopping back out, doing something out here and then hopping back in
and finishing up down here. And so it's a very simple
way that you can actually implement a lambda expression
inside of a function. Now let's also create maybe
another lambda expression here. Let's call this one repeater. So repeater we'll wanna say, maybe you wanna do
something over and over. Kind of like very similar
to like a loop would have. So let's do the same thing. Let's create the same signature here, but this time I'm gonna
say repeat five times. And then what we're gonna
do is we're gonna call the block five times over. So then what we could do is we
could do something like this and we're gonna call, I'm
just gonna comment this out for now 'cause we don't wanna
see that inside of the output. So we'll say repeater, and
then it's gonna take it out in a block. And so inside of here, I can
just say, print ln, hello. And what's gonna happen
is I'm gonna run this, repeater is gonna run
and it's gonna print it five times in a row. But now of course, we're
trying to make our application a lot more user-friendly. So we wanna be able to
provide how many times we think these things should repeat. And so what we can do is
we can go ahead and say, all right, one or five in here. And so I'm saying number
five, but there's no way we can tell repeater
to do that, but we can. So we can go down here
and say, how many times do we want you to repeat,
and that's an integer value. And I can take the energy
value and just drop it right into this repeat function. Again, we're kind of duplicating
what's happening here, but I'm basically telling,
hey, I want our little repeater to run five times and then
what's gonna end up happening is it's gonna run five times. Now for whatever reason later on, I decided I want this
repeater to run three times or 13 times, I'll run this here, our little repeater
thing will run 13 times. Now, as I start thinking about
it, eventually I realized it would be really
great if there was a way that I can get to what iteration I was on, because I realized that when
I am gonna repeat five times, I wanna say hello three times. So hello, three times. And then I wanna say goodbye two times for whatever reason. So how would I do that,
so I wanna say print line, I'll say goodbye. But how am I gonna say that on the first couple of iterations, I
don't know what that is. So what I need to do is expose
that value to the block. So basically I needed to tell the block, hey, you need to be
able to get some value. Now, remember this value, this
right here is the input type. There's no input types here, we didn't provide any parameters. It's still gonna return a unit, so it's not gonna do anything. But what I wanted to change
is to say, hey, you know what, I want this block. So again, this chunk of
code, I want it to be able to accept a parameter and this parameter is gonna be an integer value. And now immediately, what
you'll notice right here, we have a squiggly error saying, hey, there's something wrong. And what that means is
that we need to parse a value in there. Now, little did you know
that the repeat function that is built into the standard library actually it gives us an index, we just were not using it before. And so I'm just gonna
go ahead and use it now. And so I'm gonna parse an
index and every time the repeat function fires off, it's
gonna give us an index. I'm gonna send that index
directly back to our block and that's gonna get
called inside of here. And so let's go ahead and use our index. There's our index and now
we can do something with it. So what we can do in here is we can say, if index is less than
three, go ahead and do this. And else we're gonna go
ahead and print this. So now we run this, what
we're gonna see here is hello, hello, hello, goodbye, goodbye. Because what's happening is
the index is being print. So it's go ahead and
print line on the index. For each iteration of this
loop, we can see down here. Each time, so remember
what we're calling repeat, which is built into the Kotlin
center library just tells, hey, repeat this little
function however many times we tell at the time, we told it five. And each time iterate is
gonna call this block. And this block, all this block is, is this chunk of code
here, this is the block. And then what it's gonna do is gonna parse its current index in here,
which is what we decided to do here. And now we can decide to
start iterating inside of here and perhaps perform some logic. And if it's less than three, of course, we're just gonna print hello. Otherwise we're gonna print and goodbye. Now this can be done for
any number of things, we could decide to use the index. We decided not to use
the index or whatever. And again, if we don't wanna
use it, we don't have to, we can just kind of get rid of it. Again we'd have to make sure
we're not using the variable, but here we are. So this is how you can go
ahead and parse a variable into a lambda function and
basically receive that variable back into the block itself. So we've created a
function called repeater. We're gonna tell it how many
times it needs to do something. And then we're saying, hey,
every time you do something, we want you to call a particular function, this function we're gonna call it block. It's gonna take in an integer, I don't care what the integer is, but it's gonna take an integer and it's not gonna return anything. And then inside of our function, we say, hey, we're gonna use the
built-in function called repeat. It's gonna repeat. And then every time
it's gonna give an index and we're gonna parse
that index into here. Now I could parse any number I want here. If I want it through the
whole time, I can just parse number three, it doesn't matter. It doesn't have to be the index. I could be three times
1000, it doesn't matter. All this block, all this
sick function signature is saying is this block is
expecting its first parameter to be an integer and it's
not going to return anything. That's all it's saying. So it doesn't matter if this
value right here is the index, or if it's a random number,
that's up for you to decide. Here, we're parsing in the
index because we wanna know when we're using a repeater
function, what index we are. And when we're repeating,
are we the fifth iteration, et cetera, or the 20th iteration, what are we doing at that point in time. Okay, so now we have that repeater. So let's go ahead and comment this out. So let's assume we wanted
to have to do something a little different. So let's say we have,
what's called a function called Derby announcer. This Derby announcer
function is gonna take in a lambda expression. This lambda expression
will look like this. There'll be a block of code,
it's gonna take in a string. So this first parameter
is gonna be a string and then it's actually
gonna return a string. So this lambda expression
is gonna return a string and this announcer is spelled wrong. And so what we'll do here
is let me fill this out. Okay, we're back soon
and have to see me tight. So the Derby announcer
function does a few things. It has a lambda expression, it's a block. That block takes in a
string as a parameter, and then it returns a string. And then what we're gonna
do here is this gonna be like a home run Derby announcer. And these are different names of perhaps some baseball players. So you McGuire, can say,
go Honeycutt, Davis, Dolly Weiss, et cetera. And then what we're gonna do
is we're gonna randomly choose a player's name and then to the screen, we're gonna put the next player's name. The next player is whatever
this random word is. No it's gonna be, you
know, we can change this to random player. At that point, this will
print to the screen. And then what we wanna
do is maybe we wanna have some type of log that we
wanna print to the screen, but we don't know what that's gonna be, or print some other type of
thing we could call it a log, could call it something
from the announcer, announcer topic, announcer
message, do that. But we don't know what that's gonna be 'cause it could vary between announcer. And so each announcer could
be a little bit different. And so what we wanna do
is allow the announcer to say what they wanna say. And so we're gonna delegate
that back up to the block. And remember we're gonna
parse into the random player that we chose, we're gonna
parse back up to them. But this block also
returns a string, remember. So if we look at this
block, it takes in a string right here, it takes in a string and then it returns a string. So if we were to implement this, what we could do is we
would say Derby announcer, and we're just gonna parse
him a lambda expression. Again, this is gonna take in a player is what's gonna be sent in. So it's gonna take a string. We could say, this is a string, or we could use type inference. And then we can go ahead and do something. And now we need to return
something out of here. And we can say something like
this, player is a great asset to the team. And this is the value that's
gonna be returned, remember 'cause it's the last value
in the lambda expression. It's what's going to be
returned inside of here. Now, if I did length, we're
gonna run into a problem. Why, because we're expecting a string, but we're giving it back an integer. So, okay, now we have this. So the Derby announcer
is gonna say something. The Derby announcer's
gonna say this player is a great asset to the team. So now what's gonna happen if we run this is it the Derby announcer. We don't know what player we're gonna get, but here we get the
next player is Consaco. Consaco is a great asset to the team. And again, the way this
happens is Derby announcer gets called, we have a list of players. We grab a random player off of that list, we then print something to the screen. We then take that random
player, we send it to the block, which is this player here. We then do something with it,
so this could be any string. So it depends on the Derby announcer, whoever's using the Derby
announcer lambda expression. It could be a number of things. I mean, it could be
going out to a database, could be going to a web service. It could be going to a
Megatron at a big ballpark to degenerate stuff. And then once that is happens, we go ahead and take that value and we send that value of whatever message we have,
we're gonna send it back through the lambda expression saying, here's what we wanna return. The lemon expression takes
that and then does something with it and here, it's just
gonna print it to the screen. So we're basically coming
in here, hopping back out, doing something, grabbing
the value outside of this external function
of this lambda expression inside of here, bringing it
back inside of this function and then doing something with it again. So we're allowing it to
like yield some control. This line right here is allowing
us to yield some control to some outside caller,
which is really powerful. And this enables you to do
some really powerful things. And it doesn't have to be a string, it could be any types of things. It doesn't have to return a string, it could be returning all
different kinds of things. And so this is how you
can use lambda expressions by parsing them as parameters, as arguments into other functions. You can create your own lambed
expressions to parse around. You can create your own
functions that require lambda expressions. And then you can delegate
behavior back to up to the caller. You may be developing an API
and you know that you wanted to get something from the end
user, but you don't know what, but you wanna give them
a little bit of data. Here I'm saying this is my announcer. Perhaps I'm creating some
code for a baseball field. And this announcer is gonna be, you know, get a random player for whatever reason. There is a home run Derby. And so we're gonna get the random player, who knows what they're
gonna say from there, but I need to get that information back because maybe I need to
take this information and give it to a teleprompter. Maybe it needs to show up on
a teleprompter, I don't know. So all different kinds
of things you can do, but you can delegate
this stuff pretty easily using lambda expressions. Let's assume that you're
creating a lorem ipsum generator, which is Latin text that's typically used in a lot of copywriting
when you're not sure what you wanna write and
you're designing some texts. And when you're doing this,
here's for example, the code, we have a bunch of Latin words here, and we want to randomly
get a bunch of words. And we have a function here
that basically says lorem ipsum takes in the number of times we want, number of basically words
or loops we wanna do and then basically it does a repeat. And what happens is we will do
something a number of times. So it's five times, it'll
repeat this five times giving us the index of each time. And then what we do is we look
at the list of Latin words to get a random word. And then we send that back to
whatever the block is here. In that case, it's going
to be this caller here and you're gonna get back to the index. So basically the current iteration. So if it's the first one,
it would be number one, if it's the fifth iteration, it'd be five. And then whatever the random word is. And at that point, you can
do whatever you wanna do with this random words
from this lorem ipsum text. So let's say you do that
and you run this now, and then you have this text here. And of course this is
gonna change each time because everything is kind of random. Now notice one thing here is
that we're not really using this index parameter. And so we do need the word parameter. So what we can actually do in this case is a couple of things to clean this up. First of all, due to
type inference in Kotlin, we can get rid of the types. So alrighty, Kotlin is gonna know. Sounds good, we already know
that the first type here is gonna be an integer because
that's declared right here. And the second type is
going to be a string because that's declared there. So we don't need you to
define the types for us, we can do that for us. Next by default, there
is a kind of standard that when a parameter is not used, but we do need to declare
it here for the purposes of the compiler, you can use a underscore. And that basically tells Kotlin, hey, we know that there's a variable here, but just go ahead and ignore that. And so if we run this
again, we can execute it and that would work just fine. Now, another interesting
thing is if for some reason we didn't wanna do anything with it here, say, we didn't wanna use word either. Now we also get this here. We can rename both of these to underscore, and that's kind of the default mechanism and the default nomenclature for variables inside of a lambda
expression that are not used. So basically this means unused. So anytime you see an underscore inside of a lambda expression that you're using or in code somewhere, it means
whatever this variable is, it's not being used, so
therefore it's been turned into an underscore. It's very common that a
Kotlin lambda expression only has one parameter as the
repeat function does here, which is built into the
Kotlin standard library as shown here. It has one value and that's the index that is currently iterating upon. So if we were to run this
right now and we told it to repeat 10 times, we would see that this is
iteration zero through nine. Again, this is zero base indexing. So we have 10 iterations here. So this is printed 10 times. Now, anytime you're working
with a lambda expression that has one parameter the
compiler is usually smart enough to figure out in almost all
of the instances, the types. So it can move the type
that's one can prove me here, but you can actually improve
this a little bit further by actually completely
removing the parameter all together and on a single
on a lambda expression with one parameter, by default
Kotlin we'll call it it. So it can infer the type
of the actual parameter. So if it's a single
parameter in this case, repeat only has a single
parameter, it'll show as it. Now the same thing happens
for many other things like the map, instead of
the collections of map, you're gonna have
filter, you're gonna have a bunch of other things of that nature. And it only has one parameter,
you can reference it with the word it and Kotlin will automatically map it to that. So now if we run this again, we'll get the same exact result, but we save herself a number
of characters when we're coding and over the course of a long
weeks and months of times, it's a lot of code and less cognitive load for you to read in the future. And you can just replace all
the extra code with the word it and make sure you include the dollar sign if you're using anything in
regards to string interpolation. Otherwise, if you didn't
wanna use string interpolation and you were going to
use something like this, you could then go and
say it, plus maybe one, let's say you wanted to do that. And you're gonna print that line. So it again is just a variable. And now we get one through
10 because we added one to the index. And that's how you can use the it keyword in Kotlin in lambda expressions. Let's talk about how you
can call Kotlin classes from within Java. So we'll talk about
the JVM interrupt here. So let's assume we have
a couple of files here. One on the left-hand side,
we have a customer model. All it is a Kotlin data class
that contains the customer and it has a function in it. And basically the function
checks if the customer has a long, we have maybe
some type of service. So we have a couple of packages here. And inside of this application,
we have some Kotlin, some Java for whatever reason. It has to be Java, or it's
just maybe a very large file that we haven't converted to Kotlin yet. And you need to call into some Kotlin, maybe that was written. Now you can actually do that. So here we're in a Java file
and let's say that we need to interact with the customer. And so let's have a method
called process customer. So we'll say public void process customer. And then what we need
to do is we need to work with the customer, so we need
to take a parameter here. So we're gonna say customer
and there is customer, so we'll do it just like Java
has it open, closed brackets. We need to import this. Now again, this is a Kotlin class. So if we look, go through the definition, we're gonna see that now
we're back in Kotlin. And that's it, now we are able
to work with this customer. If we wanna do something
with the customer, we can say customer dot, and
then we have all of the methods that are available to us. So customer has long name,
that's a Boolean value. That's over here, that's
this method right here. If we want to get the customer's name, we don't have get name,
now this is interesting. So get name, actually goes to name, but we never defined a get name method. Now Kotlin takes care of that
forest underneath the hood basically creates a get
name and set name for us. But if we look there is no set name, and the reason why is
because this is a val. So this is a read only. So we cannot reassign this
value, it's a read only type. If for whatever reason,
this needed to be writable, needed to be mutated, we
could then change this if we had access to the Kotlin code. And then at that point, Kotlin
will give us the set name and we can parse in the value,
whatever the new name is. Now, at this point, you can
call into Kotlin very easily. You can say customer.customer
has long name, and you can do anything that
you would really like to do at this point in time. The only thing you really
have to really worry about with these classes is the different types. And you can know that
the getters and setters are generated for you automatically for through Kotlin
because inside of Kotlin, we don't need to actually
generate the getters and setters because we just will access it directly off of the name properties. We can just set the
name equal to something. And that's how you can call
very simple objects in Kotlin. Let's assume that we have this Java class and we need to call it from Kotlin. Well, that's pretty easy to do. This class has a couple of options. One is we can get a customer
from a social profile. So this would assume
that this was going out to some social media API, and
it would get the customer, and then returned customer back. Maybe we would look it up by
their social media handle. And this method basically
returns a list of process customers, which doing some
processing in the background. The implementation is not important, but what is, is the types
and how it's being called. And then we have a Kotlin
file called customer facade, following a very common facade pattern where maybe it's interacting with a bunch of different services to perform some work on some customer objects. And lastly, of course, we
have the customer object, which just has customers name
and similar methods in here, which are negligible at this point. So inside of customer facade, let's assume that we need to call into
the customer service, which is Java. How would we do that? Well, let's assume we wanted to parse it via the constructor. So what we can say, this
is customer service, and now we are working directly
with the customer service and we can say something like this. We wanna get the customer, customer. And then we can say equals
customer service.customer from social, and then maybe
we'll parse in user name. This is an example username. Here we are now calling
into Java from Kotlin, but there's something
interesting to note here, is if you look at this right here, you'll see the customer has an
exclamation point at the end. And what that means is
that it is a platform type. And this means right
here, that T could be null or it could not be null. So we're not sure, you
have to decide for yourself because you're calling into Kotlin. So there's a couple of
things you could do here. Now, of course, if I were to just type inside of my application, I
would just type in customer.name I could do this, I could say
print line customer, not name. And that should work. As long as the customer is not know about. However, there is a chance
that this customer object could be null. And if it does come back
null, I am going to get a null pointer exception. So couple of ways to handle
this inside of our Kotlin code, we could of course, just go ahead and use the double bang operator that
will force it to not be null. However, that is a little
bit of a code smell. We could use require not
null, that will work as well, but again, this will
crash the application. Sometimes that's the only thing you can do when you're calling into a library you don't have control over. Then at this point you're
saying, well, I know the customer is not gonna be null
because I'm requiring it not to be null. you could write this in a try-catch. If you have access though,
to the actual Java code, you can go to this
implementation and you can slap on an annotation. You can slap on the not null annotation, and you could re various
different ones from the JSR to I'm using JetBrains version here, there's one for Android as well. And what this will do is tell Kotlin that, hey, trust us, we know what we're doing. If we return from this,
it's not gonna be null. So now if you notice the
exclamation point went away. Kotlin saying, well, you are
telling me via this annotation that whoever calls into
customer from social will get back a customer object and that customer object will not be null. So we're gonna go ahead and trust you and then we can go ahead
and execute accordingly. And at that point in time,
I can do whatever I want. Now, this also can be a
little bit further here. Let's take a look at the other method. The other method is processed customers. So process customers here,
let's say val customers, you can say a customer
service.processed customers. Maybe you're processing them
to update account details or whatever. Now notice the same thing. Again, a platform type
is being returned here. We have the exclamation
points that are determined. Now we have two of them. We have a list and we have the customers. Now what this is saying is
like, hey, caller, Kotlin folks, we're not sure if this
is a read only list, we're not sure if this is a mutable list. We're not sure if this
is null, we're not sure if this is nullable, we
don't really know yet. Because you're calling
into a platform type, we're not sure what it is. And so you can start slapping
on additional annotations on here, but for example, you can also put a not null annotation here. That is not null annotation
and again, we'll remove that saying, hey, at this point,
this is a list that's not gonna be null. So therefore, if we do perform
some operations on the list, which we get at this point, which is nice, we can actually map over this list, which is interesting because
we're calling into Kotlin, we're getting back in a array list and then, and then array
list, we're getting it back inside of Kotlin. And now we can start
performing using our Kotlin standard library collection utilities. Now this makes life a lot easier. I can perform filtering I can perform all different types of things
directly inside of Kotlin after I've called into Java. So a bunch of different stuff you can do. One thing you just wanna be
aware of is when you are calling into these various platform
types that you check for nullables and make
sure that you don't have no pointer exceptions. Let's assume that we have
this extension function inside of a string extensions.kt file. And this extension function
extends this string class and allows us to grab the initials. So if we were to have something like this, have a string that says Donn Felker, the output would then equal DF. It would just grab those initials. So it's not a very robust function. Actually it could be quite error prone, but for simplicity sake,
this is what it does. If you parse in a string
with that separated by space, you'll get back the initials and let's just assume it the name. Now, if you wanna call this from Kotlin, of course, we're very
easy, we can do that. So with let's assume
we have a customer here and the customer has a name property. So again, if we go look
at what a customer is, it's we have a customer class. So it's a very simple data class. Now, if we have a name, a name as a string as we can see here, and we
can just get the initials off of the name, so that's
pretty easy in Kotlin. So now we have the initials,
we can do whatever we want and make sense. However, there are times
when you would like to call these extinction
functions from a Java file. The question is, can you do it? So let's assume that
we have this Java file that processes customers,
and in this method, customer from social. Assume, we are retrieving
a customer profile from a social API, and here
we have the customer object. Now we wanna use user's name. So of course we have get named,
which is generated for us. Now if we were to type
initials, we're gonna see that it doesn't exist
because extension functions don't exist in Java, but we can still use that
code that we'd wrote in Kotlin as an extension function. And previously, if you've
worked inside of Java, you've probably written
a lot of helper functions and they've been inside of
basically static classes. And so what we'll go ahead and
do is the string extensions file actually generates a file for you. So this is a top of a function
it's not inside of a class at all, but it does
generate a file for you. And so if we type string extensions, you'll see string extensions
and a kt at the end. String of tensions kt and
then there's a static method called initials, you parse in a string, and now you can get your initials. So you can say string initials equals. And this will call into
if I go to the definition, calls into this extension function. So this is how you can call
into an extension function inside of Kotlin. So any extinction function you have, you'll wanna look at the actual file name. Now there is a way to modify
the file name and using the JVM name annotation. And I'll show you that in another video, but if you wanna call into
any of your extensions. So if this was just called extensions.kt, then in that case, it would
just be extensions kt. So basically slaps the
two letter kt at the end. And then at that point in time, you can access it just as if
you did with a regular helper function that you may
have traditionally built inside of Java. And that's how you call an
extension can function from Java. Calling an extinction
function from Java works, but let's be honest, it looks
a little bit clunky here. There's this weird kt thing
that we have at the end, which we didn't specify there. Inside of the string extensions file, it just looks like this. We never specified a kt in here. So having kt riddled
throughout your code base can be kind of a weird feeling
and kind of a code smell. Thankfully, the folks
over who developed Kotlin thought of this. And what you can do is
actually change the name of the actual file is generated. And what you can do is add
this annotation called file, JVM name and provide the name of the file. So here, I would wanna
say string extensions, or I could say string utils,
so say a tree extensions. Let's save that and we'll go
back to where I'm calling it from Java. And then immediately we
noticed that our previous call in here is not working anymore. So if we were just
typed string extensions, now we just see string
extensions.initials. So I can go ahead and fix
this by just ruin the kt. Now I don't even really know
that I'm using Kotlin at all. It's not basically polluting my code base inside of my Java files. Now there may be various
reasons why you can't move this to Kotlin, and if that's the case, this might be a very good use to you. Or if you're exposing
your extension functions to outside callers in other JVM languages, you may wanna go ahead
and provide a JVM name. So it's nice and extensible and readable and doesn't pollute their code as well. Now you can name this, anything you want. It doesn't have to be
whatever the file name is. If I want this to be happy
clown file, happy clown stuff. I'm gonna go over to customer
service and we're gonna see that this doesn't work. I'm gonna have to replace
this with happy clown stuff. And so I can give that JVM
file name whatever I want, that's what that's gonna be. And so that wouldn't work anymore. So of course we would never
wanna name something like that, but we can go and take it
back to string extensions. We'll save that, again
that's not gonna work the string extensions. So anytime you are working with something like the Kotlin file that has
a lot of top level functions, which this is a top level function that's not inside of a class. It's just basically sitting
inside of a file somewhere at the top level function. And you need to expose those
to other JVM languages, such as Java, and you want it
to have a nice pretty name. Go ahead and slap the JVM
name annotation on there at the top of the file, and
it will change the name. So therefore when you call
it from another language, it'll look nice and
won't pollute the code. Quick little hack I wanna show you. Instead of any file, you
can put a top level function called main and inside
of this main function anywhere you want, you can print line or do whatever you want. And what you can do is type code here. Now I can actually run this
directly here inside of the IDE and it will execute this code. Now I do have another main file down here, which is where my main
application would normally start. However, if I'm buried
somewhere in my application and I would like to test out a quick idea, maybe I'm kind of working with
the red I wanna play with. I can just run this directly
right here and I can hit run. I can do debugging everything. And if I hit run down in the
output window, we'll see foo. So this can be anywhere,
I can take this here. I can go over to a customer class. I can put a function over here and I can do something here and I can run something from here. So anywhere you put a main
on top of main function, you can execute it with inside of the IDE for quick feedback loop
inside of IntelliJ. Sometimes you wanna
interact with your code and a session where you
can kind of play with it. One way you can do that
is with the Kotlin repl, which is a real evil print loop. And to do that again, that's
through tools, Kotlin, Kotlin repl, and once
everything is loaded, then you can start typing code into here. So for example, let's assume
that we wanted to go ahead and build a string. So we say val name equals Donn. And then I use command enter
to enter into a new line. I can say name.length, command line. It'll tell me if they integer
it's four characters long. I can do other things such
as name that substring and I'll get the nice IntelliSense in here or code completion. And we go with the first
character, there is D. I can also use, for example,
some of the classes I have. I have a customer class
here, which is a data class. I can use that inside of my
repl, 'cause it was loaded, so I can say customer. So I can go down tab, and what will happen is you'll see it actually
imported customer there for me. And so I'm gonna say val
customer equals customer Donn enter. And so had to import it
because if I don't import it, I won't know about it inside of the repl. So I can say customer and if
I hit enter on a customer, it'll tell me the type of the customer and then what the two string value is. And so I can say customer.name. I can also do customer.name.initials. Now notice this I'm actually
calling in a extension function that I have, it's called initials and we we have an exception there. And the reason why we got an exception is because the string value of customer, it does not have a space in it. So if I were to redefine my customer, so we'd do another customer, customer two equals Howard Parr. I had my full name with a space then the initials function would work. And I didn't tell it to execute yet, I just pressed the enter, and I say, initial
customer two.name.initials. Now, if I enter here, we'll see that it does execute properly. So you can actually interact
and do all different types of things inside of the Kotlin repl. And again, that's access
via the tools, Kotlin. Kotlin repl. So it's a good place to
come around and play around with some code that you're maybe testing, trying to figure out if
it's going to work or not. Maybe you're trying to
understand a certain interaction with the library and you don't
wanna fire up an application. The repl is a great place
where you can do that. And when you're done, you
can go ahead and over here and hit the close button and
then you're out of the repl. So let's go ahead and
create a actual Kotlin simple calculator. To do that, you open IntelliJ, you'll click create new project. You're gonna wanna select
Kotlin from the left-hand side, then JVM idea. You wanna give it a name, I
want to say simple calculator. You can you leave these
default settings enabled and hit finish. This is gonna go ahead and
create a new project for us. On the left-hand side if the
project window is not open, go ahead and click the
project window to open it, the project explorer. And instead of SRC, we're
just gonna create a new file. So you can right click or command in. So right click new cotton file class. I wanna call it main.kt. Now it's an empty file here. Now at this point in time,
we needed to go ahead and fill this out so we can say fun main. That's gonna be the main
function, open and close. And now we have a main function. Now typically a main function will have some type of
parameters it can be parsed to it from a command line. So that's gonna be var Argh, Argh string, which means that there
could be many variable amount of arguments that are parsed in. It's been basically great out 'cause they're not used right now. So none at this point in time, we're ready to start
building our application. And the first thing we can
do is provide some type of basic output and we'll
say, please enter and arith. Please enter an arithmetic problem. And if we save this and then
we click this little run button now and hit run main kt, will
know this is going to build down here, and then down at the bottom, the run window will open and it'll say, please enter an arithmetic problem. At this point in time,
you've actually created your first Kotlin program,
though all it does really is write some text to a console and exit, and we can see that
the program has exited. So basically the execution of program started the main function. It printed this, had nothing
left to do and it exited. So that is the beginning
of our application. So we've built the first
version of our application, but we need to receive
some input from the user. And to do that, we can
use the read line function that's built into kotlin.io. And his is built into the platform. And this allows us to read
some input from the user on the command line. And then what we can do is say print line, let's say you entered the
value that you entered using string interpolation. And if we go ahead and run
this now in the output window, down below, what you're
going to see down here is that please enter
your arithmetic problem. Let's see one plus two and
then we'll see over here that you entered one plus two, and then of course the application exited at that point in time. So this works great, we
can now get some values from the user. But just like a regular calculator, a calculator is not going
to stop after you enter one problem, so we need to
enter multiple problems. And this is a perfect
opportunity to use a wile loop. So what we're gonna say is,
while while is not null, then we can go ahead and grab some values. And so it we'll say here is we
need to get the input again, and then let's go ahead
and print this here. So we did val, we need
to make this a var now, 'cause we're gonna be
resetting it every time. Now let's go ahead and
print line what we know you entered input. Now we're gonna run into a problem here because we haven't told a way
for the while line to exit. And if we know anything
about the read line, what read line says is
it will return a read or null if the input stream
is redirected to the file. We're not redirecting to a file, so we're not going to get null back. So if I press enter, we're
probably gonna get a line feed inside of here. So what we need to do is
have a way to short circuit out of the wild loop. Otherwise this chunk of code
will just continually run on forever, there's no way
for us to get out of it. So if we were to run this
now, let's go and see what that looks like. Let's run it and that's enter a problem. One plus two, okay, two plus
three, five divided by 10. If I enter, we can't get
out of this, we're stuck. So we need to wait it to
short suit circuit out. So I'm gonna stop the program. So we do need to actually figure out a way to get out of this. So what we can do is we can check to see if the input is null. So if the input does not equal to null, that's what we're doing,
but let's also check to see if it's empty. So, and the input is not blank. So if we go look at the
implementation of that, returns true if it's not empty
and contains some characters except the white space characters. So we wanna make sure it's not blank it just contain something. So if that's the case,
then this piece of code will just continue executing. And this will be the main in a moment, this would be the main
section of our calculator app. So let's go ahead and run this again to see what this looks like. And once we run this, we'll
see, okay, one plus two, there we go, we got two plus three, we got five divided by 10, so we can keep entering
things over and over. However, now if I press
enter, what we're gonna see is that the process finished right there. So it exited out of the application, because what it made it exit
out is basically it is blank. So therefore it only
continues if it's not blank. Therefore the application
said, well, it's blank. And at that point it exited. So now you can do a couple of things here. So if you wanted to say goodbye, you could pretty much do that right here. You could say print ln goodbye. And then you would know that
the proper program exited. So we could do this very easily. You'd hit run here and now we can enter that arithmetic problems,
one plus two, three, 33 plus two and an enter
and I was like, goodbye. And then it exits. So we know that the
actual program is exiting. Of course, we do know
that already from here, but when we compile it later, it would be nice to have
some user feedback to say, hey, we're done with the program. So now we can actually
create our calculator inside of this piece here, because we can now process
many different problems. One after another. Each time we're gonna get some new input via this read line method,
which then will change. And then we can process it, which we'll end up doing right in here. Now that we have the user input, we need to decide what to
do with that user input. So when you kind of need to break it apart into a couple of sections. If we have a problem
that's entered like this, we need three different components. We need the first number when the operator and we need the second number. So we're gonna need three
different types of variables from that one string. And we can do that very easily, so it's go and get rid of this. We can do it very easily by
declaring another variable called values. And what we're going to
do is we're gonna split the string on a space. And what that will do is it'll
give us a list of strings that we can go ahead and check on. So let's go ahead and take a look at each one of these strings. So we'll say values zero. I'm gonna duplicate this line
a few times, one and two. Now of course, this is
not gonna be error proof. If for some reason we do not
get enough values in here, we could crash the program, which we'll see here in a second. If we run the main program,
what we'll see here, please enter arithmetic
problem one plus two. We'll then see that we
have all of the operators now separated, so we have
the number one, number two. If we were to do five plus four, we would get those broken apart. But if I only put five in here, we're gonna get an index
out of bounds exception right here on the 10th line, because I'm just pressing
press five in there is split on a string, so it's
not finding this and saying, hey, there is nothing inside of here. It looks like by found a
string or or a new line or something there. So we can't find anything in that regard. So we're gonna go ahead
and don't acception. So this isn't really
foolproof, but it does give us the ability to actually start
performing our operation. Now, the one that's really
important to us right now is actually this first one. And this is because this is
what's gonna give us our plus, our minus our multiplication
or our divide operator. And then at that point, we can decide how we wanna
progress in our application. And so, based upon if
this value is a plus sign, well, then at that point, I
wanna do add something together. And so it's gonna say we're
actually doing print line. There we go. If the value is a plus
sign, then we're gonna go on and go ahead and add it together. Otherwise, you know, we
can say else if values is values one equals equals minus, then we can do something else. So we could continue this
path down of else if. And this will work, there's
nothing wrong with this. We could do this, this'll work. However, it does get a little clunky. This is a perfect opportunity for us to use a when statement. So I'm gonna say when values
one, so check that value one. And when you say is plus
I wanna do something, so, say print line, when is. And it is not gonna be needed here because we're actually
just using it to directly against the value. So we'll say, plus, we can say minus that's it print line, subtract. I'll go ahead and
duplicate these real quick. And then we can go ahead
and of course we'll say multiply and divide. It'd be pretty easy and divide. And then for whatever reason, if we don't have something that matches, we might wanna say, throw
legal argument exception. Let me say invalid operator
and we'll just go ahead and parse in whatever that value is. So parse it in and values one. And then that's gonna allow us to go and get rid of this down here. This will allow us to actually
see and do to something different based upon each
one of these input types. So let's go ahead and run this program. We'll put a few of the
arithmetic problems in here. So one plus one, so it
looks like we have add. So I have one minus four, subtract. We have six times seven, multiply, 42 divided by 10 is divide. And let's go ahead and just
throw something crazy in there. Let's do like for example,
maybe we wanna do powers of, but we haven't implemented it. We could do 10 to the power of five. Boom, we have an exception
invalid operator. And we add D to the power of operator, which is normally used in
map instead of programs. So here, our calculator
is a simple calculator. It's only gonna process these
different types of inputs. So now let's go ahead and
implement each one of these and I'll be right back while I do that. So you don't have to see me type. Okay, now we're back. So I've done this very easily,
I've taken the first value. I convert it to a double
and take the second value and I converted to a double. I've converted everything to doubles here so we don't lose any precision. Now there's one thing I would like to do, that would like to clean
this up a little bit. So what I'm gonna do here
is let me go ahead and say, I'm gonna refactor this,
and I'm just gonna say, extract this into a variable. And it's gonna ask me to
replace two occurrences because it's occurring two places. Sure and we call this operator, just so it reads a little bit better. It's actually saying we can
move it into the declaration of the when. And if we were to do that,
here's what it would look like. I could just move that
into the declaration there. I don't like the way that
looks, it looks a little bit, it's too much for me to read. This is a lot easier for me to read, so I'm gonna leave it like that. So I'm the operator. And then what I could also
say is something like this. I could go ahead and extract
this good refractor it. I can extract this into a variable and it does occur four times. So we were repeating ourselves,
which could be problematic for updating our code later. So I'm gonna call this
LHS for left-hand side. I'm gonna do the same thing over here. I'm gonna go out and extract a variable. We're gonna call right-hand side RHS for all four occurrences. So now we have our ever input
and we've got our operator, our left hand side and
our right hand side. And then this is pretty easy to see. So when the operator is a plus sign, we're gonna go ahead and print out the... We're gonna convert all
these to doubles and say, all right, so we don't lose any precision which can happen sometimes in
multiplication and division, depending upon what you're doing. And then we're gonna go ahead and actually perform the operation. And then what we're gonna
do at that point in time is we're gonna go and print that value and then we're gonna go
ahead and do a read line. So let's go and run this
to see what happens. So I've entered arithmetic
problem one plus one, we get two. Two times five and we see an index right out of our exception. That's because we didn't
put a space there. So we do have a cup, some input problems that we probably do need to process. So let's go and see if we
can get you to one of these to run accordingly. So we already did a plus let's
go ahead and do a subtraction minus six. Okay, that makes sense,
eight times seven, 56. Let's do 60 divided by ten, six. So all that makes sense. So our application does work
now, we now have a application, which is a simple calculator
that shows us how to do adding, subtracting, multiplying, dividing. Our application works fine at this point, but we do need to provide
some type of validation. For example, let's go ahead
and run our application. And let's assume that we
make a mistake during typing, which could be very common. We say one plus, and we forget to enter, what, let's say one plus one works, but we do one plus and we forget to enter the additional value and we hit enter. We receive an index out
of bounds exception. So what we need to do
is check that we have the proper number of arguments. So what we can do is
say, if values dot size is less than three, 'cause we need at least three parameters,
then we're gonna throw an illegal argument exception,
let's say invalid input, expected value plus value received. And then whatever the input was. So now if we run this, what will happen is we're not gonna get index
out of bounds exception. What will happen is we can
still enter one plus one. We'll still get that. But if we enter one plus
something, we forget to enter the additional value, we'll
now get an legal argument, exception that was thrown. It's an invalid input, expected
value, blah, blah, blah. It was this particular value. Now you could decide to crash the program if that's what you'd like
to do, or you can decide to skip this completely
so we could change this. So instead of having
it throw an exception, we could actually just
have it say print line. We print line of idle
less than three else. And then we can wrap everything else since I have an else here. And what will happen now,
when we run the application, run us and we'll go one plus one is two. We do one, does it age two plus something, we make a mistake, enter, invalid input, expected value of up plus
value of received two plus. Okay, let's try it again, two plus three. Okay, it works. So now our application is a crashing. We've performed some level of validation. We're providing the feedback to the user and we can continue using
the app and it works as we would expect. But we also have another problem. The other problem is
gonna be let's go ahead and run this. What happens if for whatever reason, a user enters one plus
two and then the next time they say one plus dog,
what's gonna happen there. We get another exception from the program and the program crashes and
a calculator doesn't know how to handle the word string dog. And so it says number format exception for input string dog. And if we take a look at line number 18, it's happening right here. And the reason why it's happening
is on the right hand side, we're trying to convert
that value to RHS value, which came from here, which is the second, the third item in the list. We're trying to convert it to a double and that's where the problem occurs. So we need to do something here. So one quick thing that we can easily do, which works very well
because at this point, this isn't an illegal argument. We shouldn't be able to handle this inside of our application. What you could do is you
would say to double or null. And then what we're gonna
do here is we're gonna do the Elvis operator. And the Elvis operator says,
hey, if something happens here and this is null, the I want
you to return another value. And so this is gonna say,
throw legal argument exception, invalid input. And then we'll go ahead
and actually just go ahead and render that input values zero. Now what's gonna happen here
is if this value right here can be converted to a
double, it will be converted to a double and it will be returned here. Otherwise, if it can not
be returned to a double, a null will be returned. At that point, the Elvis
operator will interrupt and say, hey, on the left hand
side over here, this is null, so we need to do something
on the right-hand side. And then we're telling it,
hey, if you encounter null, throw an illegal argument exception. So basically we're trying to parse it. We couldn't parse it as a double. So therefore it was returned as a null, and at that point, and
then short-circuited to the right-hand side over here. So we'll do the same thing
over here to double or null. And I'm just gonna copy
this to save some typing. There we go, and then
this'll be value one. Now, if you could see over
here, all these are grayed out, 'cause we're already
casting these two a double. I can actually remove
these redundant calls. So boom, we'll just go ahead
and remove these real fast, I'll be right back. Okay, now I'm back, we
have our very succinct and clean version of our calculation. And so very nice here, very succinct here, but what's happening and we
can actually print things directly to the screen. So if we were to run it
now say one plus two, we'll get back three,
if I say one plus dog, we're gonna get an
illegal argument exception and valid input. Now, of course, this is
crashing the application. So there's a couple of
things that you could do here and in which I would challenge you that we're not gonna do,
that you could do on your own to challenge yourself,
is remove the illegal argument exception. And you can either
decide to make it a zero. So you could just do something like this that would make it a zero
so that it would be a double or zero, which wouldn't work. So because as you see here, the type inference has
given you an example, or you can go ahead
and say double or null. At that point in time,
it could be a double and the same thing down here and then check to see if you have nulls for either one of them. If you do, perhaps provide
a message to the user and then let the application
continue to keep going. Here, though we're just gonna keep this where it's gonna continue
to short circuit. So at this point in time,
we now have an application that tells the user that
they're invalid input is occurring for their
first or their second value, otherwise it's gonna continue on. Now, there's one last little thing that I would like to
do here in this program to clean it up just a little bit 'cause we have a little
bit of duplication here with this print line. And what we can do is we
can actually turn a value from a when clause. So I'm gonna say val result equals when, and then we're just gonna
get rid of these print line statements and get rid
of the other parentheses on the other end. And now we have a double that we can use. And then what we can use is just go ahead and print line the result. And this really cleans up our when clause. Now we can actually see,
it's really easy to read and it's very easy to follow. We have an operator, we have
left-hand side right-hand side. Based upon the operator if
it's plus minus multiplication or divide, we're gonna
perform those operations with the left hand side
in the right hand side. Otherwise we don't support
whatever operation is thrown and we'll basically throw an exception. And then it finally, once
that result is returned, we'll go ahead and print the results. So let's go ahead and run
the application again, just to see what it looks like. So one plus two, there's
three, three plus five, 10 times seven, 70, 90 divided by six, 15. So now our application works. We've added some validation
in here to accept the only valid chunks of input. We've made sure that we can
only accept certain parameters and we're providing feedback to the user. And then of course, when we're
done, we can just hit enter and the application will say goodbye, that we're completely done
and then the process exits. And you've now written
your first calculator app. Now the next thing is how to deploy it so other people can use it. You've built your first application and it runs within the IDE. However you would like
to share with others, to do that, you can build a jar file. What you'll need to do is go
to file, project structure. You wanna make sure that
you have the artifacts item selected on the left-hand
side, you'll hit the plus icon, select jar from modules with dependencies to make sure your module is selected. In our case, simple calculator's
the one that I want. Then I'm going to select the main file, which has main kt in our
case, we'll click, okay. Now at this point you
can press apply and okay, you're now ready to build your jar file. To do so, you'll go to build
menu and then build artifacts and you'll get the pop
up and just click build. And you'll notice the build
is happening down here in the bottom and actually
it's already done. And what will happen
is some files be placed into the outfielder, so let's
go and expand those out. You'll see artifacts,
simple calculator jar. And there is our jar
file that can be executed on the command line. So you'd be able to actually
send us to someone else and they could execute
it, so let's do that. I'm gonna right click and open this area in the terminal down here. And this is gonna open up a terminal directly in this folder path here so I can see out artifacts,
simple calculator jar. So what we see here is this
exact folder that's right here. So we are now in the
terminal of this location. We can see LS and I can
run this jar file by typing Java, jar, simple calculator.jar. Hit enter and now our
program is executing. Please enter arithmetic problem. One plus two is three,
four plus five is this. 40 divided by seven is that value. Eight times 22 is this value. Hit enter and of course we get to exit. So now you have built your
first Java application, compiled it down to a jar file, which then you can take
and send to anybody else and they can execute this
program on their computer. Now be aware if you do
send a jar file over email, most likely most email
providers are probably going to strip that file out
as it can be considered unexecutable. So you'll need to get it
to them a different way, maybe through a file sharing
service of some sorts. So that's how you can build a jar file simply through IntelliJ and
then you'll get your jar file. Congratulations on creating
your first application. Playing with Kotlin and
Simon IDE is gonna be the best way for you to learn. However, sometimes you can
also learn directly on the web. And to do that, the folks
over JetBrains have set up play.Kotlinlane.org. and
here you can log in for free, not even log in just
access, play.kotlin.org and start writing code
right here in the browser. So now what we can do is
we'll say hello from Donn, for example, and I can run this code. And what we'll see, it'll run and compile, and we'll see the output. Now, one of the coolest things
we can do inside of here, we can write a bunch of code
and then you can also share it. So you can click share
and you can copy this link or you can directly embed
it, send it to medium. And so let's say I've
taken this and I've sent it to somebody else. So I have a new incognito
window open here. If I paste this, what
we'll see here is we have play.Kotlinlane.org
and automatically loads the code that I've written. So hello from Donn has
been pasted inside here. So actually, if you actually take that URL that you saw here in the
video and pasted it in, you should see this directly from me right here in your browser. Now this is a great place to play around. There's also the hands-on
and there are some examples in koans. Now Kotlin koans are great. Koans are a series of set of
exercises that are allow you to get familiar with the actual
Kotlin programming language. And the nice thing about it
is they actually give you a test that you can actually
have to basically make pars. So you have to actually fix the code and make these things parse in real time. And you can do it right
here on your browser. They give you progress that
you can follow along the way. So if you learn Kotlin
for me and you're watching these videos and you've
picked up quite a bit, and you wanna take your
exercises a little bit further and challenge yourself, I
advise you go to Kotlin. Go to play.Kotlinlane.org. You can play in the round the playground, and you can also visit the Kotlin koans and explore other examples as well. I hope you enjoy. Woo, you made it. Congratulations on finishing the Kotlin programming language course. It was over nine hours of
content you just watched. You should feel proud of
yourself for finishing this. This is quite the accomplishment. You now should have a solid
fundamental understanding of the Kotlin programming language, and you shouldn't be able
to go start providing value at your company's project,
your own personal project. It doesn't matter if you're
a developer, engineer, scientist, doctor, or whatever. I hope you have received a
lot of value from this course. I hope you have learned
Kotlin and I hope it produces a ton of value in your
career moving forward. And I wish you nothing,
but the best going forward. Thanks again for watching the video and I'll catch you next time.