Placeholder Image

字幕列表 影片播放

  • Hi, my name is Nate and welcome to this tutorial on the Kotlin programming language. If

  • you're not familiar, Kotlin is a statically type programming language developed by JetBrains.

  • It's been around since about 2011 and has steadily increased in popularity ever since.

  • Today it's actually the primary development language for Android and in this tutorial

  • we're going to move from basic topics like setting up your first project up to more advanced

  • things like modeling data and working with higher order functions. The goal for this

  • tutorial is to help you understand how the work with Kotlin so that you can then take

  • that knowledge and apply it to building applications across mobile, web and native code. So without

  • further ado, let's jump in and start building our first Kotlin project.

  • The first thing we're going to want to do is to install JetBrains IDE Intelijay so that

  • we can work with our Kotlin code on our development machine. The first thing to do is to open

  • up your browser and search for it. Until Jay here, you should see a download link and we

  • can click on that to be taken directly to the download page. Now I'll be working on

  • a Mac, but intelligent is available on windows and Linux as well. You'll also notice here

  • that there are two different versions of intelligence. There's the ultimate edition and the community

  • edition. The ultimate edition has a lot more features that we need and there's also a paid

  • product. The community edition is supportive for JVM development and Android development,

  • so it's perfect for what we want to look at in this tutorial. When you're ready, go ahead

  • and click the download button and that should start the download. Once the download is complete,

  • we'll get started on installing the IDE and we'll take a look at hello and Kotlin. Now

  • that our downloads complete, we can go ahead and onto the installer

  • and I'm Mac. We'll drag the application into our applications folder which will start the

  • install process here for us. Once the files have been transferred to your machine, we

  • can go ahead and start intelligent for the first time to start the install process. In

  • this case, we want to do a fresh install, so click that. Do not import settings and

  • hit okay.

  • Go ahead and accept the privacy policy. Hit continue. Now here you can choose your theme.

  • I'll choose the dark theme. Go ahead with the standard and tell the J key settings here

  • and we'll go ahead and click next through to finish the setup process and then we'll

  • finally launch intelligent IDE community edition.

  • The next thing that want to do is create our first Kotlin project here in intelligence.

  • So to do that we'll click on create new project. Now over on the left side of this panel, we'll

  • see a number of different project templates to choose from because we are going to be

  • learning about Kotlin. We want to make sure that we have selected the Kotlin template.

  • Now within this, there are a number of different Collin project types that we can choose from.

  • The default here at the top is a Kotlin module for a JVM target, so this would be sort of

  • a standard, just Kotlin project that could target a the JVM. I have nothing else added

  • to it. Other examples would be, you know Caitlyn module for a Java script target or I taught

  • in the native targeted or an Android or iOS mobile target. So all of those are interesting

  • but more advanced. For this, we want to just stick with the basics so we will make sure

  • that we have selected Kotlin module for JVM target and go ahead and hit next. Then we

  • want to make sure that we name our project something meaningful. So in this case, let's

  • call it hello Caitlyn. We'll make sure that our project location is set and then we'll

  • go ahead and select finish.

  • Now we have an empty Kotlin project. Let's start by adding our first common file so that

  • we can write a simple hello world example. So do that. We'll come over here to the left

  • and here we have the project panel within intelligent. You'll see at the top we have

  • our hello Collin module. If we expand that we can see that we have a source directory

  • but it's currently empty. That's right. Click on source, go to new and then Caitlyn file

  • our class. And here we're going to type main for the name of our file and then we will

  • leave this as file so that we can create our main dot KT file. Now we can get started by

  • writing our main function here in our main dotK T file. So to start we'll type in fun

  • Maine, no parameters and then we can type print Ellen parentheses.

  • Hello Collin. And from there you'll see now that we have this little green arrow intelligent

  • recognizes that this is an executable main function within this uh hello Caitlyn module.

  • So we can actually go ahead and run this. We'll select to run main KT and that'll take

  • a second to go ahead and build the project. And then we will get our output window here

  • at the bottom and you can see that we have hello Collin. Now one extra quick fun fact

  • here and tell Jay comes with a number of live templates built it. This means that you can

  • type something and it will know how to complete that and auto-generate some code for us. So

  • we can start typing Maine and then hit enter and it will automatically generate a main

  • function for us. And then we are free to fill in with our, hello Caitlyn text.

  • Awesome. You've just written your first program in Kotlin. Now let's dive in and start actually

  • learning the language to get started. Let's look at how you can define variables in Kotlin.

  • Now there's two types of variables. In Kotlin we can define mutable variables, which can

  • have their values reassigned. Those are declared using the VAR keyword. Or we can define local

  • read only variables, which can have their value assigned only once these are defined

  • using the vow keyword. So to define a variable, we can use the keyword of our choice. So in

  • this case we use vow, then we'll define the name and then we need to indicate that type.

  • In this case I'll say a type string and then you can use an equals and then you can assign

  • the value to this variable. So in this case I've defined a variable called name of type

  • string.

  • And then I have assigned the string literal Nate to that variable. Now, like I mentioned,

  • vow our assign once variables, meaning this is read only once it has a value assigned

  • to it. So if I tried to update the name of this, we'll see that we get an error. That

  • area says vow cannot be reassigned. So this is our indicator that if we need to be able

  • to update the value of our variable, we're not going to be able to use a vow. Instead

  • we can use a VAR. Now VAR variables can be reassigned as many times as you want. So as

  • soon as I updated that keyword, now I can update the value of our name variable. And

  • then if we want to use that variable, we can pass that in to the print function here. And

  • if we then run our main function, well now see the output value of that variable at this

  • time is blank because we had reassigned it.

  • If we remove that reassignment, well now see that we get this little underlying saying

  • variables never modified and can actually be declared immutable instead using vow. So

  • this is nice that the IDE gives us that helpful hint. So in this case we'll go back to a vow.

  • And now if we rerun this once again, well now see that my name was printed out to the

  • console and this example we're using a local variable, meaning that the variable that we

  • have defined is available locally within the current scope. So in this case it's available

  • only within the main function we have defined. Now in Kotlin you can actually declare variables

  • outside of any type of enclosing function. Our class, all you have to do is declare a

  • variable within a Kotlin file. So in this case we've moved our variable name outside

  • of the main function into the main dot. Kate. He file variables defined like this are referred

  • to as top level variables. And you see now that we've moved this outside of the main

  • function, we can still run it and our variable is still completely valid.

  • Top level variables can be worked with just like a local variable. So we get a fine and

  • an additional variable. This time we'll say VAR greeting of type string equals hello.

  • We now have a publicly available top level variable called greeting. So now if we wanted

  • to come down here and print out a greeting before the name, we could do that by calling

  • print line and passing in greeting. We can also modify that greeting because this is

  • a mutable variable and if we then print out this new value, we will see that the message

  • has changed.

  • So when we hit run here, we should now see hello Nate. And then hi mate, let's take a

  • closer look at how variable types are defined. So in defining our variables, in both cases

  • we use either the voucher or VAR keyword and then we have the variable name. Both of these

  • are must haves. Next step we have the colon and then the type. So in this case we have

  • the type of string and then we assign our value. One interesting difference between

  • Java and Kotlin is that types in Kotlin are non null. By default this means that there

  • is a distinct difference between string and a nullable string. Now what does this look

  • like in practice? Well in this case because types are non all by default name is a nano

  • string. So if I tried to assign a no value to this, we'll actually get an error and it

  • will hopefully say no, cannot be a value of a non Knoll type string. So if you want to

  • actually have no as a valid value for your variable, you need to add a question Mark

  • after that string value. This indicates that now named as a knowable string. And I can

  • assign no or I can go ahead and sign an actual value. So let's look at how we could use this

  • with the greeting variable.

  • So if we make greeting a knowable variable, we can come down here and we'll go ahead and

  • print out the default greeting value. We'll print out the name and then we'll see that

  • we can modify the value of greeting to null. And then we can go ahead and print out these

  • values ones again and we can then see that greeting has been updated. And when we try

  • to print that out, it actually recognizes that it's no and prints that out to the console

  • for us. So let's look at how we can actually simplify the declaration of our variables.

  • I mentioned that vow or bar as well as the name are mandatory. However, Kotlin supports

  • type inferences on variables and properties. What does this mean? Well, this means that

  • Kotlin can recognize what type the value is that we're trying to assign to the variable

  • and if it can figure that out, we can actually omit a type declaration of the variable.

  • So in this case we could actually remove the colon and string and the name variable will

  • still be of type string. Now we can do the same for our greeting variable so we can remove

  • this. But if we come back down here and tried to assign knowl to greeting will get an error.

  • No, it cannot be a value of non node type string. So why is this? Well again, because

  • types are non nano by default and Kotlin and we are assigning a nano string literal to

  • greeting the compiler. In first the greeting is meant to be a non null string. So if you

  • want to have a knowable string you might try and just assign no. However we get a warning

  • saying implicit nothing type. This is because greeting now cannot really be inferred. It

  • doesn't know what is a null type of. So in this case we will need to go ahead and specify

  • a knowable.

  • Now down here we can assign Knowlton greeting or we could assign an actual greeting. Now

  • that we've taken a look at how to create variables and how the type system works, let's introduce

  • some basic control flow and we'll do this by working with the nullable string variable

  • of greeting. So let's go ahead and remove these values. Now let's say that we only want

  • to print out the value of greeting if it's not no. So we could do that in a couple different

  • ways. The first one we'll look at is using an if statement, if statements in Kotlin work

  • very much the same way as in Java. So we'll use the if keyword followed by parentheses,

  • we use greeting. In this case we want to say if greeting is not no printed out, so we can

  • say not equal to know and we'll finish it off with curly braces.

  • And then if we move our print statement within that, if conditional when we run this, we

  • should now have no greeting printed out. And sure enough we see that just the name is printed

  • and if we modified the value of greeting to be non null and then rerun this, we'll now

  • see that we have our greeting. Now what if we wanted to add and else claws? So just like

  • Java, we could add the else, we can add curly braces again. And so here we could define

  • a default greeting of hi. So now if we run this once again, greeting will be normal and

  • so we should see the default high value. Now another means of control flow within Kotlin

  • is the when statement. The Wednesday mint is very similar to a switch statement in Java.

  • So to create that we can come down and use the when keyword and then print the CS.

  • We didn't want to pass end to this, the value that we want to check and switch on depending

  • on its value. So in this case we'll pass in grieving followed by curly braces. Now within

  • this block we can define each value that we want to act on differently. So in this case

  • we can say no. And then to define what action to take, we use an arrow and then we'll say

  • print line, hi. And then we can define and else case here. And this will act as the default

  • if none of the values above are matched. So this case we will print out the value of greeting

  • on its own.

  • So now if we run this, we should see high printed out because greeting is still not.

  • And once again if we update greeting to be some other value and rerun, we'll see that

  • the branch of the, when statement is hit and we are printing out the value of treating.

  • So these are two of the basic ways in which you can manipulate control flow within Kotlin.

  • Now we just saw how if and when can we use as statements to take different actions based

  • on some checked value if and when can also be used as expressions to assign a value depending

  • on that, those logical conditions. So let's take a look at how we could use an expression

  • to assign a value to a local variable. So let's go down here and type vow greeting to

  • print. I can say equals. Now if the greeting variable in our top level declaration is non

  • no, then we want to go ahead and stick with that.

  • So we can say if greeting does not equal null, then we want to assign greeting to the new

  • greeting to print variable. Otherwise we want to say hi and then we'll update our print

  • line to use the local variable. Now when we print this out, we should see the ELs branch

  • of high being printed out. This is because the top level variable is currently no. If

  • we modify this to pass in a greeting. Now if greeting does not equal null or return

  • true and we'll see that hello is printed out instead. So if we want to assign different

  • values to a variable, depending on whether or not something is normal or some other logical

  • check, we could use an expression. However, we can also use a when expression if we want

  • it. So we can say when, and again, we'll pass in treating here and again we'll use our no

  • value as the first check and if it's no, we'll go ahead and return high. Otherwise we'll

  • go ahead and return the original greeting value. So now with this, when expression is

  • saying is assign the value of high to greeting to print. If greeting is know. And likewise

  • if greeting is nominal, go ahead and assign that value to the new greeting to print variable.

  • So if we run this one more time, we should see high because grieving is null.

  • And if we assign that value to greeting and rerun, we'll see that updated value of hello.

  • So again, like the if expression a when expression can be used to assign a value to a variable

  • depending on that input value that it's checking to start understanding functions in top line.

  • Let's look at this example. Within this example, we actually are using two different functions.

  • We have our main function and then we're also using the print LN function as well. So let's

  • take a look at how we can define our own new function. We'll start by clicking here and

  • we'll define the new function that is going to return this string that we want to print

  • out to the console. So the first thing to do when defining any function is to use the

  • fun keyword. This denotes that we are going to define a new function.

  • Then we want to specify the function name. So I'm going to call this function, get greeting,

  • and then you want to follow that up with open and closed parentheses. And within this we

  • can actually define any function parameters. Now for now we're going to skip any function

  • parameters and we want to define our return type for this function. So similarly to variables,

  • we can define a return type by using a colon. And then the type in this case string. And

  • then we'll do open and closed curly braces. Now like in Java we can define the return

  • value by saying return. And then in this case I'll say hello Kotlin. So now we have the

  • function called get greeting this green to return the string. Hello Caitlyn. If we want

  • to then invoke that function, we can do so by calling get greeting with open and closed

  • parentheses.

  • This should look very similar to what you're familiar with and calling methods from Java.

  • And now if we run our code, we'll see to print statements. We'll see the first one, hello

  • world. And the second one. Hello Collin. So one thing you may have noticed is that in

  • our gig greeting function, we're the return type of stream. However, in our main function

  • there's no return type specified. So why is this? Well let's illustrate this by an example.

  • Let's write a function called say hello. So again we'll use the fun keyword and it will

  • maim it. Say hello, no parameter values. And then we're going to declare this as returning

  • type unit unit. And Kotlin is essentially the absence of any useful type. It's similar

  • to saying this returns nothing useful. And the reason we're going to use unit in this

  • case is because we don't want to return anything.

  • We're going to simply print out our get greeting functions, return value, so we can then call

  • say hello. And you'll notice up here that unit is underlined and it's giving us this

  • message that says redundant unit return type. Basically what this is saying is that if you

  • have a function that returns unit, that is to say if you have a function that doesn't

  • return anything useful, you can omit that type value. So we can actually remove unit

  • from our say hello function and this is perfectly valid. So that's why in our main function,

  • we also don't have the return type specified. Aside from the rules around unit return types,

  • return types or functions work very similarly to how you would define them and treat them

  • for variables or properties. So for example, if we wanted to return a Knoll string from

  • our get greeting potion, we would modify the return type to be a nullable string and then

  • we could return note in our return statement.

  • Additionally, functions support type inference as well. So in our get greeting example here

  • we are returning a single string literal, in which case we could actually simplify this

  • to a single expression. We could remove the curly braces and add an equals and then the

  • string literal after that. So this is what's known as a single expression function because

  • the entire function definition is now in a single expression. And now this is where the

  • title inference comes into play. Again, because the compiler understands this single expression

  • function definition and it knows that it's always going to return a string. We can actually

  • remove the explicit return type. Now our get greeting is extremely simple and is functionally

  • equivalent to the previous definition. So if we run our code, once again, we'll see

  • that we now have our three different print statements. You might be noticing a theme

  • here of Kotlin allowing us to reduce the amount of code we need to write to get the equivalent

  • functionality. This is a theme that will crop up more and more as you start to learn the

  • language.

  • Now let's take a look at how we can define function parameters. Now before we get started,

  • let's clean up our code a little bit. So we'll remove everything from our main function and

  • we're going to go ahead and remove the existing function examples we've been working with.

  • So once again, we're gonna define a function named say hello and to start it will print

  • out. Hello Collin. Now this is exactly like we have seen before, but what if we wanted

  • to change the thing that we were greeting? So instead of saying hello Collin, maybe we

  • wanted to say hello world or hello mate, or hello John. So how might we do that? Well

  • that's where a function parameter comes into play. So to define a function parameter, we'll

  • go within the parentheses after the function name, and we'll define this using a notation

  • of the parameter name colon, the parameter type. So in this case we're going to say item

  • two Crete as our parameter name, colon. And then we want this to be a string value that

  • we're passing it. Now with dinner function, we can say, Val, message equals hello. Plus

  • I even went to greet.

  • Okay.

  • And then we could pass in the message. Now if we come down to our main function, we want

  • to, when folks say hello, so we can start typing, say hello. And now we need to pass

  • in a parameter value which it suggest to us in this little tool tip. So in this case we'll

  • say Caitlyn. And now if we run our main function,

  • we now see hello Kotlin is printed out to the console. If we wanted to pronounce something

  • else, we could duplicate the invocation and maybe this time we'll pass it in world. And

  • if we invoke our main function again, we'll now see hello Caitlyn and hello world printed

  • out to the console. Now if we go back up to our say hello function. Well notice that there's

  • this squiggly line here. This is unrelated to function parameters, but this is a really

  • interesting feature in Caitlyn. Caitlyn supports string templates which allow us to substitute

  • in variable values or argument values into a predefined string template. So in this case,

  • instead of using concatenation, we can say hello space. And then to define a template,

  • add value, please a dollar sign and then we can pass in that parameter name. So now if

  • we hit run once again, we'll see the same output as before.

  • So this is just one more way in which Kotlin can produce boilerplate for us by allowing

  • us to define these convenience string templates. In fact, in this scenario we can take it one

  • step further and remove the local variable all together. And then we can actually take

  • this one step further and define this as a single expression. Oh shit. Now we have a

  • say hello function that will take in a parameter value, which is the item degree. And then

  • it will always print out hello item degree. So now let's update this to take two parameters

  • so that we can customize both the greeting and whatever it is that we want to agree.

  • So to do that, we're going to add a new parameter and we will name this one in greeting and

  • have it be of type string. And now we will update our string template here to include

  • that new parameter.

  • Awesome. So now we've updated the function. Now we need to update the invocation of that

  • function. So for this first line we can say, Hey, common and now for the next one we'll

  • say hello world and if we read this well now see our desired output. So by adding that

  • second parameter value, we have now made our say hello function much more flexible. Now

  • we can add any number of parameters we want to say hello, but like any programming language,

  • if you have too many parameters in your function, it might be an indicator that your function

  • is doing too much. Now what last thing I'd like to point out about functions at this

  • time is that you'll notice that these functions are not defined within any type of enclosing

  • class. These are free functions or as they're referred to in Kotlin. These are top level

  • functions like variables, functions can be defined outside of any in closing class or

  • independent of any associated class. Now there are types of functions that are associated

  • with a class and we'll take a look at those later on in the tutorial.

  • Like most programming languages, Kotlin has support for collection data types. These are

  • things like arrays, lists and maps they can use to group values together and then operate

  • on them at a later time. So let's start off by looking at how we can define inner Ray

  • and Fallon. We'll clear out these invocations to say hello because we won't need them right

  • now, but we'll leave the say hello function definition because we'll come back to it later.

  • Your create a basic array. We'll create a new local variable named interesting things

  • and then we'll use the equal sign and then we can use a convenience function called array

  • of in parentheses. This will create an array of whatever the inferred type is and then

  • we can start defining values within this function. So in this case, as soon as I add a string

  • literal, it can infer that this is going to be an array of strings. And then we can define

  • some interesting things like Kotlin programming or comic books.

  • Now that we have this variable defined, let's see what types of operations we can perform

  • on it. If we start typing interesting things and hit dot the IDE will start to auto-complete

  • and show us some of the methods available. So you see we have a size property, we have

  • a get method, and we also have this open and closed of bracket syntax that we can use to

  • access individual elements in the array. Let's try printing out some of these values to demonstrate

  • how we can use the array to start. Let's print out the size. We can do that by saying print

  • LN and then we'll do interesting things that size. Now let's print out the first element

  • in the array. We can do that by going print, LN, interesting things. We can use the open

  • and closed bracket and then pass in an index. This is how we can conveniently index in fact

  • array.

  • This is similar to doing a jet, so if we duplicate that line, we could say get, and again, passing

  • is zero element of that array. If we now run this, we'll see three Caitlyn, Caitlyn, but

  • this is just as we would expect. Now, what if we wanted to iterate over all of the elements

  • of this array and then perhaps print out each of those values? Well, there are a number

  • of different ways we could do that. For now we'll take a look at a basic for-loop so we

  • could start typing for, and then we could say interesting thing in interesting things.

  • Then open and closed curly braces. This is now the convenient syntax of four loops within

  • Kotlin. So this is going to iterate over each value in the array and we can access those

  • values in the interesting thing variable that we have defined within this four loop.

  • So now we can type out interesting thing and if we rerun this code well now see that we

  • have printed out each element in the array. So that type of for-loop is what is probably

  • most similar to what you're used to if you're coming from Java. However, in Kotlin because

  • we have top level functions and higher order functions, really just first class of port

  • for functions across the board, we can write code that is a bit more functional. So let's

  • take a look at how we could do a more functional approach to collection iteration. So or remove

  • our four loop and now we could say interesting things doc for each. And what this is is invoking

  • a for each function that is available in the standard library. That function then takes

  • in a another function and returns unit. That function that we pass it in essentially defines

  • what to do on each iteration over this collection.

  • So within our curly braces here, this is where we can define what we want to do with each

  • element in contrasting things. Now this might look a little bit confusing at first, but

  • don't worry well explain this, but if we simply want to print out each item in this array,

  • we can now say print LN and pass in it. It is the default name for each element in the

  • array that is passed into this Lambda function in which we are defining. So if we run this

  • well, now see that we have our three elements in the array printed out to the console. Now

  • it is not always very readable. So another little quick tip here is if you want it to

  • be named something else, you can rename that to value that's passed into the Lambda. In

  • this case we could call it interesting thing and then we'll use the arrow and now instead

  • of if we can reference that value by calling it interesting thing and once again if we

  • run this, we'll see that our interesting things are printed out to the console.

  • You might be looking at this wondering why we are not using an open and closed parentheses

  • when calling the for each function. In fact, it might seem rather odd that we are not passing

  • in that argument two for each, but instead of have just specify this open and closed

  • parentheses independent of the rest of the for each call. So this is actually what's

  • known as Lambda syntax within Kotlin. Now we'll look at how to implement this later.

  • But the idea behind Lambda syntax is that if you have a function and it's only parameter

  • is another function, then you can omit the parentheses all together and you can pass

  • that function in by specifying this open and closed parentheses. So again, if we look at

  • for each, we'll see that it takes a single function as a parameter so we can omit the

  • parenthesis values and past that function into the for each function using the open

  • and closed curly braces.

  • And like I said, we'll look at how we actually define this type of higher order function

  • a little bit later in the tutorial. So here we looked at a basic for each function on

  • this array collection. But by doing it this way, we've lost the index data for whatever

  • index the current interesting thing is in the containing array. So to handle that there

  • is another function we can call. So once again we'll say interesting things and this time

  • we'll say for each indexed. Now this time it's going to pass into us the current index

  • as well as the current string. Now once again, we'll update this to be named interesting

  • thing and that one's again, we could print out these values. So we can say print,L ,N

  • and we can say interesting thing is at index.

  • And now if we print this out, we'll see that we have gotten the value from the array as

  • well as its current index. So this could be really useful if you need to iterate and still

  • maintain that index data. Now everything that we've been looking at here for res is applicable

  • for lists as well. So if we clear out some of this previous code, we now go to our declaration

  • of this interesting things variable. Now we're using the convenience function array of to

  • define this variable as an array of type string. Now there's also a list of function as well.

  • Now if we try to work with interesting things, we'll see that we have a lot more methods

  • to choose from because it's now a list rather than an array. And so like an array, we can

  • access individual elements by using a jet or also by using the bracket syntax like we're

  • familiar with with arrays as well.

  • And also like with the array, we have functions available to us to help with integration.

  • So if we wanted to print out all of the interesting things again, once again we can say interesting

  • things doc for each ad. Once again we'll say interim vesting thing. We use the arrow here

  • within our Lambda expression and then we'll print out the interesting thing and if we

  • hit run while that we have our three interesting things printed to the console. Now that we've

  • looked at arrays and lists, let's take a look at one more collection type and Kotlin which

  • is map. So let's remove this duration over our interesting things variable here. Now

  • let's create a new variable. I'll just name this map equals and once again there is a

  • map of function that we can use. Now the map of function will essentially take in pairs.

  • Pair is a simple wrapper class containing two values and that there is also the convenience

  • function to create pairs.

  • So if you want to create a basic key value map we could do so like this. We'll use a

  • key of one and then we'll use two and then the value in this case will be a, and then

  • we'll define it. Another pair, we'll use a key of two. Then we'll use the two function

  • and then a value of B. And then we'll define one more pair. And we'll say three is our

  • key to C. so what we've now done is defined a map with three pairs of values in it. The

  • keys are one, two and three, and the associated values are a, B, and C. now we can iterate

  • over this by saying map for each and it's going to return to us both the key and the

  • value. Unfortunately the default to it, not

  • very useful name. So this case we'll remain them again within our Lambda expression. So

  • we'll say key value and then we can print these out and we'll use a string template

  • and we'll just say key and then we'll define an arrow just for some separation and then

  • value. And now if we print this out, well now see that we're giving each of our key

  • and value pairs and then we could do with those whatever that we need to. We've seen

  • how you can define several different types of collections such as arrays and lists and

  • maps. And we've also seen how you can iterate over those collections and access individual

  • elements from our collection. And there's an interesting thing to point out about the

  • way Kotlin handles the collections similar to the way in which it differentiates between

  • knowable and nano types. Caitlyn also differentiates between mutable and immutable collection types.

  • Now what does this mean? This means that by default a collection type in Kotlin is immutable

  • so that you can't add or subtract values from that collection once it's initially created.

  • So let's look at an example of this. We have defined our interesting things list using

  • the list of function here. And if we wanted to try and add something to interesting things,

  • there's no function available to us to do that. That's because it's immutable by default.

  • If we wanted a immutable list, we could use the mutable list of function. Now if we want

  • to add something, we can say interesting things. Dot add and we could add a new string to our

  • interesting things list. The same goes for map. If we wanted to add a new key value paired

  • wire map, you could say map doc put, but there's no put method available. But if we change

  • to immutable map, now we could say map dot put and we can define a new key of four and

  • a new value of D. so this is something to keep in mind. If you have a collection that's

  • going to be static, once it's defined, then you're fine to use the regular list of array

  • of map up, et cetera functions. And that is a good thing because immutability is often

  • a desirable trait in your code. However, if you're going to want to modify the values

  • in that collection, then you'll have to create a mutable collection so that you have access

  • to things like put or add that let you modify that collection.

  • Okay,

  • now that we have an understanding of working with collections, let's modify RSA hello function

  • to take a collection parameter so that we can greet multiple things, will modify first

  • the name of item to greet two items to greet because it's now going to be plural because

  • it will be a collection. And that will update from string to list of string. And then now

  • we're going to update the implementation of this function. So instead of being a single

  • expression function, we'll add a function body. And then now we're going to want to

  • iterate over the items to greet parameter. So we'll say items to greet dot for each.

  • And then we'll paste it back in our original print statement. And then we'll go ahead and

  • update the receiver value here from it to item to Crete. It'll add our arrow. And so

  • now we have a say hello function that you can pass a collection into. And then it'll

  • print out multiple lines. So now we can say, say hello and we can still pass in our custom

  • greeting so we can say hi. And then we can pass it in our interesting things variable.

  • And now if we click run, we now see high Caitlyn, hi programming and high comic books. So this

  • just a quick example of how you can pass it in a collection type to a function as a parameter.

  • There's nothing wrong with including a collection parameter in your function, however functions.

  • And Kotlin do provide an additional piece of functionality that can satisfy this use

  • case and provides a little additional flexibility. Now to demonstrate why this might be interesting

  • to us. Let's look at an example. So let's say we want to call say hello and we'll pass

  • on or greeting, but then we don't want to pass in any interesting things in this case.

  • Well, because of the way that this function is currently defined, we have to pass in the

  • second argument. So in this case, if we wanted to pass in no items, we would have to pass

  • in an empty list, which isn't really a big deal, but it's also not the most flexible

  • way of handling things. So let's take a look and alternative means of achieving this functionality.

  • If we come up here to our say hello function, we're going to modify this second.

  • So that is a VAR arch perimeter VAR ARG is a keyword in Kotlin. It essentially represents

  • a variable number of arguments. So in this case, instead of taking a list of string,

  • we'll define a VAR R of string. This tells the compiler that we're going to take a variable

  • number of string arguments after the initial greeting argument to this function. So now

  • if we try to pass something in to say hello, well first pass in our grieving and now we

  • don't actually have to pass anything in after the initial argument. This is because the

  • [inaudible] parameter will essentially be treated as an array of whichever type it's

  • used to specify. So in this case, items to GRI is now an array of type string. So if

  • we don't pass any items after the greeting, it will be treated as an empty array. If we

  • did want to start to pass items, we can do that by separating them with commas. So it

  • could say Kotlin and now this would be an array of size one. But where the real flexibility

  • comes is we can now start to define many argument values here.

  • And so now all of those arguments that are passed in will be grouped together, treated

  • as an array. And so in our function implementation, we can still iterate over all the elements

  • in that array. So if we now run this, we should get the same outfit as before. So by using

  • our VAR arc parameter, we've eliminated the need to always pass in a value after the initial

  • greeting argument and lets us have greater flexibility because it will support zero one

  • or any other number of argument values to be passed it. Now it's very convenient to

  • be able to pass multiple arguments to this [inaudible] hard perimeter. However, you're

  • usually not going to be hard coding those arguments in manually during compiled time.

  • More likely you're going to get a array of values from a network request or a database

  • and then you're going to want to pass it those in. So you might think that it would be as

  • simple as passing in an array after that initial greeting. So let's try that. We could change

  • list of two array of, and then after I,

  • we'll pass in interesting things. Oh, unfortunately this does not work. And if you look at the

  • air, the see a requires string found array of string. So how do you actually pass in

  • an array of existing values to this far ARG perimeter? Well, you can do that with the

  • spread operator and all the spread operator is, is applying the asterisk before the array

  • variable when you pass it in as an argument value. So now if we hit run, we'll see that

  • the compiler is now accepting that array and we are iterating over each item in that interesting

  • things array. So this is how you can pass in an existing collection as a VAR ARD parameter.

  • Another really interesting and powerful feature with Kotlin functions are named arguments.

  • Now let's take a look at an example of what name arguments provide to us. Let's start

  • by cleaning out our main function and then we're going to define a new simple function

  • that will take a greeting and a name and then print that up.

  • So now when we want to call this new Greek person function secret person, hi, and then

  • I'll use my name here. Now this is fine and it's very easy to understand because the ID

  • is helping us and showing, okay, this is the greeting argument. This is the name argument.

  • However, if you are in a code review, you might not be able to know exactly which order

  • these arguments are supposed to be passed in. Also, if you wanted to modify the function

  • signature of Greek person down the line, you'd have to make sure that these are in the same

  • order because since they share the same type, you could mix that order up without getting

  • any type of compiler pair. Now what made arguments allow us to do is specify which parameter

  • this argument value is going to be used for. So what does that actually look like in practice?

  • Well, it looks like defining the name of the parameter and then an equal sign. And then

  • here we can say main equals. And so now we're saying very explicitly assigned, high to greeting

  • and Nate. To me, the cool thing that this allows us to do is actually mix up the order

  • of these arguments. So now we can actually pass the second parameter first and the first

  • parameter second so that we could actually theoretically modify the signature of Greek

  • person changing the order of these parameters and it wouldn't impact the invocations of

  • that function. Caitlyn allows us to take this flexibility one step further by leveraging

  • default parameter values. So once again, let's look at our Greek person example. So here

  • we are now able to pass the arguments in whatever order we want. If we're using name arguments

  • in tax, but what if we wanted to pass main first and then not even passing the greeting?

  • Well now we get an error because it says no value past for perimeter greeting. So as great

  • persons currently defined, it must take both arguments, even if they are in a mixed up

  • order. Default parameter values allow us to change that. It allows us to tell the compiler

  • what the default value should be if not as specified. So for greeting, we could provide

  • a default value of hello and for name we'd get provided default value of Kotlin. You'll

  • see now great person can be called by only specifying a single argument value. And if

  • we run this, we'll see. It's going to say hello mate. So it's giving the default greeting

  • value and then it was using the value for the name that we passed in now because both

  • arguments have defaults, we could actually call this without passing any arguments in.

  • And if we run it now, we'll see it's using both defaults and prints out.

  • Hello Kotlin. Now this becomes a really powerful feature because now we can not only mix up

  • the order in which we pass arguments, but we don't even have to pass all of them in.

  • This actually allows us to replicate functionality of the builder pattern without actually having

  • to write getters and setters and have private constructors and all of that. We can configure

  • and reuse functions and objects by leveraging these default values and the named arguments,

  • syntax, Wilde, Decaux parameter values, main argument and VAR. Our parameters are really

  • convenient and flexible and powerful. They do have limitations as well. So I want to

  • illustrate one of those limitations. So we're going to go back to our say hello function.

  • Let's redefine our interesting things are right. And so now if I want to invoke, say

  • hello and I want to pass things in order with the greeting and then the interesting things

  • I can do that no problem. And if I run this, we'll get our three lines of output.

  • And so now what if we wanted to use named arguments in techs? Well we could do that

  • as well. Breathing equals high. However, as soon as I add the name argument syntax to

  • the first parameter, I get this air saying mixing name and position arguments is not

  • allowed. So this is one of those limitations. As soon as you use named arguments in tax

  • for what argument, everything that follows, that must also be named. So in this case,

  • I could fix this by saying items to treat equals and now I can run this again and I'll

  • get the desired three lines of output once again. Now I could mix these up though

  • and because both of them are using names, argument syntax, there are no problems here.

  • And once again, we could run this and we would get our desired output.

  • Now we're going to take a look at how we can create a simple class in Kotlin. Now up until

  • this point, we've been working within a single main dot K T file. However, now that we're

  • going to move into classes, let's go ahead and add a new file. So we'll come over to

  • our project panel, right click source, go to new Kotlin file or class, and we're going

  • to come down and on this dropdown we're going to select class and then we're going to name

  • this class person and then hit enter. We can see here, then it automatically has created

  • a person class for us and I might notice that this class is quite simple. So let's take

  • a look at how this class actually works. To start, we have the class keyword followed

  • by the class name and then really that's it. We could actually even optionally remove these

  • curly braces. Since we're not defining any properties or methods at this time, if we

  • wanted to then use this class, we could return to our main function here. And then we can

  • create an instance of the class like this. So we'll create a variable named person equals

  • person. Now this syntax right here is how you create a new instance of a class. Notice

  • there's no new keyword and Caitlyn, you do not have to explicitly call new. You can simply

  • specify the class name and then the constructor and any arguments that you need to pass into.

  • It can may notice

  • that we were able to create an instance of the person class using this empty constructor.

  • However, if we go back to our class definition, we don't have any constructor defined. This

  • is because when you're defining a class in Claplan, if you do not have any properties

  • defined in your primary constructor or any arguments defined in your primary constructor,

  • then you can actually just omit that primary constructor altogether. So what we see here,

  • class person is really a shorthand form of this. If we wanted to explicitly define this

  • primary constructor, we can do so by adding the constructor keyword and then the opening

  • closed parentheses. You'll see here that it actually gives us a message recommending that

  • we remove the empty primary constructor. Now we could also modify this primary constructor

  • by just removing the constructor keyword and moving the open and closed parentheses directly

  • after the classmate. However, we still get that same recommendation to remove the empty

  • primary constructor. So let's add a primary constructor once again. And this time let's

  • actually define a parameter that must be passed into this constructor. So if we're creating

  • a person in class, let's pass in a first and last name for this person. So we could say

  • first name string, last name string.

  • So now we have two unused parameters that we pass it into the constructor. And now if

  • we come back here to the creation of an instance of our person class, we'll see that we now

  • have a compiler error saying no value pass for our printers. So I'll go ahead and I'll

  • pass it in my first and last name here so we can come back here and we're not actually

  • doing anything yet with these perimeters. So let's change that. Let's define our first

  • property on our person class. So since we're passing in first name and last name, let's

  • define properties for first name and last name. So we can say Val, first name street,

  • thou last name street. Now you notice that both of these, now I have red areas underneath

  • them saying property must be initialized or be abstract. And there's a couple of different

  • ways that we can initialize these.

  • The first way we'll look at is using and then hit block can define it in a net block by

  • using the unit keyword and then open and close curly braces. And a net block is a piece of

  • code that is run anytime. An instance of this class is run and you can actually have multiple

  • admit blocks that will be processed in the order in which they are defined within your

  • class body. So within this a net block we can initialize our property values using the

  • parameters from our primary constructor. So we'll say first name equals underscore, first

  • name, last name equals underscore, last name. Now we have initialized properties. But if

  • we look up at where those properties are declared, we will see these little warnings saying,

  • can be joined with assignment. What does that mean? Well this is the other way in which

  • we could initialize these values.

  • We could actually get rid of the NIC block here and we could initialize these at the

  • point where they're declared by saying equals underscore first name equals underscore last

  • name. So now we're passing in those parameters to the constructor and then immediately declaring

  • and initializing properties on the class. So now if we go back to our usage of this

  • person class, after we create the instance of person, we can now access those properties.

  • Jax as the properties where you type person. Dot and then we can access the properties

  • by their names directly. So we can say last name or person dot first name. Now you noticed

  • that we're not using a getter here in Kotlin. This is known as property access syntax. You

  • can reference properties directly by their name without having to worry about the getter

  • or the setter. So now if we go back over track class, we could actually simplify this even

  • a little bit more. And to do that we'll go ahead and remove these properties.

  • And so now instead of passing in a parameter to the constructor and then defining a separate

  • property that mirrors that parameter, we can actually declare the property directly and

  • the primary constructor. So to do that, we'll come up here, we'll remove this underscore

  • since this is now going to be the property name and then we'll add the vow keyword. And

  • now when we have defined a first name and last name properties within our primary constructor

  • directly, and if we go back to our usage, we see that nothing has changed here. We can

  • still initialize the class in the same way and still access our last name and first properties

  • the same way. So far we've been working with the primary constructor within our class declaration,

  • but it's also possible to define what are known as secondary constructors. Secondary

  • constructors can provide alternative means for you to instantiate an instance of your

  • class.

  • So let's work with an example. Let's say we want to create a secondary constructor that

  • takes no parameters so that we don't have to always pass values in what we want to create

  • a new person object. So to create a secondary constructor, we'll use the constructor keyword

  • and then open and close parentheses. And in this example we're not going to pass in any

  • parameters. We didn't need to call through to the primary constructor. To do that we

  • use colon and then the this keyword open and closed parentheses. And then now we need to

  • satisfy any parameters that are declared in the primary constructor. So in this case,

  • let's define some default first and last name values. So for our first name we'll use Peter

  • and last name. We'll use Parker.

  • Okay.

  • And then we can define a body for the secondary constructor. And to just take a look at how

  • this works with the NetBox. Let's go ahead and add a print statement here that says secondary

  • constructor

  • [inaudible].

  • Well then add and then that block, and we'll put a message here that says, and that one.

  • And then just for fun, let's add a second, a net block after the secondary constructor

  • and we'll print out in it too. Now let's run our main function and just see what happens.

  • [inaudible]

  • so in this case we're using the primary constructor so we can specify and explicit values for

  • the first and last name. So we'll see that the secondary constructor is never called,

  • but both admit box are called and log out to the console. So now let's remove the explicit

  • arguments that are being passed in. And now let's rerun this

  • [inaudible]

  • and now this time we'll see that I didn't block one is run and Nickboch two is run and

  • then our secondary constructor was run

  • [inaudible].

  • So what this shows is that the admit blocks are always going to run before the secondary

  • constructor. Now the Invitbox will execute in order in which they're defined within the

  • class body and the secondary constructor will be called. Now in this example, and actually

  • in many practical examples when using Kotlin on real projects, a secondary constructor

  • isn't strictly necessary because of the power of default parameter values. So in this case

  • we can remove all of this within our class body and instead we can define default values

  • here in the primary constructor.

  • Okay.

  • Now if we go back over to our usage, we can still use the person class as if it had this

  • empty primary constructor because both parameters have default values.

  • Now let's look a bit more closely at class properties. Now we've already defined two

  • properties within our primary constructor. Both of these are read only properties so

  • they have no center, but they do have a getter available. That Gitter is how we are able

  • to leverage property access and tax and reference those properties directly as we are doing

  • here in our main function. Let's explore this more fully by adding another property. Let's

  • add a nickname property. In this case we'll use VAR because it's not going to be set initially.

  • We'll call it nickname string and we're going to go ahead and make this a notable string

  • and then we'll go ahead and set that initially to know. Now let's see if we type person.

  • Dot. We see that we have a nickname property, but unlike last name and first name, this

  • is a mutable property.

  • So we can actually assign a value to this. So we can say equals. And then my nickname

  • growing up was shades. So assign that string to this nickname property. So if we go back

  • to our person class, let's look at this property a bit more closely. We've already mentioned

  • that properties and Caitlyn will get getters and setters generated for them automatically

  • by the compiler. So if your property is a vow, it will have a get or generated. If it's

  • a bar, it will have it getter and a setter generated. But what if you don't want to rely

  • on the default behavior of these getters and senators? Maybe you want to do some complex

  • logic within that. Or maybe you want to log something out for debugging purposes. Well,

  • you can override the behavior of these default getters and setters and provide your own implementations.

  • So let's log out every time a new nickname is set.

  • To do that. We go to our nipping declaration and then we'll go to the next line and then

  • we'll space over four times. And now if we start typing set, we'll see auto-completion

  • comes up with several options here. So I'm going to choose this bottom one. So what this

  • essentially does is allows us to define the function behavior for wins set is called.

  • Now when we do this, this will generate a backing field for this property. So to actually

  • assign the new value to our nickname property, we need to use a special keyword called field

  • equals value. If we didn't make this call, then the value of nickname would never actually

  • be updated. And now we are free to implement whatever you want. So in this case we can

  • update this with a log message that says the new make name is dollar value. So now if we

  • go back over to our main, let's see what this looks like. So we're assigning one nickname,

  • person that nickname that. Let's assign a nother nickname. In this case we'll just say

  • new nickname. Now if we run this, we can take a look at the log. So you see here each time

  • for assigning a value to the nickname property, our log statement is being run. Similarly,

  • we can override the default Gether. We do this very much the same way. I'll start by

  • saying get,

  • there's no new value to set so there's no value being passed in. So instead we'll just

  • lock this out. Say print line. The return value is dollar field. We still have that

  • field backing value, which is what his storing the actual value of nickname. And then we're

  • going to return the value of field. So now we'll come back over to our main, we'll use

  • a print statement here, person dot nickname and if we run this, we're seeing that our

  • center is being called multiple times. Then our getter is being called and the value logged

  • out. And then finally our print statement here in the main function. Now that we've

  • explored class properties, let's take a look at how we can add a method to our person class.

  • To add a method, we really just need to define it function within our class declaration.

  • That's great. A method called print info. It'll take no parameters

  • and it's just going to print out the user's info. So in this case we'll use a print statement

  • and then we'll use a string template to pronounce the first name, the nickname, and the last

  • name. So we go back over here to our main class. Let's go ahead and remove most of this.

  • Now if we want to call the method on our person variable, we can type person dot and then

  • print info. So now if we run this, we see Peter, no Parker. So our method worked, however,

  • the formatting is maybe not quite what we would have wanted because nickname was no

  • like print info was called, we printed out the word no rather than anything possibly

  • more useful. So let's refactor this method a little bit and see if we can improve that.

  • So that's great. A variable called nickname to print. And then let's check whether or

  • not this is no. So we can say if nickname does not equal no, we'll go ahead and use

  • the nickname else. We'll use this more descriptive string of no nickname and now we can update

  • this implementation and instead of using nickname directly, we'll use this new local variable.

  • So now if we go over to our main again and we run this, now we see our output is formatted

  • a little bit better now while the output now looks better, this expression right here is

  • a little bit verbose. This type of check where we're comparing whether or not something is

  • no and then providing one of two values comes up quite a bit and Kotlin and because of that

  • there's actually a convenient syntax we can use that simplifies this expression. So what

  • we can do is this like maybe question Mark Colon, no nickname.

  • The question Mark Colon is what's known as the Elvis operator and Caitlyn, what this

  • expression is saying is check what's on the left side of the Elvis operator. If that side

  • of the expression is not no, then go ahead and return that. Otherwise return what is

  • ever on the right hand side of the expression. So if we go back to Maine and run this once

  • again, well now see that we're still getting our updated output. So this case, the Elvis

  • operator is just a much more concise way of doing that. If else check. Now I want to take

  • a minute and talk about visibility modifiers within Kotlin. Looking at this code here,

  • you'll see nowhere do we have any type of visibility modifier specified. However, if

  • we go over here to our main, we're able to create a new instance of this class. We are

  • able to call the print info method and we are able to access all of the properties.

  • This is because in Kotlin classes, properties, methods, really visibility in general is public

  • by default. If we wanted to modify the visibility of any of these, we can add one of for visibility

  • modifiers. So from the class we could add public here. However, because it's public

  • by default, this is not needed. We could add internal. Internal means that this class is

  • public within the module. So in our case, because we're in a single module, this doesn't

  • change anything. We can also make this private.

  • Once we make it private, we'll now see that it's no longer available in our main dotK

  • T file and this case a private class is only available within the file in which it's implemented.

  • Now we get to apply similar rules to our nickname property. If we make this an internal property,

  • nothing changes and we can still access that. If we make this protected and go back to our

  • main function, we'll now see that we're getting an air cannot access nickname. It is protected

  • in person. A protected property or method will only be available within that class or

  • within any subclasses. And as you might expect, if we make this a private property, once again,

  • we cannot access it from our main dot KT file. And the same goes for our method. If we make

  • this private or protected, it's not going to be available within main bat. K T now that

  • we have an understanding of how classes work in Kotlin, let's take a look at how interfaces

  • work. So we'll go back to our source directory, go to new Kotlin file or class. This time

  • in the kind drop down, we'll select the interface and let's go ahead and call this person info

  • provider and we'll hit okay.

  • So now the IDE has created a person info provider dotK T file and it's auto generated this empty

  • person info provider interface for us. Now, like with the class, because the curly braces

  • are empty, we can actually remove those and this is a completely valid interface within

  • Kotlin. It's MD. There's no methods that can be implemented and there are no properties

  • that can be implemented. However, this could still be used as a marker interface, for example,

  • in other classes, could in fact implement this interface. In fact, why don't we do that

  • right now? Let's create a class called the basic info provider, the implements person,

  • info provider. We can actually do that within the same file. We don't need to have one file

  • per class or interface within Collin. So to start we can say class basic info provider.

  • Now we want to indicate that this class is going to implement person and vote provider.

  • To do that we'll use a colon and then we'll type the name of the interface and just like

  • that, we've now created a new class basic info provider that implements person info

  • provider. And because person info provider does not currently have any methods or properties,

  • basic info provider has nothing that needs to implement. Oh, let's add a method to our

  • person. Info provider interface can do that. We'll come back up to the interface declaration,

  • we'll add back our braces, and now we're going to define a function signature within this

  • interface. Now we don't have to actually implement this, we just have to define the name and

  • the parameters that are required by this method. Now once we've added this, we'll notice down

  • below now that our basic info provider class has a compiler error saying that it does not

  • implement the required interfaces or add the abstract keyword.

  • So let's take a look at how we can address this issue. Could you, so we're going to start

  • off by adding a main function so that we can play around with this class. Now what are

  • the ways that we could solve the compile issue with basic info provider is by declaring it

  • as an abstract class. This means it doesn't need to implement all the methods available

  • on the interfaces that includes, but it also can't be instantiated. So if we tried to come

  • down here and say vow provider equals basic info provider, we'll get an error saying cannot

  • create an instance of an abstract

  • class. So this case we don't want to make this abstract cause we do want to work with

  • this class so we can remove the abstract class keyword and that we want to actually implement

  • the required methods from person info provider. So to do that we can start typing print info

  • and the IDE will recognize that. And if we hit enter, it will generate a step down version

  • of that print info method.

  • Now let's take a look at how this was generated. We see that it starts by including the override

  • key word. This is different than in Java where it was an override annotation. And Caitlyn,

  • if you remove the override keyword, it'll actually give you a compile error in this

  • case saying print info hides member of super tight and needs the override modifier. So

  • it's very specific in indicating that you do need to include that override. And then

  • after that it's simply matches the rest of the method declaration from the interface.

  • So here we're now free to define the behavior of this interface, however we want you also

  • seen down below that now that we have implemented the interface fully, we can actually create

  • an instance of this class. So if we implement this right for now, just printing out print

  • info,

  • yeah,

  • we can come down to our main function, we can type provider doc and then we can invoke

  • the print info method and we'll pass it in a empty instance of the person class and we'll

  • see here that it executes that print info method

  • [inaudible].

  • So that's a very simple example of how we can define an interface to find a method on

  • that interface, implement it, and then run it on that. Implementing class. Let's improve

  • upon the implementation of print info. So here we're going to say basic info provider

  • and then below that we're actually going to call the print info method on our person class.

  • So now if we run this, we'll see that we have that basic info provider being printed out

  • and then the info from the person. Now perhaps we want to encapsulate this logic within the

  • interface itself. Maybe this print info method, it should always generally work in this same

  • way. Well, we could actually move the implementation that we've just defined right here up into

  • our interface C and Kotlin interfaces provide default implementation of an interface method.

  • So now we can actually remove the implementation of print info from basic info provider and

  • the code will still compile and run.

  • So now if we run this, we're going to get the same output. However, there's an issue

  • with this. We see now in our person info provider interface, we are including the basic info

  • providers string. Well, we probably don't want that since it is an implementation detail

  • of basic info provider. So here we could actually leverage another interesting feature interfaces

  • in Kotlin. We can provide properties on our interfaces as well as methods. So we'll define

  • a property called provider info of type strength. Now you might be tempted to give this a default

  • value, but if you do, you'll see that we actually get a compiler error saying property initializers

  • are not allowed to interfaces. So you will in fact have to override this and any implementing

  • class. But now that we have this provider info string, we could modify our print info

  • default implementation to print out that provider info.

  • So now we've kind of encapsulated this logic into the interface itself. And then the basic

  • info provider class can now just override that provider info property. And we override

  • a property in much the same way as a method. So we'll use override vow provider info type

  • string and then we have to provide the getter. So in this case we'll say basic info provider.

  • And now if we run this once again that we'll see that we are picking up the overwritten

  • property value and then still relying on the default implementation of print info in person

  • info provider. And now if we wanted to still override print info we could absolutely do

  • that and we could call through to the super implementation if we would like and then we

  • can print out anything else here and if we were in this one last time we'll

  • see that we are now relying on the property, the default implementation of print info as

  • well as now our additional logic and the overwritten implementation of print info. Next up, let's

  • look at how we can implement multiple interfaces with a single class. To start we'll add a

  • new interface called session info provider

  • and then we'll add a method to those called get session ID and that will return a string.

  • And so now if we come down to basic info provider, we want to make this class implement session

  • info provider as well. Well I'll be asked to do is to add a comma after the previous

  • interface declaration and now add session info provider as well. And now once we do

  • that we'll now see you basic info provider telling us that we don't implement the required

  • methods so we can come down here and implement get session ID and we can return some session

  • ID. Now down here on our provider class, we can now see that we can call get session ID

  • on our basic info provider instance. Now's a good time to talk about how type checking

  • and typecasting work in Kotlin. To do this we're going to create a new function here

  • called check types and we're going to take a parameter of type person info provider.

  • Now let's say that we want to check whether this person info provider is also an instance

  • of a session info provider. How about we go about doing that? Well we can say if info

  • provider is session and vote provider and then we'll print that out. Say is a session

  • info provider. Otherwise print Ellen, not a session info provider and now we will call

  • this check types function and we'll pass in our provider variable. So now if we run this

  • we'll see is a session invoke provider printed out to the console. So this conditional was

  • able to determine that the past in info provider was also an instance of a session in both

  • provider. Now if we wanted to flip this logic and check that it is not a session info provider,

  • we can add an exclamation point before that and then we'll just flip these print statements

  • here and now once again if we run this we'll see is a session in both providers.

  • So you have the flexibility there to check that either way. Now let's take a look at

  • how typecasting works. So within this else block we've already checked that info provider

  • is a session info provider. So we can cast it and then call methods on it as if it was

  • a session info provider. So we could say info provider as session info provider. The as

  • is the keyword used to cast something to another type doc, get session ID. So now we're able

  • to cast info provider is that session and from a provider and call any methods or access

  • any properties on it that are specific to session info provider. Now Caitlyn also includes

  • what is known as smart casting, which means that if the compiler can check a type and

  • validate that that type will not change, then you don't need to do any additional casting.

  • So in this case we've already validated that info provider is a session info provider.

  • So we don't actually need to explicitly recast this. We could say info provider dot. Get

  • session info. And the compiler is performing a smart cast for us. So here we can access

  • get session ID or other properties and methods on the session info provider without having

  • to explicitly cast it each time.

  • We've never seen how a class can implement multiple interfaces as an example of our basic

  • info provider. Let's now take a look at how a class can inherit from another existing

  • class and override methods and properties on that base class. To start, let's create

  • a new file called fancy info provider. Within this file we're going to create a new class

  • called fancy info provider. We didn't want this class to extend the basic info provider

  • that we already defined. So we can do that by adding a colon and then typing the name

  • of the class that we want to inherit from in this case basic info provider. Now as soon

  • as I do this, you may notice that we have a red squiggly line here indicating an error.

  • The error says this type is final, so it cannot be inherited from this is a characteristic

  • of classes in Kotlin by default and Caitlyn classes are closed, meaning they cannot be

  • inherited from or extended. To extend this basic info provider class, we meet to add

  • the open keyword by adding the open keyword, it now means that you can inherit from this

  • class. So if we go back to our fancy info

  • provider, you'll now see that our error has gone away and we can now override methods

  • and properties in this class. Now let's start by overriding the provider info property.

  • So we'll add the opening closed curly braces to our class definition and then I can start

  • typing provider info and you'll see that the IDE is suggesting the property available to

  • us to overwrite. So I'll hit enter and that will go ahead and auto complete the property.

  • Now notice it has the override modifier indicating that this property is being overridden and

  • I noticed that it automatically provides a custom getter and you'll see that it defaults

  • to deferring to the super implementation of this. So we could actually override this just

  • like this by saying fancy info provider. If we were to then come back to our main function

  • here and replace this with an instance of fancy info provider and we rerun this, what

  • mousey is printing out fancy info provider so that provider info is being correctly overwritten

  • in our new extended class.

  • Now let's try overwriting the print info implementation in our fancy info provider class. So if I

  • start typing print info, once again, we'll see the IDE suggesting the method that can

  • be overwritten. I'll hit enter and again by default this will call through to the super

  • implementation of print info within basic info provider. And so I can then add another

  • line here that just maybe says something like fancy info. And if I come back and run my

  • main function and that we'll see the base implementation is the basic info provider

  • implementation. And now this extra line added by our implementation of fancy info provider.

  • Now I want to illustrate one last point in regards to inheritance, but before we do,

  • let's refactor basic info provider a little bit. Instead of hard coding the session ID

  • here, let's add a property to hold that value. So we'll come here and we'll say Val and we'll

  • say session IB prefix, let's say equals session. And now we roll return session ID prefix right

  • here in our implementation of GIP session ID. So now if I come into fancy info provider,

  • I want to override that new session info prefix.

  • So to do that I might start typing session and you'll notice that it's not auto suggesting

  • that new property that we just added. This is because to overwrite a property and a derived

  • class, you have to Mark that property as open. This is just like extending a class so we

  • can come here to session ID prefix and add the open modifier as soon as we do that. If

  • we start typing once again, now we'll see it's suggesting the option to override session

  • ID prefix. So just like the provider info property, I can now override this and I can

  • say fancy session. So this is just one other way in which Kotlin works to enforce immutability.

  • It forces you to Mark both your classes, your properties, and your methods as being explicitly

  • open for extension. Now there's a small problem with this new session ID prefix that we've

  • added.

  • It's really meant to be an implementation detail of the class. However, if we come here

  • to our call site where we're using a fancy info provider variable, you might notice that

  • we can actually access that prefix directly. This isn't ideal because like I said, it's

  • an implementation detail. Our API shouldn't really be exposing that property. Now the

  • reason it's available is because we have defined it as a public property. So what can we do

  • about this? Well, if we want it to be available and our child classes but not to the public

  • API, we could add the protected modifier. So now that property is protected down here,

  • when we try to access it, we get an error saying cannot access session ID prefix. And

  • if we come back to fancy info provider, you'll see that we can still override that property

  • without any trouble.

  • Now that we've explored how we can extend an existing named class, let's look at how

  • we can create an instance of an anonymous interclass using an object expression. To

  • do that. We'll come over to our main function here and now instead of instantiating an instance

  • of fancy info provider, we're going to create an anonymous interclass. So we'll delete that.

  • And to start off to create our object expression, we can type object, colon and then the name

  • of the class that we want to extend. In this case it'll be person info provider. Now within

  • this class we can

  • override any available properties or methods. So in this case I'll update the provider info

  • and just say something like new info provider. Now notice below here that our provider dot

  • get session ID call is now being marked as an error. That's because there is no guest

  • session ID on person info provider. But we could go ahead and add a new method to our

  • object expression here. So we can just say fun, get session, I ID equals and then we'll

  • just put in a value here. So you see you can not only override the existing properties

  • and methods, but you can add to them as well. Just like you could in any other name to class.

  • And now if we run this code, we'll see new info provider being printed out to the screen.

  • So an object expression allows you to create an anonymous inter class so you don't have

  • to create a new named class. So this might be useful for things like a click listener.

  • If you were working in, let's say, Android development.

  • Now that we've explored object expressions, we're going to now look at companion objects.

  • And to do that, we're going to create a new file and we're going to name that file entity

  • factory. Now imagine we want to create a factory to create instances of something called entity.

  • So to start we might create an entity class and maybe that class will have a single ID

  • property to start. Now we want to make this a factory like we said. So what we might want

  • to do is change this constructor to be private. And so now if we add a main function and we

  • try to create an instance of entity, we'll see that we have an issue here. Well notice

  • that there is this error saying cannot access in it. It is private to entity. So this is

  • because of that private constructor. Well, so what can we do?

  • This is where a companion object could come in handy. A companion object is an object

  • is scoped to an instance of another class. So within our block body here, we can type

  • companion object. Now we could create a create function called fun create and we'll have

  • that simply return an instance of entity. And for now we'll just pass it in a placeholder

  • ID. So now we can come back down to our main function and we can type entity dot companion

  • dot create. And we can use this to create an instance of that class. This works because

  • companion objects have access to private properties and methods of that in closing class. Now

  • in this case, we can actually shorten this by removing the reference to companion altogether.

  • That new companion is implicit and if you're using it from Kotlin, you can leave it off.

  • However, if you were using this companion object from Java, you would have to reference

  • that companion object instance directly. You can also rename your companion object. So

  • if we wanted to name this something like factory to be a bit more explicit, we can then say

  • doc factory and reference it that way. And so again, not needed from Kotlin but it could

  • be a good way to make your code more understandable from the Java side of things. If you're doing

  • a lot of Java to Kotlin interrupt, we can also store properties within our companion

  • objects as well. So in this case we can create a const thou well

  • ID equals IB and then we can come down here and replace our entity and pass that in. Now

  • that we have this ID property added to our companion object, we can reference it from

  • other calling code as if it was a static property like we're familiar with from Java. So we

  • could do that by typing entity dot. And then we can reference that ID property directly.

  • Now competing objects are like any other class and that they can also implement other interfaces

  • to demonstrate that we'll create a new interface called ID provider with a single method this

  • called get ID. It will return a string. Now then come down to our companion object declaration

  • and we can make it implement ID provider the same way we would with any other class. We

  • can then choose to implement the required members and then here we will just return

  • a simple ID and so now when we create our instance of ID, we could rely on this ID method

  • if we want it. So you see companion objects can be quite flexible if you need them to.

  • You could use those to compose other types of behavior, store your semi static properties

  • or methods and use them to create factories by referencing private inner properties or

  • methods of the enclosing class. This is really what you would want to leverage if you want

  • functionality similar to that of static members

  • and field from the world of Java. Now that we've covered object expressions and companion

  • objects, let's take a look at creating an object declaration. To start, we're going

  • to clean up some of this code we've been working with so we will remove this implementation

  • of ID provider and we will go back to using a placeholder ID. We'll remove this reference

  • to entity ID and we can remove this ID provider interface. Now what our object declarations

  • and object declaration is a convenient way of creating threads saved singletons within

  • Kotlin. We can do this by using the object keyword and then a class name in this case

  • entity factory. Now within this you can add any types of properties or methods that you

  • would like. So let's start by migrating our create method from our companion object into

  • heart entity factory and now we can remove that companion object and instead we can reference

  • entity factory dot create.

  • Now there's one small problem with this so far, which is that entity still has only a

  • private constructor. Now we're going to remove that private modifier for now so that we can

  • use that constructor. However very shortly we will continue to refactor this code to

  • limit the ways in which entities can be created. Now before we go on and continue to explore

  • some of these other class types in Kotlin, let's add to our entity class by implementing

  • two string so that if we print out an instance of entity, we get some nice user readable

  • text. So we can start typing two string. And then I'll use a string template here and we'll

  • say ID colon is ID. And then we will also add in a name property here, Val name of type

  • string.

  • And then we'll say name and then substitute in that main property. And then here in our

  • create method we will just put in a generic name. And now down here we can use a print

  • line statement and pass in our instance of entity. And now we see our new two stream

  • texts being printed out to the console. So this will help us going forward demonstrate

  • some of how these other classes are going to work. All right, now that we can print

  • out useful information about an instance of an entity, let's refactor our create factory

  • method to actually differentiate instances of entity. So to do this, we're going to change

  • this and make it no longer a single expression function. So we will add a return type of

  • entity and then we'll add a return keyword. And that will, we'll add entity. Now the first

  • thing you want to do here is actually add a proper ID value.

  • So here you could say Val ID equals you do ID dot random U U ID dot two string. So this

  • will give us a new random identifier and then we can pass that into our entity. But now

  • we have this main property. So what can we do to pass in a name here? Well one thing

  • we might do is think about differentiating between different types of entities. So in

  • a very basic case, maybe you want to differentiate between easy, medium, and hard difficulties

  • of these entity types. So we might want to then have some way of passing or indicating

  • to this factory method, what those different types should be. So what am I, we could do,

  • this is with an ENM class. Now if you're familiar with Java and Enim class is going to be very

  • similar to what you're familiar with from [inaudible] in Java. To do that, we can start

  • typing email and then class. And then in this case we might name this something like entity

  • type and open and closed curly braces. And then we can iterate the different instances

  • of the email. So in this case we might say easy, medium, hard. So now we can come down

  • here to our create method and then we can add a type parameter of entity type.

  • And so now we could say vow name equals when type. And then we are going to add in the

  • remaining branches. So now we have a branch for each of our entity types. And then for

  • a basic name, I'm just going to map these to a string. So say easy, medium and hard.

  • And so now I can pass in that name. So now our factory method actually allows us to differentiate

  • and create different types of instances. So down here we might start off by creating an

  • easy entity and then we'll print that out. And then we might say vow medium entity equals

  • entity, factory dot. Create entity tight medium. And then we can print that out as well.

  • And if we run this well, now see that we have a unique identifier for each entity. And then

  • we have the customized name based on that entity type. So the addition of this ENM class

  • to represent our entity type has allowed us to pass in different types to our factory

  • method and then customize the way that those entities are created by mapping the entity

  • type to a name. Now in this case, we're mapping the name very closely to the name of the actual

  • class itself. So to make this a little bit easier and more encapsulated, there's a couple

  • of things we could do. So the first thing we could do is take advantage of the name

  • property on an ITAM class. So to do that, we could reference our type dot nay. So this

  • is referencing the name of that actual [inaudible] class. And if we run this, we can see what

  • that name looks like.

  • So you see it's easy all in capital letters. This matches exactly the way that the class

  • name is actually defined. So this allows us to reference the classes name directly without

  • having to map it manually. Now this is nice, however, we don't have a lot of control over

  • the formatting here. So another thing we could do is actually add a new method to argue in

  • class. So in this case we get add fun, get formatted name, and then we can reference

  • that named property.to lowercase dot capitalize. So this will return us that preform at a name

  • and capitalize the first letter. So now down here we can update our medium mapping and

  • type type dot get format in name. And so now if we run this code again, we'll see that

  • the first one by using the name property directly is all capitalized. But now by using our new

  • format and method, we have a nicer format similar to what we were using before. So that's

  • just one example of how you can define an Enon class and then add additional properties

  • and methods that class like you would any other class.

  • Now let's continue refactoring this code to further differentiate between different types

  • of entities. To do that, we're going to leverage a sealed class seal classes allow us to define

  • restricted class hierarchies. What this means is that we could define a set number of classes

  • all extending a base type, but those classes will be the only ones that can extend that

  • base type. So one example of this could be a loading state or results state for a network

  • operation. It's either going to succeed or fail and there aren't really any other options.

  • So in the right place we're going to create a sealed class with an easy, medium, hard

  • and help entity types. To start creating our sealed class hierarchy. We're first going

  • to remove the properties from our entity class as well as this existing override of the two

  • string method. The next step is to add the sealed keyword before the class keyword in

  • the entity class declaration.

  • As soon as we do that, we'll start getting an error above where we tried to create an

  • instance of entity. This is because you can't instantiate based sealed class type directly.

  • So this is now where we will create each type within our sealed class hierarchy. So the

  • first type we're going to create is a David class to represent easy entities. And then

  • we will add the properties we want in this case the ID and name, and then we want to

  • make sure that we inherit from entity. So next up we can copy that and update the name

  • for the medium type. And now for the third type, once again we'll copy that, we'll name

  • this hard, but now we're going to add an additional property. This property will be called multiplier

  • and we'll be afloat and this can represent some type of difficulty, multiple fire if

  • we were creating a game for example.

  • Now notice that all of these types within the sealed class all extend from entity but

  • have different types of properties. This is one of the key differentiators between sealed

  • classes and [inaudible] classes. With seal classes, you can have different properties

  • and methods on each of these type and the compiler can perform smart casting to allow

  • you to use these different properties and methods as you would like. We can also use

  • different types of classes itself within our sealed class. So you notice that these are

  • all created as data classes. However, if we wanted to remove data from one of these, that

  • would be perfectly fine. We could also use object declarations within our seal class

  • hierarchy. So this case will create an object class called help to represent some type of

  • generic static help entity within our program. Now because help doesn't have a constructor

  • because it's static. In this case, we can add a class body and we could add a name and

  • add help directly. And in this case, more ad ID. Since it's a Singleton and there's

  • only going to ever be one instance anyways,

  • now that we have our seal classes defined, we're going to update our factory method to

  • instantiate and return different types of entity classes. So we'll come up here to our

  • return statement and instead of returning an entity directly, we're going to use a wind

  • expression based on the entity type being in class. We'll then add all of the needed

  • branches. And so when we have an easy type, we want to instantiate an instance of the

  • easy class. So to do that we'll type D Z and then we will pass an ID and name. And similarly

  • for media type, entity dot. Media ID, combat name. And now for hard, once again we'll pass

  • it and entity dot hard ID name. But now again we have this additional property type and

  • the compiler recognizes that. So for now we'll just pass it in to F as our multiplier.

  • Now notice though that we have this help entity being unused. So let's update the factory

  • to allow us to create instances of the help type. So we'll come up to our entity type

  • Unum class and add a help type here. Now notice as soon as we added that additional type on

  • the entity type in them class are when expressions one to us that we need to add an additional

  • branch. So to do that, I'll add the remaining branch here and I'll default to typed up get

  • format name. And once again below here I'll add the remaining branch. And in this case

  • I'm just going to return help directly.

  • Oh, notice here that help is giving us an error. It's saying required and to be found

  • entity that help. This was done to demonstrate what happens if you do not extend from the

  • base entity type. So if we come down to our entity class here and you notice our object

  • declaration for help, if we then add a colon entity to extend from entity, we'll now see

  • that error go away. So this is a good example of how the compiler can help give us this

  • nice static type checking and all of these things. And if we are combining even classes

  • was sealed classes with these, when expressions get allows us to be sure that if we add a

  • new type or a new branch somewhere that we have to handle that effectively because the

  • compiler will warn us or even give errors if we're not handling all of those different

  • branches. Now let's come down to our main function and demonstrate one of the advantages

  • of representing our entities as a seal class hierarchy.

  • So if I remove everything, but this first instance of creating an entity, I'm going

  • to specifically add a type here of entity. And so now if we, if we come down again, we

  • can use a one expression and we'll say, now we can go down here and use a wet expression

  • to do some type checking about the entity that we have just instantiated. So here we'll

  • say Val, message equals when entity. And now again we're going to rely on the IBE to add

  • all the remaining branches. So there's a few things of interest to note here. So we'll

  • see that for an easy, medium and hard, it's adding. This is check. So this is going to

  • basically tell if it's an instance of that class or not. But then notice for the help

  • class, because that's an object declaration and as a Singleton there's no need to have

  • it is.

  • So in that case we can reference that class directly. And so now here we could add whatever

  • message we wanted. So we could say help class, easy class, medium class and hard class. And

  • then if we simply print that message out and run the code, we can see in this case we're

  • getting an easy class. And then if we change what we pass into our factory method and rerun

  • this, well now see that we're getting the help class. So now we have static type checking

  • both and specifying what type of entity we want back and and checking the type that we're

  • actually getting back from that. And so we could use this to then call any methods or

  • properties that are specific to that class. And if we were operating on these types as

  • in a one expression here, if we ever added a new type, that compiler would be sure to

  • make sure that we handled the addition of that new type.

  • So now let's return to our sealed class hierarchy for a second and dive more deeply into what

  • data classes are. So you see here both easy and medium and hard are all defined as data

  • classes. Data classes are cotton's way of providing very concise, immutable data types.

  • By defining a class as a data class, it means that it is going to generate methods such

  • as equals hashcode into string automatically for you. What this allows us to do is perform

  • a quality comparisons on instances of these data classes and treat them as equal if the

  • data they contain is equal. So here's an example. Let's explore what this looks like. So we

  • can say Val entity one equals entity factory that create and will create an easy entity.

  • And then we're going to create another version of this. And then now we can check their equality

  • comparison.

  • So you can say if entity one equals entity two per DeLeon, they are equal else per Delon,

  • they are not equal to. Now if we run this, what will we see? They are not equal. And

  • that's to the expected. That's because if we come back up to our factory, we'll notice

  • that we are creating different unique ideas each time. So even though that the name is

  • the same, the unique ID is different. So now let's update this and see what it looks like

  • if we pass the same data in. So in this case we could create an easy directly and this

  • case will pass in ID comma name and then we will duplicate this for entity two. And so

  • now if we run this, we're going to expect to see you. They are equal and of course they

  • are. So this is really convenient. This allows us to represent data within our applications

  • and compare this data no matter where it comes from.

  • And as long as those properties are all the same, we're going to be able to evaluate these

  • as true. Now another really interesting thing that data classes give us are effective copy

  • constructors. So we can create an instance of entity two by copying entity one entity,

  • one dot copy. And because this is a direct copy, if we run this once again, we're going

  • to see they are equal. However, we could also use named arguments with the copy constructor

  • to change the value. So let's say we only wanted to change the BAME and you could say

  • name equals new name. And once again, if we rerun this, we're going to see they are not

  • equal. So you could see changing a single property and the data class is going to impact

  • whether or not two instances evaluate to true or not when compare.

  • Now one thing to notice is this is comparing the value of the data themselves. If we wanted

  • to U S referential comparison, we hit add a third equal sign here and this will check

  • whether or not it's the exact same reference or not. So in this case they are not equal.

  • However, this isn't all that surprising since the data was also equal. So what about if

  • we revert this and make this an exact copy again? So before if we were just using two

  • equal sign, the data would be the same. So it would print, they are equal. However, by

  • using three equal signs and using referential equality, we see they are not equal. That's

  • because it's not the same exact reference of the object. If we updated this to be entity

  • one equal equal equals entity one and run this, now we'll see they are equal.

  • So that's just one way in which we can check whether or not we have the exact same object

  • or if it's two different objects that have the same data. Now also keep in mind that

  • these equality comparisons are working off of the generated equals and hash code methods

  • generated by the compiler when indicating a data class. However, we could update this

  • to change how the equals or hash code is evaluated and to do that we would do it like any other

  • class. We could add a class body and then we could simply override equals and or hash

  • code. Now as in Java best practice, if you're going to override one of these, you should

  • really override both of them and you have to follow the same rules, but you have that

  • freedom if you would like to.

  • Another really useful feature in Kotlin is the ability to define extension functions

  • or extension properties on an existing class. This is particularly powerful if you're working

  • with classes that you can't control but would like to modify the way in which they're used.

  • You can define your own properties and methods and defined kind of a new API around the existing

  • class. So an example of this would be adding a new method to the medium class without actually

  • defining that method within the definition of the medium class. So to do that, let's

  • come down here and you can start off by typing the fun keyword. And then instead of directly

  • typing the method name, we can reference the class name. Dot. And this case will type print

  • info.

  • And then we can define our function buddy. So in this case we'll just say medium class

  • with the ID and that'll be it. And so if we wanted to come down here and now create an

  • instance of entity dot medium directly, we could do that. And then we could call that

  • print info method. And if we run that code, we'll see a medium class and then that ID

  • printed out. So this is great if we know that we have that exact type that we're working

  • with. And in cases where we don't know if we have that direct type, we could rely on

  • smart casting. So if we update this to you, their factory say entity factory, create entity

  • type medium. Now we can say if entity two is medium entity, now we can reference that

  • new print info method.

  • This is done because the if statement will only evaluate to true if that cast is successful.

  • So anywhere within that context it will automatically perform the smart cast for us. And like I

  • said before, not only can we define extension methods, but we can also define extension

  • properties as well. To do that, we could start off by saying Val or VAR. In this case we'll

  • say vow and then again we'll reference the class type. So medium dot we'll say info will

  • be this property name string equals some info. If you do that, notice that we have this air.

  • If you look, it says extension property cannot be initialized because it has no backing field.

  • So to actually create an extension of property for an existing class, you need to rely on

  • backing fields. Thankfully the IDE can generate this forest, convert extension property initializer

  • to a getter.

  • So once we do that and notice here that we have still defined our property but now we're

  • relying on this custom getter for that property and so now if we come back down here within

  • our, if statement that's doing our smart cast for us, we could reference that new info property

  • directly. So this is how extension functions and properties work. You could use these anytime

  • you want to add additional functionality to existing class. You might notice within the

  • Kotlin standard library that many functions and operations work by using extension functions

  • in classes be are particularly effective when using them with template ID types

  • because it allows you to define the same common functionality across any type that matches

  • that template. Now up until this point, we've covered a lot of things. We've looked at the

  • basic type system of Kotlin, how to work with different variable types, how to work with

  • basic functions and then diving into more advanced functional topics like named arguments

  • and default parameter values. And then we took a deep dive into modeling data with Kotlin.

  • So now I'm going to circle back to functions and specifically take a look at higher order

  • functions and how to work with functional data types. Now what are higher order functions?

  • Higher order functions are functions that either return another function or that take

  • functions as perimeter values. Now much of Kotlin standard library is built on top of

  • higher order functions and it's what really allows us to write highly functional code

  • by leveraging that standard library.

  • So let's take a look at how we can write our own higher order function. To start we have

  • a new Kotlin file and we're going to define a new function. So we'll call this fun. And

  • then we're going to call this print filtered strengths. And now the first argument to this

  • is going to be a list of strings. So we'll call this list and then define it as list

  • of string. And now the next thing we're going to do is define a parameter which will in

  • fact be a function. That function will take in a string and return a bullying. We can

  • then use that to filter out values in the past collection. So to define a function parameter,

  • you could start off by defining the parameter name as usual. And this case, we'll name it

  • credit kit, followed by colon. And now you have to define the type as you normally would

  • to define a functional type.

  • You can start by adding your parentheses. This will define the parameters of the function

  • being passed in to your other function. So in this case we are going to take a string.

  • He'll then add the arrow, and then you want to define the return type. So in this case,

  • that will be bullying. And now we'll add the open and closed curly braces to define our

  • block body. So now we have a parameter called predicate, which will be a function that takes

  • in a string parameter and returns a Boolean. Now we can implement our print filtered strings

  • function to make use of that predicate function to filter out any strings in that past list.

  • So to implement this function, first off, we want to iterate over each string in the

  • past list. So to do that, we could say list doc for each and now we will be able to iterate

  • over each of those strings.

  • So now what we want to do is evaluate the predicate for each stream in the collection.

  • So we can call the predicate function in several different ways. So to start we'll say if,

  • and then the easiest way to invoke the predicate is to simply say predicate open and close

  • parentheses and pass in the parameter value. A parameter that is a functional type can

  • be called as if it was a regular function. As long as you can satisfy the required arguments.

  • So in this case we can say if predit kit returns true, then we can print out that string. Now

  • to test this, we'll come down here and we will add a main function and we will say vow

  • list equals list of, and then we can say something like Kotlin, Java C plus plus Java script.

  • And now we could call print filtered strings pass in our list.

  • And now we need to pass in a function as the second parameter to print filters, drinks.

  • So we can do that by specifying a Lambda, and in this case we will say it starts with

  • K. so this Lambda is going to evaluate to true if any of the past strings begins with

  • a K. now if we run this function, we'll see only Kotlin print it out to the screen. If

  • we were to update this to print things out, that started with a J, well now see Java script

  • and Java. Now one thing to notice is it in our invocation of print filtered strings,

  • we've passed our Lambda within the parentheses of that function in vacation. However, this

  • is something that we don't have to do. As we mentioned earlier, we can take use of Landus

  • syntax, which says that if the last parameter of a function is a function, you can specify

  • that as a Lambda outside the function body. So we can restructure our function to look

  • like this. We can pass in the list first and then specify or Lambda outside of the parentheses.

  • So this is actually very similar looking to the for each function which we called up above.

  • And in fact if you look at the implementation of for each is in fact a higher order function.

  • The Lambda that we specify after invoking for each is a function which will operate

  • over each string and that list. Now if we come back up here to our implementation notice

  • we are calling the function parameter directly as if it was a regular function. So this works

  • absolutely great in most situations. However, if we were to make this function, type a NOLA

  • ball type by wrapping it in parentheses and adding new question Mark. Well now see an

  • error in our implementation of print filtered strings. That error basically says that you

  • cannot invoke that function parameter by using the parentheses directly. If it's a nullable

  • type to get around this, we can make use of the invoke method on that functional type

  • and then we can make use of the safe call operator and now, but updating this to do

  • a safe invoke call on the predicate function.

  • We can handle this rather not the predicate is no calling invoke will invoke the function

  • just as it would any other indication of a function. So now down here nothing has changed

  • and how we can call print filtered strings. However, we could also pass it in list and

  • now we could pass in no as a no function. So we've seen how we can treat functions as

  • parameters to other functions and these function parameters are really treated as tight. Just

  • the same as let's say integer or string. Caitlyn has this idea of functional types. It's a

  • first-class part of the language. This means that we could define a variable of a functional

  • type and then pass that variable in any time. We needed a function parameter that matched

  • that function signature. So an example of this might be something like vow credit kit

  • and then we will define our function type to match that of our print filtered strings

  • function.

  • So in this case it'll take a string and return bullion and now we'll define our function

  • the same way that we were doing it before. By saying if the string starts with aJ , go

  • ahead and return true. Now instead of invoking print filters, strings with a landed pass

  • to it, we can pass in our predicate variable directly. And now if we run this, we'll see

  • the same output as we would before. So this allows us to store function as variables.

  • This can be really useful for things like optional input handling. For example, maybe

  • you have a view on some screen and you want to be able to specify a ClickList center for

  • that view. You could define that as a Lambda property on some class and allow client code

  • to set that ClickList center as needed. As we mentioned before, higher order functions

  • include functions which take other functions as parameters, as well as functions that return

  • other functions.

  • So let's define a function called get print predicate and it'll take no parameters, but

  • we defined its return type as a function which takes a string and returns a bullion. And

  • now we can return that value by saying return. And then we could pass a Lambda and say it.

  • That starts with J. So we're passing essentially the same type of Lambda that we've been using

  • in these other examples. But now we've wrapped it in this other function and so now and so

  • then passing predicate directly or instead of defining a new Lambda as our function parameter,

  • we could instead call get print predicate as a function on its own, which will then

  • return a function which then can be used as the predicate for print filtered strings.

  • And if we run this once again, we'll see that our output hasn't changed though. So higher

  • order functions can work as both inputs and outputs and Kotlin allows you to define properties

  • with functional types.

  • So through this function's really become a very powerful and first-class part of the

  • language that can start to replace a lot of other instances. For example, you might find

  • yourself relying more heavily on functions to define things like event or a ClickList

  • centers rather than defining concrete interfaces for those same types of functionality. Now

  • this was recently mentioned. Much of the Kotlin standard library is built around higher order

  • functions and especially a higher order functions defined with generic types. So if we look

  • at the implementation of four each, well notice that this is actually an extension function

  • as well as a higher order function. So for each works on generic Iterable type and takes

  • in a function parameter that takes in that generic type and returns unit. So this essentially

  • allows us to iterate over each element in the collection and then call that action on

  • it and it doesn't have to return anything.

  • And similarly for each index takes in a single function parameter as well. But this one takes

  • in an event to represent the index as well as the generic type. This allows us to iterate

  • over each element in the collection while incrementing a counter and then passing that

  • counter into the function parameter as the index. The power of generic types, extension

  • functions and higher order functions allows us to write single implementations of these

  • methods and then reuse them over any type that we can think of. Now this is very powerful

  • and can allow us to write much more functional code without having to redefine these methods

  • and functions for all of our different types. So let's take a look at example of how we

  • can combine some of these different functional operators to perform complex operations with

  • very little code. We'll come into this new main function here and we'll start off by

  • defining a list of strings.

  • Once again. Now let's look at some ways in which we can chain these functional operators

  • together to do more interesting things. So as we've seen before, we can do a simple for

  • each to iterate over each item in this collection and print it out. And if we run it, we'll

  • notice that we see all of the programming language printed out to the console. Now what

  • if we wanted to print out only the strings that start with J plus similar to the functions

  • we were working with before, we could do that by making use of a filter operation. So we

  • have a lot of options to choose from. In this case, we will just choose a generic filter

  • and then we will use a predicate which says it starts with J and now if we run this was

  • he, he had only Java and Java script printed out. Now, what if our collection included

  • some no values?

  • So as soon as we add, no, we see now here in our filter operation, it's warning us that

  • Hey, this value might be no, you need to add a safe call weld in Kotlin. Oftentimes we

  • don't want to work with no, we want to try and hide no as much as possible. And so we

  • could make use of another functional operator called filter not know. What this does is

  • immediately filter out any no values up front. So everything past that in the functional

  • chain will be guaranteed to be not. No. So as soon as we added filter, not know, we no

  • longer had to deal with a possible no string. And if we run this once again, we'll see only

  • Java and JavaScript printed out.

  • Now what if we wanted to change the type of this? Let's say we wanted to convert this

  • from a string to an integer, which represents the length of that input string. We could

  • do this type of transformation using a map function. The map function will take in whatever

  • the previous type is in this case string, but it'll allow us to return any other type

  • we want. So in this case, we might define our map function as simply returning the length

  • of the string. As soon as we've done that. Now below that in the for each, the type has

  • changed from string to end. And now if we print this out, we'll see four and 10 printed

  • out for representing the four characters in Java and 10 representing the 10 characters

  • in Java script. Now let's remove this mapping and let's remove the filter. And instead,

  • let's imagine that we want to take only a certain number of items from this collection.

  • So we can do that by using the take function and passing in. Let's say three. What that'll

  • do is we'll take the first three items from that collection and then we'll be printing

  • out each of those three names. So you see in this case we're getting Kotlin, Java and

  • C plus plus. Alternatively, if we didn't want to take the first three elements in the collection,

  • we could use take last today, the last three. So in this case we see Java C plus plus and

  • Java script and it has skipped over Kotlin since that was not one of the last three elements.

  • We can also do other transformations such as associating the input values with some

  • other value to return a map. So let's create a map that essentially maps the string to

  • the number of characters in that string. So to do that we could say associate, and then

  • in this case we could say it to it dot length. And so now in our, for each function, instead

  • of iterating over strings, we're iterating over map entries of string and event. So in

  • this case we can now use a template string and say it got value comma it dot key.

  • And if we print this out, we'll see the length comma followed by the name. This makes it

  • really easy to map all of the input strings to some other value and then iterate over

  • that map. Now, what if we didn't want to iterate over the map but instead just wanted to hold

  • on to that in a variable? Well, instead of using a fork each at the end, we could assign

  • this to a variable just like this. The continent standard library also provides a variety of

  • functions to help us pull out individual elements from a collection to demonstrate that that's

  • created a variable called language. And then we're going to perform different operations

  • on our list to grab a single language string from our list. So we could do that in a number

  • of ways. We could say list dot first and if we print this out, we'll expect to see Kotlin

  • as that is the first language in the list.

  • Alternatively, we could say we'll start last and in this case you'll see that it's actually

  • printing out. No, since [inaudible] was the last value in that list. Now, if we didn't

  • want to retrieve a null value from our list and instead wanted the Alaskan non-male value,

  • once again, we could add the filter, not no function, which we used previously. And now

  • if we rerun this, we'll see Java script printed out instead, since this is the last non no

  • value. Now what if we wanted to find a specific item in the list? Let's say we wanted to use

  • the find function and in our predicate we'll say it got starts with and we'll pass in Java

  • as a street. So this is going to find the first value in this list that starts with

  • Java. So in this case it actually returns us Java and alternatively we could use find

  • last to find the last element in the collection that matches this predicate, in which case

  • it's going to return JavaScript.

  • Now what happens if we are searching for a string which doesn't match our predicate?

  • We can test that by looking for a string which starts with food. If we then run this, we'll

  • see no print it out to the console. This is because there is no matching string. So fine.

  • Last is going to return. No. And then the print line statement, we'll print out. No

  • if it has a null value. Well what if we didn't want to work with no? What if instead we wanted

  • to use an empty string as the placeholder? Well, strings in Kotlin have a useful function

  • called or empty. So we can actually chain that directly off of find last here and call

  • or empty. So at this will do is return either a nano string or a static empty string. So

  • now if we run this once again, instead of no, we're just seeing empty, we're not printing

  • anything out.

  • So this is one way in which you could default your collections or your strings to an empty

  • value as opposed to a no value. And this is something you might want to consider doing

  • more and more of in Kotlin as you start to move away from relying on null. So as we've

  • seen, Caitlyn has first-class support for functions including functional types and higher

  • order functions, and the Kotlin standard library builds upon those tools and provides a rich

  • set of functional operators for us to use. This allows us to build powerful functional

  • chains to transform our data and make complex workflows much simpler. All right, that's

  • it for this tutorial. You now have a good understanding of the fundamentals of Kotlin

  • and how to work with it, and you're now ready to start taking that knowledge and applying

  • it to other domains. Until next time, devs.

Hi, my name is Nate and welcome to this tutorial on the Kotlin programming language. If

字幕與單字

單字即點即查 點擊單字可以查詢單字解釋

B1 中級

Kotlin課程--初學者教程。 (Kotlin Course - Tutorial for Beginners)

  • 6 1
    林宜悉 發佈於 2021 年 01 月 14 日
影片單字