TRANSCRIPTEnglish

Kotlin Course - Tutorial for Beginners

2h 38m 17s21,892 words1,388 segmentsEnglish

FULL TRANSCRIPT

0:00

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

0:06

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

0:11

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

0:17

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

0:22

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

0:27

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

0:33

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

0:38

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

0:45

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

0:55

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

1:01

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

1:08

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

1:14

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

1:19

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

1:26

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

1:31

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

1:36

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

1:42

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

1:48

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

1:54

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

2:00

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

2:11

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

2:16

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

2:20

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

2:26

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

2:32

hit okay.

2:33

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

2:40

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

2:49

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

2:53

finally launch intelligent IDE community edition.

2:57

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

3:02

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

3:11

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

3:15

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

3:20

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

3:26

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

3:32

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

3:39

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

3:45

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

3:52

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

3:58

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

4:05

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

4:09

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

4:17

go ahead and select finish.

4:24

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

4:29

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

4:34

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

4:41

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

4:47

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

4:54

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

5:01

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

5:08

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

5:17

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

5:26

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

5:33

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

5:39

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

5:47

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

5:51

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

5:59

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

6:06

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

6:11

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

6:17

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

6:25

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

6:31

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

6:42

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

6:49

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

6:59

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

7:05

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

7:14

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

7:22

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

7:27

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

7:34

string.

7:35

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

7:43

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

7:48

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

7:56

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

8:01

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

8:07

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

8:17

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

8:24

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

8:31

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

8:38

time is blank because we had reassigned it.

8:42

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

8:49

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

8:55

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

9:01

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

9:07

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

9:12

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

9:21

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

9:28

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

9:38

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

9:45

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

9:52

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

9:57

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

10:06

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

10:13

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

10:21

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

10:28

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

10:35

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

10:45

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

10:55

has changed.

10:59

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

11:05

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

11:12

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

11:18

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

11:25

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

11:31

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

11:38

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

11:44

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

11:51

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

11:58

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

12:05

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

12:12

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

12:19

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

12:26

with the greeting variable.

12:30

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

12:37

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

12:43

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

12:49

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

12:54

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

12:58

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

13:07

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

13:14

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

13:20

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

13:26

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

13:32

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

13:42

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

13:52

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

14:00

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

14:07

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

14:15

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

14:23

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

14:33

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

14:40

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

14:49

a knowable.

14:52

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

15:00

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

15:08

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

15:15

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

15:22

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

15:28

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

15:34

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

15:41

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

15:46

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

15:53

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

16:01

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

16:06

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

16:18

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

16:28

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

16:35

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

16:43

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

16:50

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

16:56

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

17:03

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

17:09

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

17:18

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

17:26

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

17:36

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

17:46

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

17:52

on its own.

17:55

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

18:05

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

18:15

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

18:22

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

18:28

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

18:35

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

18:42

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

18:49

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

18:58

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

19:07

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

19:11

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

19:21

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

19:33

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

19:41

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

19:49

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

19:58

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

20:06

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

20:11

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

20:19

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

20:29

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

20:38

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

20:43

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

20:52

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

21:00

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

21:10

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

21:18

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

21:25

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

21:38

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

21:44

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

21:51

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

21:58

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

22:04

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

22:10

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

22:15

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

22:24

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

22:30

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

22:35

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

22:44

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

22:51

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

22:57

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

23:06

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

23:12

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

23:22

parentheses.

23:23

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

23:29

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

23:35

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

23:42

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

23:50

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

23:56

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

24:03

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

24:12

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

24:21

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

24:27

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

24:29

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

24:40

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

24:48

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

24:54

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

24:59

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

25:07

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

25:13

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

25:22

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

25:29

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

25:36

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

25:43

we could return note in our return statement.

25:50

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

25:59

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

26:06

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

26:13

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

26:21

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

26:26

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

26:33

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

26:38

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

26:47

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

26:55

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

26:59

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

27:05

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

27:10

language.

27:13

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

27:20

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

27:26

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

27:31

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

27:41

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

27:49

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

27:55

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

28:03

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

28:12

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

28:18

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

28:26

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

28:34

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

28:45

I even went to greet.

28:49

Okay.

28:51

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

28:57

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

29:04

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

29:11

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

29:17

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

29:24

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

29:31

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

29:37

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

29:42

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

29:49

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

29:56

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

30:04

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

30:11

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

30:19

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

30:24

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

30:28

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

30:34

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

30:41

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

30:49

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

30:56

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

31:03

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

31:08

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

31:16

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

31:23

that new parameter.

31:27

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

31:36

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

31:45

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

31:55

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

32:02

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

32:08

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

32:12

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

32:18

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

32:25

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

32:32

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

32:40

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

32:45

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

32:53

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

32:59

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

33:05

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

33:11

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

33:16

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

33:21

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

33:30

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

33:36

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

33:45

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

33:52

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

33:58

some interesting things like Kotlin programming or comic books.

34:11

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

34:15

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

34:21

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

34:27

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

34:34

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

34:41

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

34:46

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

34:55

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

35:01

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

35:08

array.

35:09

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

35:18

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

35:27

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

35:34

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

35:40

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

35:46

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

36:00

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

36:06

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

36:13

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

36:19

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

36:31

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

36:38

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

36:44

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

36:49

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

36:55

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

37:01

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

37:13

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

37:21

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

37:29

what to do on each iteration over this collection.

37:33

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

37:39

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

37:44

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

37:49

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

37:58

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

38:04

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

38:14

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

38:20

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

38:26

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

38:33

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

38:40

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

38:45

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

38:50

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

38:57

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

39:02

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

39:08

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

39:13

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

39:19

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

39:26

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

39:32

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

39:41

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

39:47

and closed curly braces.

39:51

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

39:56

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

40:04

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

40:11

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

40:18

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

40:22

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

40:33

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

40:41

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

40:50

and we can say interesting thing is at index.

41:01

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

41:08

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

41:14

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

41:22

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

41:31

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

41:37

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

41:47

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

41:51

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

41:57

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

42:08

familiar with with arrays as well.

42:13

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

42:19

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

42:24

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

42:33

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

42:42

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

42:48

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

42:55

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

43:04

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

43:13

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

43:21

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

43:28

function to create pairs.

43:30

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

43:38

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

43:47

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

43:55

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

44:01

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

44:12

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

44:20

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

44:29

value. Unfortunately the default to it, not

44:32

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

44:37

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

44:45

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

44:53

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

45:02

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

45:08

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

