Coding

Kotlin Course – Tutorial for Beginners

  • 00:00:00 Hi, my name is Nate and welcome to this tutorial on the Kotlin programming language. If
  • 00:00:06 you're not familiar, Kotlin is a statically type programming language developed by JetBrains.
  • 00:00:11 It's been around since about 2011 and has steadily increased in popularity ever since.
  • 00:00:17 Today it's actually the primary development language for Android and in this tutorial
  • 00:00:22 we're going to move from basic topics like setting up your first project up to more advanced
  • 00:00:27 things like modeling data and working with higher order functions. The goal for this
  • 00:00:33 tutorial is to help you understand how the work with Kotlin so that you can then take
  • 00:00:38 that knowledge and apply it to building applications across mobile, web and native code. So without
  • 00:00:45 further ado, let's jump in and start building our first Kotlin project.
  • 00:00:55 The first thing we're going to want to do is to install JetBrains IDE Intelijay so that
  • 00:01:01 we can work with our Kotlin code on our development machine. The first thing to do is to open
  • 00:01:08 up your browser and search for it. Until Jay here, you should see a download link and we
  • 00:01:14 can click on that to be taken directly to the download page. Now I'll be working on
  • 00:01:19 a Mac, but intelligent is available on windows and Linux as well. You'll also notice here
  • 00:01:26 that there are two different versions of intelligence. There's the ultimate edition and the community
  • 00:01:31 edition. The ultimate edition has a lot more features that we need and there's also a paid
  • 00:01:36 product. The community edition is supportive for JVM development and Android development,
  • 00:01:42 so it's perfect for what we want to look at in this tutorial. When you're ready, go ahead
  • 00:01:48 and click the download button and that should start the download. Once the download is complete,
  • 00:01:54 we'll get started on installing the IDE and we'll take a look at hello and Kotlin. Now
  • 00:02:00 that our downloads complete, we can go ahead and onto the installer
  • 00:02:11 and I'm Mac. We'll drag the application into our applications folder which will start the
  • 00:02:16 install process here for us. Once the files have been transferred to your machine, we
  • 00:02:20 can go ahead and start intelligent for the first time to start the install process. In
  • 00:02:26 this case, we want to do a fresh install, so click that. Do not import settings and
  • 00:02:32 hit okay.
  • 00:02:33 Go ahead and accept the privacy policy. Hit continue. Now here you can choose your theme.
  • 00:02:40 I'll choose the dark theme. Go ahead with the standard and tell the J key settings here
  • 00:02:49 and we'll go ahead and click next through to finish the setup process and then we'll
  • 00:02:53 finally launch intelligent IDE community edition.
  • 00:02:57 The next thing that want to do is create our first Kotlin project here in intelligence.
  • 00:03:02 So to do that we'll click on create new project. Now over on the left side of this panel, we'll
  • 00:03:11 see a number of different project templates to choose from because we are going to be
  • 00:03:15 learning about Kotlin. We want to make sure that we have selected the Kotlin template.
  • 00:03:20 Now within this, there are a number of different Collin project types that we can choose from.
  • 00:03:26 The default here at the top is a Kotlin module for a JVM target, so this would be sort of
  • 00:03:32 a standard, just Kotlin project that could target a the JVM. I have nothing else added
  • 00:03:39 to it. Other examples would be, you know Caitlyn module for a Java script target or I taught
  • 00:03:45 in the native targeted or an Android or iOS mobile target. So all of those are interesting
  • 00:03:52 but more advanced. For this, we want to just stick with the basics so we will make sure
  • 00:03:58 that we have selected Kotlin module for JVM target and go ahead and hit next. Then we
  • 00:04:05 want to make sure that we name our project something meaningful. So in this case, let's
  • 00:04:09 call it hello Caitlyn. We'll make sure that our project location is set and then we'll
  • 00:04:17 go ahead and select finish.
  • 00:04:24 Now we have an empty Kotlin project. Let's start by adding our first common file so that
  • 00:04:29 we can write a simple hello world example. So do that. We'll come over here to the left
  • 00:04:34 and here we have the project panel within intelligent. You'll see at the top we have
  • 00:04:41 our hello Collin module. If we expand that we can see that we have a source directory
  • 00:04:47 but it's currently empty. That's right. Click on source, go to new and then Caitlyn file
  • 00:04:54 our class. And here we're going to type main for the name of our file and then we will
  • 00:05:01 leave this as file so that we can create our main dot KT file. Now we can get started by
  • 00:05:08 writing our main function here in our main dotK T file. So to start we'll type in fun
  • 00:05:17 Maine, no parameters and then we can type print Ellen parentheses.
  • 00:05:26 Hello Collin. And from there you'll see now that we have this little green arrow intelligent
  • 00:05:33 recognizes that this is an executable main function within this uh hello Caitlyn module.
  • 00:05:39 So we can actually go ahead and run this. We'll select to run main KT and that'll take
  • 00:05:47 a second to go ahead and build the project. And then we will get our output window here
  • 00:05:51 at the bottom and you can see that we have hello Collin. Now one extra quick fun fact
  • 00:05:59 here and tell Jay comes with a number of live templates built it. This means that you can
  • 00:06:06 type something and it will know how to complete that and auto-generate some code for us. So
  • 00:06:11 we can start typing Maine and then hit enter and it will automatically generate a main
  • 00:06:17 function for us. And then we are free to fill in with our, hello Caitlyn text.
  • 00:06:25 Awesome. You've just written your first program in Kotlin. Now let's dive in and start actually
  • 00:06:31 learning the language to get started. Let's look at how you can define variables in Kotlin.
  • 00:06:42 Now there's two types of variables. In Kotlin we can define mutable variables, which can
  • 00:06:49 have their values reassigned. Those are declared using the VAR keyword. Or we can define local
  • 00:06:59 read only variables, which can have their value assigned only once these are defined
  • 00:07:05 using the vow keyword. So to define a variable, we can use the keyword of our choice. So in
  • 00:07:14 this case we use vow, then we'll define the name and then we need to indicate that type.
  • 00:07:22 In this case I'll say a type string and then you can use an equals and then you can assign
  • 00:07:27 the value to this variable. So in this case I've defined a variable called name of type
  • 00:07:34 string.
  • 00:07:35 And then I have assigned the string literal Nate to that variable. Now, like I mentioned,
  • 00:07:43 vow our assign once variables, meaning this is read only once it has a value assigned
  • 00:07:48 to it. So if I tried to update the name of this, we'll see that we get an error. That
  • 00:07:56 area says vow cannot be reassigned. So this is our indicator that if we need to be able
  • 00:08:01 to update the value of our variable, we're not going to be able to use a vow. Instead
  • 00:08:07 we can use a VAR. Now VAR variables can be reassigned as many times as you want. So as
  • 00:08:17 soon as I updated that keyword, now I can update the value of our name variable. And
  • 00:08:24 then if we want to use that variable, we can pass that in to the print function here. And
  • 00:08:31 if we then run our main function, well now see the output value of that variable at this
  • 00:08:38 time is blank because we had reassigned it.
  • 00:08:42 If we remove that reassignment, well now see that we get this little underlying saying
  • 00:08:49 variables never modified and can actually be declared immutable instead using vow. So
  • 00:08:55 this is nice that the IDE gives us that helpful hint. So in this case we'll go back to a vow.
  • 00:09:01 And now if we rerun this once again, well now see that my name was printed out to the
  • 00:09:07 console and this example we're using a local variable, meaning that the variable that we
  • 00:09:12 have defined is available locally within the current scope. So in this case it's available
  • 00:09:21 only within the main function we have defined. Now in Kotlin you can actually declare variables
  • 00:09:28 outside of any type of enclosing function. Our class, all you have to do is declare a
  • 00:09:38 variable within a Kotlin file. So in this case we've moved our variable name outside
  • 00:09:45 of the main function into the main dot. Kate. He file variables defined like this are referred
  • 00:09:52 to as top level variables. And you see now that we've moved this outside of the main
  • 00:09:57 function, we can still run it and our variable is still completely valid.
  • 00:10:06 Top level variables can be worked with just like a local variable. So we get a fine and
  • 00:10:13 an additional variable. This time we'll say VAR greeting of type string equals hello.
  • 00:10:21 We now have a publicly available top level variable called greeting. So now if we wanted
  • 00:10:28 to come down here and print out a greeting before the name, we could do that by calling
  • 00:10:35 print line and passing in greeting. We can also modify that greeting because this is
  • 00:10:45 a mutable variable and if we then print out this new value, we will see that the message
  • 00:10:55 has changed.
  • 00:10:59 So when we hit run here, we should now see hello Nate. And then hi mate, let's take a
  • 00:11:05 closer look at how variable types are defined. So in defining our variables, in both cases
  • 00:11:12 we use either the voucher or VAR keyword and then we have the variable name. Both of these
  • 00:11:18 are must haves. Next step we have the colon and then the type. So in this case we have
  • 00:11:25 the type of string and then we assign our value. One interesting difference between
  • 00:11:31 Java and Kotlin is that types in Kotlin are non null. By default this means that there
  • 00:11:38 is a distinct difference between string and a nullable string. Now what does this look
  • 00:11:44 like in practice? Well in this case because types are non all by default name is a nano
  • 00:11:51 string. So if I tried to assign a no value to this, we'll actually get an error and it
  • 00:11:58 will hopefully say no, cannot be a value of a non Knoll type string. So if you want to
  • 00:12:05 actually have no as a valid value for your variable, you need to add a question Mark
  • 00:12:12 after that string value. This indicates that now named as a knowable string. And I can
  • 00:12:19 assign no or I can go ahead and sign an actual value. So let's look at how we could use this
  • 00:12:26 with the greeting variable.
  • 00:12:30 So if we make greeting a knowable variable, we can come down here and we'll go ahead and
  • 00:12:37 print out the default greeting value. We'll print out the name and then we'll see that
  • 00:12:43 we can modify the value of greeting to null. And then we can go ahead and print out these
  • 00:12:49 values ones again and we can then see that greeting has been updated. And when we try
  • 00:12:54 to print that out, it actually recognizes that it's no and prints that out to the console
  • 00:12:58 for us. So let's look at how we can actually simplify the declaration of our variables.
  • 00:13:07 I mentioned that vow or bar as well as the name are mandatory. However, Kotlin supports
  • 00:13:14 type inferences on variables and properties. What does this mean? Well, this means that
  • 00:13:20 Kotlin can recognize what type the value is that we're trying to assign to the variable
  • 00:13:26 and if it can figure that out, we can actually omit a type declaration of the variable.
  • 00:13:32 So in this case we could actually remove the colon and string and the name variable will
  • 00:13:42 still be of type string. Now we can do the same for our greeting variable so we can remove
  • 00:13:52 this. But if we come back down here and tried to assign knowl to greeting will get an error.
  • 00:14:00 No, it cannot be a value of non node type string. So why is this? Well again, because
  • 00:14:07 types are non nano by default and Kotlin and we are assigning a nano string literal to
  • 00:14:15 greeting the compiler. In first the greeting is meant to be a non null string. So if you
  • 00:14:23 want to have a knowable string you might try and just assign no. However we get a warning
  • 00:14:33 saying implicit nothing type. This is because greeting now cannot really be inferred. It
  • 00:14:40 doesn't know what is a null type of. So in this case we will need to go ahead and specify
  • 00:14:49 a knowable.
  • 00:14:52 Now down here we can assign Knowlton greeting or we could assign an actual greeting. Now
  • 00:15:00 that we've taken a look at how to create variables and how the type system works, let's introduce
  • 00:15:08 some basic control flow and we'll do this by working with the nullable string variable
  • 00:15:15 of greeting. So let's go ahead and remove these values. Now let's say that we only want
  • 00:15:22 to print out the value of greeting if it's not no. So we could do that in a couple different
  • 00:15:28 ways. The first one we'll look at is using an if statement, if statements in Kotlin work
  • 00:15:34 very much the same way as in Java. So we'll use the if keyword followed by parentheses,
  • 00:15:41 we use greeting. In this case we want to say if greeting is not no printed out, so we can
  • 00:15:46 say not equal to know and we'll finish it off with curly braces.
  • 00:15:53 And then if we move our print statement within that, if conditional when we run this, we
  • 00:16:01 should now have no greeting printed out. And sure enough we see that just the name is printed
  • 00:16:06 and if we modified the value of greeting to be non null and then rerun this, we'll now
  • 00:16:18 see that we have our greeting. Now what if we wanted to add and else claws? So just like
  • 00:16:28 Java, we could add the else, we can add curly braces again. And so here we could define
  • 00:16:35 a default greeting of hi. So now if we run this once again, greeting will be normal and
  • 00:16:43 so we should see the default high value. Now another means of control flow within Kotlin
  • 00:16:50 is the when statement. The Wednesday mint is very similar to a switch statement in Java.
  • 00:16:56 So to create that we can come down and use the when keyword and then print the CS.
  • 00:17:03 We didn't want to pass end to this, the value that we want to check and switch on depending
  • 00:17:09 on its value. So in this case we'll pass in grieving followed by curly braces. Now within
  • 00:17:18 this block we can define each value that we want to act on differently. So in this case
  • 00:17:26 we can say no. And then to define what action to take, we use an arrow and then we'll say
  • 00:17:36 print line, hi. And then we can define and else case here. And this will act as the default
  • 00:17:46 if none of the values above are matched. So this case we will print out the value of greeting
  • 00:17:52 on its own.
  • 00:17:55 So now if we run this, we should see high printed out because greeting is still not.
  • 00:18:05 And once again if we update greeting to be some other value and rerun, we'll see that
  • 00:18:15 the branch of the, when statement is hit and we are printing out the value of treating.
  • 00:18:22 So these are two of the basic ways in which you can manipulate control flow within Kotlin.
  • 00:18:28 Now we just saw how if and when can we use as statements to take different actions based
  • 00:18:35 on some checked value if and when can also be used as expressions to assign a value depending
  • 00:18:42 on that, those logical conditions. So let's take a look at how we could use an expression
  • 00:18:49 to assign a value to a local variable. So let's go down here and type vow greeting to
  • 00:18:58 print. I can say equals. Now if the greeting variable in our top level declaration is non
  • 00:19:07 no, then we want to go ahead and stick with that.
  • 00:19:11 So we can say if greeting does not equal null, then we want to assign greeting to the new
  • 00:19:21 greeting to print variable. Otherwise we want to say hi and then we'll update our print
  • 00:19:33 line to use the local variable. Now when we print this out, we should see the ELs branch
  • 00:19:41 of high being printed out. This is because the top level variable is currently no. If
  • 00:19:49 we modify this to pass in a greeting. Now if greeting does not equal null or return
  • 00:19:58 true and we'll see that hello is printed out instead. So if we want to assign different
  • 00:20:06 values to a variable, depending on whether or not something is normal or some other logical
  • 00:20:11 check, we could use an expression. However, we can also use a when expression if we want
  • 00:20:19 it. So we can say when, and again, we'll pass in treating here and again we'll use our no
  • 00:20:29 value as the first check and if it's no, we'll go ahead and return high. Otherwise we'll
  • 00:20:38 go ahead and return the original greeting value. So now with this, when expression is
  • 00:20:43 saying is assign the value of high to greeting to print. If greeting is know. And likewise
  • 00:20:52 if greeting is nominal, go ahead and assign that value to the new greeting to print variable.
  • 00:21:00 So if we run this one more time, we should see high because grieving is null.
  • 00:21:10 And if we assign that value to greeting and rerun, we'll see that updated value of hello.
  • 00:21:18 So again, like the if expression a when expression can be used to assign a value to a variable
  • 00:21:25 depending on that input value that it's checking to start understanding functions in top line.
  • 00:21:38 Let's look at this example. Within this example, we actually are using two different functions.
  • 00:21:44 We have our main function and then we're also using the print LN function as well. So let's
  • 00:21:51 take a look at how we can define our own new function. We'll start by clicking here and
  • 00:21:58 we'll define the new function that is going to return this string that we want to print
  • 00:22:04 out to the console. So the first thing to do when defining any function is to use the
  • 00:22:10 fun keyword. This denotes that we are going to define a new function.
  • 00:22:15 Then we want to specify the function name. So I'm going to call this function, get greeting,
  • 00:22:24 and then you want to follow that up with open and closed parentheses. And within this we
  • 00:22:30 can actually define any function parameters. Now for now we're going to skip any function
  • 00:22:35 parameters and we want to define our return type for this function. So similarly to variables,
  • 00:22:44 we can define a return type by using a colon. And then the type in this case string. And
  • 00:22:51 then we'll do open and closed curly braces. Now like in Java we can define the return
  • 00:22:57 value by saying return. And then in this case I'll say hello Kotlin. So now we have the
  • 00:23:06 function called get greeting this green to return the string. Hello Caitlyn. If we want
  • 00:23:12 to then invoke that function, we can do so by calling get greeting with open and closed
  • 00:23:22 parentheses.
  • 00:23:23 This should look very similar to what you're familiar with and calling methods from Java.
  • 00:23:29 And now if we run our code, we'll see to print statements. We'll see the first one, hello
  • 00:23:35 world. And the second one. Hello Collin. So one thing you may have noticed is that in
  • 00:23:42 our gig greeting function, we're the return type of stream. However, in our main function
  • 00:23:50 there's no return type specified. So why is this? Well let's illustrate this by an example.
  • 00:23:56 Let's write a function called say hello. So again we'll use the fun keyword and it will
  • 00:24:03 maim it. Say hello, no parameter values. And then we're going to declare this as returning
  • 00:24:12 type unit unit. And Kotlin is essentially the absence of any useful type. It's similar
  • 00:24:21 to saying this returns nothing useful. And the reason we're going to use unit in this
  • 00:24:27 case is because we don't want to return anything.
  • 00:24:29 We're going to simply print out our get greeting functions, return value, so we can then call
  • 00:24:40 say hello. And you'll notice up here that unit is underlined and it's giving us this
  • 00:24:48 message that says redundant unit return type. Basically what this is saying is that if you
  • 00:24:54 have a function that returns unit, that is to say if you have a function that doesn't
  • 00:24:59 return anything useful, you can omit that type value. So we can actually remove unit
  • 00:25:07 from our say hello function and this is perfectly valid. So that's why in our main function,
  • 00:25:13 we also don't have the return type specified. Aside from the rules around unit return types,
  • 00:25:22 return types or functions work very similarly to how you would define them and treat them
  • 00:25:29 for variables or properties. So for example, if we wanted to return a Knoll string from
  • 00:25:36 our get greeting potion, we would modify the return type to be a nullable string and then
  • 00:25:43 we could return note in our return statement.
  • 00:25:50 Additionally, functions support type inference as well. So in our get greeting example here
  • 00:25:59 we are returning a single string literal, in which case we could actually simplify this
  • 00:26:06 to a single expression. We could remove the curly braces and add an equals and then the
  • 00:26:13 string literal after that. So this is what's known as a single expression function because
  • 00:26:21 the entire function definition is now in a single expression. And now this is where the
  • 00:26:26 title inference comes into play. Again, because the compiler understands this single expression
  • 00:26:33 function definition and it knows that it's always going to return a string. We can actually
  • 00:26:38 remove the explicit return type. Now our get greeting is extremely simple and is functionally
  • 00:26:47 equivalent to the previous definition. So if we run our code, once again, we'll see
  • 00:26:55 that we now have our three different print statements. You might be noticing a theme
  • 00:26:59 here of Kotlin allowing us to reduce the amount of code we need to write to get the equivalent
  • 00:27:05 functionality. This is a theme that will crop up more and more as you start to learn the
  • 00:27:10 language.
  • 00:27:13 Now let's take a look at how we can define function parameters. Now before we get started,
  • 00:27:20 let's clean up our code a little bit. So we'll remove everything from our main function and
  • 00:27:26 we're going to go ahead and remove the existing function examples we've been working with.
  • 00:27:31 So once again, we're gonna define a function named say hello and to start it will print
  • 00:27:41 out. Hello Collin. Now this is exactly like we have seen before, but what if we wanted
  • 00:27:49 to change the thing that we were greeting? So instead of saying hello Collin, maybe we
  • 00:27:55 wanted to say hello world or hello mate, or hello John. So how might we do that? Well
  • 00:28:03 that's where a function parameter comes into play. So to define a function parameter, we'll
  • 00:28:12 go within the parentheses after the function name, and we'll define this using a notation
  • 00:28:18 of the parameter name colon, the parameter type. So in this case we're going to say item
  • 00:28:26 two Crete as our parameter name, colon. And then we want this to be a string value that
  • 00:28:34 we're passing it. Now with dinner function, we can say, Val, message equals hello. Plus
  • 00:28:45 I even went to greet.
  • 00:28:49 Okay.
  • 00:28:51 And then we could pass in the message. Now if we come down to our main function, we want
  • 00:28:57 to, when folks say hello, so we can start typing, say hello. And now we need to pass
  • 00:29:04 in a parameter value which it suggest to us in this little tool tip. So in this case we'll
  • 00:29:11 say Caitlyn. And now if we run our main function,
  • 00:29:17 we now see hello Kotlin is printed out to the console. If we wanted to pronounce something
  • 00:29:24 else, we could duplicate the invocation and maybe this time we'll pass it in world. And
  • 00:29:31 if we invoke our main function again, we'll now see hello Caitlyn and hello world printed
  • 00:29:37 out to the console. Now if we go back up to our say hello function. Well notice that there's
  • 00:29:42 this squiggly line here. This is unrelated to function parameters, but this is a really
  • 00:29:49 interesting feature in Caitlyn. Caitlyn supports string templates which allow us to substitute
  • 00:29:56 in variable values or argument values into a predefined string template. So in this case,
  • 00:30:04 instead of using concatenation, we can say hello space. And then to define a template,
  • 00:30:11 add value, please a dollar sign and then we can pass in that parameter name. So now if
  • 00:30:19 we hit run once again, we'll see the same output as before.
  • 00:30:24 So this is just one more way in which Kotlin can produce boilerplate for us by allowing
  • 00:30:28 us to define these convenience string templates. In fact, in this scenario we can take it one
  • 00:30:34 step further and remove the local variable all together. And then we can actually take
  • 00:30:41 this one step further and define this as a single expression. Oh shit. Now we have a
  • 00:30:49 say hello function that will take in a parameter value, which is the item degree. And then
  • 00:30:56 it will always print out hello item degree. So now let's update this to take two parameters
  • 00:31:03 so that we can customize both the greeting and whatever it is that we want to agree.
  • 00:31:08 So to do that, we're going to add a new parameter and we will name this one in greeting and
  • 00:31:16 have it be of type string. And now we will update our string template here to include
  • 00:31:23 that new parameter.
  • 00:31:27 Awesome. So now we've updated the function. Now we need to update the invocation of that
  • 00:31:36 function. So for this first line we can say, Hey, common and now for the next one we'll
  • 00:31:45 say hello world and if we read this well now see our desired output. So by adding that
  • 00:31:55 second parameter value, we have now made our say hello function much more flexible. Now
  • 00:32:02 we can add any number of parameters we want to say hello, but like any programming language,
  • 00:32:08 if you have too many parameters in your function, it might be an indicator that your function
  • 00:32:12 is doing too much. Now what last thing I'd like to point out about functions at this
  • 00:32:18 time is that you'll notice that these functions are not defined within any type of enclosing
  • 00:32:25 class. These are free functions or as they're referred to in Kotlin. These are top level
  • 00:32:32 functions like variables, functions can be defined outside of any in closing class or
  • 00:32:40 independent of any associated class. Now there are types of functions that are associated
  • 00:32:45 with a class and we'll take a look at those later on in the tutorial.
  • 00:32:53 Like most programming languages, Kotlin has support for collection data types. These are
  • 00:32:59 things like arrays, lists and maps they can use to group values together and then operate
  • 00:33:05 on them at a later time. So let's start off by looking at how we can define inner Ray
  • 00:33:11 and Fallon. We'll clear out these invocations to say hello because we won't need them right
  • 00:33:16 now, but we'll leave the say hello function definition because we'll come back to it later.
  • 00:33:21 Your create a basic array. We'll create a new local variable named interesting things
  • 00:33:30 and then we'll use the equal sign and then we can use a convenience function called array
  • 00:33:36 of in parentheses. This will create an array of whatever the inferred type is and then
  • 00:33:45 we can start defining values within this function. So in this case, as soon as I add a string
  • 00:33:52 literal, it can infer that this is going to be an array of strings. And then we can define
  • 00:33:58 some interesting things like Kotlin programming or comic books.
  • 00:34:11 Now that we have this variable defined, let's see what types of operations we can perform
  • 00:34:15 on it. If we start typing interesting things and hit dot the IDE will start to auto-complete
  • 00:34:21 and show us some of the methods available. So you see we have a size property, we have
  • 00:34:27 a get method, and we also have this open and closed of bracket syntax that we can use to
  • 00:34:34 access individual elements in the array. Let's try printing out some of these values to demonstrate
  • 00:34:41 how we can use the array to start. Let's print out the size. We can do that by saying print
  • 00:34:46 LN and then we'll do interesting things that size. Now let's print out the first element
  • 00:34:55 in the array. We can do that by going print, LN, interesting things. We can use the open
  • 00:35:01 and closed bracket and then pass in an index. This is how we can conveniently index in fact
  • 00:35:08 array.
  • 00:35:09 This is similar to doing a jet, so if we duplicate that line, we could say get, and again, passing
  • 00:35:18 is zero element of that array. If we now run this, we'll see three Caitlyn, Caitlyn, but
  • 00:35:27 this is just as we would expect. Now, what if we wanted to iterate over all of the elements
  • 00:35:34 of this array and then perhaps print out each of those values? Well, there are a number
  • 00:35:40 of different ways we could do that. For now we'll take a look at a basic for-loop so we
  • 00:35:46 could start typing for, and then we could say interesting thing in interesting things.
  • 00:36:00 Then open and closed curly braces. This is now the convenient syntax of four loops within
  • 00:36:06 Kotlin. So this is going to iterate over each value in the array and we can access those
  • 00:36:13 values in the interesting thing variable that we have defined within this four loop.
  • 00:36:19 So now we can type out interesting thing and if we rerun this code well now see that we
  • 00:36:31 have printed out each element in the array. So that type of for-loop is what is probably
  • 00:36:38 most similar to what you're used to if you're coming from Java. However, in Kotlin because
  • 00:36:44 we have top level functions and higher order functions, really just first class of port
  • 00:36:49 for functions across the board, we can write code that is a bit more functional. So let's
  • 00:36:55 take a look at how we could do a more functional approach to collection iteration. So or remove
  • 00:37:01 our four loop and now we could say interesting things doc for each. And what this is is invoking
  • 00:37:13 a for each function that is available in the standard library. That function then takes
  • 00:37:21 in a another function and returns unit. That function that we pass it in essentially defines
  • 00:37:29 what to do on each iteration over this collection.
  • 00:37:33 So within our curly braces here, this is where we can define what we want to do with each
  • 00:37:39 element in contrasting things. Now this might look a little bit confusing at first, but
  • 00:37:44 don't worry well explain this, but if we simply want to print out each item in this array,
  • 00:37:49 we can now say print LN and pass in it. It is the default name for each element in the
  • 00:37:58 array that is passed into this Lambda function in which we are defining. So if we run this
  • 00:38:04 well, now see that we have our three elements in the array printed out to the console. Now
  • 00:38:14 it is not always very readable. So another little quick tip here is if you want it to
  • 00:38:20 be named something else, you can rename that to value that's passed into the Lambda. In
  • 00:38:26 this case we could call it interesting thing and then we'll use the arrow and now instead
  • 00:38:33 of if we can reference that value by calling it interesting thing and once again if we
  • 00:38:40 run this, we'll see that our interesting things are printed out to the console.
  • 00:38:45 You might be looking at this wondering why we are not using an open and closed parentheses
  • 00:38:50 when calling the for each function. In fact, it might seem rather odd that we are not passing
  • 00:38:57 in that argument two for each, but instead of have just specify this open and closed
  • 00:39:02 parentheses independent of the rest of the for each call. So this is actually what's
  • 00:39:08 known as Lambda syntax within Kotlin. Now we'll look at how to implement this later.
  • 00:39:13 But the idea behind Lambda syntax is that if you have a function and it's only parameter
  • 00:39:19 is another function, then you can omit the parentheses all together and you can pass
  • 00:39:26 that function in by specifying this open and closed parentheses. So again, if we look at
  • 00:39:32 for each, we'll see that it takes a single function as a parameter so we can omit the
  • 00:39:41 parenthesis values and past that function into the for each function using the open
  • 00:39:47 and closed curly braces.
  • 00:39:51 And like I said, we'll look at how we actually define this type of higher order function
  • 00:39:56 a little bit later in the tutorial. So here we looked at a basic for each function on
  • 00:40:04 this array collection. But by doing it this way, we've lost the index data for whatever
  • 00:40:11 index the current interesting thing is in the containing array. So to handle that there
  • 00:40:18 is another function we can call. So once again we'll say interesting things and this time
  • 00:40:22 we'll say for each indexed. Now this time it's going to pass into us the current index
  • 00:40:33 as well as the current string. Now once again, we'll update this to be named interesting
  • 00:40:41 thing and that one's again, we could print out these values. So we can say print,L ,N
  • 00:40:50 and we can say interesting thing is at index.
  • 00:41:01 And now if we print this out, we'll see that we have gotten the value from the array as
  • 00:41:08 well as its current index. So this could be really useful if you need to iterate and still
  • 00:41:14 maintain that index data. Now everything that we've been looking at here for res is applicable
  • 00:41:22 for lists as well. So if we clear out some of this previous code, we now go to our declaration
  • 00:41:31 of this interesting things variable. Now we're using the convenience function array of to
  • 00:41:37 define this variable as an array of type string. Now there's also a list of function as well.
  • 00:41:47 Now if we try to work with interesting things, we'll see that we have a lot more methods
  • 00:41:51 to choose from because it's now a list rather than an array. And so like an array, we can
  • 00:41:57 access individual elements by using a jet or also by using the bracket syntax like we're
  • 00:42:08 familiar with with arrays as well.
  • 00:42:13 And also like with the array, we have functions available to us to help with integration.
  • 00:42:19 So if we wanted to print out all of the interesting things again, once again we can say interesting
  • 00:42:24 things doc for each ad. Once again we'll say interim vesting thing. We use the arrow here
  • 00:42:33 within our Lambda expression and then we'll print out the interesting thing and if we
  • 00:42:42 hit run while that we have our three interesting things printed to the console. Now that we've
  • 00:42:48 looked at arrays and lists, let's take a look at one more collection type and Kotlin which
  • 00:42:55 is map. So let's remove this duration over our interesting things variable here. Now
  • 00:43:04 let's create a new variable. I'll just name this map equals and once again there is a
  • 00:43:13 map of function that we can use. Now the map of function will essentially take in pairs.
  • 00:43:21 Pair is a simple wrapper class containing two values and that there is also the convenience
  • 00:43:28 function to create pairs.
  • 00:43:30 So if you want to create a basic key value map we could do so like this. We'll use a
  • 00:43:38 key of one and then we'll use two and then the value in this case will be a, and then
  • 00:43:47 we'll define it. Another pair, we'll use a key of two. Then we'll use the two function
  • 00:43:55 and then a value of B. And then we'll define one more pair. And we'll say three is our
  • 00:44:01 key to C. so what we've now done is defined a map with three pairs of values in it. The
  • 00:44:12 keys are one, two and three, and the associated values are a, B, and C. now we can iterate
  • 00:44:20 over this by saying map for each and it's going to return to us both the key and the
  • 00:44:29 value. Unfortunately the default to it, not
  • 00:44:32 very useful name. So this case we'll remain them again within our Lambda expression. So
  • 00:44:37 we'll say key value and then we can print these out and we'll use a string template
  • 00:44:45 and we'll just say key and then we'll define an arrow just for some separation and then
  • 00:44:53 value. And now if we print this out, well now see that we're giving each of our key
  • 00:45:02 and value pairs and then we could do with those whatever that we need to. We've seen
  • 00:45:08 how you can define several different types of collections such as arrays and lists and
  • 00:45:14 maps. And we've also seen how you can iterate over those collections and access individual
  • 00:45:20 elements from our collection. And there's an interesting thing to point out about the
  • 00:45:25 way Kotlin handles the collections similar to the way in which it differentiates between
  • 00:45:32 knowable and nano types. Caitlyn also differentiates between mutable and immutable collection types.
  • 00:45:39 Now what does this mean? This means that by default a collection type in Kotlin is immutable
  • 00:45:47 so that you can't add or subtract values from that collection once it's initially created.
  • 00:45:53 So let's look at an example of this. We have defined our interesting things list using
  • 00:45:58 the list of function here. And if we wanted to try and add something to interesting things,
  • 00:46:06 there's no function available to us to do that. That's because it's immutable by default.
  • 00:46:12 If we wanted a immutable list, we could use the mutable list of function. Now if we want
  • 00:46:21 to add something, we can say interesting things. Dot add and we could add a new string to our
  • 00:46:30 interesting things list. The same goes for map. If we wanted to add a new key value paired
  • 00:46:37 wire map, you could say map doc put, but there's no put method available. But if we change
  • 00:46:46 to immutable map, now we could say map dot put and we can define a new key of four and
  • 00:46:58 a new value of D. so this is something to keep in mind. If you have a collection that's
  • 00:47:05 going to be static, once it's defined, then you're fine to use the regular list of array
  • 00:47:11 of map up, et cetera functions. And that is a good thing because immutability is often
  • 00:47:17 a desirable trait in your code. However, if you're going to want to modify the values
  • 00:47:23 in that collection, then you'll have to create a mutable collection so that you have access
  • 00:47:30 to things like put or add that let you modify that collection.
  • 00:47:34 Okay,
  • 00:47:36 now that we have an understanding of working with collections, let's modify RSA hello function
  • 00:47:42 to take a collection parameter so that we can greet multiple things, will modify first
  • 00:47:49 the name of item to greet two items to greet because it's now going to be plural because
  • 00:47:56 it will be a collection. And that will update from string to list of string. And then now
  • 00:48:04 we're going to update the implementation of this function. So instead of being a single
  • 00:48:09 expression function, we'll add a function body. And then now we're going to want to
  • 00:48:16 iterate over the items to greet parameter. So we'll say items to greet dot for each.
  • 00:48:24 And then we'll paste it back in our original print statement. And then we'll go ahead and
  • 00:48:30 update the receiver value here from it to item to Crete. It'll add our arrow. And so
  • 00:48:41 now we have a say hello function that you can pass a collection into. And then it'll
  • 00:48:47 print out multiple lines. So now we can say, say hello and we can still pass in our custom
  • 00:48:56 greeting so we can say hi. And then we can pass it in our interesting things variable.
  • 00:49:05 And now if we click run, we now see high Caitlyn, hi programming and high comic books. So this
  • 00:49:14 just a quick example of how you can pass it in a collection type to a function as a parameter.
  • 00:49:21 There's nothing wrong with including a collection parameter in your function, however functions.
  • 00:49:30 And Kotlin do provide an additional piece of functionality that can satisfy this use
  • 00:49:36 case and provides a little additional flexibility. Now to demonstrate why this might be interesting
  • 00:49:44 to us. Let's look at an example. So let's say we want to call say hello and we'll pass
  • 00:49:50 on or greeting, but then we don't want to pass in any interesting things in this case.
  • 00:49:55 Well, because of the way that this function is currently defined, we have to pass in the
  • 00:50:00 second argument. So in this case, if we wanted to pass in no items, we would have to pass
  • 00:50:07 in an empty list, which isn't really a big deal, but it's also not the most flexible
  • 00:50:14 way of handling things. So let's take a look and alternative means of achieving this functionality.
  • 00:50:22 If we come up here to our say hello function, we're going to modify this second.
  • 00:50:26 So that is a VAR arch perimeter VAR ARG is a keyword in Kotlin. It essentially represents
  • 00:50:38 a variable number of arguments. So in this case, instead of taking a list of string,
  • 00:50:45 we'll define a VAR R of string. This tells the compiler that we're going to take a variable
  • 00:50:53 number of string arguments after the initial greeting argument to this function. So now
  • 00:51:00 if we try to pass something in to say hello, well first pass in our grieving and now we
  • 00:51:10 don't actually have to pass anything in after the initial argument. This is because the
  • 00:51:19 [inaudible] parameter will essentially be treated as an array of whichever type it's
  • 00:51:25 used to specify. So in this case, items to GRI is now an array of type string. So if
  • 00:51:32 we don't pass any items after the greeting, it will be treated as an empty array. If we
  • 00:51:40 did want to start to pass items, we can do that by separating them with commas. So it
  • 00:51:45 could say Kotlin and now this would be an array of size one. But where the real flexibility
  • 00:51:52 comes is we can now start to define many argument values here.
  • 00:52:05 And so now all of those arguments that are passed in will be grouped together, treated
  • 00:52:10 as an array. And so in our function implementation, we can still iterate over all the elements
  • 00:52:17 in that array. So if we now run this, we should get the same outfit as before. So by using
  • 00:52:25 our VAR arc parameter, we've eliminated the need to always pass in a value after the initial
  • 00:52:33 greeting argument and lets us have greater flexibility because it will support zero one
  • 00:52:39 or any other number of argument values to be passed it. Now it's very convenient to
  • 00:52:46 be able to pass multiple arguments to this [inaudible] hard perimeter. However, you're
  • 00:52:53 usually not going to be hard coding those arguments in manually during compiled time.
  • 00:53:00 More likely you're going to get a array of values from a network request or a database
  • 00:53:06 and then you're going to want to pass it those in. So you might think that it would be as
  • 00:53:11 simple as passing in an array after that initial greeting. So let's try that. We could change
  • 00:53:19 list of two array of, and then after I,
  • 00:53:25 we'll pass in interesting things. Oh, unfortunately this does not work. And if you look at the
  • 00:53:33 air, the see a requires string found array of string. So how do you actually pass in
  • 00:53:40 an array of existing values to this far ARG perimeter? Well, you can do that with the
  • 00:53:48 spread operator and all the spread operator is, is applying the asterisk before the array
  • 00:53:57 variable when you pass it in as an argument value. So now if we hit run, we'll see that
  • 00:54:06 the compiler is now accepting that array and we are iterating over each item in that interesting
  • 00:54:13 things array. So this is how you can pass in an existing collection as a VAR ARD parameter.
  • 00:54:21 Another really interesting and powerful feature with Kotlin functions are named arguments.
  • 00:54:27 Now let's take a look at an example of what name arguments provide to us. Let's start
  • 00:54:32 by cleaning out our main function and then we're going to define a new simple function
  • 00:54:38 that will take a greeting and a name and then print that up.
  • 00:54:44 So now when we want to call this new Greek person function secret person, hi, and then
  • 00:54:53 I'll use my name here. Now this is fine and it's very easy to understand because the ID
  • 00:54:59 is helping us and showing, okay, this is the greeting argument. This is the name argument.
  • 00:55:03 However, if you are in a code review, you might not be able to know exactly which order
  • 00:55:10 these arguments are supposed to be passed in. Also, if you wanted to modify the function
  • 00:55:15 signature of Greek person down the line, you'd have to make sure that these are in the same
  • 00:55:20 order because since they share the same type, you could mix that order up without getting
  • 00:55:25 any type of compiler pair. Now what made arguments allow us to do is specify which parameter
  • 00:55:33 this argument value is going to be used for. So what does that actually look like in practice?
  • 00:55:40 Well, it looks like defining the name of the parameter and then an equal sign. And then
  • 00:55:49 here we can say main equals. And so now we're saying very explicitly assigned, high to greeting
  • 00:55:55 and Nate. To me, the cool thing that this allows us to do is actually mix up the order
  • 00:56:03 of these arguments. So now we can actually pass the second parameter first and the first
  • 00:56:11 parameter second so that we could actually theoretically modify the signature of Greek
  • 00:56:17 person changing the order of these parameters and it wouldn't impact the invocations of
  • 00:56:25 that function. Caitlyn allows us to take this flexibility one step further by leveraging
  • 00:56:32 default parameter values. So once again, let's look at our Greek person example. So here
  • 00:56:39 we are now able to pass the arguments in whatever order we want. If we're using name arguments
  • 00:56:46 in tax, but what if we wanted to pass main first and then not even passing the greeting?
  • 00:56:53 Well now we get an error because it says no value past for perimeter greeting. So as great
  • 00:57:00 persons currently defined, it must take both arguments, even if they are in a mixed up
  • 00:57:08 order. Default parameter values allow us to change that. It allows us to tell the compiler
  • 00:57:17 what the default value should be if not as specified. So for greeting, we could provide
  • 00:57:25 a default value of hello and for name we'd get provided default value of Kotlin. You'll
  • 00:57:33 see now great person can be called by only specifying a single argument value. And if
  • 00:57:41 we run this, we'll see. It's going to say hello mate. So it's giving the default greeting
  • 00:57:50 value and then it was using the value for the name that we passed in now because both
  • 00:57:57 arguments have defaults, we could actually call this without passing any arguments in.
  • 00:58:04 And if we run it now, we'll see it's using both defaults and prints out.
  • 00:58:09 Hello Kotlin. Now this becomes a really powerful feature because now we can not only mix up
  • 00:58:17 the order in which we pass arguments, but we don't even have to pass all of them in.
  • 00:58:23 This actually allows us to replicate functionality of the builder pattern without actually having
  • 00:58:30 to write getters and setters and have private constructors and all of that. We can configure
  • 00:58:37 and reuse functions and objects by leveraging these default values and the named arguments,
  • 00:58:44 syntax, Wilde, Decaux parameter values, main argument and VAR. Our parameters are really
  • 00:58:52 convenient and flexible and powerful. They do have limitations as well. So I want to
  • 00:58:58 illustrate one of those limitations. So we're going to go back to our say hello function.
  • 00:59:04 Let's redefine our interesting things are right. And so now if I want to invoke, say
  • 00:59:09 hello and I want to pass things in order with the greeting and then the interesting things
  • 00:59:16 I can do that no problem. And if I run this, we'll get our three lines of output.
  • 00:59:24 And so now what if we wanted to use named arguments in techs? Well we could do that
  • 00:59:31 as well. Breathing equals high. However, as soon as I add the name argument syntax to
  • 00:59:36 the first parameter, I get this air saying mixing name and position arguments is not
  • 00:59:40 allowed. So this is one of those limitations. As soon as you use named arguments in tax
  • 00:59:47 for what argument, everything that follows, that must also be named. So in this case,
  • 00:59:53 I could fix this by saying items to treat equals and now I can run this again and I'll
  • 01:00:04 get the desired three lines of output once again. Now I could mix these up though
  • 01:00:17 and because both of them are using names, argument syntax, there are no problems here.
  • 01:00:22 And once again, we could run this and we would get our desired output.
  • 01:00:29 Now we're going to take a look at how we can create a simple class in Kotlin. Now up until
  • 01:00:35 this point, we've been working within a single main dot K T file. However, now that we're
  • 01:00:41 going to move into classes, let's go ahead and add a new file. So we'll come over to
  • 01:00:47 our project panel, right click source, go to new Kotlin file or class, and we're going
  • 01:00:55 to come down and on this dropdown we're going to select class and then we're going to name
  • 01:01:00 this class person and then hit enter. We can see here, then it automatically has created
  • 01:01:08 a person class for us and I might notice that this class is quite simple. So let's take
  • 01:01:16 a look at how this class actually works. To start, we have the class keyword followed
  • 01:01:23 by the class name and then really that's it. We could actually even optionally remove these
  • 01:01:33 curly braces. Since we're not defining any properties or methods at this time, if we
  • 01:01:39 wanted to then use this class, we could return to our main function here. And then we can
  • 01:01:48 create an instance of the class like this. So we'll create a variable named person equals
  • 01:01:56 person. Now this syntax right here is how you create a new instance of a class. Notice
  • 01:02:06 there's no new keyword and Caitlyn, you do not have to explicitly call new. You can simply
  • 01:02:12 specify the class name and then the constructor and any arguments that you need to pass into.
  • 01:02:18 It can may notice
  • 01:02:20 that we were able to create an instance of the person class using this empty constructor.
  • 01:02:26 However, if we go back to our class definition, we don't have any constructor defined. This
  • 01:02:34 is because when you're defining a class in Claplan, if you do not have any properties
  • 01:02:41 defined in your primary constructor or any arguments defined in your primary constructor,
  • 01:02:47 then you can actually just omit that primary constructor altogether. So what we see here,
  • 01:02:55 class person is really a shorthand form of this. If we wanted to explicitly define this
  • 01:03:07 primary constructor, we can do so by adding the constructor keyword and then the opening
  • 01:03:11 closed parentheses. You'll see here that it actually gives us a message recommending that
  • 01:03:16 we remove the empty primary constructor. Now we could also modify this primary constructor
  • 01:03:23 by just removing the constructor keyword and moving the open and closed parentheses directly
  • 01:03:28 after the classmate. However, we still get that same recommendation to remove the empty
  • 01:03:33 primary constructor. So let's add a primary constructor once again. And this time let's
  • 01:03:40 actually define a parameter that must be passed into this constructor. So if we're creating
  • 01:03:47 a person in class, let's pass in a first and last name for this person. So we could say
  • 01:03:56 first name string, last name string.
  • 01:04:09 So now we have two unused parameters that we pass it into the constructor. And now if
  • 01:04:14 we come back here to the creation of an instance of our person class, we'll see that we now
  • 01:04:20 have a compiler error saying no value pass for our printers. So I'll go ahead and I'll
  • 01:04:26 pass it in my first and last name here so we can come back here and we're not actually
  • 01:04:34 doing anything yet with these perimeters. So let's change that. Let's define our first
  • 01:04:41 property on our person class. So since we're passing in first name and last name, let's
  • 01:04:46 define properties for first name and last name. So we can say Val, first name street,
  • 01:04:56 thou last name street. Now you notice that both of these, now I have red areas underneath
  • 01:05:06 them saying property must be initialized or be abstract. And there's a couple of different
  • 01:05:11 ways that we can initialize these.
  • 01:05:13 The first way we'll look at is using and then hit block can define it in a net block by
  • 01:05:19 using the unit keyword and then open and close curly braces. And a net block is a piece of
  • 01:05:26 code that is run anytime. An instance of this class is run and you can actually have multiple
  • 01:05:32 admit blocks that will be processed in the order in which they are defined within your
  • 01:05:37 class body. So within this a net block we can initialize our property values using the
  • 01:05:43 parameters from our primary constructor. So we'll say first name equals underscore, first
  • 01:05:49 name, last name equals underscore, last name. Now we have initialized properties. But if
  • 01:05:58 we look up at where those properties are declared, we will see these little warnings saying,
  • 01:06:04 can be joined with assignment. What does that mean? Well this is the other way in which
  • 01:06:10 we could initialize these values.
  • 01:06:12 We could actually get rid of the NIC block here and we could initialize these at the
  • 01:06:19 point where they're declared by saying equals underscore first name equals underscore last
  • 01:06:26 name. So now we're passing in those parameters to the constructor and then immediately declaring
  • 01:06:34 and initializing properties on the class. So now if we go back to our usage of this
  • 01:06:41 person class, after we create the instance of person, we can now access those properties.
  • 01:06:48 Jax as the properties where you type person. Dot and then we can access the properties
  • 01:06:55 by their names directly. So we can say last name or person dot first name. Now you noticed
  • 01:07:04 that we're not using a getter here in Kotlin. This is known as property access syntax. You
  • 01:07:12 can reference properties directly by their name without having to worry about the getter
  • 01:07:18 or the setter. So now if we go back over track class, we could actually simplify this even
  • 01:07:26 a little bit more. And to do that we'll go ahead and remove these properties.
  • 01:07:35 And so now instead of passing in a parameter to the constructor and then defining a separate
  • 01:07:40 property that mirrors that parameter, we can actually declare the property directly and
  • 01:07:46 the primary constructor. So to do that, we'll come up here, we'll remove this underscore
  • 01:07:51 since this is now going to be the property name and then we'll add the vow keyword. And
  • 01:07:59 now when we have defined a first name and last name properties within our primary constructor
  • 01:08:05 directly, and if we go back to our usage, we see that nothing has changed here. We can
  • 01:08:10 still initialize the class in the same way and still access our last name and first properties
  • 01:08:15 the same way. So far we've been working with the primary constructor within our class declaration,
  • 01:08:22 but it's also possible to define what are known as secondary constructors. Secondary
  • 01:08:28 constructors can provide alternative means for you to instantiate an instance of your
  • 01:08:33 class.
  • 01:08:34 So let's work with an example. Let's say we want to create a secondary constructor that
  • 01:08:39 takes no parameters so that we don't have to always pass values in what we want to create
  • 01:08:45 a new person object. So to create a secondary constructor, we'll use the constructor keyword
  • 01:08:53 and then open and close parentheses. And in this example we're not going to pass in any
  • 01:08:58 parameters. We didn't need to call through to the primary constructor. To do that we
  • 01:09:03 use colon and then the this keyword open and closed parentheses. And then now we need to
  • 01:09:09 satisfy any parameters that are declared in the primary constructor. So in this case,
  • 01:09:17 let's define some default first and last name values. So for our first name we'll use Peter
  • 01:09:26 and last name. We'll use Parker.
  • 01:09:30 Okay.
  • 01:09:31 And then we can define a body for the secondary constructor. And to just take a look at how
  • 01:09:39 this works with the NetBox. Let's go ahead and add a print statement here that says secondary
  • 01:09:47 constructor
  • 01:09:48 [inaudible].
  • 01:09:49 Well then add and then that block, and we'll put a message here that says, and that one.
  • 01:10:00 And then just for fun, let's add a second, a net block after the secondary constructor
  • 01:10:05 and we'll print out in it too. Now let's run our main function and just see what happens.
  • 01:10:20 [inaudible]
  • 01:10:22 so in this case we're using the primary constructor so we can specify and explicit values for
  • 01:10:29 the first and last name. So we'll see that the secondary constructor is never called,
  • 01:10:33 but both admit box are called and log out to the console. So now let's remove the explicit
  • 01:10:42 arguments that are being passed in. And now let's rerun this
  • 01:10:46 [inaudible]
  • 01:10:50 and now this time we'll see that I didn't block one is run and Nickboch two is run and
  • 01:10:55 then our secondary constructor was run
  • 01:10:57 [inaudible].
  • 01:10:59 So what this shows is that the admit blocks are always going to run before the secondary
  • 01:11:06 constructor. Now the Invitbox will execute in order in which they're defined within the
  • 01:11:11 class body and the secondary constructor will be called. Now in this example, and actually
  • 01:11:18 in many practical examples when using Kotlin on real projects, a secondary constructor
  • 01:11:24 isn't strictly necessary because of the power of default parameter values. So in this case
  • 01:11:31 we can remove all of this within our class body and instead we can define default values
  • 01:11:40 here in the primary constructor.
  • 01:11:45 Okay.
  • 01:11:47 Now if we go back over to our usage, we can still use the person class as if it had this
  • 01:11:53 empty primary constructor because both parameters have default values.
  • 01:11:59 Now let's look a bit more closely at class properties. Now we've already defined two
  • 01:12:05 properties within our primary constructor. Both of these are read only properties so
  • 01:12:12 they have no center, but they do have a getter available. That Gitter is how we are able
  • 01:12:19 to leverage property access and tax and reference those properties directly as we are doing
  • 01:12:25 here in our main function. Let's explore this more fully by adding another property. Let's
  • 01:12:32 add a nickname property. In this case we'll use VAR because it's not going to be set initially.
  • 01:12:39 We'll call it nickname string and we're going to go ahead and make this a notable string
  • 01:12:49 and then we'll go ahead and set that initially to know. Now let's see if we type person.
  • 01:12:56 Dot. We see that we have a nickname property, but unlike last name and first name, this
  • 01:13:01 is a mutable property.
  • 01:13:03 So we can actually assign a value to this. So we can say equals. And then my nickname
  • 01:13:08 growing up was shades. So assign that string to this nickname property. So if we go back
  • 01:13:14 to our person class, let's look at this property a bit more closely. We've already mentioned
  • 01:13:19 that properties and Caitlyn will get getters and setters generated for them automatically
  • 01:13:25 by the compiler. So if your property is a vow, it will have a get or generated. If it's
  • 01:13:32 a bar, it will have it getter and a setter generated. But what if you don't want to rely
  • 01:13:38 on the default behavior of these getters and senators? Maybe you want to do some complex
  • 01:13:44 logic within that. Or maybe you want to log something out for debugging purposes. Well,
  • 01:13:50 you can override the behavior of these default getters and setters and provide your own implementations.
  • 01:13:56 So let's log out every time a new nickname is set.
  • 01:14:02 To do that. We go to our nipping declaration and then we'll go to the next line and then
  • 01:14:08 we'll space over four times. And now if we start typing set, we'll see auto-completion
  • 01:14:16 comes up with several options here. So I'm going to choose this bottom one. So what this
  • 01:14:21 essentially does is allows us to define the function behavior for wins set is called.
  • 01:14:28 Now when we do this, this will generate a backing field for this property. So to actually
  • 01:14:35 assign the new value to our nickname property, we need to use a special keyword called field
  • 01:14:45 equals value. If we didn't make this call, then the value of nickname would never actually
  • 01:14:51 be updated. And now we are free to implement whatever you want. So in this case we can
  • 01:14:58 update this with a log message that says the new make name is dollar value. So now if we
  • 01:15:10 go back over to our main, let's see what this looks like. So we're assigning one nickname,
  • 01:15:19 person that nickname that. Let's assign a nother nickname. In this case we'll just say
  • 01:15:26 new nickname. Now if we run this, we can take a look at the log. So you see here each time
  • 01:15:35 for assigning a value to the nickname property, our log statement is being run. Similarly,
  • 01:15:42 we can override the default Gether. We do this very much the same way. I'll start by
  • 01:15:52 saying get,
  • 01:15:53 there's no new value to set so there's no value being passed in. So instead we'll just
  • 01:15:58 lock this out. Say print line. The return value is dollar field. We still have that
  • 01:16:08 field backing value, which is what his storing the actual value of nickname. And then we're
  • 01:16:16 going to return the value of field. So now we'll come back over to our main, we'll use
  • 01:16:24 a print statement here, person dot nickname and if we run this, we're seeing that our
  • 01:16:38 center is being called multiple times. Then our getter is being called and the value logged
  • 01:16:46 out. And then finally our print statement here in the main function. Now that we've
  • 01:16:52 explored class properties, let's take a look at how we can add a method to our person class.
  • 01:16:58 To add a method, we really just need to define it function within our class declaration.
  • 01:17:03 That's great. A method called print info. It'll take no parameters
  • 01:17:11 and it's just going to print out the user's info. So in this case we'll use a print statement
  • 01:17:17 and then we'll use a string template to pronounce the first name, the nickname, and the last
  • 01:17:22 name. So we go back over here to our main class. Let's go ahead and remove most of this.
  • 01:17:31 Now if we want to call the method on our person variable, we can type person dot and then
  • 01:17:38 print info. So now if we run this, we see Peter, no Parker. So our method worked, however,
  • 01:17:53 the formatting is maybe not quite what we would have wanted because nickname was no
  • 01:17:58 like print info was called, we printed out the word no rather than anything possibly
  • 01:18:04 more useful. So let's refactor this method a little bit and see if we can improve that.
  • 01:18:11 So that's great. A variable called nickname to print. And then let's check whether or
  • 01:18:21 not this is no. So we can say if nickname does not equal no, we'll go ahead and use
  • 01:18:28 the nickname else. We'll use this more descriptive string of no nickname and now we can update
  • 01:18:38 this implementation and instead of using nickname directly, we'll use this new local variable.
  • 01:18:45 So now if we go over to our main again and we run this, now we see our output is formatted
  • 01:18:53 a little bit better now while the output now looks better, this expression right here is
  • 01:19:01 a little bit verbose. This type of check where we're comparing whether or not something is
  • 01:19:07 no and then providing one of two values comes up quite a bit and Kotlin and because of that
  • 01:19:14 there's actually a convenient syntax we can use that simplifies this expression. So what
  • 01:19:20 we can do is this like maybe question Mark Colon, no nickname.
  • 01:19:32 The question Mark Colon is what's known as the Elvis operator and Caitlyn, what this
  • 01:19:38 expression is saying is check what's on the left side of the Elvis operator. If that side
  • 01:19:45 of the expression is not no, then go ahead and return that. Otherwise return what is
  • 01:19:51 ever on the right hand side of the expression. So if we go back to Maine and run this once
  • 01:19:58 again, well now see that we're still getting our updated output. So this case, the Elvis
  • 01:20:05 operator is just a much more concise way of doing that. If else check. Now I want to take
  • 01:20:12 a minute and talk about visibility modifiers within Kotlin. Looking at this code here,
  • 01:20:18 you'll see nowhere do we have any type of visibility modifier specified. However, if
  • 01:20:25 we go over here to our main, we're able to create a new instance of this class. We are
  • 01:20:30 able to call the print info method and we are able to access all of the properties.
  • 01:20:40 This is because in Kotlin classes, properties, methods, really visibility in general is public
  • 01:20:48 by default. If we wanted to modify the visibility of any of these, we can add one of for visibility
  • 01:20:55 modifiers. So from the class we could add public here. However, because it's public
  • 01:21:02 by default, this is not needed. We could add internal. Internal means that this class is
  • 01:21:11 public within the module. So in our case, because we're in a single module, this doesn't
  • 01:21:17 change anything. We can also make this private.
  • 01:21:22 Once we make it private, we'll now see that it's no longer available in our main dotK
  • 01:21:30 T file and this case a private class is only available within the file in which it's implemented.
  • 01:21:41 Now we get to apply similar rules to our nickname property. If we make this an internal property,
  • 01:21:50 nothing changes and we can still access that. If we make this protected and go back to our
  • 01:22:00 main function, we'll now see that we're getting an air cannot access nickname. It is protected
  • 01:22:06 in person. A protected property or method will only be available within that class or
  • 01:22:15 within any subclasses. And as you might expect, if we make this a private property, once again,
  • 01:22:22 we cannot access it from our main dot KT file. And the same goes for our method. If we make
  • 01:22:31 this private or protected, it's not going to be available within main bat. K T now that
  • 01:22:37 we have an understanding of how classes work in Kotlin, let's take a look at how interfaces
  • 01:22:45 work. So we'll go back to our source directory, go to new Kotlin file or class. This time
  • 01:22:53 in the kind drop down, we'll select the interface and let's go ahead and call this person info
  • 01:23:02 provider and we'll hit okay.
  • 01:23:05 So now the IDE has created a person info provider dotK T file and it's auto generated this empty
  • 01:23:16 person info provider interface for us. Now, like with the class, because the curly braces
  • 01:23:23 are empty, we can actually remove those and this is a completely valid interface within
  • 01:23:29 Kotlin. It's MD. There's no methods that can be implemented and there are no properties
  • 01:23:36 that can be implemented. However, this could still be used as a marker interface, for example,
  • 01:23:42 in other classes, could in fact implement this interface. In fact, why don't we do that
  • 01:23:48 right now? Let's create a class called the basic info provider, the implements person,
  • 01:23:56 info provider. We can actually do that within the same file. We don't need to have one file
  • 01:24:03 per class or interface within Collin. So to start we can say class basic info provider.
  • 01:24:13 Now we want to indicate that this class is going to implement person and vote provider.
  • 01:24:18 To do that we'll use a colon and then we'll type the name of the interface and just like
  • 01:24:26 that, we've now created a new class basic info provider that implements person info
  • 01:24:31 provider. And because person info provider does not currently have any methods or properties,
  • 01:24:38 basic info provider has nothing that needs to implement. Oh, let's add a method to our
  • 01:24:43 person. Info provider interface can do that. We'll come back up to the interface declaration,
  • 01:24:50 we'll add back our braces, and now we're going to define a function signature within this
  • 01:24:57 interface. Now we don't have to actually implement this, we just have to define the name and
  • 01:25:02 the parameters that are required by this method. Now once we've added this, we'll notice down
  • 01:25:08 below now that our basic info provider class has a compiler error saying that it does not
  • 01:25:15 implement the required interfaces or add the abstract keyword.
  • 01:25:21 So let's take a look at how we can address this issue. Could you, so we're going to start
  • 01:25:27 off by adding a main function so that we can play around with this class. Now what are
  • 01:25:33 the ways that we could solve the compile issue with basic info provider is by declaring it
  • 01:25:39 as an abstract class. This means it doesn't need to implement all the methods available
  • 01:25:46 on the interfaces that includes, but it also can't be instantiated. So if we tried to come
  • 01:25:52 down here and say vow provider equals basic info provider, we'll get an error saying cannot
  • 01:26:02 create an instance of an abstract
  • 01:26:04 class. So this case we don't want to make this abstract cause we do want to work with
  • 01:26:08 this class so we can remove the abstract class keyword and that we want to actually implement
  • 01:26:15 the required methods from person info provider. So to do that we can start typing print info
  • 01:26:22 and the IDE will recognize that. And if we hit enter, it will generate a step down version
  • 01:26:27 of that print info method.
  • 01:26:31 Now let's take a look at how this was generated. We see that it starts by including the override
  • 01:26:38 key word. This is different than in Java where it was an override annotation. And Caitlyn,
  • 01:26:44 if you remove the override keyword, it'll actually give you a compile error in this
  • 01:26:50 case saying print info hides member of super tight and needs the override modifier. So
  • 01:26:56 it's very specific in indicating that you do need to include that override. And then
  • 01:27:02 after that it's simply matches the rest of the method declaration from the interface.
  • 01:27:09 So here we're now free to define the behavior of this interface, however we want you also
  • 01:27:16 seen down below that now that we have implemented the interface fully, we can actually create
  • 01:27:22 an instance of this class. So if we implement this right for now, just printing out print
  • 01:27:31 info,
  • 01:27:32 yeah,
  • 01:27:33 we can come down to our main function, we can type provider doc and then we can invoke
  • 01:27:39 the print info method and we'll pass it in a empty instance of the person class and we'll
  • 01:27:50 see here that it executes that print info method
  • 01:27:54 [inaudible].
  • 01:27:55 So that's a very simple example of how we can define an interface to find a method on
  • 01:28:00 that interface, implement it, and then run it on that. Implementing class. Let's improve
  • 01:28:05 upon the implementation of print info. So here we're going to say basic info provider
  • 01:28:16 and then below that we're actually going to call the print info method on our person class.
  • 01:28:24 So now if we run this, we'll see that we have that basic info provider being printed out
  • 01:28:32 and then the info from the person. Now perhaps we want to encapsulate this logic within the
  • 01:28:41 interface itself. Maybe this print info method, it should always generally work in this same
  • 01:28:47 way. Well, we could actually move the implementation that we've just defined right here up into
  • 01:28:57 our interface C and Kotlin interfaces provide default implementation of an interface method.
  • 01:29:06 So now we can actually remove the implementation of print info from basic info provider and
  • 01:29:14 the code will still compile and run.
  • 01:29:16 So now if we run this, we're going to get the same output. However, there's an issue
  • 01:29:21 with this. We see now in our person info provider interface, we are including the basic info
  • 01:29:27 providers string. Well, we probably don't want that since it is an implementation detail
  • 01:29:33 of basic info provider. So here we could actually leverage another interesting feature interfaces
  • 01:29:39 in Kotlin. We can provide properties on our interfaces as well as methods. So we'll define
  • 01:29:45 a property called provider info of type strength. Now you might be tempted to give this a default
  • 01:29:54 value, but if you do, you'll see that we actually get a compiler error saying property initializers
  • 01:30:01 are not allowed to interfaces. So you will in fact have to override this and any implementing
  • 01:30:08 class. But now that we have this provider info string, we could modify our print info
  • 01:30:15 default implementation to print out that provider info.
  • 01:30:18 So now we've kind of encapsulated this logic into the interface itself. And then the basic
  • 01:30:26 info provider class can now just override that provider info property. And we override
  • 01:30:33 a property in much the same way as a method. So we'll use override vow provider info type
  • 01:30:42 string and then we have to provide the getter. So in this case we'll say basic info provider.
  • 01:30:54 And now if we run this once again that we'll see that we are picking up the overwritten
  • 01:30:59 property value and then still relying on the default implementation of print info in person
  • 01:31:06 info provider. And now if we wanted to still override print info we could absolutely do
  • 01:31:14 that and we could call through to the super implementation if we would like and then we
  • 01:31:19 can print out anything else here and if we were in this one last time we'll
  • 01:31:32 see that we are now relying on the property, the default implementation of print info as
  • 01:31:38 well as now our additional logic and the overwritten implementation of print info. Next up, let's
  • 01:31:45 look at how we can implement multiple interfaces with a single class. To start we'll add a
  • 01:31:51 new interface called session info provider
  • 01:32:00 and then we'll add a method to those called get session ID and that will return a string.
  • 01:32:09 And so now if we come down to basic info provider, we want to make this class implement session
  • 01:32:16 info provider as well. Well I'll be asked to do is to add a comma after the previous
  • 01:32:24 interface declaration and now add session info provider as well. And now once we do
  • 01:32:32 that we'll now see you basic info provider telling us that we don't implement the required
  • 01:32:36 methods so we can come down here and implement get session ID and we can return some session
  • 01:32:46 ID. Now down here on our provider class, we can now see that we can call get session ID
  • 01:32:54 on our basic info provider instance. Now's a good time to talk about how type checking
  • 01:33:00 and typecasting work in Kotlin. To do this we're going to create a new function here
  • 01:33:07 called check types and we're going to take a parameter of type person info provider.
  • 01:33:19 Now let's say that we want to check whether this person info provider is also an instance
  • 01:33:27 of a session info provider. How about we go about doing that? Well we can say if info
  • 01:33:35 provider is session and vote provider and then we'll print that out. Say is a session
  • 01:33:46 info provider. Otherwise print Ellen, not a session info provider and now we will call
  • 01:33:59 this check types function and we'll pass in our provider variable. So now if we run this
  • 01:34:08 we'll see is a session invoke provider printed out to the console. So this conditional was
  • 01:34:15 able to determine that the past in info provider was also an instance of a session in both
  • 01:34:22 provider. Now if we wanted to flip this logic and check that it is not a session info provider,
  • 01:34:29 we can add an exclamation point before that and then we'll just flip these print statements
  • 01:34:36 here and now once again if we run this we'll see is a session in both providers.
  • 01:34:48 So you have the flexibility there to check that either way. Now let's take a look at
  • 01:34:52 how typecasting works. So within this else block we've already checked that info provider
  • 01:35:00 is a session info provider. So we can cast it and then call methods on it as if it was
  • 01:35:07 a session info provider. So we could say info provider as session info provider. The as
  • 01:35:16 is the keyword used to cast something to another type doc, get session ID. So now we're able
  • 01:35:24 to cast info provider is that session and from a provider and call any methods or access
  • 01:35:30 any properties on it that are specific to session info provider. Now Caitlyn also includes
  • 01:35:36 what is known as smart casting, which means that if the compiler can check a type and
  • 01:35:41 validate that that type will not change, then you don't need to do any additional casting.
  • 01:35:47 So in this case we've already validated that info provider is a session info provider.
  • 01:35:53 So we don't actually need to explicitly recast this. We could say info provider dot. Get
  • 01:36:00 session info. And the compiler is performing a smart cast for us. So here we can access
  • 01:36:07 get session ID or other properties and methods on the session info provider without having
  • 01:36:14 to explicitly cast it each time.
  • 01:36:19 We've never seen how a class can implement multiple interfaces as an example of our basic
  • 01:36:25 info provider. Let's now take a look at how a class can inherit from another existing
  • 01:36:33 class and override methods and properties on that base class. To start, let's create
  • 01:36:40 a new file called fancy info provider. Within this file we're going to create a new class
  • 01:36:51 called fancy info provider. We didn't want this class to extend the basic info provider
  • 01:37:01 that we already defined. So we can do that by adding a colon and then typing the name
  • 01:37:10 of the class that we want to inherit from in this case basic info provider. Now as soon
  • 01:37:17 as I do this, you may notice that we have a red squiggly line here indicating an error.
  • 01:37:23 The error says this type is final, so it cannot be inherited from this is a characteristic
  • 01:37:29 of classes in Kotlin by default and Caitlyn classes are closed, meaning they cannot be
  • 01:37:35 inherited from or extended. To extend this basic info provider class, we meet to add
  • 01:37:45 the open keyword by adding the open keyword, it now means that you can inherit from this
  • 01:37:53 class. So if we go back to our fancy info
  • 01:37:56 provider, you'll now see that our error has gone away and we can now override methods
  • 01:38:02 and properties in this class. Now let's start by overriding the provider info property.
  • 01:38:08 So we'll add the opening closed curly braces to our class definition and then I can start
  • 01:38:15 typing provider info and you'll see that the IDE is suggesting the property available to
  • 01:38:21 us to overwrite. So I'll hit enter and that will go ahead and auto complete the property.
  • 01:38:28 Now notice it has the override modifier indicating that this property is being overridden and
  • 01:38:36 I noticed that it automatically provides a custom getter and you'll see that it defaults
  • 01:38:42 to deferring to the super implementation of this. So we could actually override this just
  • 01:38:50 like this by saying fancy info provider. If we were to then come back to our main function
  • 01:39:00 here and replace this with an instance of fancy info provider and we rerun this, what
  • 01:39:09 mousey is printing out fancy info provider so that provider info is being correctly overwritten
  • 01:39:17 in our new extended class.
  • 01:39:20 Now let's try overwriting the print info implementation in our fancy info provider class. So if I
  • 01:39:27 start typing print info, once again, we'll see the IDE suggesting the method that can
  • 01:39:33 be overwritten. I'll hit enter and again by default this will call through to the super
  • 01:39:38 implementation of print info within basic info provider. And so I can then add another
  • 01:39:45 line here that just maybe says something like fancy info. And if I come back and run my
  • 01:39:54 main function and that we'll see the base implementation is the basic info provider
  • 01:40:00 implementation. And now this extra line added by our implementation of fancy info provider.
  • 01:40:05 Now I want to illustrate one last point in regards to inheritance, but before we do,
  • 01:40:12 let's refactor basic info provider a little bit. Instead of hard coding the session ID
  • 01:40:18 here, let's add a property to hold that value. So we'll come here and we'll say Val and we'll
  • 01:40:27 say session IB prefix, let's say equals session. And now we roll return session ID prefix right
  • 01:40:41 here in our implementation of GIP session ID. So now if I come into fancy info provider,
  • 01:40:49 I want to override that new session info prefix.
  • 01:40:54 So to do that I might start typing session and you'll notice that it's not auto suggesting
  • 01:41:02 that new property that we just added. This is because to overwrite a property and a derived
  • 01:41:09 class, you have to Mark that property as open. This is just like extending a class so we
  • 01:41:17 can come here to session ID prefix and add the open modifier as soon as we do that. If
  • 01:41:26 we start typing once again, now we'll see it's suggesting the option to override session
  • 01:41:31 ID prefix. So just like the provider info property, I can now override this and I can
  • 01:41:40 say fancy session. So this is just one other way in which Kotlin works to enforce immutability.
  • 01:41:48 It forces you to Mark both your classes, your properties, and your methods as being explicitly
  • 01:41:56 open for extension. Now there's a small problem with this new session ID prefix that we've
  • 01:42:02 added.
  • 01:42:03 It's really meant to be an implementation detail of the class. However, if we come here
  • 01:42:10 to our call site where we're using a fancy info provider variable, you might notice that
  • 01:42:17 we can actually access that prefix directly. This isn't ideal because like I said, it's
  • 01:42:23 an implementation detail. Our API shouldn't really be exposing that property. Now the
  • 01:42:30 reason it's available is because we have defined it as a public property. So what can we do
  • 01:42:37 about this? Well, if we want it to be available and our child classes but not to the public
  • 01:42:43 API, we could add the protected modifier. So now that property is protected down here,
  • 01:42:53 when we try to access it, we get an error saying cannot access session ID prefix. And
  • 01:43:00 if we come back to fancy info provider, you'll see that we can still override that property
  • 01:43:05 without any trouble.
  • 01:43:09 Now that we've explored how we can extend an existing named class, let's look at how
  • 01:43:14 we can create an instance of an anonymous interclass using an object expression. To
  • 01:43:20 do that. We'll come over to our main function here and now instead of instantiating an instance
  • 01:43:27 of fancy info provider, we're going to create an anonymous interclass. So we'll delete that.
  • 01:43:34 And to start off to create our object expression, we can type object, colon and then the name
  • 01:43:42 of the class that we want to extend. In this case it'll be person info provider. Now within
  • 01:43:49 this class we can
  • 01:43:52 override any available properties or methods. So in this case I'll update the provider info
  • 01:43:59 and just say something like new info provider. Now notice below here that our provider dot
  • 01:44:09 get session ID call is now being marked as an error. That's because there is no guest
  • 01:44:15 session ID on person info provider. But we could go ahead and add a new method to our
  • 01:44:23 object expression here. So we can just say fun, get session, I ID equals and then we'll
  • 01:44:32 just put in a value here. So you see you can not only override the existing properties
  • 01:44:39 and methods, but you can add to them as well. Just like you could in any other name to class.
  • 01:44:45 And now if we run this code, we'll see new info provider being printed out to the screen.
  • 01:44:52 So an object expression allows you to create an anonymous inter class so you don't have
  • 01:44:57 to create a new named class. So this might be useful for things like a click listener.
  • 01:45:03 If you were working in, let's say, Android development.
  • 01:45:09 Now that we've explored object expressions, we're going to now look at companion objects.
  • 01:45:14 And to do that, we're going to create a new file and we're going to name that file entity
  • 01:45:19 factory. Now imagine we want to create a factory to create instances of something called entity.
  • 01:45:30 So to start we might create an entity class and maybe that class will have a single ID
  • 01:45:39 property to start. Now we want to make this a factory like we said. So what we might want
  • 01:45:45 to do is change this constructor to be private. And so now if we add a main function and we
  • 01:45:53 try to create an instance of entity, we'll see that we have an issue here. Well notice
  • 01:46:02 that there is this error saying cannot access in it. It is private to entity. So this is
  • 01:46:07 because of that private constructor. Well, so what can we do?
  • 01:46:12 This is where a companion object could come in handy. A companion object is an object
  • 01:46:19 is scoped to an instance of another class. So within our block body here, we can type
  • 01:46:26 companion object. Now we could create a create function called fun create and we'll have
  • 01:46:38 that simply return an instance of entity. And for now we'll just pass it in a placeholder
  • 01:46:44 ID. So now we can come back down to our main function and we can type entity dot companion
  • 01:46:54 dot create. And we can use this to create an instance of that class. This works because
  • 01:47:01 companion objects have access to private properties and methods of that in closing class. Now
  • 01:47:10 in this case, we can actually shorten this by removing the reference to companion altogether.
  • 01:47:18 That new companion is implicit and if you're using it from Kotlin, you can leave it off.
  • 01:47:24 However, if you were using this companion object from Java, you would have to reference
  • 01:47:29 that companion object instance directly. You can also rename your companion object. So
  • 01:47:35 if we wanted to name this something like factory to be a bit more explicit, we can then say
  • 01:47:41 doc factory and reference it that way. And so again, not needed from Kotlin but it could
  • 01:47:50 be a good way to make your code more understandable from the Java side of things. If you're doing
  • 01:47:56 a lot of Java to Kotlin interrupt, we can also store properties within our companion
  • 01:48:02 objects as well. So in this case we can create a const thou well
  • 01:48:06 ID equals IB and then we can come down here and replace our entity and pass that in. Now
  • 01:48:16 that we have this ID property added to our companion object, we can reference it from
  • 01:48:21 other calling code as if it was a static property like we're familiar with from Java. So we
  • 01:48:27 could do that by typing entity dot. And then we can reference that ID property directly.
  • 01:48:34 Now competing objects are like any other class and that they can also implement other interfaces
  • 01:48:40 to demonstrate that we'll create a new interface called ID provider with a single method this
  • 01:48:50 called get ID. It will return a string. Now then come down to our companion object declaration
  • 01:49:00 and we can make it implement ID provider the same way we would with any other class. We
  • 01:49:07 can then choose to implement the required members and then here we will just return
  • 01:49:12 a simple ID and so now when we create our instance of ID, we could rely on this ID method
  • 01:49:19 if we want it. So you see companion objects can be quite flexible if you need them to.
  • 01:49:25 You could use those to compose other types of behavior, store your semi static properties
  • 01:49:32 or methods and use them to create factories by referencing private inner properties or
  • 01:49:39 methods of the enclosing class. This is really what you would want to leverage if you want
  • 01:49:43 functionality similar to that of static members
  • 01:49:48 and field from the world of Java. Now that we've covered object expressions and companion
  • 01:49:56 objects, let's take a look at creating an object declaration. To start, we're going
  • 01:50:01 to clean up some of this code we've been working with so we will remove this implementation
  • 01:50:06 of ID provider and we will go back to using a placeholder ID. We'll remove this reference
  • 01:50:15 to entity ID and we can remove this ID provider interface. Now what our object declarations
  • 01:50:21 and object declaration is a convenient way of creating threads saved singletons within
  • 01:50:28 Kotlin. We can do this by using the object keyword and then a class name in this case
  • 01:50:35 entity factory. Now within this you can add any types of properties or methods that you
  • 01:50:43 would like. So let's start by migrating our create method from our companion object into
  • 01:50:52 heart entity factory and now we can remove that companion object and instead we can reference
  • 01:51:01 entity factory dot create.
  • 01:51:05 Now there's one small problem with this so far, which is that entity still has only a
  • 01:51:09 private constructor. Now we're going to remove that private modifier for now so that we can
  • 01:51:16 use that constructor. However very shortly we will continue to refactor this code to
  • 01:51:22 limit the ways in which entities can be created. Now before we go on and continue to explore
  • 01:51:28 some of these other class types in Kotlin, let's add to our entity class by implementing
  • 01:51:35 two string so that if we print out an instance of entity, we get some nice user readable
  • 01:51:42 text. So we can start typing two string. And then I'll use a string template here and we'll
  • 01:51:52 say ID colon is ID. And then we will also add in a name property here, Val name of type
  • 01:52:05 string.
  • 01:52:08 And then we'll say name and then substitute in that main property. And then here in our
  • 01:52:15 create method we will just put in a generic name. And now down here we can use a print
  • 01:52:24 line statement and pass in our instance of entity. And now we see our new two stream
  • 01:52:33 texts being printed out to the console. So this will help us going forward demonstrate
  • 01:52:37 some of how these other classes are going to work. All right, now that we can print
  • 01:52:44 out useful information about an instance of an entity, let's refactor our create factory
  • 01:52:51 method to actually differentiate instances of entity. So to do this, we're going to change
  • 01:52:57 this and make it no longer a single expression function. So we will add a return type of
  • 01:53:04 entity and then we'll add a return keyword. And that will, we'll add entity. Now the first
  • 01:53:10 thing you want to do here is actually add a proper ID value.
  • 01:53:15 So here you could say Val ID equals you do ID dot random U U ID dot two string. So this
  • 01:53:27 will give us a new random identifier and then we can pass that into our entity. But now
  • 01:53:33 we have this main property. So what can we do to pass in a name here? Well one thing
  • 01:53:39 we might do is think about differentiating between different types of entities. So in
  • 01:53:45 a very basic case, maybe you want to differentiate between easy, medium, and hard difficulties
  • 01:53:51 of these entity types. So we might want to then have some way of passing or indicating
  • 01:53:58 to this factory method, what those different types should be. So what am I, we could do,
  • 01:54:04 this is with an ENM class. Now if you're familiar with Java and Enim class is going to be very
  • 01:54:10 similar to what you're familiar with from [inaudible] in Java. To do that, we can start
  • 01:54:15 typing email and then class. And then in this case we might name this something like entity
  • 01:54:22 type and open and closed curly braces. And then we can iterate the different instances
  • 01:54:29 of the email. So in this case we might say easy, medium, hard. So now we can come down
  • 01:54:39 here to our create method and then we can add a type parameter of entity type.
  • 01:54:48 And so now we could say vow name equals when type. And then we are going to add in the
  • 01:54:59 remaining branches. So now we have a branch for each of our entity types. And then for
  • 01:55:05 a basic name, I'm just going to map these to a string. So say easy, medium and hard.
  • 01:55:17 And so now I can pass in that name. So now our factory method actually allows us to differentiate
  • 01:55:24 and create different types of instances. So down here we might start off by creating an
  • 01:55:31 easy entity and then we'll print that out. And then we might say vow medium entity equals
  • 01:55:39 entity, factory dot. Create entity tight medium. And then we can print that out as well.
  • 01:55:54 And if we run this well, now see that we have a unique identifier for each entity. And then
  • 01:56:00 we have the customized name based on that entity type. So the addition of this ENM class
  • 01:56:07 to represent our entity type has allowed us to pass in different types to our factory
  • 01:56:12 method and then customize the way that those entities are created by mapping the entity
  • 01:56:18 type to a name. Now in this case, we're mapping the name very closely to the name of the actual
  • 01:56:25 class itself. So to make this a little bit easier and more encapsulated, there's a couple
  • 01:56:31 of things we could do. So the first thing we could do is take advantage of the name
  • 01:56:36 property on an ITAM class. So to do that, we could reference our type dot nay. So this
  • 01:56:45 is referencing the name of that actual [inaudible] class. And if we run this, we can see what
  • 01:56:51 that name looks like.
  • 01:56:52 So you see it's easy all in capital letters. This matches exactly the way that the class
  • 01:57:00 name is actually defined. So this allows us to reference the classes name directly without
  • 01:57:06 having to map it manually. Now this is nice, however, we don't have a lot of control over
  • 01:57:12 the formatting here. So another thing we could do is actually add a new method to argue in
  • 01:57:19 class. So in this case we get add fun, get formatted name, and then we can reference
  • 01:57:27 that named property.to lowercase dot capitalize. So this will return us that preform at a name
  • 01:57:38 and capitalize the first letter. So now down here we can update our medium mapping and
  • 01:57:46 type type dot get format in name. And so now if we run this code again, we'll see that
  • 01:57:55 the first one by using the name property directly is all capitalized. But now by using our new
  • 01:58:00 format and method, we have a nicer format similar to what we were using before. So that's
  • 01:58:06 just one example of how you can define an Enon class and then add additional properties
  • 01:58:12 and methods that class like you would any other class.
  • 01:58:16 Now let's continue refactoring this code to further differentiate between different types
  • 01:58:24 of entities. To do that, we're going to leverage a sealed class seal classes allow us to define
  • 01:58:31 restricted class hierarchies. What this means is that we could define a set number of classes
  • 01:58:37 all extending a base type, but those classes will be the only ones that can extend that
  • 01:58:43 base type. So one example of this could be a loading state or results state for a network
  • 01:58:50 operation. It's either going to succeed or fail and there aren't really any other options.
  • 01:58:56 So in the right place we're going to create a sealed class with an easy, medium, hard
  • 01:59:02 and help entity types. To start creating our sealed class hierarchy. We're first going
  • 01:59:07 to remove the properties from our entity class as well as this existing override of the two
  • 01:59:15 string method. The next step is to add the sealed keyword before the class keyword in
  • 01:59:22 the entity class declaration.
  • 01:59:24 As soon as we do that, we'll start getting an error above where we tried to create an
  • 01:59:29 instance of entity. This is because you can't instantiate based sealed class type directly.
  • 01:59:36 So this is now where we will create each type within our sealed class hierarchy. So the
  • 01:59:43 first type we're going to create is a David class to represent easy entities. And then
  • 01:59:51 we will add the properties we want in this case the ID and name, and then we want to
  • 01:59:55 make sure that we inherit from entity. So next up we can copy that and update the name
  • 02:00:05 for the medium type. And now for the third type, once again we'll copy that, we'll name
  • 02:00:12 this hard, but now we're going to add an additional property. This property will be called multiplier
  • 02:00:19 and we'll be afloat and this can represent some type of difficulty, multiple fire if
  • 02:00:23 we were creating a game for example.
  • 02:00:26 Now notice that all of these types within the sealed class all extend from entity but
  • 02:00:32 have different types of properties. This is one of the key differentiators between sealed
  • 02:00:36 classes and [inaudible] classes. With seal classes, you can have different properties
  • 02:00:41 and methods on each of these type and the compiler can perform smart casting to allow
  • 02:00:46 you to use these different properties and methods as you would like. We can also use
  • 02:00:52 different types of classes itself within our sealed class. So you notice that these are
  • 02:00:56 all created as data classes. However, if we wanted to remove data from one of these, that
  • 02:01:02 would be perfectly fine. We could also use object declarations within our seal class
  • 02:01:08 hierarchy. So this case will create an object class called help to represent some type of
  • 02:01:14 generic static help entity within our program. Now because help doesn't have a constructor
  • 02:01:23 because it's static. In this case, we can add a class body and we could add a name and
  • 02:01:32 add help directly. And in this case, more ad ID. Since it's a Singleton and there's
  • 02:01:37 only going to ever be one instance anyways,
  • 02:01:40 now that we have our seal classes defined, we're going to update our factory method to
  • 02:01:44 instantiate and return different types of entity classes. So we'll come up here to our
  • 02:01:51 return statement and instead of returning an entity directly, we're going to use a wind
  • 02:01:56 expression based on the entity type being in class. We'll then add all of the needed
  • 02:02:05 branches. And so when we have an easy type, we want to instantiate an instance of the
  • 02:02:11 easy class. So to do that we'll type D Z and then we will pass an ID and name. And similarly
  • 02:02:21 for media type, entity dot. Media ID, combat name. And now for hard, once again we'll pass
  • 02:02:30 it and entity dot hard ID name. But now again we have this additional property type and
  • 02:02:39 the compiler recognizes that. So for now we'll just pass it in to F as our multiplier.
  • 02:02:44 Now notice though that we have this help entity being unused. So let's update the factory
  • 02:02:51 to allow us to create instances of the help type. So we'll come up to our entity type
  • 02:02:59 Unum class and add a help type here. Now notice as soon as we added that additional type on
  • 02:03:06 the entity type in them class are when expressions one to us that we need to add an additional
  • 02:03:13 branch. So to do that, I'll add the remaining branch here and I'll default to typed up get
  • 02:03:22 format name. And once again below here I'll add the remaining branch. And in this case
  • 02:03:29 I'm just going to return help directly.
  • 02:03:33 Oh, notice here that help is giving us an error. It's saying required and to be found
  • 02:03:40 entity that help. This was done to demonstrate what happens if you do not extend from the
  • 02:03:46 base entity type. So if we come down to our entity class here and you notice our object
  • 02:03:52 declaration for help, if we then add a colon entity to extend from entity, we'll now see
  • 02:04:00 that error go away. So this is a good example of how the compiler can help give us this
  • 02:04:06 nice static type checking and all of these things. And if we are combining even classes
  • 02:04:12 was sealed classes with these, when expressions get allows us to be sure that if we add a
  • 02:04:18 new type or a new branch somewhere that we have to handle that effectively because the
  • 02:04:23 compiler will warn us or even give errors if we're not handling all of those different
  • 02:04:28 branches. Now let's come down to our main function and demonstrate one of the advantages
  • 02:04:33 of representing our entities as a seal class hierarchy.
  • 02:04:38 So if I remove everything, but this first instance of creating an entity, I'm going
  • 02:04:45 to specifically add a type here of entity. And so now if we, if we come down again, we
  • 02:04:53 can use a one expression and we'll say, now we can go down here and use a wet expression
  • 02:05:04 to do some type checking about the entity that we have just instantiated. So here we'll
  • 02:05:10 say Val, message equals when entity. And now again we're going to rely on the IBE to add
  • 02:05:20 all the remaining branches. So there's a few things of interest to note here. So we'll
  • 02:05:26 see that for an easy, medium and hard, it's adding. This is check. So this is going to
  • 02:05:31 basically tell if it's an instance of that class or not. But then notice for the help
  • 02:05:37 class, because that's an object declaration and as a Singleton there's no need to have
  • 02:05:43 it is.
  • 02:05:44 So in that case we can reference that class directly. And so now here we could add whatever
  • 02:05:51 message we wanted. So we could say help class, easy class, medium class and hard class. And
  • 02:06:08 then if we simply print that message out and run the code, we can see in this case we're
  • 02:06:18 getting an easy class. And then if we change what we pass into our factory method and rerun
  • 02:06:25 this, well now see that we're getting the help class. So now we have static type checking
  • 02:06:32 both and specifying what type of entity we want back and and checking the type that we're
  • 02:06:38 actually getting back from that. And so we could use this to then call any methods or
  • 02:06:44 properties that are specific to that class. And if we were operating on these types as
  • 02:06:51 in a one expression here, if we ever added a new type, that compiler would be sure to
  • 02:06:57 make sure that we handled the addition of that new type.
  • 02:07:02 So now let's return to our sealed class hierarchy for a second and dive more deeply into what
  • 02:07:09 data classes are. So you see here both easy and medium and hard are all defined as data
  • 02:07:15 classes. Data classes are cotton's way of providing very concise, immutable data types.
  • 02:07:24 By defining a class as a data class, it means that it is going to generate methods such
  • 02:07:30 as equals hashcode into string automatically for you. What this allows us to do is perform
  • 02:07:37 a quality comparisons on instances of these data classes and treat them as equal if the
  • 02:07:44 data they contain is equal. So here's an example. Let's explore what this looks like. So we
  • 02:07:50 can say Val entity one equals entity factory that create and will create an easy entity.
  • 02:07:59 And then we're going to create another version of this. And then now we can check their equality
  • 02:08:08 comparison.
  • 02:08:09 So you can say if entity one equals entity two per DeLeon, they are equal else per Delon,
  • 02:08:23 they are not equal to. Now if we run this, what will we see? They are not equal. And
  • 02:08:33 that's to the expected. That's because if we come back up to our factory, we'll notice
  • 02:08:38 that we are creating different unique ideas each time. So even though that the name is
  • 02:08:44 the same, the unique ID is different. So now let's update this and see what it looks like
  • 02:08:50 if we pass the same data in. So in this case we could create an easy directly and this
  • 02:08:57 case will pass in ID comma name and then we will duplicate this for entity two. And so
  • 02:09:07 now if we run this, we're going to expect to see you. They are equal and of course they
  • 02:09:13 are. So this is really convenient. This allows us to represent data within our applications
  • 02:09:19 and compare this data no matter where it comes from.
  • 02:09:22 And as long as those properties are all the same, we're going to be able to evaluate these
  • 02:09:29 as true. Now another really interesting thing that data classes give us are effective copy
  • 02:09:36 constructors. So we can create an instance of entity two by copying entity one entity,
  • 02:09:44 one dot copy. And because this is a direct copy, if we run this once again, we're going
  • 02:09:51 to see they are equal. However, we could also use named arguments with the copy constructor
  • 02:09:58 to change the value. So let's say we only wanted to change the BAME and you could say
  • 02:10:03 name equals new name. And once again, if we rerun this, we're going to see they are not
  • 02:10:11 equal. So you could see changing a single property and the data class is going to impact
  • 02:10:17 whether or not two instances evaluate to true or not when compare.
  • 02:10:24 Now one thing to notice is this is comparing the value of the data themselves. If we wanted
  • 02:10:31 to U S referential comparison, we hit add a third equal sign here and this will check
  • 02:10:40 whether or not it's the exact same reference or not. So in this case they are not equal.
  • 02:10:46 However, this isn't all that surprising since the data was also equal. So what about if
  • 02:10:52 we revert this and make this an exact copy again? So before if we were just using two
  • 02:10:57 equal sign, the data would be the same. So it would print, they are equal. However, by
  • 02:11:04 using three equal signs and using referential equality, we see they are not equal. That's
  • 02:11:10 because it's not the same exact reference of the object. If we updated this to be entity
  • 02:11:15 one equal equal equals entity one and run this, now we'll see they are equal.
  • 02:11:24 So that's just one way in which we can check whether or not we have the exact same object
  • 02:11:29 or if it's two different objects that have the same data. Now also keep in mind that
  • 02:11:37 these equality comparisons are working off of the generated equals and hash code methods
  • 02:11:43 generated by the compiler when indicating a data class. However, we could update this
  • 02:11:51 to change how the equals or hash code is evaluated and to do that we would do it like any other
  • 02:11:59 class. We could add a class body and then we could simply override equals and or hash
  • 02:12:08 code. Now as in Java best practice, if you're going to override one of these, you should
  • 02:12:13 really override both of them and you have to follow the same rules, but you have that
  • 02:12:19 freedom if you would like to.
  • 02:12:28 Another really useful feature in Kotlin is the ability to define extension functions
  • 02:12:33 or extension properties on an existing class. This is particularly powerful if you're working
  • 02:12:40 with classes that you can't control but would like to modify the way in which they're used.
  • 02:12:46 You can define your own properties and methods and defined kind of a new API around the existing
  • 02:12:52 class. So an example of this would be adding a new method to the medium class without actually
  • 02:12:59 defining that method within the definition of the medium class. So to do that, let's
  • 02:13:05 come down here and you can start off by typing the fun keyword. And then instead of directly
  • 02:13:12 typing the method name, we can reference the class name. Dot. And this case will type print
  • 02:13:23 info.
  • 02:13:27 And then we can define our function buddy. So in this case we'll just say medium class
  • 02:13:37 with the ID and that'll be it. And so if we wanted to come down here and now create an
  • 02:13:46 instance of entity dot medium directly, we could do that. And then we could call that
  • 02:13:59 print info method. And if we run that code, we'll see a medium class and then that ID
  • 02:14:05 printed out. So this is great if we know that we have that exact type that we're working
  • 02:14:11 with. And in cases where we don't know if we have that direct type, we could rely on
  • 02:14:16 smart casting. So if we update this to you, their factory say entity factory, create entity
  • 02:14:25 type medium. Now we can say if entity two is medium entity, now we can reference that
  • 02:14:37 new print info method.
  • 02:14:44 This is done because the if statement will only evaluate to true if that cast is successful.
  • 02:14:50 So anywhere within that context it will automatically perform the smart cast for us. And like I
  • 02:14:58 said before, not only can we define extension methods, but we can also define extension
  • 02:15:04 properties as well. To do that, we could start off by saying Val or VAR. In this case we'll
  • 02:15:10 say vow and then again we'll reference the class type. So medium dot we'll say info will
  • 02:15:21 be this property name string equals some info. If you do that, notice that we have this air.
  • 02:15:29 If you look, it says extension property cannot be initialized because it has no backing field.
  • 02:15:35 So to actually create an extension of property for an existing class, you need to rely on
  • 02:15:40 backing fields. Thankfully the IDE can generate this forest, convert extension property initializer
  • 02:15:46 to a getter.
  • 02:15:48 So once we do that and notice here that we have still defined our property but now we're
  • 02:15:53 relying on this custom getter for that property and so now if we come back down here within
  • 02:15:59 our, if statement that's doing our smart cast for us, we could reference that new info property
  • 02:16:06 directly. So this is how extension functions and properties work. You could use these anytime
  • 02:16:13 you want to add additional functionality to existing class. You might notice within the
  • 02:16:18 Kotlin standard library that many functions and operations work by using extension functions
  • 02:16:25 in classes be are particularly effective when using them with template ID types
  • 02:16:30 because it allows you to define the same common functionality across any type that matches
  • 02:16:35 that template. Now up until this point, we've covered a lot of things. We've looked at the
  • 02:16:42 basic type system of Kotlin, how to work with different variable types, how to work with
  • 02:16:48 basic functions and then diving into more advanced functional topics like named arguments
  • 02:16:53 and default parameter values. And then we took a deep dive into modeling data with Kotlin.
  • 02:17:00 So now I'm going to circle back to functions and specifically take a look at higher order
  • 02:17:04 functions and how to work with functional data types. Now what are higher order functions?
  • 02:17:10 Higher order functions are functions that either return another function or that take
  • 02:17:17 functions as perimeter values. Now much of Kotlin standard library is built on top of
  • 02:17:23 higher order functions and it's what really allows us to write highly functional code
  • 02:17:29 by leveraging that standard library.
  • 02:17:32 So let's take a look at how we can write our own higher order function. To start we have
  • 02:17:37 a new Kotlin file and we're going to define a new function. So we'll call this fun. And
  • 02:17:43 then we're going to call this print filtered strengths. And now the first argument to this
  • 02:17:50 is going to be a list of strings. So we'll call this list and then define it as list
  • 02:17:56 of string. And now the next thing we're going to do is define a parameter which will in
  • 02:18:02 fact be a function. That function will take in a string and return a bullying. We can
  • 02:18:10 then use that to filter out values in the past collection. So to define a function parameter,
  • 02:18:18 you could start off by defining the parameter name as usual. And this case, we'll name it
  • 02:18:23 credit kit, followed by colon. And now you have to define the type as you normally would
  • 02:18:30 to define a functional type.
  • 02:18:31 You can start by adding your parentheses. This will define the parameters of the function
  • 02:18:39 being passed in to your other function. So in this case we are going to take a string.
  • 02:18:46 He'll then add the arrow, and then you want to define the return type. So in this case,
  • 02:18:54 that will be bullying. And now we'll add the open and closed curly braces to define our
  • 02:18:59 block body. So now we have a parameter called predicate, which will be a function that takes
  • 02:19:04 in a string parameter and returns a Boolean. Now we can implement our print filtered strings
  • 02:19:11 function to make use of that predicate function to filter out any strings in that past list.
  • 02:19:19 So to implement this function, first off, we want to iterate over each string in the
  • 02:19:25 past list. So to do that, we could say list doc for each and now we will be able to iterate
  • 02:19:34 over each of those strings.
  • 02:19:36 So now what we want to do is evaluate the predicate for each stream in the collection.
  • 02:19:43 So we can call the predicate function in several different ways. So to start we'll say if,
  • 02:19:49 and then the easiest way to invoke the predicate is to simply say predicate open and close
  • 02:19:55 parentheses and pass in the parameter value. A parameter that is a functional type can
  • 02:20:01 be called as if it was a regular function. As long as you can satisfy the required arguments.
  • 02:20:06 So in this case we can say if predit kit returns true, then we can print out that string. Now
  • 02:20:15 to test this, we'll come down here and we will add a main function and we will say vow
  • 02:20:22 list equals list of, and then we can say something like Kotlin, Java C plus plus Java script.
  • 02:20:38 And now we could call print filtered strings pass in our list.
  • 02:20:45 And now we need to pass in a function as the second parameter to print filters, drinks.
  • 02:20:49 So we can do that by specifying a Lambda, and in this case we will say it starts with
  • 02:20:59 K. so this Lambda is going to evaluate to true if any of the past strings begins with
  • 02:21:06 a K. now if we run this function, we'll see only Kotlin print it out to the screen. If
  • 02:21:14 we were to update this to print things out, that started with a J, well now see Java script
  • 02:21:20 and Java. Now one thing to notice is it in our invocation of print filtered strings,
  • 02:21:26 we've passed our Lambda within the parentheses of that function in vacation. However, this
  • 02:21:32 is something that we don't have to do. As we mentioned earlier, we can take use of Landus
  • 02:21:37 syntax, which says that if the last parameter of a function is a function, you can specify
  • 02:21:44 that as a Lambda outside the function body. So we can restructure our function to look
  • 02:21:52 like this. We can pass in the list first and then specify or Lambda outside of the parentheses.
  • 02:21:58 So this is actually very similar looking to the for each function which we called up above.
  • 02:22:03 And in fact if you look at the implementation of for each is in fact a higher order function.
  • 02:22:13 The Lambda that we specify after invoking for each is a function which will operate
  • 02:22:19 over each string and that list. Now if we come back up here to our implementation notice
  • 02:22:25 we are calling the function parameter directly as if it was a regular function. So this works
  • 02:22:32 absolutely great in most situations. However, if we were to make this function, type a NOLA
  • 02:22:40 ball type by wrapping it in parentheses and adding new question Mark. Well now see an
  • 02:22:46 error in our implementation of print filtered strings. That error basically says that you
  • 02:22:53 cannot invoke that function parameter by using the parentheses directly. If it's a nullable
  • 02:22:59 type to get around this, we can make use of the invoke method on that functional type
  • 02:23:08 and then we can make use of the safe call operator and now, but updating this to do
  • 02:23:16 a safe invoke call on the predicate function.
  • 02:23:20 We can handle this rather not the predicate is no calling invoke will invoke the function
  • 02:23:28 just as it would any other indication of a function. So now down here nothing has changed
  • 02:23:35 and how we can call print filtered strings. However, we could also pass it in list and
  • 02:23:42 now we could pass in no as a no function. So we've seen how we can treat functions as
  • 02:23:49 parameters to other functions and these function parameters are really treated as tight. Just
  • 02:23:56 the same as let's say integer or string. Caitlyn has this idea of functional types. It's a
  • 02:24:03 first-class part of the language. This means that we could define a variable of a functional
  • 02:24:09 type and then pass that variable in any time. We needed a function parameter that matched
  • 02:24:15 that function signature. So an example of this might be something like vow credit kit
  • 02:24:24 and then we will define our function type to match that of our print filtered strings
  • 02:24:28 function.
  • 02:24:29 So in this case it'll take a string and return bullion and now we'll define our function
  • 02:24:40 the same way that we were doing it before. By saying if the string starts with aJ , go
  • 02:24:45 ahead and return true. Now instead of invoking print filters, strings with a landed pass
  • 02:24:52 to it, we can pass in our predicate variable directly. And now if we run this, we'll see
  • 02:24:59 the same output as we would before. So this allows us to store function as variables.
  • 02:25:06 This can be really useful for things like optional input handling. For example, maybe
  • 02:25:12 you have a view on some screen and you want to be able to specify a ClickList center for
  • 02:25:17 that view. You could define that as a Lambda property on some class and allow client code
  • 02:25:25 to set that ClickList center as needed. As we mentioned before, higher order functions
  • 02:25:30 include functions which take other functions as parameters, as well as functions that return
  • 02:25:36 other functions.
  • 02:25:38 So let's define a function called get print predicate and it'll take no parameters, but
  • 02:25:48 we defined its return type as a function which takes a string and returns a bullion. And
  • 02:25:58 now we can return that value by saying return. And then we could pass a Lambda and say it.
  • 02:26:07 That starts with J. So we're passing essentially the same type of Lambda that we've been using
  • 02:26:13 in these other examples. But now we've wrapped it in this other function and so now and so
  • 02:26:19 then passing predicate directly or instead of defining a new Lambda as our function parameter,
  • 02:26:28 we could instead call get print predicate as a function on its own, which will then
  • 02:26:34 return a function which then can be used as the predicate for print filtered strings.
  • 02:26:40 And if we run this once again, we'll see that our output hasn't changed though. So higher
  • 02:26:48 order functions can work as both inputs and outputs and Kotlin allows you to define properties
  • 02:26:55 with functional types.
  • 02:26:57 So through this function's really become a very powerful and first-class part of the
  • 02:27:02 language that can start to replace a lot of other instances. For example, you might find
  • 02:27:07 yourself relying more heavily on functions to define things like event or a ClickList
  • 02:27:13 centers rather than defining concrete interfaces for those same types of functionality. Now
  • 02:27:20 this was recently mentioned. Much of the Kotlin standard library is built around higher order
  • 02:27:27 functions and especially a higher order functions defined with generic types. So if we look
  • 02:27:33 at the implementation of four each, well notice that this is actually an extension function
  • 02:27:41 as well as a higher order function. So for each works on generic Iterable type and takes
  • 02:27:48 in a function parameter that takes in that generic type and returns unit. So this essentially
  • 02:27:56 allows us to iterate over each element in the collection and then call that action on
  • 02:28:03 it and it doesn't have to return anything.
  • 02:28:06 And similarly for each index takes in a single function parameter as well. But this one takes
  • 02:28:12 in an event to represent the index as well as the generic type. This allows us to iterate
  • 02:28:19 over each element in the collection while incrementing a counter and then passing that
  • 02:28:24 counter into the function parameter as the index. The power of generic types, extension
  • 02:28:32 functions and higher order functions allows us to write single implementations of these
  • 02:28:37 methods and then reuse them over any type that we can think of. Now this is very powerful
  • 02:28:43 and can allow us to write much more functional code without having to redefine these methods
  • 02:28:51 and functions for all of our different types. So let's take a look at example of how we
  • 02:28:56 can combine some of these different functional operators to perform complex operations with
  • 02:29:03 very little code. We'll come into this new main function here and we'll start off by
  • 02:29:07 defining a list of strings.
  • 02:29:10 Once again. Now let's look at some ways in which we can chain these functional operators
  • 02:29:16 together to do more interesting things. So as we've seen before, we can do a simple for
  • 02:29:23 each to iterate over each item in this collection and print it out. And if we run it, we'll
  • 02:29:31 notice that we see all of the programming language printed out to the console. Now what
  • 02:29:36 if we wanted to print out only the strings that start with J plus similar to the functions
  • 02:29:43 we were working with before, we could do that by making use of a filter operation. So we
  • 02:29:51 have a lot of options to choose from. In this case, we will just choose a generic filter
  • 02:29:56 and then we will use a predicate which says it starts with J and now if we run this was
  • 02:30:06 he, he had only Java and Java script printed out. Now, what if our collection included
  • 02:30:12 some no values?
  • 02:30:14 So as soon as we add, no, we see now here in our filter operation, it's warning us that
  • 02:30:22 Hey, this value might be no, you need to add a safe call weld in Kotlin. Oftentimes we
  • 02:30:28 don't want to work with no, we want to try and hide no as much as possible. And so we
  • 02:30:34 could make use of another functional operator called filter not know. What this does is
  • 02:30:44 immediately filter out any no values up front. So everything past that in the functional
  • 02:30:49 chain will be guaranteed to be not. No. So as soon as we added filter, not know, we no
  • 02:30:54 longer had to deal with a possible no string. And if we run this once again, we'll see only
  • 02:31:01 Java and JavaScript printed out.
  • 02:31:05 Now what if we wanted to change the type of this? Let's say we wanted to convert this
  • 02:31:10 from a string to an integer, which represents the length of that input string. We could
  • 02:31:17 do this type of transformation using a map function. The map function will take in whatever
  • 02:31:24 the previous type is in this case string, but it'll allow us to return any other type
  • 02:31:29 we want. So in this case, we might define our map function as simply returning the length
  • 02:31:37 of the string. As soon as we've done that. Now below that in the for each, the type has
  • 02:31:44 changed from string to end. And now if we print this out, we'll see four and 10 printed
  • 02:31:52 out for representing the four characters in Java and 10 representing the 10 characters
  • 02:31:58 in Java script. Now let's remove this mapping and let's remove the filter. And instead,
  • 02:32:06 let's imagine that we want to take only a certain number of items from this collection.
  • 02:32:13 So we can do that by using the take function and passing in. Let's say three. What that'll
  • 02:32:21 do is we'll take the first three items from that collection and then we'll be printing
  • 02:32:26 out each of those three names. So you see in this case we're getting Kotlin, Java and
  • 02:32:31 C plus plus. Alternatively, if we didn't want to take the first three elements in the collection,
  • 02:32:38 we could use take last today, the last three. So in this case we see Java C plus plus and
  • 02:32:46 Java script and it has skipped over Kotlin since that was not one of the last three elements.
  • 02:32:52 We can also do other transformations such as associating the input values with some
  • 02:32:58 other value to return a map. So let's create a map that essentially maps the string to
  • 02:33:06 the number of characters in that string. So to do that we could say associate, and then
  • 02:33:15 in this case we could say it to it dot length. And so now in our, for each function, instead
  • 02:33:25 of iterating over strings, we're iterating over map entries of string and event. So in
  • 02:33:34 this case we can now use a template string and say it got value comma it dot key.
  • 02:33:54 And if we print this out, we'll see the length comma followed by the name. This makes it
  • 02:34:01 really easy to map all of the input strings to some other value and then iterate over
  • 02:34:06 that map. Now, what if we didn't want to iterate over the map but instead just wanted to hold
  • 02:34:12 on to that in a variable? Well, instead of using a fork each at the end, we could assign
  • 02:34:20 this to a variable just like this. The continent standard library also provides a variety of
  • 02:34:28 functions to help us pull out individual elements from a collection to demonstrate that that's
  • 02:34:33 created a variable called language. And then we're going to perform different operations
  • 02:34:40 on our list to grab a single language string from our list. So we could do that in a number
  • 02:34:45 of ways. We could say list dot first and if we print this out, we'll expect to see Kotlin
  • 02:34:56 as that is the first language in the list.
  • 02:35:01 Alternatively, we could say we'll start last and in this case you'll see that it's actually
  • 02:35:09 printing out. No, since [inaudible] was the last value in that list. Now, if we didn't
  • 02:35:15 want to retrieve a null value from our list and instead wanted the Alaskan non-male value,
  • 02:35:22 once again, we could add the filter, not no function, which we used previously. And now
  • 02:35:31 if we rerun this, we'll see Java script printed out instead, since this is the last non no
  • 02:35:38 value. Now what if we wanted to find a specific item in the list? Let's say we wanted to use
  • 02:35:46 the find function and in our predicate we'll say it got starts with and we'll pass in Java
  • 02:35:55 as a street. So this is going to find the first value in this list that starts with
  • 02:36:01 Java. So in this case it actually returns us Java and alternatively we could use find
  • 02:36:09 last to find the last element in the collection that matches this predicate, in which case
  • 02:36:15 it's going to return JavaScript.
  • 02:36:18 Now what happens if we are searching for a string which doesn't match our predicate?
  • 02:36:25 We can test that by looking for a string which starts with food. If we then run this, we'll
  • 02:36:33 see no print it out to the console. This is because there is no matching string. So fine.
  • 02:36:39 Last is going to return. No. And then the print line statement, we'll print out. No
  • 02:36:44 if it has a null value. Well what if we didn't want to work with no? What if instead we wanted
  • 02:36:49 to use an empty string as the placeholder? Well, strings in Kotlin have a useful function
  • 02:36:58 called or empty. So we can actually chain that directly off of find last here and call
  • 02:37:05 or empty. So at this will do is return either a nano string or a static empty string. So
  • 02:37:14 now if we run this once again, instead of no, we're just seeing empty, we're not printing
  • 02:37:20 anything out.
  • 02:37:22 So this is one way in which you could default your collections or your strings to an empty
  • 02:37:28 value as opposed to a no value. And this is something you might want to consider doing
  • 02:37:32 more and more of in Kotlin as you start to move away from relying on null. So as we've
  • 02:37:38 seen, Caitlyn has first-class support for functions including functional types and higher
  • 02:37:44 order functions, and the Kotlin standard library builds upon those tools and provides a rich
  • 02:37:50 set of functional operators for us to use. This allows us to build powerful functional
  • 02:37:56 chains to transform our data and make complex workflows much simpler. All right, that's
  • 02:38:01 it for this tutorial. You now have a good understanding of the fundamentals of Kotlin
  • 02:38:08 and how to work with it, and you're now ready to start taking that knowledge and applying
  • 02:38:13 it to other domains. Until next time, devs.