45:14

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

45:20

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

45:25

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

45:32

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

45:39

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

45:47

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

45:53

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

45:58

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

46:06

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

46:12

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

46:21

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

46:30

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

46:37

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

46:46

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

46:58

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

47:05

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

47:11

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

47:17

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

47:23

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

47:30

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

47:34

Okay,

47:36

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

47:42

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

47:49

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

47:56

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

48:04

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

48:09

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

48:16

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

48:24

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

48:30

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

48:41

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

48:47

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

48:56

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

49:05

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

49:14

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

49:21

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

49:30

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

49:36

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

49:44

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

49:50

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

49:55

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

50:00

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

50:07

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

50:14

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

50:22

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

50:26

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

50:38

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

50:45

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

50:53

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

51:00

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

51:10

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

51:19

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

51:25

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

51:32

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

51:40

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

51:45

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

51:52

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

52:05

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

52:10

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

52:17

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

52:25

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

52:33

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

52:39

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

52:46

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

52:53

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

53:00

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

53:06

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

53:11

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

53:19

list of two array of, and then after I,

53:25

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

53:33

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

53:40

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

53:48

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

53:57

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

54:06

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

54:13

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

54:21

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

54:27

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

54:32

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

54:38

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

54:44

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

54:53

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

54:59

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

55:03

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

55:10

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

55:15

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

55:20

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

55:25

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

55:33

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

55:40

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

55:49

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

55:55

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

56:03

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

56:11

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

56:17

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

56:25

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

56:32

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

56:39

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

56:46

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

56:53

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

57:00

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

57:08

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

57:17

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

57:25

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

57:33

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

57:41

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

57:50

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

57:57

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

58:04

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

58:09

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

58:17

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

58:23

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

58:30

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

58:37

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

58:44

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

58:52

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

58:58

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

59:04

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

59:09

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

59:16

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

59:24

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

59:31

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

59:36

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

59:40

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

59:47

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

59:53

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

60:04

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

60:17

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

60:22

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

60:29

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

60:35

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

60:41

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

60:47

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

60:55

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

61:00

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

61:08

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

61:16

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

61:23

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

61:33

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

61:39

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

61:48

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

61:56

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

62:06

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

62:12

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

62:18

It can may notice

62:20

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

62:26

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

62:34

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

62:41

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

62:47

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

62:55

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

63:07

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

63:11

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

63:16

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

63:23

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

63:28

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

63:33

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

63:40

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

63:47

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

63:56

first name string, last name string.

64:09

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

64:14

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

64:20

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

64:26

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

64:34

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

64:41

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

64:46

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

64:56

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

65:06

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

65:11

ways that we can initialize these.

65:13

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

65:19

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

65:26

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

65:32

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

65:37

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

65:43

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

65:49

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

65:58

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

66:04

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

66:10

we could initialize these values.

66:12

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

66:19

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

66:26

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

66:34

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

66:41

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

66:48

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

66:55

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

67:04

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

67:12

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

67:18

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

67:26

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

67:35

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

67:40

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

67:46

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

67:51

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

67:59

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

68:05

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

68:10

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

68:15

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

68:22

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

68:28

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

68:33

class.

68:34

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

68:39

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

68:45

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

68:53

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

68:58

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

69:03

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

69:09

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

69:17

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

69:26

and last name. We'll use Parker.

69:30

Okay.

69:31

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

69:39

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

69:47

constructor

69:48

[inaudible].

69:49

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

70:00

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

70:05

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

70:20

[inaudible]

70:22

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

70:29

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

70:33

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

70:42

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

70:46

[inaudible]

70:50

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

70:55

then our secondary constructor was run

70:57

[inaudible].

70:59

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

71:06

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

71:11

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

71:18

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

71:24

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

71:31

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

71:40

here in the primary constructor.

71:45

Okay.

71:47

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

71:53

empty primary constructor because both parameters have default values.

71:59

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

72:05

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

72:12

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

72:19

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

72:25

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

72:32

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

72:39

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

72:49

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

72:56

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

73:01

is a mutable property.

73:03

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

73:08

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

73:14

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

73:19

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

73:25

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

73:32

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

73:38

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

73:44

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

73:50

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

73:56

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

74:02

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

74:08

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

74:16

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

74:21

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

74:28

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

74:35

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

74:45

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

74:51

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

74:58

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

75:10

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

75:19

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

75:26

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

75:35

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

75:42

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

75:52

saying get,

75:53

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

75:58

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

76:08

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

76:16

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

76:24

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

76:38

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

76:46

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

76:52

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

76:58

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

77:03

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

77:11

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

77:17

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

77:22

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

77:31

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

77:38

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

77:53

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

77:58

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

78:04

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

78:11

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

78:21

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

78:28

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

78:38

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

78:45

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

78:53

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

79:01

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

79:07

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

79:14

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

79:20

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

79:32

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

79:38

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

79:45

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

79:51

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

79:58

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

80:05

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

80:12

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

80:18

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

80:25

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

80:30

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

80:40

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

80:48

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

80:55

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

81:02

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

81:11

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

81:17

change anything. We can also make this private.

81:22

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

81:30

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

81:41

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

81:50

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

82:00

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

82:06

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

82:15

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

82:22

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

82:31

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

82:37

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

82:45

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

82:53

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

83:02

provider and we'll hit okay.

83:05

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

83:16

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

83:23

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

83:29

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

83:36

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

83:42

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

83:48

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

83:56

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

84:03

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

84:13

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

84:18

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

84:26

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

84:31

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

84:38

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

84:43

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

84:50

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

84:57

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

85:02

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

85:08

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

85:15

implement the required interfaces or add the abstract keyword.

85:21

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

85:27

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

85:33

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

85:39

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

85:46

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

85:52

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

86:02

create an instance of an abstract

86:04

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

86:08

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

86:15

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

86:22

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

86:27

of that print info method.

86:31

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

86:38

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

86:44

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

86:50

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

86:56

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

87:02

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

87:09

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

87:16

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

87:22

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

87:31

info,

87:32

yeah,

87:33

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

87:39

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

87:50

see here that it executes that print info method

87:54

[inaudible].

87:55

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

88:00

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

88:05

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

88:16

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

88:24

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

88:32

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

88:41

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

88:47

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

88:57

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

89:06

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

89:14

the code will still compile and run.

89:16

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

89:21

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

89:27

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

89:33

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

89:39

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

89:45

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

89:54

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

90:01

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

90:08

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

90:15

default implementation to print out that provider info.

90:18

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

90:26

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

90:33

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

90:42

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

90:54

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

90:59

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

91:06

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

91:14

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

91:19

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

91:32

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

91:38

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

91:45

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

91:51

new interface called session info provider

92:00

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

92:09

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

92:16

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

92:24

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

92:32

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

92:36

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

92:46

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

92:54

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

93:00

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

93:07

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

93:19

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

93:27

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

93:35

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

93:46

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

93:59

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

94:08

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

94:15

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

94:22

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

94:29

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

94:36

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

94:48

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

94:52

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

95:00

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

95:07

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

95:16

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

95:24

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

95:30

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

95:36

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

95:41

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

95:47

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

95:53

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

96:00

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

96:07

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

96:14

to explicitly cast it each time.

96:19

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

96:25

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

96:33

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

96:40

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

96:51

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

97:01

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

97:10

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

97:17

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

97:23

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

97:29

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

97:35

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

97:45

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

97:53

class. So if we go back to our fancy info

97:56

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

98:02

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

98:08

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

98:15

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

98:21

us to overwrite. So I'll hit enter and that will go ahead and auto complete the property.

98:28

Now notice it has the override modifier indicating that this property is being overridden and

98:36

I noticed that it automatically provides a custom getter and you'll see that it defaults

98:42

to deferring to the super implementation of this. So we could actually override this just

98:50

like this by saying fancy info provider. If we were to then come back to our main function

99:00

here and replace this with an instance of fancy info provider and we rerun this, what

99:09

mousey is printing out fancy info provider so that provider info is being correctly overwritten

99:17

in our new extended class.

99:20

Now let's try overwriting the print info implementation in our fancy info provider class. So if I

99:27

start typing print info, once again, we'll see the IDE suggesting the method that can

99:33

be overwritten. I'll hit enter and again by default this will call through to the super

99:38

implementation of print info within basic info provider. And so I can then add another

99:45

line here that just maybe says something like fancy info. And if I come back and run my

99:54

main function and that we'll see the base implementation is the basic info provider

100:00

implementation. And now this extra line added by our implementation of fancy info provider.

100:05

Now I want to illustrate one last point in regards to inheritance, but before we do,

100:12

let's refactor basic info provider a little bit. Instead of hard coding the session ID

100:18

here, let's add a property to hold that value. So we'll come here and we'll say Val and we'll

100:27

say session IB prefix, let's say equals session. And now we roll return session ID prefix right

100:41

here in our implementation of GIP session ID. So now if I come into fancy info provider,

100:49

I want to override that new session info prefix.

100:54

So to do that I might start typing session and you'll notice that it's not auto suggesting

101:02

that new property that we just added. This is because to overwrite a property and a derived

101:09

class, you have to Mark that property as open. This is just like extending a class so we

101:17

can come here to session ID prefix and add the open modifier as soon as we do that. If

101:26

we start typing once again, now we'll see it's suggesting the option to override session

101:31

ID prefix. So just like the provider info property, I can now override this and I can

101:40

say fancy session. So this is just one other way in which Kotlin works to enforce immutability.

101:48

It forces you to Mark both your classes, your properties, and your methods as being explicitly

101:56

open for extension. Now there's a small problem with this new session ID prefix that we've

102:02

added.

102:03

It's really meant to be an implementation detail of the class. However, if we come here

102:10

to our call site where we're using a fancy info provider variable, you might notice that

102:17

we can actually access that prefix directly. This isn't ideal because like I said, it's

102:23

an implementation detail. Our API shouldn't really be exposing that property. Now the

102:30

reason it's available is because we have defined it as a public property. So what can we do

102:37

about this? Well, if we want it to be available and our child classes but not to the public

102:43

API, we could add the protected modifier. So now that property is protected down here,

102:53

when we try to access it, we get an error saying cannot access session ID prefix. And

103:00

if we come back to fancy info provider, you'll see that we can still override that property

103:05

without any trouble.

103:09

Now that we've explored how we can extend an existing named class, let's look at how

103:14

we can create an instance of an anonymous interclass using an object expression. To

103:20

do that. We'll come over to our main function here and now instead of instantiating an instance

103:27

of fancy info provider, we're going to create an anonymous interclass. So we'll delete that.

103:34

And to start off to create our object expression, we can type object, colon and then the name

103:42

of the class that we want to extend. In this case it'll be person info provider. Now within

103:49

this class we can

103:52

override any available properties or methods. So in this case I'll update the provider info

103:59

and just say something like new info provider. Now notice below here that our provider dot

104:09

get session ID call is now being marked as an error. That's because there is no guest

104:15

session ID on person info provider. But we could go ahead and add a new method to our

104:23

object expression here. So we can just say fun, get session, I ID equals and then we'll

104:32

just put in a value here. So you see you can not only override the existing properties

104:39

and methods, but you can add to them as well. Just like you could in any other name to class.

104:45

And now if we run this code, we'll see new info provider being printed out to the screen.

104:52

So an object expression allows you to create an anonymous inter class so you don't have

104:57

to create a new named class. So this might be useful for things like a click listener.

105:03

If you were working in, let's say, Android development.

105:09

Now that we've explored object expressions, we're going to now look at companion objects.

105:14

And to do that, we're going to create a new file and we're going to name that file entity

105:19

factory. Now imagine we want to create a factory to create instances of something called entity.

105:30

So to start we might create an entity class and maybe that class will have a single ID

105:39

property to start. Now we want to make this a factory like we said. So what we might want

105:45

to do is change this constructor to be private. And so now if we add a main function and we

105:53

try to create an instance of entity, we'll see that we have an issue here. Well notice

106:02

that there is this error saying cannot access in it. It is private to entity. So this is

106:07

because of that private constructor. Well, so what can we do?

106:12

This is where a companion object could come in handy. A companion object is an object

106:19

is scoped to an instance of another class. So within our block body here, we can type

106:26

companion object. Now we could create a create function called fun create and we'll have

106:38

that simply return an instance of entity. And for now we'll just pass it in a placeholder

106:44

ID. So now we can come back down to our main function and we can type entity dot companion

106:54

dot create. And we can use this to create an instance of that class. This works because

107:01

companion objects have access to private properties and methods of that in closing class. Now

107:10

in this case, we can actually shorten this by removing the reference to companion altogether.

107:18

That new companion is implicit and if you're using it from Kotlin, you can leave it off.

107:24

However, if you were using this companion object from Java, you would have to reference

107:29

that companion object instance directly. You can also rename your companion object. So

107:35

if we wanted to name this something like factory to be a bit more explicit, we can then say

107:41

doc factory and reference it that way. And so again, not needed from Kotlin but it could

107:50

be a good way to make your code more understandable from the Java side of things. If you're doing

107:56

a lot of Java to Kotlin interrupt, we can also store properties within our companion

108:02

objects as well. So in this case we can create a const thou well

108:06

ID equals IB and then we can come down here and replace our entity and pass that in. Now

108:16

that we have this ID property added to our companion object, we can reference it from

108:21

other calling code as if it was a static property like we're familiar with from Java. So we

108:27

could do that by typing entity dot. And then we can reference that ID property directly.

108:34

Now competing objects are like any other class and that they can also implement other interfaces

108:40

to demonstrate that we'll create a new interface called ID provider with a single method this

108:50

called get ID. It will return a string. Now then come down to our companion object declaration

109:00

and we can make it implement ID provider the same way we would with any other class. We

109:07

can then choose to implement the required members and then here we will just return

109:12

a simple ID and so now when we create our instance of ID, we could rely on this ID method

109:19

if we want it. So you see companion objects can be quite flexible if you need them to.

109:25

You could use those to compose other types of behavior, store your semi static properties

109:32

or methods and use them to create factories by referencing private inner properties or

109:39

methods of the enclosing class. This is really what you would want to leverage if you want

109:43

functionality similar to that of static members

109:48

and field from the world of Java. Now that we've covered object expressions and companion

109:56

objects, let's take a look at creating an object declaration. To start, we're going

110:01

to clean up some of this code we've been working with so we will remove this implementation

110:06

of ID provider and we will go back to using a placeholder ID. We'll remove this reference

110:15

to entity ID and we can remove this ID provider interface. Now what our object declarations

110:21

and object declaration is a convenient way of creating threads saved singletons within

110:28

Kotlin. We can do this by using the object keyword and then a class name in this case

110:35

entity factory. Now within this you can add any types of properties or methods that you

110:43

would like. So let's start by migrating our create method from our companion object into

110:52

heart entity factory and now we can remove that companion object and instead we can reference

111:01

entity factory dot create.

111:05

Now there's one small problem with this so far, which is that entity still has only a

111:09

private constructor. Now we're going to remove that private modifier for now so that we can

111:16

use that constructor. However very shortly we will continue to refactor this code to

111:22

limit the ways in which entities can be created. Now before we go on and continue to explore

111:28

some of these other class types in Kotlin, let's add to our entity class by implementing

111:35

two string so that if we print out an instance of entity, we get some nice user readable

111:42

text. So we can start typing two string. And then I'll use a string template here and we'll

111:52

say ID colon is ID. And then we will also add in a name property here, Val name of type

112:05

string.

112:08

And then we'll say name and then substitute in that main property. And then here in our

112:15

create method we will just put in a generic name. And now down here we can use a print

112:24

line statement and pass in our instance of entity. And now we see our new two stream

112:33

texts being printed out to the console. So this will help us going forward demonstrate

112:37

some of how these other classes are going to work. All right, now that we can print

112:44

out useful information about an instance of an entity, let's refactor our create factory

112:51

method to actually differentiate instances of entity. So to do this, we're going to change

112:57

this and make it no longer a single expression function. So we will add a return type of

113:04

entity and then we'll add a return keyword. And that will, we'll add entity. Now the first

113:10

thing you want to do here is actually add a proper ID value.

113:15

So here you could say Val ID equals you do ID dot random U U ID dot two string. So this

113:27

will give us a new random identifier and then we can pass that into our entity. But now

113:33

we have this main property. So what can we do to pass in a name here? Well one thing

113:39

we might do is think about differentiating between different types of entities. So in

113:45

a very basic case, maybe you want to differentiate between easy, medium, and hard difficulties

113:51

of these entity types. So we might want to then have some way of passing or indicating

113:58

to this factory method, what those different types should be. So what am I, we could do,

114:04

this is with an ENM class. Now if you're familiar with Java and Enim class is going to be very

114:10

similar to what you're familiar with from [inaudible] in Java. To do that, we can start

114:15

typing email and then class. And then in this case we might name this something like entity

114:22

type and open and closed curly braces. And then we can iterate the different instances

114:29

of the email. So in this case we might say easy, medium, hard. So now we can come down

114:39

here to our create method and then we can add a type parameter of entity type.

114:48

And so now we could say vow name equals when type. And then we are going to add in the

114:59

remaining branches. So now we have a branch for each of our entity types. And then for

115:05

a basic name, I'm just going to map these to a string. So say easy, medium and hard.

115:17

And so now I can pass in that name. So now our factory method actually allows us to differentiate

115:24

and create different types of instances. So down here we might start off by creating an

115:31

easy entity and then we'll print that out. And then we might say vow medium entity equals

115:39

entity, factory dot. Create entity tight medium. And then we can print that out as well.

115:54

And if we run this well, now see that we have a unique identifier for each entity. And then

116:00

we have the customized name based on that entity type. So the addition of this ENM class

116:07

to represent our entity type has allowed us to pass in different types to our factory

116:12

method and then customize the way that those entities are created by mapping the entity

116:18

type to a name. Now in this case, we're mapping the name very closely to the name of the actual

116:25

class itself. So to make this a little bit easier and more encapsulated, there's a couple

116:31

of things we could do. So the first thing we could do is take advantage of the name

116:36

property on an ITAM class. So to do that, we could reference our type dot nay. So this

116:45

is referencing the name of that actual [inaudible] class. And if we run this, we can see what

116:51

that name looks like.

116:52

So you see it's easy all in capital letters. This matches exactly the way that the class

117:00

name is actually defined. So this allows us to reference the classes name directly without

117:06

having to map it manually. Now this is nice, however, we don't have a lot of control over

117:12

the formatting here. So another thing we could do is actually add a new method to argue in

117:19

class. So in this case we get add fun, get formatted name, and then we can reference

117:27

that named property.to lowercase dot capitalize. So this will return us that preform at a name

117:38

and capitalize the first letter. So now down here we can update our medium mapping and

117:46

type type dot get format in name. And so now if we run this code again, we'll see that

117:55

the first one by using the name property directly is all capitalized. But now by using our new

118:00

format and method, we have a nicer format similar to what we were using before. So that's

118:06

just one example of how you can define an Enon class and then add additional properties

118:12

and methods that class like you would any other class.

118:16

Now let's continue refactoring this code to further differentiate between different types

118:24

of entities. To do that, we're going to leverage a sealed class seal classes allow us to define

118:31

restricted class hierarchies. What this means is that we could define a set number of classes

118:37

all extending a base type, but those classes will be the only ones that can extend that

118:43

base type. So one example of this could be a loading state or results state for a network

118:50

operation. It's either going to succeed or fail and there aren't really any other options.

118:56

So in the right place we're going to create a sealed class with an easy, medium, hard

119:02

and help entity types. To start creating our sealed class hierarchy. We're first going

119:07

to remove the properties from our entity class as well as this existing override of the two

119:15

string method. The next step is to add the sealed keyword before the class keyword in

119:22

the entity class declaration.

119:24

As soon as we do that, we'll start getting an error above where we tried to create an

119:29

instance of entity. This is because you can't instantiate based sealed class type directly.

119:36

So this is now where we will create each type within our sealed class hierarchy. So the

119:43

first type we're going to create is a David class to represent easy entities. And then

119:51

we will add the properties we want in this case the ID and name, and then we want to

119:55

make sure that we inherit from entity. So next up we can copy that and update the name

120:05

for the medium type. And now for the third type, once again we'll copy that, we'll name

120:12

this hard, but now we're going to add an additional property. This property will be called multiplier

120:19

and we'll be afloat and this can represent some type of difficulty, multiple fire if

120:23

we were creating a game for example.

120:26

Now notice that all of these types within the sealed class all extend from entity but

120:32

have different types of properties. This is one of the key differentiators between sealed

120:36

classes and [inaudible] classes. With seal classes, you can have different properties

120:41

and methods on each of these type and the compiler can perform smart casting to allow

120:46

you to use these different properties and methods as you would like. We can also use

120:52

different types of classes itself within our sealed class. So you notice that these are

120:56

all created as data classes. However, if we wanted to remove data from one of these, that

121:02

would be perfectly fine. We could also use object declarations within our seal class

121:08

hierarchy. So this case will create an object class called help to represent some type of

121:14

generic static help entity within our program. Now because help doesn't have a constructor

121:23

because it's static. In this case, we can add a class body and we could add a name and

121:32

add help directly. And in this case, more ad ID. Since it's a Singleton and there's

121:37

only going to ever be one instance anyways,

121:40

now that we have our seal classes defined, we're going to update our factory method to

121:44

instantiate and return different types of entity classes. So we'll come up here to our

121:51

return statement and instead of returning an entity directly, we're going to use a wind

121:56

expression based on the entity type being in class. We'll then add all of the needed

122:05

branches. And so when we have an easy type, we want to instantiate an instance of the

122:11

easy class. So to do that we'll type D Z and then we will pass an ID and name. And similarly

122:21

for media type, entity dot. Media ID, combat name. And now for hard, once again we'll pass

122:30

it and entity dot hard ID name. But now again we have this additional property type and

122:39

the compiler recognizes that. So for now we'll just pass it in to F as our multiplier.

122:44

Now notice though that we have this help entity being unused. So let's update the factory

122:51

to allow us to create instances of the help type. So we'll come up to our entity type

122:59

Unum class and add a help type here. Now notice as soon as we added that additional type on

123:06

the entity type in them class are when expressions one to us that we need to add an additional

123:13

branch. So to do that, I'll add the remaining branch here and I'll default to typed up get

123:22

format name. And once again below here I'll add the remaining branch. And in this case

123:29

I'm just going to return help directly.

123:33

Oh, notice here that help is giving us an error. It's saying required and to be found

123:40

entity that help. This was done to demonstrate what happens if you do not extend from the

123:46

base entity type. So if we come down to our entity class here and you notice our object

123:52

declaration for help, if we then add a colon entity to extend from entity, we'll now see

124:00

that error go away. So this is a good example of how the compiler can help give us this

124:06

nice static type checking and all of these things. And if we are combining even classes

124:12

was sealed classes with these, when expressions get allows us to be sure that if we add a

124:18

new type or a new branch somewhere that we have to handle that effectively because the

124:23

compiler will warn us or even give errors if we're not handling all of those different

124:28

branches. Now let's come down to our main function and demonstrate one of the advantages

124:33

of representing our entities as a seal class hierarchy.

124:38

So if I remove everything, but this first instance of creating an entity, I'm going

124:45

to specifically add a type here of entity. And so now if we, if we come down again, we

124:53

can use a one expression and we'll say, now we can go down here and use a wet expression

125:04

to do some type checking about the entity that we have just instantiated. So here we'll

125:10

say Val, message equals when entity. And now again we're going to rely on the IBE to add

125:20

all the remaining branches. So there's a few things of interest to note here. So we'll

125:26

see that for an easy, medium and hard, it's adding. This is check. So this is going to

125:31

basically tell if it's an instance of that class or not. But then notice for the help

125:37

class, because that's an object declaration and as a Singleton there's no need to have

125:43

it is.

125:44

So in that case we can reference that class directly. And so now here we could add whatever

125:51

message we wanted. So we could say help class, easy class, medium class and hard class. And

126:08

then if we simply print that message out and run the code, we can see in this case we're

126:18

getting an easy class. And then if we change what we pass into our factory method and rerun

126:25

this, well now see that we're getting the help class. So now we have static type checking

126:32

both and specifying what type of entity we want back and and checking the type that we're

126:38

actually getting back from that. And so we could use this to then call any methods or

126:44

properties that are specific to that class. And if we were operating on these types as

126:51

in a one expression here, if we ever added a new type, that compiler would be sure to

126:57

make sure that we handled the addition of that new type.

127:02

So now let's return to our sealed class hierarchy for a second and dive more deeply into what

127:09

data classes are. So you see here both easy and medium and hard are all defined as data

127:15

classes. Data classes are cotton's way of providing very concise, immutable data types.

127:24

By defining a class as a data class, it means that it is going to generate methods such

127:30

as equals hashcode into string automatically for you. What this allows us to do is perform

127:37

a quality comparisons on instances of these data classes and treat them as equal if the

127:44

data they contain is equal. So here's an example. Let's explore what this looks like. So we

127:50

can say Val entity one equals entity factory that create and will create an easy entity.

127:59

And then we're going to create another version of this. And then now we can check their equality

128:08

comparison.

128:09

So you can say if entity one equals entity two per DeLeon, they are equal else per Delon,

128:23

they are not equal to. Now if we run this, what will we see? They are not equal. And

128:33

that's to the expected. That's because if we come back up to our factory, we'll notice

128:38

that we are creating different unique ideas each time. So even though that the name is

128:44

the same, the unique ID is different. So now let's update this and see what it looks like

128:50

if we pass the same data in. So in this case we could create an easy directly and this

128:57

case will pass in ID comma name and then we will duplicate this for entity two. And so

129:07

now if we run this, we're going to expect to see you. They are equal and of course they

129:13

are. So this is really convenient. This allows us to represent data within our applications

129:19

and compare this data no matter where it comes from.

129:22

And as long as those properties are all the same, we're going to be able to evaluate these

129:29

as true. Now another really interesting thing that data classes give us are effective copy

129:36

constructors. So we can create an instance of entity two by copying entity one entity,

129:44

one dot copy. And because this is a direct copy, if we run this once again, we're going

129:51

to see they are equal. However, we could also use named arguments with the copy constructor

129:58

to change the value. So let's say we only wanted to change the BAME and you could say

130:03

name equals new name. And once again, if we rerun this, we're going to see they are not

130:11

equal. So you could see changing a single property and the data class is going to impact

130:17

whether or not two instances evaluate to true or not when compare.

130:24

Now one thing to notice is this is comparing the value of the data themselves. If we wanted

130:31

to U S referential comparison, we hit add a third equal sign here and this will check

130:40

whether or not it's the exact same reference or not. So in this case they are not equal.

130:46

However, this isn't all that surprising since the data was also equal. So what about if

130:52

we revert this and make this an exact copy again? So before if we were just using two

130:57

equal sign, the data would be the same. So it would print, they are equal. However, by

131:04

using three equal signs and using referential equality, we see they are not equal. That's

131:10

because it's not the same exact reference of the object. If we updated this to be entity

131:15

one equal equal equals entity one and run this, now we'll see they are equal.

131:24

So that's just one way in which we can check whether or not we have the exact same object

131:29

or if it's two different objects that have the same data. Now also keep in mind that

131:37

these equality comparisons are working off of the generated equals and hash code methods

131:43

generated by the compiler when indicating a data class. However, we could update this

131:51

to change how the equals or hash code is evaluated and to do that we would do it like any other

131:59

class. We could add a class body and then we could simply override equals and or hash

132:08

code. Now as in Java best practice, if you're going to override one of these, you should

132:13

really override both of them and you have to follow the same rules, but you have that

132:19

freedom if you would like to.

132:28

Another really useful feature in Kotlin is the ability to define extension functions

132:33

or extension properties on an existing class. This is particularly powerful if you're working

132:40

with classes that you can't control but would like to modify the way in which they're used.

132:46

You can define your own properties and methods and defined kind of a new API around the existing

132:52

class. So an example of this would be adding a new method to the medium class without actually

132:59

defining that method within the definition of the medium class. So to do that, let's

133:05

come down here and you can start off by typing the fun keyword. And then instead of directly

133:12

typing the method name, we can reference the class name. Dot. And this case will type print

133:23

info.

133:27

And then we can define our function buddy. So in this case we'll just say medium class

133:37

with the ID and that'll be it. And so if we wanted to come down here and now create an

133:46

instance of entity dot medium directly, we could do that. And then we could call that

133:59

print info method. And if we run that code, we'll see a medium class and then that ID

134:05

printed out. So this is great if we know that we have that exact type that we're working

134:11

with. And in cases where we don't know if we have that direct type, we could rely on

134:16

smart casting. So if we update this to you, their factory say entity factory, create entity

134:25

type medium. Now we can say if entity two is medium entity, now we can reference that

134:37

new print info method.

134:44

This is done because the if statement will only evaluate to true if that cast is successful.

134:50

So anywhere within that context it will automatically perform the smart cast for us. And like I

134:58

said before, not only can we define extension methods, but we can also define extension

135:04

properties as well. To do that, we could start off by saying Val or VAR. In this case we'll

135:10

say vow and then again we'll reference the class type. So medium dot we'll say info will

135:21

be this property name string equals some info. If you do that, notice that we have this air.

135:29

If you look, it says extension property cannot be initialized because it has no backing field.

135:35

So to actually create an extension of property for an existing class, you need to rely on

135:40

backing fields. Thankfully the IDE can generate this forest, convert extension property initializer

135:46

to a getter.

135:48

So once we do that and notice here that we have still defined our property but now we're

135:53

relying on this custom getter for that property and so now if we come back down here within

135:59

our, if statement that's doing our smart cast for us, we could reference that new info property

136:06

directly. So this is how extension functions and properties work. You could use these anytime

136:13

you want to add additional functionality to existing class. You might notice within the

136:18

Kotlin standard library that many functions and operations work by using extension functions

136:25

in classes be are particularly effective when using them with template ID types

136:30

because it allows you to define the same common functionality across any type that matches

136:35

that template. Now up until this point, we've covered a lot of things. We've looked at the

136:42

basic type system of Kotlin, how to work with different variable types, how to work with

136:48

basic functions and then diving into more advanced functional topics like named arguments

136:53

and default parameter values. And then we took a deep dive into modeling data with Kotlin.

137:00

So now I'm going to circle back to functions and specifically take a look at higher order

137:04

functions and how to work with functional data types. Now what are higher order functions?

137:10

Higher order functions are functions that either return another function or that take

137:17

functions as perimeter values. Now much of Kotlin standard library is built on top of

137:23

higher order functions and it's what really allows us to write highly functional code

137:29

by leveraging that standard library.

137:32

So let's take a look at how we can write our own higher order function. To start we have

137:37

a new Kotlin file and we're going to define a new function. So we'll call this fun. And

137:43

then we're going to call this print filtered strengths. And now the first argument to this

137:50

is going to be a list of strings. So we'll call this list and then define it as list

137:56

of string. And now the next thing we're going to do is define a parameter which will in

138:02

fact be a function. That function will take in a string and return a bullying. We can

138:10

then use that to filter out values in the past collection. So to define a function parameter,

138:18

you could start off by defining the parameter name as usual. And this case, we'll name it

138:23

credit kit, followed by colon. And now you have to define the type as you normally would

138:30

to define a functional type.

138:31

You can start by adding your parentheses. This will define the parameters of the function

138:39

being passed in to your other function. So in this case we are going to take a string.

138:46

He'll then add the arrow, and then you want to define the return type. So in this case,

138:54

that will be bullying. And now we'll add the open and closed curly braces to define our

138:59

block body. So now we have a parameter called predicate, which will be a function that takes

139:04

in a string parameter and returns a Boolean. Now we can implement our print filtered strings

139:11

function to make use of that predicate function to filter out any strings in that past list.

139:19

So to implement this function, first off, we want to iterate over each string in the

139:25

past list. So to do that, we could say list doc for each and now we will be able to iterate

139:34

over each of those strings.

139:36

So now what we want to do is evaluate the predicate for each stream in the collection.

139:43

So we can call the predicate function in several different ways. So to start we'll say if,

139:49

and then the easiest way to invoke the predicate is to simply say predicate open and close

139:55

parentheses and pass in the parameter value. A parameter that is a functional type can

140:01

be called as if it was a regular function. As long as you can satisfy the required arguments.

140:06

So in this case we can say if predit kit returns true, then we can print out that string. Now

140:15

to test this, we'll come down here and we will add a main function and we will say vow

140:22

list equals list of, and then we can say something like Kotlin, Java C plus plus Java script.

140:38

And now we could call print filtered strings pass in our list.

140:45

And now we need to pass in a function as the second parameter to print filters, drinks.

140:49

So we can do that by specifying a Lambda, and in this case we will say it starts with

140:59

K. so this Lambda is going to evaluate to true if any of the past strings begins with

141:06

a K. now if we run this function, we'll see only Kotlin print it out to the screen. If

141:14

we were to update this to print things out, that started with a J, well now see Java script

141:20

and Java. Now one thing to notice is it in our invocation of print filtered strings,

141:26

we've passed our Lambda within the parentheses of that function in vacation. However, this

141:32

is something that we don't have to do. As we mentioned earlier, we can take use of Landus

141:37

syntax, which says that if the last parameter of a function is a function, you can specify

141:44

that as a Lambda outside the function body. So we can restructure our function to look

141:52

like this. We can pass in the list first and then specify or Lambda outside of the parentheses.

141:58

So this is actually very similar looking to the for each function which we called up above.

142:03

And in fact if you look at the implementation of for each is in fact a higher order function.

142:13

The Lambda that we specify after invoking for each is a function which will operate

142:19

over each string and that list. Now if we come back up here to our implementation notice

142:25

we are calling the function parameter directly as if it was a regular function. So this works

142:32

absolutely great in most situations. However, if we were to make this function, type a NOLA

142:40

ball type by wrapping it in parentheses and adding new question Mark. Well now see an

142:46

error in our implementation of print filtered strings. That error basically says that you

142:53

cannot invoke that function parameter by using the parentheses directly. If it's a nullable

142:59

type to get around this, we can make use of the invoke method on that functional type

143:08

and then we can make use of the safe call operator and now, but updating this to do

143:16

a safe invoke call on the predicate function.

143:20

We can handle this rather not the predicate is no calling invoke will invoke the function

143:28

just as it would any other indication of a function. So now down here nothing has changed

143:35

and how we can call print filtered strings. However, we could also pass it in list and

143:42

now we could pass in no as a no function. So we've seen how we can treat functions as

143:49

parameters to other functions and these function parameters are really treated as tight. Just

143:56

the same as let's say integer or string. Caitlyn has this idea of functional types. It's a

144:03

first-class part of the language. This means that we could define a variable of a functional

144:09

type and then pass that variable in any time. We needed a function parameter that matched

144:15

that function signature. So an example of this might be something like vow credit kit

144:24

and then we will define our function type to match that of our print filtered strings

144:28

function.

144:29

So in this case it'll take a string and return bullion and now we'll define our function

144:40

the same way that we were doing it before. By saying if the string starts with aJ , go

144:45

ahead and return true. Now instead of invoking print filters, strings with a landed pass

144:52

to it, we can pass in our predicate variable directly. And now if we run this, we'll see

144:59

the same output as we would before. So this allows us to store function as variables.

145:06

This can be really useful for things like optional input handling. For example, maybe

145:12

you have a view on some screen and you want to be able to specify a ClickList center for

145:17

that view. You could define that as a Lambda property on some class and allow client code

145:25

to set that ClickList center as needed. As we mentioned before, higher order functions

145:30

include functions which take other functions as parameters, as well as functions that return

145:36

other functions.

145:38

So let's define a function called get print predicate and it'll take no parameters, but

145:48

we defined its return type as a function which takes a string and returns a bullion. And

145:58

now we can return that value by saying return. And then we could pass a Lambda and say it.

146:07

That starts with J. So we're passing essentially the same type of Lambda that we've been using

146:13

in these other examples. But now we've wrapped it in this other function and so now and so

146:19

then passing predicate directly or instead of defining a new Lambda as our function parameter,

146:28

we could instead call get print predicate as a function on its own, which will then

146:34

return a function which then can be used as the predicate for print filtered strings.

146:40

And if we run this once again, we'll see that our output hasn't changed though. So higher

146:48

order functions can work as both inputs and outputs and Kotlin allows you to define properties

146:55

with functional types.

146:57

So through this function's really become a very powerful and first-class part of the

147:02

language that can start to replace a lot of other instances. For example, you might find

147:07

yourself relying more heavily on functions to define things like event or a ClickList

147:13

centers rather than defining concrete interfaces for those same types of functionality. Now

147:20

this was recently mentioned. Much of the Kotlin standard library is built around higher order

147:27

functions and especially a higher order functions defined with generic types. So if we look

147:33

at the implementation of four each, well notice that this is actually an extension function

147:41

as well as a higher order function. So for each works on generic Iterable type and takes

147:48

in a function parameter that takes in that generic type and returns unit. So this essentially

147:56

allows us to iterate over each element in the collection and then call that action on

148:03

it and it doesn't have to return anything.

148:06

And similarly for each index takes in a single function parameter as well. But this one takes

148:12

in an event to represent the index as well as the generic type. This allows us to iterate

148:19

over each element in the collection while incrementing a counter and then passing that

148:24

counter into the function parameter as the index. The power of generic types, extension

148:32

functions and higher order functions allows us to write single implementations of these

148:37

methods and then reuse them over any type that we can think of. Now this is very powerful

148:43

and can allow us to write much more functional code without having to redefine these methods

148:51

and functions for all of our different types. So let's take a look at example of how we

148:56

can combine some of these different functional operators to perform complex operations with

149:03

very little code. We'll come into this new main function here and we'll start off by

149:07

defining a list of strings.

149:10

Once again. Now let's look at some ways in which we can chain these functional operators

149:16

together to do more interesting things. So as we've seen before, we can do a simple for

149:23

each to iterate over each item in this collection and print it out. And if we run it, we'll

149:31

notice that we see all of the programming language printed out to the console. Now what

149:36

if we wanted to print out only the strings that start with J plus similar to the functions

149:43

we were working with before, we could do that by making use of a filter operation. So we

149:51

have a lot of options to choose from. In this case, we will just choose a generic filter

149:56

and then we will use a predicate which says it starts with J and now if we run this was

150:06

he, he had only Java and Java script printed out. Now, what if our collection included

150:12

some no values?

150:14

So as soon as we add, no, we see now here in our filter operation, it's warning us that

150:22

Hey, this value might be no, you need to add a safe call weld in Kotlin. Oftentimes we

150:28

don't want to work with no, we want to try and hide no as much as possible. And so we

150:34

could make use of another functional operator called filter not know. What this does is

150:44

immediately filter out any no values up front. So everything past that in the functional

150:49

chain will be guaranteed to be not. No. So as soon as we added filter, not know, we no

150:54

longer had to deal with a possible no string. And if we run this once again, we'll see only

151:01

Java and JavaScript printed out.

151:05

Now what if we wanted to change the type of this? Let's say we wanted to convert this

151:10

from a string to an integer, which represents the length of that input string. We could

151:17

do this type of transformation using a map function. The map function will take in whatever

151:24

the previous type is in this case string, but it'll allow us to return any other type

151:29

we want. So in this case, we might define our map function as simply returning the length

151:37

of the string. As soon as we've done that. Now below that in the for each, the type has

151:44

changed from string to end. And now if we print this out, we'll see four and 10 printed

151:52

out for representing the four characters in Java and 10 representing the 10 characters

151:58

in Java script. Now let's remove this mapping and let's remove the filter. And instead,

152:06

let's imagine that we want to take only a certain number of items from this collection.

152:13

So we can do that by using the take function and passing in. Let's say three. What that'll

152:21

do is we'll take the first three items from that collection and then we'll be printing

152:26

out each of those three names. So you see in this case we're getting Kotlin, Java and

152:31

C plus plus. Alternatively, if we didn't want to take the first three elements in the collection,

152:38

we could use take last today, the last three. So in this case we see Java C plus plus and

152:46

Java script and it has skipped over Kotlin since that was not one of the last three elements.

152:52

We can also do other transformations such as associating the input values with some

152:58

other value to return a map. So let's create a map that essentially maps the string to

153:06

the number of characters in that string. So to do that we could say associate, and then

153:15

in this case we could say it to it dot length. And so now in our, for each function, instead

153:25

of iterating over strings, we're iterating over map entries of string and event. So in

153:34

this case we can now use a template string and say it got value comma it dot key.

153:54

And if we print this out, we'll see the length comma followed by the name. This makes it

154:01

really easy to map all of the input strings to some other value and then iterate over

154:06

that map. Now, what if we didn't want to iterate over the map but instead just wanted to hold

154:12

on to that in a variable? Well, instead of using a fork each at the end, we could assign

154:20

this to a variable just like this. The continent standard library also provides a variety of

154:28

functions to help us pull out individual elements from a collection to demonstrate that that's

154:33

created a variable called language. And then we're going to perform different operations

154:40

on our list to grab a single language string from our list. So we could do that in a number

154:45

of ways. We could say list dot first and if we print this out, we'll expect to see Kotlin

154:56

as that is the first language in the list.

155:01

Alternatively, we could say we'll start last and in this case you'll see that it's actually

155:09

printing out. No, since [inaudible] was the last value in that list. Now, if we didn't

155:15

want to retrieve a null value from our list and instead wanted the Alaskan non-male value,

155:22

once again, we could add the filter, not no function, which we used previously. And now

155:31

if we rerun this, we'll see Java script printed out instead, since this is the last non no

155:38

value. Now what if we wanted to find a specific item in the list? Let's say we wanted to use

155:46

the find function and in our predicate we'll say it got starts with and we'll pass in Java

155:55

as a street. So this is going to find the first value in this list that starts with

156:01

Java. So in this case it actually returns us Java and alternatively we could use find

156:09

last to find the last element in the collection that matches this predicate, in which case

156:15

it's going to return JavaScript.

156:18

Now what happens if we are searching for a string which doesn't match our predicate?

156:25

We can test that by looking for a string which starts with food. If we then run this, we'll

156:33

see no print it out to the console. This is because there is no matching string. So fine.

156:39

Last is going to return. No. And then the print line statement, we'll print out. No

156:44

if it has a null value. Well what if we didn't want to work with no? What if instead we wanted

156:49

to use an empty string as the placeholder? Well, strings in Kotlin have a useful function

156:58

called or empty. So we can actually chain that directly off of find last here and call

157:05

or empty. So at this will do is return either a nano string or a static empty string. So

157:14

now if we run this once again, instead of no, we're just seeing empty, we're not printing

157:20

anything out.

157:22

So this is one way in which you could default your collections or your strings to an empty

157:28

value as opposed to a no value. And this is something you might want to consider doing

157:32

more and more of in Kotlin as you start to move away from relying on null. So as we've

157:38

seen, Caitlyn has first-class support for functions including functional types and higher

157:44

order functions, and the Kotlin standard library builds upon those tools and provides a rich

157:50

set of functional operators for us to use. This allows us to build powerful functional

157:56

chains to transform our data and make complex workflows much simpler. All right, that's

158:01

it for this tutorial. You now have a good understanding of the fundamentals of Kotlin

158:08

and how to work with it, and you're now ready to start taking that knowledge and applying

158:13

it to other domains. Until next time, devs.

UNLOCK MORE

Sign up free to access premium features

INTERACTIVE VIEWER

Watch the video with synced subtitles, adjustable overlay, and full playback control.

SIGN UP FREE TO UNLOCK

AI SUMMARY

Get an instant AI-generated summary of the video content, key points, and takeaways.

SIGN UP FREE TO UNLOCK

TRANSLATE

Translate the transcript to 100+ languages with one click. Download in any format.

SIGN UP FREE TO UNLOCK

MIND MAP

Visualize the transcript as an interactive mind map. Understand structure at a glance.

SIGN UP FREE TO UNLOCK

CHAT WITH TRANSCRIPT

Ask questions about the video content. Get answers powered by AI directly from the transcript.

SIGN UP FREE TO UNLOCK

GET MORE FROM YOUR TRANSCRIPTS

Sign up for free and unlock interactive viewer, AI summaries, translations, mind maps, and more. No credit card required.