Programming Kotlin Enhance Your Skills For Android Development Using Kotlin by Alexander Aronowitz PDF
Programming Kotlin Enhance Your Skills For Android Development Using Kotlin by Alexander Aronowitz PDF
Introducing Kotli n
... .................................................. .................................................. ?Why Kotlin
........................................... .................................................. ?Who is this book for
................................ .............................................. How to use this book
............................................ ................................................ For the curious
....... .................................................. ................................................. Tasks
............................... ................................................ Typographic conventions
............................................... ................................................ Looking ahead
............................................. ................................................ From the publisher
Chapte r
................... ............................................. 6 . Null safety and exceptions
.......... .................................................. ................................................. Nullability
............................................ ............................................. Explicit type null in Kotlin
................. ............................................. Compile time and run time
................................................. ............................................... Null safety
. ............................................ First Option: Safe Call Operator
............................... ............................................ !! Option two: operator
.................................. Third option: check the value for null
...... .................................................. ................................................. Exceptions
.............................. ................................................ Raising Exceptions
....................... ................................................ Custom Exceptions
.................................. ................................................ Exception Handling
.......................................... ................................................ Checking conditions
.................................. .................................................. ?Null: what's good about it
........................... For the curious: checked and unchecked exceptions
....................................... ?For the curious: how is null support provided
?Why Kotlin
To appreciate the attractiveness of Kotlin, you should first figure out which
Java occupies a place in the modern world of software development. The
Kotlin code is runs under the Java Virtual Machine, so the two languages
are closely
.interrelated
Java is the most trusted and reliable language used for
developing industrial applications for many years. But the language
Java was created back in 1995, and since that time, the criteria for
evaluating good programming languages have changed. Java lacks many of
the conveniences it has in languages used by developers now. Kotlin
creators have checked out lessons from design decisions made when
designing Java (and other languages such as Scala) and have lost their
relevance. Its development went beyond the limits of the capabilities of old
languages and it has been corrected a lot that caused a lot of inconvenience.
.Learn how Kotlin is better than Java in this book
.and why it is more convenient to work with it
Kotlin is not just an improved language for the Java Virtual Machine. It is
multi- general-purpose platform language: you can write applications in
Kotlin
Introducing Kotlin for Windows and MacOS, JavaScript and of course Android.
.Independence from the system implies a wide range of Kotlin applications
Tasks
Most of the chapters end with one or two tasks. Their solution
will help you understand the Kotlin language better. We propose to perform
.them for mastering the Kotlin language
T ypographic conventions
In the process of developing projects from the book, we will support you,
revealing introduce a topic and then show you how to put theory into
practice. For more For clarity, we adhere to certain typographic
.conventions
-Variables, their values and types are printed in monospaced font. Klas
.Sy, functions, and interface names are in bold
All listings with code are printed in monospaced font. If you need will add
the code in the listing, this code will be highlighted in bold. If the code you
need to remove it from the listing, it will be crossed out. In the example
below
at least you are told to remove the line of code that declares
: variable y , and adding the variable z to the code
"var x = "Python
"var y = "Java
"var z = "Kotlin
Looking ahead
Take your time to bring the examples in this book to life. Having mastered
the syntax Kotlin, you will make sure that the development process in this
language is clear, flexible cue and pragmatic. Until that happens, just carry
on
.get to know him: learning a new language is always beneficial
Then follow the instructions for your system in the instructions for
:installation and configuration on the JetBrains website
-Jetbrains.com/help/idea/install-and-set-up
. product.html
In the New Project window, select Kotlin on the left and Kotlin / JVM on the right ,
.as shown in fig. 1.4
In IntelliJ, you can write code in languages other than Kotlin, for example
,Java
Python, Scala and Groovy. Choosing Kotlin / JVM indicates that you are going
to write to Kotlin. Moreover, Kotlin / JVM indicates that you are going to write
code that will run under the Java Virtual Machine. One of the benefits
Kotlin is all about having a set of tools that allow you to write
.code that runs on different operating systems and on different platforms
From now on, we'll abbreviate Java Virtual Machine to JVM. This)
abbreviation
viatura is often used in the Java developer community. To learn more
Figure: 1.4. Creating a Kotlin / JVM project
about programming for the JVM can be found in the section "For the
(.curious: programming JVM programming ”at the end of the chapter
Click Next on the New project window . IntelliJ will display the settings
window of your new project (Fig. 1.5). In the Project name field , enter the
name of the project "Sandbox". The Project location field will be filled in
automatically. You can leave the path by default or change it by clicking the
... button to the right of the field. Select version Java 1.8 from Project SDK
dropdown to link your project to Java Development Kit (JDK) of the eighth
.version
Why do I need a JDK to write a Kotlin program? JDK opens environment
IntelliJ access to JVM and Java tools which are needed for translation
Kotlin code into bytecode (more on that below). Any version is technically
,fine
starting from the sixth. But as far as I know, at the time of this writing, JDK
8
.works most stably
If Java 1.8 is not listed in the Project SDK dropdown , it means
,thinks that you still do not have JDK 8 installed. Before proceeding
/do this: download JDK 8 for your system from oracle.com
.technetwork / java / javase / downloads / jdk8-downloads-2133151.html
,Install JDK and restart IntelliJ. Repeat the steps described earlier
.to create a new project
. When your settings window looks like fig. 1.5, click Finish
IntelliJ will generate a project called Sandbox and display the project in
standard nominal two-pane view (fig. 1.6). IntelliJ will create a folder on
disk
and a series of subfolders with project files in the path specified in the Project
. location field
The panel on the left displays a window with project tools . The panel on the
right is the moment is empty. This will display the editor window where
you can view and edit the contents of your Kotlin files. Pay
attention to the window with tools on the left. Click the triangle icon
to the left of the Sandbox project name . A list of files used by the
.in the project as shown in fig. 1.7
The project includes all the source code of the program, as well as
information
See the dependency and configuration information. The project can be split
into one or more modules that are considered subprojects. Default new
Figure: 1.6. Standard two-pane view
Click OK . IntelliJ will create a new file src / Hello.kt and display its contents
in the editor window on the right (Fig. 1.10). The .kt extension indicates that
the file contains
lives the source code in Kotlin language, similar to how the java extension
,says
.that the file contains Java code or .py - Python code
Figure: 1.10. Displaying an empty Hello.kt file in the editor window
You are finally ready to write Kotlin code. Stretch your fingers and apply
pay. Enter the following code into the Hello.kt editor window . (Recall that on
throughout the book, the code you must enter will be highlighted
(.bold
(Listing 1.1. "Hello, world!" on Kotlin (Hello.kt
The code you write may look strange. Don't despair - towards the end
reading and writing in Kotlin will come naturally to you. While
.that it is enough to understand the code at a superficial level
The code in Listing 1.1 creates a new function . A function is a group of
instructions options that can be performed later. You will learn more about
.the functions
.details in chapter 4
This particular function - main - has a special meaning in Kotlin. Main
function
defines the starting point of the program. This place is called the entry point
;
,so that the Sandbox project (or any other program) can be started
an entry point must be created in it. All projects in this book
. start from the main function
Your main function contains one instruction (sometimes the instructions are
called operators ): println ("Hello, world!") . println () is also a function
it, built into the Kotlin standard library . After starting the program
will execute the println ("Hello, world!") method , and IntelliJ will print the
,string
.( !specified in parentheses (without quotes, that is, just Hello, world
Kotlin REPL
Sometimes it may be necessary to test a small piece
code in Kotlin to see what happens when it is executed. it
is like writing a sequence of calculations on paper. Especially this
useful in the process of learning the Kotlin language. You're in luck: IntelliJ
provides a tool for quickly testing code without creating a file. This tool
the cop is called the Kotlin REPL . We'll explain the name later, but now
.we'll see what he does
In IntelliJ, open the Kotlin REPL by choosing Tools → Kotlin → Kotlin REPL
from the menu
.(fig. 1.14)
Figure: 1.14. Opening the Kotlin REPL tool window
IntelliJ will display a REPL panel at the bottom of the window (Figure
.(1.15
Each platform, such as Windows or macOS, has its own set of instructions
tions. JVM virtual machine is a bridge between bytecode and various
software and hardware, it reads the bytecode and executes
the corresponding machine instructions. This allows developers to
in the Kotlin language, write platform-independent code only once that
after compilation to bytecode will execute on many devices outside
.depending on the operating system
Since Kotlin can be translated to bytecode for the JVM, it is considered a
language JVM. Java is the most famous JVM language because it was the
first. In- as a result, other JVM languages appeared, such as Scala and
Kotlin, which, by according to their authors, should have eliminated the
.existing drawbacks of Java
Kotlin is not limited to JVM support. At the time of this writing, the code
for
Kotlin could also be translated to JavaScript or even binaries executable
files that can be run on the chosen platform - Windows, Linux or macOS -
.no virtual machine installed
Types
.Data stored in variables and constants is of a specific type
The type describes the data assigned to a constant or variable and how when
compilation will check it . This check prevents assignment to a variable or
.data constant of the wrong type
To see how this idea works, add a file to your Sandbox project by creating
given in chapter 1. Open IntelliJ. The Sandbox project will likely open
automatically, as IntelliJ opens the last project on startup. If
this did not happen, select Sandbox in the list of recent projects to the left of
. welcome windows or like this: File → Open Recent → Sandbox
First add a new file to the project by right clicking on src folder in the
project toolbox. (You may need to open Sandbox by clicking on the triangle
icon to see the src .) Select The New → Kotlin the File / Class , and name the file
.TypeIntro . The new file will open in a window editor
The main function , as shown in Chapter 1, defines the entry point for a
.program
In IntelliJ it is possible to simply write "main" in TypeIntro.kt and press the
key
I hang Tab . Then IntelliJ will automatically add all the base elements of the
.given functions, as shown in Listing 2.1
.(Listing 2.1. Adding the main function (TypeIntro.kt
Variable declaration
Imagine you are writing an adventure game that allows the player
,explore the interactive world. You might want to have a variable
.storing the points earned by the player
In TypeIntro.kt create the first variable named experiencePoints and
.give it meaning
(Listing 2.2. ExperiencePoints variable declaration (TypeIntro.kt
So, you've created a variable called Int and named experiencePoints . let's
.Let's take a closer look at what we got
You have defined a variable using the var keyword which starts
.declaring a new variable, and after the keyword indicated its name
Next, you defined the type of the variable: Int . This means that experiencePoints
.will store an integer
-Finally, you used the assignment operator ( = ) to assign
put the value on the right (value of type Int , namely - 5) of the variable on
.( the left ( experiencePoints
.In fig. 2.1 shows an experiencePoints declaration as a diagram
After the declaration, the value of the variable can be printed to the console
. using println functions
Assigned value
Assignment operator
Keyword
ads
variable
Variable name
Ad type
Run the program by clicking Run next to the main function and choosing Run
.'TypeIntro
kt ' . The console will display the number 5, that is, the value you assigned
. experiencePoints
-Now try setting experiencePoints to "thirty-two" . (Strikethrough
(.A wrinkled line means that this code must be deleted
(Listing 2.3. Setting “thirty-two” to experiencePoints (TypeIntro.kt
Run main by clicking the start button again. This time the compiler
:Kotlin will report an error
Error: (2, 33) Kotlin: Type mismatch: inferred type is String but Int was
ExpectedWhile dialing the code, you may have noticed a red underline under
. ""thirty-two
This is how IntelliJ tells you that there is a bug in the program. Hover your
pointer mouse over thirty-two to read a description of the detected problem
Kotlin uses static typing , that is, the compiler checks all types in the source
.code to make sure the code you write is correct
IntelliJ, in turn, inspects the code as it is typed and highlights attempts
assign a variable of the wrong type. This is called static type consistency
.checker and helps you see errors before compiling the code
Read-only variables
Until now, you have only come across variables that can assign new values.
But often it becomes necessary to use variables that are unchanged
.throughout the entire execution time of the program
For example, in a text adventure game, the player's name should not change
.after the initial assignment
The Kotlin language offers the ability to declare variables available
read-only , - such variables cannot be changed after assignment
.initial value
A variable that can be changed is declared with a key the words var . To
.declare a read-only variable, use the keyword val is called
. In colloquial speech, variables that can be changed are called vars
and read-only variables are vals . We will honor this the rule from now on,
since the phrases "variable" and "variable, read-only ”take up too much
space. vars and vals are all "variables" and we will use these abbreviations to
denote
.them in the text
Add a val declaration to hold the player name and add its output to
.after displaying the player's points
(Listing 2.6. Adding val playerName (TypeIntro.kt
Run the program by clicking the Run button next to the main function and
selecting Run 'TypeIntroKt' . The experiencePoints and playerName values should appear
:in the console
ten
Estragon
Since the playerName does not change anywhere, there is no need (and so
do not) declare it as var . Note that IntelliJ has highlighted the line with the
var keyword in mustard color. If you hover your mouse with the var keyword
.(, IntelliJ will report the proposed change (Figure 2.3
As you can see, Kotlin has determined that your declaration is of type
?"redundant". What it means
Kotlin supports automatic type detection , which allows you to omit
.style types for variables that are assigned values when declared
Since when declaring the variable playerName , a value of the type
String and experiencePoints are set to Int , which is the Kotlin piler automatically
.detects the type of each variable
Just as IntelliJ can help change var to val , it can help remove unnecessary
type declaration. Click on declaration of type String (: String) next to playerName
and press Option-Return ( Alt-Enter ). Then click on Remove exclicit type specification in
.(the menu that appears (Figure 2.7
String will disappear. Repeat the process for experiencePoints var to remove :
. Int :
Regardless of whether you use the automatic detection
type definitions or specify the type in the declaration of each variable, the
compiler
Figure: 2.7. Removing an explicit type definition
String❍
Int ❍
Double ❍
Float ❍
Long ❍
Short ❍
Byte ❍
Char ❍
Boolean ❍
The Kotlin bytecode tool window will open (Figure 2.10). (You can also
(. open it by choosing the Tools → Kotlin → Show Kotlin of Bytecode
-Even though you omitted the type declaration when declaring both re
variables in Kotlin, the generated bytecode contains an explicit type
.declaration
This is how variables would be declared in Java, and this is how the
bytecode
allows you to see the work of the automatic type detection mechanism in
.the language Kotlin from the inside
We will delve deeper into Java bytecode in the next chapters. Close TypeIntro
.now
decompiled.java (by clicking X on the tab) and the bytecode tool window (using
.(using the icon in the upper right corner
, In this chapter, you learned how to store basic data types in vals and vars
and also considered when it is worth using them depending on the
necessary
change their values. You've seen how to declare immutable values
as compile-time constants. Finally, we learned how Kotlin uses automatic
type detection to save time by reducing total keystrokes when declaring
.variables. You have repeatedly use these basic tools as you read this book
In the next chapter, you will learn how to express more complex states with
.by the power of conventional constructions
There are several reasons for this. First, in the absence of a choice between
you cannot corner yourself as easily as if you have
such a choice. For example, imagine you have declared a variable of simple
type, and then it turned out that the generalization mechanism requires the
use of reference new type? Supporting only reference types in Kotlin
.forever removes you from this problem
If you are familiar with Java, you are probably thinking now: “But
primitives provide better performance than reference types! " And it is true.
But
,let's see what the experiencePoints variable looks like in bytecode
:which you saw before
;int experiencePoints = 5
As you can see, a simple type is used instead of a reference type. How is
this possible, if Kotlin only supports reference types? Kotlin compiler if
there is such a possibility, uses Java bytecode primitives, because
.they actually provide the best performance
Kotlin provides reference type convenience and performance for
mitives. In Kotlin, you will find corresponding reference types for eight
.primitives that you may already be familiar with from Java
Quest: hasSteed
Here's your first assignment: In our text adventure game, the player can
You can tame a dragon or minotaur in order to move around on it. Declare
a variable named hasSteed (be the master of the mount) to keep track of
availability of transport. Set the variable to an initial state indicating
.that at the moment the player does not have it
If / else statements
Let's get started. Open IntelliJ and create a new project. (If you have IntelliJ
already open, create a new project like this: File → New → Project… ) Select
.Kotlin / JVM and name your project NyetHack
Expand the NyetHack list in the project tool window and right click
mouse click on src to create a new Kotlin File / Class . Name your file
Game . In Game.kt, add an entry point to the program, the main function , by
"typing "main
:and pressing the Tab key . Your function should look like this
} (fun main (args: Array
{
Let's analyze the new code line by line. First, declare the val named name
and assign it a string value with the name of your brave player. Further
declare a var named healthPoints and give it an initial
. value 100, that is, ideal condition. Then add an if / else statement
In an if / else statement, you kind of ask yourself a question that can be
answered
Yes" or "no" ( true / false ): "Does the player have 100 healthPoints ?" This"
the question can be expressed using the == comparison operator . It reads
like
."Equal", which means your condition will be "If healthPoints is 100"
The if statement is followed by curly braces ( {} ). The code inside the curly
-sko
side is what you want the program to do if the if statement finds out that
The catch is true. In this case, this will happen if healhPoints has
.value equal to 100
} (if (healthPoints == 100
("!println (name + "is in excellent condition
{
Operator
Description
>
Evaluates whether the value on the left is less than the value on the right
=>
Evaluates whether the value on the left is less than or equal to the value
on right
<
Evaluates whether the value on the left is greater than the value on the right
=<
Evaluates whether the value on the left is greater than or equal to the value
on right
==
Evaluates whether the value on the left is equal to the value on the right
=!
Evaluates whether the value on the left is not equal to the value on the right
===
Evaluates whether two references refer to the same object
== !
Evaluates whether two references are not referring to the same object
Let's get back to business. Start Game.kt by clicking Run to the left of the main
:function . You will see write the following output
Since the given state healtPoints == 100 is true, the if
Madrigal is in excellent condition!
branch was executed operator if / else . (We used the word branch because
the thread is code will follow the branch that matches the condition.) Now
ask
.Please change the healtPoints value to 89
(Listing 3.2. Changing healtPoints (Game.kt
Now the given condition is false, since 89 is not equal to 100, and the
program will execute
. nil else branch
Adding conditions
Our health code is doing its job too inaccurate, since it is ... raw. If the
player's healtPoints is 89, we are told that he is "in bad shape" and that doesn't
.make sense. After all, he can just get scratched
More conditions can be added to make the if / else statement more precise
for testing and more branches for all possible results. This can achieve with
the else if statement , whose syntax is similar to if (from if to else ). Add three else
. if branches to the if / else statement that test for intermediate values of healtPoints
(Listing 3.3. Checking for more player health conditions (Game.kt
You have added a more detailed display of the player's health status,
including
,by writing else if statements with a large number of conditions to be checked
when the if condition evaluates to false. Try changing the value healthPoints to
check the output of other messages. When you're done make value healtPoints
.again equal to 89
A boolean value val has been added to represent that the player has a good
Slovenia. An if / else statement was also added to output additional
message when the player has between 75 and 89 health units and has a
.blessing
Assuming healthPoints is 89, you would expect to see new message after
:starting the program. Run it and check. Output should be like this
!Madrigal has some minor wounds but is healing quite quickly
.If you get a different output, check to see if the code matches Listing 3.4
.specifically, whether healthPoints is 89
Nested statements allow you to add additional branches inside
.branches, which helps to express more precise and complex conditions
Operator
Description
&&
(Logical "and": true when both expressions are true (otherwise false
||
-Logical "or": true when at least one expression is true (false
(but, if both are false
!
Logical "not": turns truth into falsehood and falsehood into truth
("logical "not) !
less than), <= (less than or equal), > (greater than), > = (greater than or) >
(equal
(equal) ,! = (not equal) ==
(boolean and) &&
("boolean "or) ||
:Let's go back to NyetHack and analyze the new condition
} (if (isBlessed && healthPoints> 50 || isImmortal
("println ("GREEN
{
Or, to read it differently: if the player is blessed and has more than 50
points
-health or if the player is immortal, then a green aura glows around him. Ma
Drigal is not immortal, but is blessed and has 89 health points. And the first
time the condition is met, then Madrigal's aura will be visible. Run the
,program
:to check if this is the case. You will see
GREEN
!Madrigal has some minor wounds but is healing quite quickly
(Game.kt)
} (<fun main (args: Array <String
...
Aura //
} (if (isBlessed && healthPoints> 50 || isImmortal
val auraVisible = isBlessed && healthPoints> 50 || isImmortal
} (if (auraVisible
("println ("GREEN
} else {
("println ("NONE
{
...
{
You have written the result of checking the condition into a new value val
named auraVisible and changed the if / else statement to check for this new value
nie. This code is functionally equivalent to the previous one, but you
express
rules through value assignment. The meaning name clearly reflects the
meaning
rules in a "human readable" form: the visibility of the aura. This technique
is especially useful when the rules become more complex, helping to
.understand the geek of work to future readers of your code
.Run the program again to make sure it works as before
.The conclusion should be the same
Conditional expressions
Now the if / else statement not only displays the player's health, but also adds a
.little clarification
On the other hand, the code turned out to be a little cumbersome, because in
all
branches use similar println statements . Now imagine that it was necessary
to change the general format of the player status message. You will have to
go through each branch in the if / else statement and change the message in
. every call to println function
The problem can be solved by replacing the if / else statement with a conditional
. expression
A conditional expression is almost a conditional operator, with the only
difference, that the result of the if / else statement is assigned to a variable that
will
.used in the future. Update the health finding to see how it works
(Listing 3.7. Using Conditional Expression (Game.kt
Run the code again to make sure everything works as intended. You
.you will see the same output, but the code is simpler and clearer
You may have noticed that in the conditional expression that returns the
color of the aura, the curly braces disappeared. Let's discuss why this
.happened
-This version of the conditional healthStatus acts exactly the same as ver
this in your code. She even expresses the same logic, but in less
?code. Which version do you find easier to read and understand
By choosing the version with brackets (as in your example code), you will
.stop at the style preferred by the Kotlin language community
We recommend that you do not strip parentheses in conditional statements
and expressions, which are longer than one line. First, no parentheses as the
number increases conditions, it is more difficult to understand where one
.branch ends and another begins
Second, if you remove the curly braces, a new project participant can
it is tempting to update the wrong branch or misunderstand the code. Such
.risks are not worth saving a couple of keystrokes
Also, even though both versions of the code above, with and without
parentheses, act in exactly the same way, this is not always true. If the
branch includes several expressions and you decide to remove the curly
braces, then in this branch only the first expression will be executed. For
:instance
var arrowsInQuiver = 2 // arrows in the quiver
} (if (arrowsInQuiver> = 5
println ("Plenty of arrows") // enough arrows
println ("Cannot hold any more arrows") // no more arrows can be carried
{
,If the hero has five or more arrows, it means that he has enough of them
and he cannot carry more arrows. If the hero has two arrows, then nothing
:in the console will not be displayed. Without parentheses, the logic changes
var arrowsInQuiver = 2
(if (arrowsInQuiver> = 5
("println ("Plenty of arrows
("println ("Cannot hold any more arrows
By the way, if you're thinking, “Okay, but I don't like the if / else syntax ,
even
with curly braces, he's so ugly ! ”… don't worry! Soon we
rewrite the health state expression one last time using more
.compact and clear syntax
Intervals
All conditions in the if / else statement for healthStatus , in fact, check the integer
the numerical value of healthPoints . Some use the comparison operator
tests to check if healthPoints are equal to some value, otherwise
multiple comparison operators are used to check if it hits healthPoints value
between two numbers. For the second case there is a better alternative:
.Kotlin provides spacing for linear set of values
The interval is determined by the .. operator , for example 1..5 . The interval
includes all values starting from the one to the left of the operator .. and
ending with the on the right. For example, 1..5 includes numbers 1, 2, 3, 4
.and 5. The intervals can represent sequences of characters
To check if a given number falls within the interval, you can use
keyword in (inside). Refactoring the healthStatus expression
.and use intervals instead of comparison operators
(Listing 3.9. Refactoring healthStatus to use intervals (Game.kt
First, let's declare val race . Then the next variable is faction , which
assign the result of the conditional expression to when . The expression tests
the value of the race in correspondence to each value on the left of the
operator -> ( arrow ), and if it finds a matching value, assigns faction to the
right ( -> is often used in many languages and actually has other areas
.(applications in Kotlin, which we'll talk about next
.By default, the when conditional expression acts like the compare operator
Nia == between the argument that you have specified in parentheses, and
value, the indicated nym in curly braces. (The argument is the input for a
.piece of code
(.You will learn more about them in Chapter 4
In this example, when race is an argument, so
the compiler will compare race , which in the example is "gnome" , with the
first
condition. They are not equal, so the compiler goes on to the next condition.
The following comparison will return the true result, so the value faction will
. "be set to "Keepers of the Mines
Now that you've seen the benefits of the when conditional , remove
the logic for computing healthStatus . Unlike the previously used operator the if
/ the else , the operator when making the code more simple and compact. On
practice, a simple rule of thumb applies: use the operator
. when if if / else contains else if branches
. Update your healthStatus logic using when
(Listing 3.10. Refactor healthStatus code with the when statement (Game.kt
Template strings
You have already seen that a string can be constructed from the values of
variables and even from the results of conditional expressions. To simplify
this task and make To make the code clearer, Kotlin provides template
strings . Templates allow you to enclose variable values in quotes. Refresh
the display
.player states using template strings as shown below
You added the name and healthStatus values to the status bar
player by prefixing each variable with $ . This special character
.tells Kotlin that you want to include val or var in the defined string
.Note that the template values appear inside quotes, defining a string
:Run the program. The console will display the same text as last time
GREEN
!Madrigal has some minor wounds but is healing quite quickly
Kotlin allows you to compute a value within a string and interpolate the
result
tat, that is, embed it in a string. The meaning of any expression enclosed
in curly braces after the dollar sign ( $ {} ) will be automatically inserted
to a string. Add a blessing sign and color to the player status report
auras to see how it works. Don't forget to remove the existing
. the inference statement for auraColor
(Listing 3.12. Converting isBlessed to string (Game.kt
} (<fun main (args: Array <String
...
Aura //
val auraVisible = isBlessed && healthPoints> 50 || isImmortal
"val auraColor = if (auraVisible) "GREEN" else "NONE
(print (auraColor
...
Player state //
+ "(println ("(Aura: $ auraColor
("({" Blessed: $ {if (isBlessed)" YES "else" NO)"
("println ("$ name $ healthStatus
{
This new code tells the compiler to output the character string (Blessed: and
. "the result of the conditional expression if ( isBlessed ) "YES" else "NO
Please note that the variant without curly braces is used here. Here
:it turns out the same as in the following case
} (if (isBlessed
"YES"
} else {
"NO"
{
in 1..3 1
() toList. (3..1)
in 3 downTo 1 1
in 1 until 3 1
in 1 until 3 3
in 1..3 2
In 1..3 !2
'x' in 'a' .. 'z'
Determine the value of karma using the formula above and output the color
of the aura using conditional expression. Correct the player state display
.code so that it output the color of the aura, if it should be displayed
Player state //
+ "(println ("(Aura: $ auraColor
("({" Blessed: $ {if (isBlessed)" YES "else" NO)"
("println ("$ name $ healthStatus
In this more complex task, we will make the status bar customizable with
by the power of the template string. Use B to denote blessing
,venia, A - for aura, H - for healthStatus , HP - for healthPoints . For instance
:template string
"val statusFormatString = "(HP) (A) -> H
Page 80
80
Chapter 4. Functions
There is new code in our formatHealthStatus function . Let's break it down
.repair it
Anatomy of function
In fig. 4.3 shows the two main parts of a function, the header and the body ,
:in which the name formatHealthStatus is used as a model
Function body
Function header
Function header
The first part of the function is the header. The function header consists of
five
parts: visibility modifier, function declaration keyword, name
.(function, function parameters, return type (Fig. 4.4
Modifier
visibility
Assigning a name
function
Function name
Function parameters
Return type
haunted
meaning
Visibility modifier
.Not all features need to be visible or accessible to other features
Some functions can manipulate data that cannot be must be accessible
.outside of the specific file
If necessary, a function declaration can begin with a modifier visibility (fig.
4.5). The visibility modifier determines what other functions
.tions will be able to see and use this feature
Function parameters
.(Next are the parameters of the function (fig. 4.7
Figure: 4.7. Function parameters
Parameters define the names and types of input required by the function
to solve the problem. Functions may require from zero to several or more
parameters. Their number depends on the task for which they were
requested
.designed
So that the formatHealthStatus function can determine which message about
, the status display health status , the variables healthPoints and isBlessed are required
because the conditional expression when should check these values. therefore
in the function declaration formatHealthStatus these two variables are specified
as
:parameters
} private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean): String
} (val healthStatus = when (healthPoints
...
} (in 75..89 -> if (isBlessed
...
} else {
...
{
...
{
return healthStatus
{
Return type
- Many functions generate output; this is their main task
rotate a value of some kind back to where they are called from. the last part
function header is the return type that defines
.the type of output data of the function after its completion
The String type of the return value formatHealthStatus indicates that the function
.(returns a string (Figure 4.8
Figure: 4.8. Return type
Function body
The heading is followed by the body of the function, enclosed in curly
braces. Body - this is the part of the function where the main action takes
place. It can
.contain a return statement that defines the data to be returned
In our case, the function highlight command moved the val declaration
healthStatus (the code you selected earlier when starting the program) to the
body
. formatHealthStatus functions
This is followed by a new line return healthStatus . The return keyword indicates
tells the compiler that the function has finished and is ready to pass the
weekend data. The output in our case is healthStatus . That is, the function
will return the value of the variable healthStatus - a string based on logic
. healthStatus definitions
Function scope
.Note that the healthStatus variable is declared and initialized
:inside the body of the function and its value is returned at the end
If the code does not access the variable before it is initialized, the compiler
will
.considers it valid
Function call
IntelliJ not only generated the formatHealthStatus function but also added
:line of code to where the function is highlighted from
} (<fun main (args: Array <String
"val name = "Madrigal
var healthPoints = 89
var isBlessed = true
...
(val healthStatus = formatHealthStatus (healthPoints, isBlessed
...
{
Although the output has not changed, the NyetHack code is now more
.organized and lightweight accompanied
Refactoring functions
Let's continue separating the logic from the previous main function into
separate functions, using the feature highlighting feature. Let's start by
refactoring the code, defining the color of the aura. Highlight the code
starting at the line where you define visibility of the aura, up to the line
where the if / else statement ends , which checks the boolean value we want to
:output
...
Aura //
val auraVisible = isBlessed && healthPoints> 50 || isImmortal
"val auraColor = if (auraVisible) "GREEN" else "NONE
...
Next, select the Extract Function command . This can be done by right-clicking
Control-click on the selected code and select Refractor → Extract → Function ...
.as you did earlier. You can simply select Refractor → Extract from the menu
.( Function… Or use the keyboard shortcut Ctrl-Alt-M ( Command-Option-M →
.Whichever method you choose, a dialog box will appear, as in Fig. 4.2
. Name the new function auraColor
If you want to look at the final code, please be patient: we will show)
you the whole file after you highlight a few more functions.) Next, extract
.the logic that outputs the player's state into a new function
: Select two println functions in main
...
Player state //
+ "(println ("(Aura: $ auraColor
("({" Blessed: $ {if (isBlessed)" YES "else" NO)"
("println ("$ name $ healthStatus
...
We split the headers of the printPlayerStatus and auraColor functions into several)
lines for easy reading.) Start NyetHack. You will see the familiar state of
:the hero and the color of his aura
(Aura: GREEN) (Blessed: YES)
!Madrigal has some minor wounds, but is healing quite quickly
Now call castFireball from the main function . ( castFireball declared without
parameters, so you do not need to pass arguments to call it – pro leave the
.(parentheses blank
(Listing 4.2. Function call castFireball (Game.kt
Default arguments
Sometimes a function argument can have a "frequent" meaning. For
example, for functions castFireball five spells in a row is somehow too much.
Usually
two are enough. To make it easier to call castFireball , add an argument by
. default
. You saw in Chapter 2 that you can assign an initial value to var
-and then change. Similarly, you can assign a default value to the para
meter, it will be used in the absence of a specific argument. Update
. the castFireball function by adding a default value for numFireballs
(Listing 4.4. Default value for numFireballs parameter (Game.kt
Start NyetHack again. With the castFireball call with no argument, you will see
:following message
Since you did not pass an argument for the numFireballs parameter , it will get
.default value 2
.In Kotlin, functions like this are known as return type functions
Unit . Select the castFireball function with the mouse and press Ctrl-P ( Control-
.( Shift-P
.(IntelliJ will display the return type (Figure 4.9
Figure: 4.9. castFireball is a function with a return value of Unit
What kind of unit is this ? Kotlin uses the Unit type to denote
a function that does not "return" a value. If the return keyword is not
. is used, the function is considered to return a value of type Unit
Earlier, before Kotlin, many languages faced the problem of describing a
function,which returns nothing. Some languages have chosen the void
, keyword
which stands for "no return type, skip it, since it does not apply. " This is a
.fairly obvious thought: if the function returns nothing, then skip the type
Unfortunately, this solution is not suitable for an important opportunity
implemented by noah in modern languages - generalizations. Generalization
is a feature of modern different compiled languages, which allows for great
.flexibility
You will learn about the generalization feature in Kotlin, which allows
.denote functions that can work with many types in chapter 17
What do generic functions have in common with Unit or void ? Languages
used
have the void keyword , do not have sufficient capabilities to work
.with generic functions that return nothing. Void is not a type
.In fact, this keyword says: "the type information is meaningless
la; you can skip it. " Therefore, these languages have no way to describe
.a generic function that returns nothing
Kotlin solved this problem by introducing Unit as its return type. Unit
shows a function that returns nothing but can be applied to generic
.functions that require at least some type to work with
.This is why Kotlin uses Unit : you get the best from both approaches
Named function arguments
Let's see how the call to printPlayerStatus function will look like
:with passing arguments
(printPlayerStatus ("NONE", true, "Madrigal", status
Without using named arguments, they can only be passed in the volume
the order in which they appear in the function header. Named arguments
can be passed in any order, regardless of their order in the header
.functions
Another plus of named arguments is that they make the code clearer. If
the function has a large number of parameters, it is sometimes difficult to
understand which arguments cops what parameters correspond. This is
especially noticeable when the names passed variables do not match the
.names of the function parameters
Named arguments always have the same names as their corresponding
.im parameters
In this chapter, you saw how to declare functions and encapsulate logic to
work
you. The code has become much more streamlined and cleaner. You also
learned about some of the
properties built into the syntax of Kotlin language functions that will allow
you to write less code in a function with a single expression, named
arguments
and default arguments. In the next chapter, you will learn how to use more
one kind of functions available in Kotlin is anonymous functions.
Remember to save NyetHack and create a copy before proceeding to the
.tasks below
} () fun performCombat
("!println ("You see nothing to fight
{
} (fun performCombat (enemyName: String
(".println ("You begin fighting $ enemyName
{
} (fun performCombat (enemyName: String, isBlessed: Boolean
} (if (isBlessed
("!println ("You begin fighting $ enemyName. You are blessed with 2X damage
} else {
(".println ("You begin fighting $ enemyName
{
{
() performCombat
("performCombat ("Ulrich
(performCombat ("Hildr", true
: `** ~ !And then you can try calling `** ~ prolly not a good idea
() `** ~ !prolly not a good idea ~ **`
Why is this feature needed? By the way, you never need to call a function
prolly not a good idea! ~ **` . (Or use emoticons. Please ~ **`
(.treat backquotes responsibly
There are several good reasons why why do we need function names in
.quotes
First, this feature is needed to maintain compatibility with Java. Kotlin
supports the ability to call Java methods from existing code in a file Kotlin.
(See Chapter 20 for an overview of Java compatibility features.) Since
Kotlin
and Java have different reserved keywords , that is, words that
cannot be used as names for functions, function names in reverse
quotation marks allow you to avoid incompatibilities in cases where
.needed
:For example, imagine a Java method from an old Java project
} () public static void is
...
{
Level
condition
1-10
(Tipsy (tipsy
20–11
(Sloshed (drunk
21-30
(Soused (drunk
31-40
(Stewed (very drunk
41-50
(t0aSt3d (insole..
Anonymous functions 5
and functional types
In the previous chapter, you learned how to declare functions in the Kotlin
language, give them names and call them by these names. In this chapter,
you will see another way function declarations are anonymous. We will take
a short break from the project NyetHack to work with anonymous functions
in a Sandbox project, but don't despair: we'll come back to NyetHack in the
.next chapter
.Functions like the ones you wrote in Chapter 4 are called named functions
-tions . Functions declared without a name are called anonymous functions
mi . They are similar to named ones, but there are significant differences:
anonymous functions are not named when declared and therefore interact
the rest of the code is slightly different. This is due to the fact that they
usually transmit are returned in arguments or returned from other functions.
It is possible thanks by supporting the declaration of arguments and return
.values with type the function that will be discussed in this chapter
Anonymous functions
Anonymous functions are an important part of the Kotlin language. One of
their uses is adaptation of functions from the Kotlin standard library to
solve specific
tasks. Anonymous function will allow you to add rules for functions from
.standard library and customize their behavior. Let's look at an example
One of the many functions in the standard library is count . Summoned from
as a string, the count function returns the number of characters in a string.
:"Code below counts the number of letters in a line - "Mississippi
. The dot syntax for calling the count function was used here)
the syntax is always used when the called function is part
(.type definitions
"And what if you want to count only certain characters in the word "Mississipi
,
? 'eg only 's
To solve this problem, the Kotlin standard library allows you to add
rules for the count function that determine what characters should be
-counted. You set these rules by passing an argument to count with an
:function. It looks like this
}
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
{
Behind the closing curly brace that ends the definition with an anonymous
functions, call this function by adding a pair of empty parentheses. If
do not add parentheses after the definition of the anonymous function, then
the responsible message will not be printed. Like named, anonymous
functions need to be called by adding parentheses with the required
arguments
:(tami (in this case, the function requires no arguments
}
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
() {
Functional types
In Chapter 2, you learned about data types such as Int and String .
Anonymous functions also have a type called a functional type . Variables
of this type
can contain anonymous functions and be passed through the code as usual
.variables
Do not confuse functional types with Function types . Functional types)
defines specific features of a function, such as quantity and types
input parameters, as well as the return type. Soon you can make sure of
(.this
Add to SimVillage.kt the declaration of the variable containing the function and
give it an anonymous function that returns the greeting text. Syntax
.looks unfamiliar, but we'll explain it after you add the code
Implicit return
You may have noticed that the return keyword is missing from the declared
:anonymous function
} = val greetingFunction: () -> String
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
{
However, the function type clearly shows that the function should return
-string, and the compiler has no objection. As can be judged by you
water, the line was indeed returned - and this is the mayor's greeting. But
how
? is it possible if the return keyword is missing
Unlike a named function, an anonymous function does not require - but
rather
even forbids in such cases - to use the return keyword to return
.data. Anonymous functions implicitly , or automatically, return results
tat execution of the last statement in the function body, allowing you to
discard
. the return keyword
This feature of anonymous functions is both convenient and necessary for
.their syntax
The return keyword is forbidden in the anonymous function, as it creates
ambiguity for the compiler: from which function to return the value - from
the function in which the anonymous function was called, or from the
.anonymous function
Functional arguments
Like named functions, an anonymous function can take any the number of
arguments of any type. Anonymous function parameters are declared
are enumerated types in the function type definition and are named
.in the definition of the anonymous function itself
Modify the declaration of the greetingFunction variable to accept argument with
.the name of the player
(Listing 5.3. Add the playerName parameter to the anonymous function (SimVillage.kt
Here we have indicated that the anonymous function takes a string. String
name
parameter is defined inside the function, immediately after the opening
curly
:brackets, followed by an arrow
The expression now declares two parameters, playerName and numBuildings , and
accepts Two arguments are given. Since now we have more than one
.parameter in the expression, keyword it is no longer available
Start SimVillage again. This time you will see not only a greeting, but and
:the number of buildings built
Adding 2 houses
(Welcome to SimVillage, Guyal! (copyright 2018
Support for automatic type definitions
Automatic type detection in Kotlin language works with functional
with types in the same way as with the types discussed in this book
,earlier: if an anonymous function is assigned when declaring a variable
.you do not need to explicitly declare its type
:That is, the anonymous function with no arguments, which we wrote above
} = val greetingFunction: () -> String
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
{
:can be written without type definition
} = val greetingFunction
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
{
(SimVillage.kt)
} () fun main
<- val greetingFunction: (String, Int) -> String = {playerName, numBuildings
<- val greetingFunction = {playerName: String, numBuildings: Int
val currentYear = 2018
("println ("Adding $ numBuildings houses
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
((println (greetingFunction ("Guyal", 2
{
This syntax is easier to read and better conveys the essence of your
.function
The shorthand syntax can only be used when the lambda is being passed
into the function as the last argument. When you write your functions,
declare
Make the parameters of the functional type last so that when to your
.function, you could take advantage of this scheme
In SimVillage, the abbreviated invocation syntax can be applied to our
function
tion runSimulation . runSimulation expects two arguments: a string and a
.function
Pass arguments to runSimulation that are not functions, in parentheses. Then
.add the last argument to the function outside the parentheses
(Listing 5.8. Passing lambda using shorthand syntax (SimVillage.kt
} (<fun main (args: Array <String
<- val greetingFunction = {playerName: String, numBuildings: Int
<- runSimulation ("Guyal") {playerName, numBuildings
val currentYear = 2018
("println ("Adding $ numBuildings houses
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
{
} (fun runSimulation (playerName: String, greetingFunction: (String, Int) -> String
val numBuildings = (1..3) .shuffled (). last () // Randomly pick 1, 2, or 3
((println (greetingFunction (playerName, numBuildings
{
Built-in functions
Lambdas are useful because they give you more freedom when writing
.programs
.However, freedom comes at a price
When you declare a lambda, it appears to the JVM as an instance of the
declared ect. JVM also allocates memory for all variables available in the
lambda, and this increases memory consumption. Simply put, lambdas are
very inefficient consume memory, which affects performance. And the loss
.of production duration should be avoided
Fortunately, there is an option to enable optimization that will save you
from over- measured memory consumption when using lambdas as
.arguments
-This capability is called embedding (inlining). Embedding from
prevents the JVM from having to use an instance of an object and allocate
.memory for variables in lambda
-To inline a lambda, mark the function that accepts a lambda key
. with the word inline . Add inline keyword to function definition runSimulation
Now that you added the inline keyword , instead of calling runSimulation
with an instance of a lambda object, the compiler will "copy and paste" the
body of the function where the call was made. Look at the compiled
bytecode
: the main function in SimVillage.kt , where the runSimulation function is called
...
} (public static final void main (@NotNull String [] args
;("Intrinsics.checkParameterIsNotNull (args, "args
;"String playerName $ iv = "Guyal
;byte var2 = 1
= int numBuildings $ iv
(Number) CollectionsKt.last (CollectionsKt.shuffled ((Iterable))
;() new IntRange (var2, 3))))). intValue)
;int currentYear = 2018
;"String var7 = "Adding" + numBuildings $ iv + "houses
;(System.out.println (var7
!" + String var10 = "Welcome to SimVillage," + playerName $ iv
;'(' + copyright "+ currentYear)
;(System.out.println (var10
{
...
Note that instead of calling the runSimulation function in main, there was
the runSimulation body was inserted along with the lambda, so there was no
need to the ability to pass a lambda (and create another instance of the
.(object
In general, mark with the inline keyword functions that accept lambdas in
.arguments is good practice. But sometimes this is not possible
An example where it is not possible to use embedding is a recursive
function that takes a lambda because the result is an inline using such a
.function will result in an endless copy and paste cycle
function body. The compiler will warn you if you try to inline
.function in violation of the rules
Function reference
Up to this point, you have declared lambdas to be passed in another
.argument
functions. You can do it differently: pass a reference to the function . Link
to
function converts a named function (a function declared with a key
chevym word fun ) to a value which can be passed as an argument. Link
.per function can be used wherever a lambda expression is allowed
To see a link to a function, declare a new function and give it a name
. printConstructionCost
Now add in runSimulation option costPrinter to the type of function and used
.use it inside runSimulation to display the construction cost of buildings
Start SimVillage.kt . You will see that in addition to the number of buildings
now
.the total cost of construction costs is also displayed
Function references are useful in a number of situations. If you have a
named
function that corresponds to a parameter with a function type, then instead
of declaring lambda phenomena you can use a function reference. Or
,maybe
you will want to pass a function from the standard library as an argument.
You
.See more examples of using function references in Chapter 9
...
} () fun runSimulation
() val greetingFunction = configureGreetingFunction
(("println (greetingFunction ("Guyal
(("println (greetingFunction ("Guyal
{
...
At first glance, it looks almost equivalent to what Kotlin offers - the ability
to pass a lambda expression. But if you dig deeper, then it becomes it is
clear that Java requires the declaration of named interfaces or classes to
represent a function defining a lambda, although instances of these types
look like they were written using the same shorthand syntax available in
Kotlin. If you just want to pass a function without declaring an interface,
.you will find that Java does not support such laconic syntax
:For example, take a look at the Runnable interface in Java
} public interface Runnable
;() public abstract void run
{
Null security 6
and exceptions
Null is a special value that indicates that the values are val or var
does not exist. In many programming languages, including Java, null is
a common cause of failures, because a nonexistent quantity cannot fulfill
thread work. Kotlin requires special declaration if var or val can take null as a
.value. This helps to avoid mistakes
In this chapter, you will learn why null crashes, how Kotlin protects against
null at compile time and how is it safe to deal with null values if need to.
You will also see how to work with exceptions that show that something
.went wrong in the program
.To see these issues manifest, you will add a tavern to the NyetHack game
well, which takes player input and tries to satisfy booze requests from picky
.visitors. You will also add a dangerous the ability to juggle with a sword
Nullability
Some elements in Kotlin can be set to null, while others
no. The former are nullable (null is possible) and the latter are non-nullable
. For example measures, the variable in NyetHack with the sign of the
player's horse must maintain null, since not every player has a mount. but
the variable with the number of health points should definitely not take on
,the value null. Each player must have a certain number of health points
otherwise it is simply illogical. Yes, the value of a variable can be 0, but 0 is
.it is not null. Null is no value
Open NyetHack and create a new file called Tavern.kt . Add the function
.the main , from which the code execution will start
Before opening a tavern for customers, let's do an experiment. Add
in main variable var and assign a value to it and then assign a value to it
.null
(Listing 6.1. Re-assigning null to var (Tavern.kt
Even before running the code, IntelliJ will warn you with a red underline
:that something not this way. Run the code
Null can not be a value of a non-null type String
.Start Tavern.kt . Nothing happens because the program is waiting for input
Click on the console window, enter the name of the drink, press Return.
.Introduced the given name will be re-printed to the console
?What if you don't enter the name of the drink and just press Return)
,beverage to null? No. This variable will be assigned an empty string
(.which will also return to you
.As a reminder, a variable of type String? can contain a string or null
That is, if the variable beverage is set to null, the program will still
.will be compiled. Try it
Start Tavern.kt and, as before, introduce your drink of your choice. This time
not- what matters is what you enter - null will be printed to the console. In
.this case you will have to do without a drink, as well as without a mistake
Before moving on, remove the null assignment so that your tavern
could serve visitors. IntelliJ offers a quick way to comment
coding: place the cursor anywhere on the line with the code and press
Command- / ( Ctrl- / ). Commenting out that line of code instead of removing it
will give you the ability to return the assignment of null to the beverage
variable by removing the comment (using the same hotkey combination).
This method makes it easy try out the null handling strategies given in this
.chapter
Null safety
-Since Kotlin distinguishes between nullable and non-nullable types, com
the piler knows about the possible danger of accessing a variable declared
with a nullable type when it may not exist. In order to
to protect against such a nuisance, Kotlin forbids calling functions for
values that can be null until you take over
.responsibility for this situation
To understand how this looks in practice, try calling the function
for beverage . We have a trendy establishment, so the names of the drinks
should be made with a capital letter. Try to call the capitalize function on
(.beverage . (You will learn more functions for String in Chapter 7
(Listing 6.5. Using a nullable variable (Tavern.kt
Kotlin does not allow you to call the capitalize function because you are not
took the possibility of beverage to be null. Even though beverage gets a non-null
value from the console, its type remains the same him supporting null.
Kotlin aborted compilation with an error using a nullable type, because
otherwise
.run-time error
Now you are probably thinking: “So what to do with the opportunity to
have
null? An important issue with a capital letter in the title needs to be resolved
,drink ". There are several options for safe work with a type
.null, and now we will analyze three of them
To begin with, consider option number zero: use type, not supported
null if possible. These types are easier to handle because
.are guaranteed to contain a value that can be passed to functions
So ask yourself, “Why do I need a nullable type here? Maybe type
will it work the same without null support? " Often a nullable type is simply
not
.needed. And when it is not needed, abandoning it is the best decision
Please note that Kotlin did not report a bug. When the compiler
meets a safe call operator, he knows to check the value
.by null. Upon finding null, it will skip the function call and just return null
For example, if beverage stores a non-null value, the result is that you
get the drink name starting with a capital letter. (Try - and you will see.) If
.beverage is null, the capitalize function will not be called
In cases like the example above, we say that the capitalize function
. thrown "safely" and there is no risk of a NullPointerException
Using a safe call with let
Safe calling allows you to call a function with a type that supports
null, but what if you want to do some extra work like introducing
the new value or call other functions if the value of the variable
other than null? One way to achieve this is to use the operator
safe call with let function . let is called with a value, and the point is
.to allow variable declarations for the selected scope
(.Remember what you learned about scoping in Chapter 4)
Since let creates its own scope, you can use the safe
call with let to cover multiple expressions that require a variable
with a non-nullable type. You will learn more about this in Chapter 9, but
now
.let's change the definition of beverage to see how it's done
(Listing 6.7. Using let with a safe call operator (Tavern.kt
Now if beverage is null you will get the following message instead of
.error
!I can't do that without crashing - beverage was null
:?Operator
Another option for checking a value for null is the ? Operator : (null
coalescing
operator, or null coalescing operator, also known as operator
Elvis" because it looks like Elvis Presley's hairstyle). The operator is like"
".says: "If the operand to my left is null, do the operation to the right
Add a default beverage selection using the ?: Operator . If beverage
. equals null, output the name of the branded drink Buttered Ale
(println (beverage
"val beverageServed: String = beverage?: "Buttered Ale
(println (beverageServed
{
Most often in this book we omit the type of a variable if the Kotlin compiler
can detect it automatically. We added it to the example so that
. :? show the role of the operator
If beverage is not null, it will be assigned to the variable beverageServed . If
beverage is null then the value will be assigned "Buttered Ale" . Either way,
beverageServed is assigned value of type String , not String? ... This is good,
.because the visitor is guaranteed will receive the existing drink
Consider the ? Operator : as a way to ensure that a value is not null, and
Return the default value if it is still null. Operator ?: Helps
.get rid of the null values so you can work with ease
Start Tavern.kt . As long as beverage is not null, you will
receive an order for a drink with a capital letter. As soon as beverage receives
:null, you will see the following message
!I can't do that without crashing - beverage was null
Buttered ale
:with this
() var beverage = readLine
} beverage? .let
() beverage = it.capitalize
("!println ("I can't do that without crashing - beverage was null :?{
Exceptions
Like many other languages, Kotlin uses exceptions to show that
something went wrong in the program. This is important because the
.NyetHack action takes place in a world where everything is unpredictable
Let's look at some examples. Let's start by creating a new file in NyetHack
. named SwordJuggler.kt and add the main function to it
Contrary to common sense, a group of tavern patrons convinced you to
juggle
swords. The number of swords you juggle will be kept in
a nullable integer variable. Why? If swordsJuggling is null, which means your
juggling skills are so low that your journey in the world of NyetHack can
quickly end. Start by adding variables to store the number of swords that
you can juggle and score your skill. Dexterity assessment can be
get using the same random number mechanism that was used in chapter 5.
.If you are a skilled juggler, output the number of swords to the console
(Listing 6.11. Adding Sword Juggling Logic (SwordJuggler.kt
Raising Exceptions
Kotlin, like other programming languages, allows you to manually send
signal about an exception. This is achieved by using the operator
throw and is called throwing an exception. There are many different
exceptions
.except for Null Pointer Exception
Why raise an exception? The answer lies in the name - the exceptions are
necessary us to describe exceptions . Faced with an irresistible
problem, the code can raise an exception and in this way inform that
.it must be eliminated before proceeding further
One such common exception that you will come across is
IllegalStateException . IllegalStateException - vague wording, it means that the
program has reached a state that you consider unacceptable stimulating.
This exception is convenient in that you can pass a string with it and
.communicate more information about the error
The world of NyetHack is mysterious, but our tavern is not without kind
people. One fun- Chuck noticed that you weren't good at juggling and
volunteered
help to prevent the irreparable. Add a function named proficiencyCheck in
SwordJuggler and call it in main . If swordsJuggling co- holds null, intervene by
.throwing an IllegalStateException before it happens something terrible
(Listing 6.13. Throwing IllegalStateException (SwordJuggler.kt
Custom exceptions
You learned how to use the throw statement to report an exception
situation. Excitation exception IllegalStateException allows report an invalid
.state and add a line with an additional specific information
To add more detail to an exception, you can create a custom
tel exception for a specific problem. To do this, you need to declare
a new class that inherits some other exception. Classes allow
define "items" in the program - monsters, weapons, food, tools
and so on. We'll learn more about classes in Chapter 12, so for now the
syntax can you can’t delve into it. Declare a custom exception named
UnskilledSwordJugglerException
. at SwordJuggler.kt
Notice that both the exception name and the You line appeared in the console
juggle null swords! ... This is an essential detail, since the last line is
found after the try / catch block is executed . Unhandled Exception
will crash and stop program execution. But since you have processed
exception by try / catch block , code execution continued as if the dangerous
operation has never been performed. Run SwordJuggler.kt several times to see
.all the outcomes
Checking conditions
.Unexpected values can cause the program to behave unpredictably
You will have to spend a lot of time checking the validity of the input
.values
to make sure you are working with the intended values. Some sources of
.exceptions are trivial, such as unexpected null values
The Kotlin standard library has a couple of handy functions that make
checking and debugging input data. They allow you to raise an exception
.with an arbitrary message
These functions are called condition check functions because they allow
.declare conditions that must be met before executing the code
For example, in this chapter you have seen several options for protecting
against NullPointerException and other exceptions. The last option is to use
using a condition-checking function such as checkNotNull , which
checks the value for null and returns it if it is not null, but otherwise
Th throws an IllegalStateException . Try to replace excitement
.UnskilledSwordJugglerException by calling the condition check function
(Listing 6.17. Using the condition check function (SwordJuggler.kt
a Claim checking is outside the scope of this book. For detailed information
-formations see kotlinlang.org/api/latest/jvm/stdlib/kotlin/assert.html or docs.oracle.com/cd/E19683
. assert-4 / index.html / 806-7930 / 01
To put on a real show, the player must juggle at least three
with swords. By using require at the beginning of a function declaration, we
. clearly provide Anyone who wishes to invoke juggleSwords will know this
There are two mechanisms to ensure that non-null parameters are not
it will be possible to pass arguments with the value null. First, pay attention
craze for @NonNull annotations next to each non-simple parameter
in printPlayerStatus . These annotations serve as a signal for the code to call
.this Java method that annotated parameters do not accept null
isBlessed does not require the @NotNull annotation , as in Java boolean values
are
.are of a simple type, and as such cannot be null
Annotations @NotNull can be seen in many Java-projects, but they are
especially
useful for those calling Java methods from Kotlin because the compiler
the latter uses them to determine if a method parameter supports
Java null. You will learn more about Kotlin's Java compatibility in Chapter
.20
, The Kotlin compiler goes one step further to ensure that the auraColor
.name , healthStatus will not get null: it uses the Intrinsics
checkParameterIsNotNull . This method is called for every parameter, not
, supporting null, and throws an IllegalArgumentException exception
.if you try to pass null in the argument
Simply put, any function declared in Kotlin will play by the rules
.Kotlin for null, even after translating to Java code for the JVM
So, Kotlin provides double protection against NullPointerException for your
.functions
tions that have parameters with non-nullable types, even when
.comes to work with languages that are less stringent about null
strings 7
In programming, text data is represented by strings - persistent
arranged sequences of characters. You have already used lines
:in SimVillage
"(Welcome to SimVillage, Mayor! (Copyright 2020"
In this chapter, you will learn what else you can do with strings using the
set
functions for the String type from the Kotlin standard library. In the process
you
modify the NyetHack tavern by allowing visitors to order food and drinks
.from the menu. This is important for any tavern
Extracting a substring
To allow visitors to place orders, we will look at two ways to
. derivation of one string from another: substring and split
substring
Your first assignment: write a function that allows the player to do
order through the innkeeper. Open Tavern.kt in the NyetHack project and add
. a variable for the name of the tavern, as well as the placeOrder function
Inside placeOrder use functions for String indexOf and substring to
extract the name of the innkeeper from the string TAVERN_NAME and print it.
(We detail let 's take a look at the placeOrder function after we add the code.)
Also remove the old drink name identification code from the last exercise.
In the tavern not only drinks will not be there. And besides, Buttered Ale
.has long been outlawed. in the kingdom
Start Tavern.kt . You will see the output: Madrigal speaks with Taernyl about
. their order
Let's take a closer look at how placeOrder retrieves the innkeeper's name from
the
.the rank of the tavern
First , let's call the indexOf function to get the index of the first apostrophe
:in line
(' '\') val indexOfFirstApostrophe = TAVERN_NAME.indexOf
The index is an integer corresponding to the position of the character in the
string. The first the character is at index 0. The second character is at index
.1, the next at 2, and so on
The Char type , whose values are specified in single quotes, is identified by
Includes single characters in a string. By passing Char to indexOf , we tell
.function that it should find the first instance of Char and return its index
We handed in indexOf argument '\' ' , so indexOf will scan row, until it finds the
same value, and then returns the apostrophe index. Why do I need \ in the
argument? An apostrophe is also a single quote. If you write `` '' , then the
compiler will count the apostrophe in the middle of a single a quotation
mark that closes the null character definition. I must say comp- that we
mean the apostrophe character, and for this we use the escape character \ ,
.which cancels the special meaning of some symbols for the compiler
Table 7.1 lists the escaped sequences (consisting of \ and the character to be
.escaped) and their compiler meaning
Table 7.1. Escape sequences
Managers
sequences
Value
t\
Tab character
b\
Backspace
n\
Line feed character
r\
Carriage return character
"\
Double quote character
'\
Single quote character
$\
Dollar symbol
u\
Unicode character
Once you've got the index of the first apostrophe in the string, you can call
substring , which will return a new string from the existing one for the given
:pairs - meters
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfFirstApostrophe
split
The tavern menu will be represented by a line in which, separated by
,commas
:are: type of drink, name, price (in coins). For instance
shandy, Dragon's Breath, 5.91
Start Tavern.kt. This time you will notice that the hero's speech has acquired a
:dragon accent
.Madrigal speaks with Taernyl about their order
Madrigal buys a Dragon's breath (shandy) for 5.91
!Madrigal exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
In this example, you used the capabilities available for String to generate a
.phrase in dragon language
.The version of replace that you used takes two arguments
The first argument is a regular expression that determines which symbols
oxen you want to replace. A regular expression, or regex , is a pattern for
search for the desired characters. The second argument is an anonymous
.function that determines which characters and what to replace
-Take a look at the first argument you passed to replace , that is, at re
:a regular expression that defines the replacement characters
} (("[phrase.replace (Regex ("[aeiou
...
{
Regex takes an argument with the pattern "[aeiou]" , which defines characters
to find and replace. Kotlin uses the same regular expression syntax ny as
Java. Documentation with its description of expressions is available here
.docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html
Having determined what characters replace should find , you must specify
.what these characters are replace by declaring an anonymous function
} (("[phrase.replace (Regex ("[aeiou
} (when (it.value
"a" -> "4"
"e" -> "3"
"i" -> "1"
"o" -> "0"
"| _ |" <- "u"
else -> it.value
{
{
...
} (private fun placeOrder (menuData: String
...
"!val phrase = "Ah, delicious $ name
("{(println ("Madrigal exclaims: $ {toDragonSpeak (phrase
} ("val phrase = if (name == "Dragon's Breath
"{(" !Madrigal exclaims $ {toDragonSpeak (" Ah, delicious $ name"
} else {
".Madrigal says: Thanks for the $ name"
{
(println (phrase
{
Comment out the order of Dragon's Breath to the main function - we still
have to revise it Let's go - and add a new call to placeOrder , but with
.different data
(Listing 7.7. Replacing data in the menu (Tavern.kt
Have you checked the structural equality of the variable name and the value
of "Dragon's Breath " using the structural equality operator == . You've already
seen this operator when we were comparing numeric values. When applied
to strings
it compares them character by character, that is, considers strings equal if
.they match keep the same characters, following in the same order
There is another way to check equality of two variables: equality of
, references
which checks if two variables store a reference to the same instance
type, - in other words, checks the fact that two variables are
. === are directed to the same object. Reference equality is checked using
Comparing links does not always give the desired result. It usually doesn't
matter that strings are different instances, and what matters is that they
contain or not contain the same characters in the same order (that is, two
independent
.(instances have or do not have the same structure
If you are familiar with Java, you know that the behavior of the == operator
when comparing strings differs from expected because in Java the ==
operator is used to compare links. In Java, strings are compared using the
. equals function
In this chapter, you learned how to work with strings in Kotlin. We saw
how to use call the indexOf function to find out the index of a specific
character, and adjust lar expressions - for searching by specified patterns.
Did you meet
with a destructuring syntax that allows you to declare a set variables and
assign them values with just one expression, and learned how Kotlin uses
. the == structured comparison operator
In the next chapter, you will learn how to work with numbers in Kotlin by
.adding to your project taverns the ability to pay in gold or silver
But not all 136,690 characters can be entered from the keyboard. The
: second option is to use using escaped sequence with Unicode code \ u
For the letter "A" there is a key on the keyboard, but for the character
.there is no key
The only available option to represent such a symbol in the program is
use the character code in single quotes. To try this opportunity Optionally,
create a new Kotlin file in the project. Enter the following into it code and
run. (Delete the file when done by right clicking mouse over it in the project
(. tool window and select Delete
(Listing 7.8. Om ... (draft
D
r
a
g
o
n
'
S
B
r
e
a
t
h
Many of these functions are also available for the List type , and most of the
functions The traversal tricks you learn in Chapter 10 are also available for
.lines. In many ways, a String in Kotlin behaves like a list of characters
Numeric types
All numeric types in Kotlin, like in Java, are signed, that is, they can
put positive and negative numbers. In addition to supporting fractional
values, numeric types differ in the number of bits that in memory, and the
.maximum and minimum values
Table 8.1 lists some numeric types in Kotlin, number of bits
-and the maximum / minimum value supported by this type. (Under
(.more detailed below
What is the relationship between the type size in bits and the maximum and
minimum values? Computers store integers in binary form with a fixed
:with a fixed number of bits. A bit can only have one of two values
.or 1 0
.Kotlin uses a limited number of bits to represent a number
depending on the selected numeric type. The leftmost bit expresses the sign
plus or minus). The remaining bits express powers of 2, where the)
rightmost bit represents 2 0 . To calculate the value of a binary number, add
all
.powers of 2 corresponding to bits 1
.In fig. 8.1 shows example 42 in binary form
The Int type contains 32 bits, so the largest value that can store Int ,
represented in binary by 31 ones. If you add up everything powers of two,
.we get the largest value for the Int type in Kotlin - 2147483647
Since the number of bits determines the maximum and minimum value that
can represent a numeric type, the difference between types is the quantity
bits available to express the number. Type Long uses 64 bits instead of
.( so it can store much more (2 63 ,32
A little about Short and Byte : both are rarely used for presentation
traditional numbers. They are used in special cases and for support
compatibility with old Java programs. For example, the Byte type can be
use to read a data stream from a file or when working with graphics
pixel color is often expressed in three bytes: one for each color)
.(in RGB
,The Short type is sometimes used to work with machine code of processors
not supporting 32-bit commands. However, in most cases for
to represent integers, the Int type is used , and if you need more
. an empty value, of type Long
Integer values
In Chapter 2, you learned that an integer value is a number that does not
have
fractional part, that is, an integer, and in Kotlin it is represented by the Int
type . Int good for expressing qualitative or quantitative indicators tel:
leftover pints of honey, number of tavern visitors or number of coins from
.the player
It's time to do some coding. Open Tavern.kt and add variables of type Int to
represent the number of gold and silver coins the player has in your wallet.
Uncomment the call to placeOrder with the Dragon's drink order
.Breath and delete the Shirley's Temple drink order
Add a scoped performPurchase function that will handle
purchase logic, and a function that displays the contents of the player's
.wallet
. Call the new performPurchase function on placeOrder
(Listing 8.1. Setting up a player's wallet (Tavern.kt
Fractional numbers
: Take another look at the line in menuData
shandy, Dragon's Breath, 5.91" The player needs 5.91 coins to buy Dragon's"
.Breath, so after payment order, the playersGold value should decrease by 5.91
Fractional numbers in Kotlin represent the Float and Double types . Modify
Tavern.kt , so that a Double value with a price is passed to the performPurchase
.function purchases
toFloat❍
toDouble ❍
toDoubleOrNull ❍
toIntOrNull ❍
toLong ❍
toBigDecimal ❍
...
} (fun performPurchase (price: Double
() displayBalance
(val totalPurse = playerGold + (playerSilver / 100.0
("println ("Total purse: $ totalPurse
("println ("Purchasing item for $ price
val remainingBalance = totalPurse - price
{
...
First, we calculate the total amount totalPurse and display it. Pay attention
, mania that the divisor used when translating playerSilver to totalPurse
.contains the fractional part —100.0, not just 100
If you divide playerSilver , the Int value , by 100, which is also is of type Int ,
Kotlin will not return 0.10 of type Double . Instead, you get another Int value
(.is 0, having lost such an important fractional part. (Try it's in the REPL
Since both numbers are integers, Kotlin uses an integer arithmetic that does
.not result in the fractional part
To get the fractional part, you need Kotlin to do the arithmetic floating point
actions, for which it is enough to include in the operation though would be
one type that supports fractional values. Try again you- perform
calculations in the REPL, but this time add a fractional to one of the
numbers part to hint that floating arithmetic should be used dot, and the
.(result is Double (0.1
After converting the contents of the wallet to totalPurse , subtract the price of
:Dragon's Breath
val remainingBalance = totalPurse - price
To see the calculation result, enter 10.1-5.91 in the REPL. If you not
worked with numeric types in another programming language, the result
.may surprise you
-Expecting to see 4.19 result, you will get 4.1899999999999995. So com
-pewter thinks of floating-point fractional numbers . The word "pla
decimal "means that the decimal point can be located anywhere
she swims"). Floating point numbers in a computer represent only)
approximate value of the real number. This is due to the desire to give
the ability to represent a wide range of numbers with different numbers of
digits
.and ensure high performance
The accuracy of the representation of a number with a fractional part is
dictated by requirements to calculations. For example, if you were
programming a central server bank NyetHack, which performs a huge
number of financial transactions with fractional numbers, you would prefer
to express numbers with greater precision, even if it leads to a loss of time.
Generally, for financial calculations it is preferable to use the BigDecimal type
in order to get higher
What kind of rounding precision in floating point calculations. (This is the
.(same type BigDecimal , which you may be familiar with from Java
However, for our virtual tavern, the accuracy is quite enough, which gives
. the type to Double
Formatting Double Values
Let's say you don't want to work with the number of coins
4.1899999999999995 and want round it to 4.19. The String format function
will help you round a Double with given accuracy. Add the formatting of the
remainder to the performPurchase code
.the amount in the wallet
(Listing 8.5. Double formatting (Tavern.kt
...
} (fun performPurchase (price: Double
() displayBalance
(val totalPurse = playerGold + (playerSilver / 100.0
("println ("Total purse: $ totalPurse
("println ("Purchasing item for $ price
val remainingBalance = totalPurse - price
("{(println ("Remaining balance: $ {"%. 2f ".format (remainingBalance
{
...
import kotlin.math.roundToInt
"const val TAVERN_NAME = "Taernyl's Folly
...
} (fun performPurchase (price: Double
() displayBalance
(val totalPurse = playerGold + (playerSilver / 100.0
("println ("Total purse: $ totalPurse
("println ("Purchasing item for $ price
val remainingBalance = totalPurse - price
("{(println ("Remaining balance: $ {"%. 2f ".format (remainingBalance
() val remainingGold = remainingBalance.toInt
() val remainingSilver = (remainingBalance% 1 * 100) .roundToInt
playerGold = remainingGold
playerSilver = remainingSilver
() displayBalance
{
...
In this chapter, you worked with numeric types in Kotlin and learned how
to handle There are two main types of numbers: whole and fractional. You
also learned how to perform conversions between different types and what
are the possibilities supports every type. The next chapter will discuss the
standard
.Kotlin functions are a collection of useful functions available for all types
Function
Description
Example
.Integer
toBinaryString
-Returns binary pre
integer insertion
(Integer.toBinaryString (42
101010 //
(shl (bitcount
Left shift by specified
number of digits
(shl (2.42
10101000 //
(shr (bitcount
Shift to the right by the specified
number of digits
(shr (2.42
1010 //
() inv
Bit inversion
() inv.42
11111111111111111111111111010101 //
(xor (number
Performs logical operations
"radio "exclusive OR
with bits and for each
zion returns 1 if bit
in this position in one number
equals 1, and in the other 0
(xor (33.42
001011 //
(and (number
Performs logical operations
radio "I" with bits and for
each position returns
only if both numbers 1
,have a bit in this position
equal to 1
(and (10.42
1010 //
apply
The first one on our way is the apply function . apply can be thought of as a
function on- construction: it allows you to call multiple functions on the
receiver object and configure it for future use. After completing the
specified
.lambda expressions apply returns a customized receiver object
-apply can be used to reduce the number of repetitions when sub
preparing the object for use. Here is an example of setting up a file instance
: without apply
("val menuFile = File ("menu-file.txt
(menuFile.setReadable (true
(menuFile.setWritable (true
(menuFile.setExecutable (false
apply allows you to drop the variable name in every function call that does
dummy to customize the receiver object, because all the functions in the
lambda
are called relative to the target object for which it is called
.function
This behavior is sometimes referred to as limiting the relative scope of the
gence (relative scoping), because it calls all the functions within the lambda
:refer to the destination object
} val menuFile = File ("menu-file.txt"). apply
(setReadable (true) // Actually, menuFile.setReadable (true
(setWritable (true) // Actually, menuFile.setWritable (true
(setExecutable (false) // Actually, menuFile.setExecutable (false
{
let
Another commonly used standard function is let , with which you
seen in Chapter 6. let defines a variable in the scope of the
This lambda
and allows the use of the keyword IT , with whom you
met in chapter 5 for reference. Here is a let example that raises
:square the first number in the list
Without let, you would have to assign the first element to a variable to do
:multiplication
() val firstElement = listOf (1,2,3) .first
val firstItemSquared = firstElement * firstElement
When combined with other Kotlin features, the let function provides an
additional new benefits. You saw in Chapter 6 that to work with types that
support null, you can use the operator ?: and let . Take a look at the
following example, which changes the greeting text depending on how the
:innkeeper learned player or not
} fun formatGreeting (vipGuest: String?): String
} return vipGuest? .let
".Welcome, $ it. Please, go straight back - your table is ready"
".Welcome to the tavern. You'll be seated soon" :?{
{
Of course, the second line in this example can be replaced by a direct call
nameIsLong ("Madrigal") , however, the benefits of using run become more
obvious when you need to call multiple functions: chaining using run easier
to read and analyze. For example, take a look at the following blowing code
that checks the length of the player's name generates a message depending
.on the result and outputs it
fun nameIsLong (name: String) = name.length> = 20
} fun playerCreateMessage (nameTooLong: Boolean): String
} (return if (nameTooLong
".Name is too long. Please choose another name"
} else {
"Welcome, adventurer"
{
{
"Polarcubis, Supreme Master of NyetHack"
(run (:: nameIsLong.
(run (:: playerCreateMessage.
(run (:: println.
:Compare the call chaining in run with three nested function calls
println (playerCreateMessage (nameIsLong ("Polarcubis, Supreme Master
(((" of NyetHack
Nested function calls are harder to understand because the reader must
.read the code from right to left, not left to right, as we are used to
.Note that there is another form of calling run , without a receiver object
ka. This form is much less common, but we'll include it here for
.completeness
:Descriptions
With
is a variation on run . She behaves in a similar way, but uses
with
,other calling conventions. Unlike standard functions
previously, with requires that the receiver be passed to it in the first
argument, and not as a subject of the call, as is the case in other standard
:functions
} ("val nameTooLong = with ("Polarcubis, Supreme Master of NyetHack
length> = 20
{
also
The also function is similar to the let function . Like let , also passes a when -
object emnik as an argument to a lambda. But there is one big difference
: between let and also
.the second returns the receiver object, not the lambda result
This also makes it especially useful for adding various side effects. The
example below calls also twice to do two different operations: the first prints
the file name, and the second writes the contents of the file
. to the fileContents variable
<var fileContents: List <String
("File ("file.txt
} also.
(print (it.name
} also. {
() fileContents = it.readLines
{
{
Since also returns the receiver object, not the lambda result, with its help
.you can call a long chain of functions relative to the original target object
takeIf
,The last standard function is takeIf . takeIf works a little differently
, than other standard functions: it evaluates a condition, or predicate
given in a lambda that returns true or false. If
the condition is true, takeIf will return the receiver object. If the condition is
.false, it will return null
Consider the following example, which reads a file only if the file is before
:steps for reading and writing
("val fileContents = File ("myfile.txt
{() takeIf {it.canRead () && it.canWrite.
() readText. ?
:Without takeIf, it looks more cumbersome
The takeIf variant does not require a temporary file variable and no explicit
return null. takeIf is useful for checking a condition before assignment
values of a variable or continuation of work. Conceptually takeIf is
an if statement , but with the advantage of directly affecting the instance,
which is often
.allows you to get rid of the temporary variable
takeUnless
Above, we said that we have finished the review, but there is one more
function that complements
a takeIf function worth mentioning to warn against
, use: takeUnless . TakeUnless function acts the same as takeIf
but returns a receiver object if the condition is false . The following code
reads
:(file if not hidden (and returns null if hidden
() val fileContents = File ("myfile.txt"). takeUnless {it.isHidden} ?. readText
We recommend limiting the use of takeUnless , especially for checking
difficult conditions, because the understanding of the program among
people reading it will take a long time. Compare the "intelligibility" of these
:two phrases
If it took you a while to comprehend it, then you might feel that
takeUnless looks like a less natural way of describing the logic that
.need to be expressed
For simple conditions (like in the example above) takeUnless is not a problem.
But in bo- in more complex examples, it will be more difficult to
understand the work of takeUnless (for a person
.(at least
Lists
In Chapter 7, we already touched on working with lists when we used the
function split to extract three items from the drink description in the menu.
List contains an ordered collection of values, and can contain duplicate
.values
Let's continue working on our virtual tavern in Tavern.kt by adding a list
visitors using the listOf function . listOf returns the list available
read-only (more on that later) filled with elements derived from
.arguments. Create a list of three visitors
...
(var patronList: List <String> = listOf ("Eli", "Mordoc", "Sophie", 1
...
import kotlin.math.roundToInt
"const val TAVERN_NAME = "Taernyl's Folly
var playerGold = 10
var playerSilver = 10
("val patronList : List <String> = listOf ("Eli", "Mordoc", "Sophie
} (<fun main (args: Array <String
("placeOrder ("shandy, Dragon's Breath, 5.91
( [println (patronList [0
{
...
. Start Tavern.kt . You will see the name of the first visitor, Eli , in the console
List also provides other convenient index-based access functions, such as
:measures to extract the first and last element
patronList.first () // Eli
patronList.last () // Sophie
This time, the result is Unknown Patron . Since for- the requested index was not
.found, to get the default value an anonymous function was called
Another safe access function, getOrNull , returns null instead of using
inclusions. If you are using getOrNull , you must decide what to do with
null, as shown in Chapter 6. One option is to bind
null and default. Try using getOrNull
.with the operator ?: in the REPL
...
} (<fun main (args: Array <String
} (("if (patronList.contains ("Eli
(".println ("The tavern master says: Eli's in the back playing cards
} else {
(".println ("The tavern master says: Eli isn't here
{
("placeOrder ("shandy, Dragon's Breath, 5.91
([println (patronList [0
{
...
-Start Tavern.kt . Since patronList contains "Eli" , you will see the response track
. shooter: "Eli's in the back playing cards." after the call to placeOrder is displayed
Note that the contains function does a structural comparison
.elements in the list, just like the structural equality operator
You can check the simultaneous presence of several visitors by
by the power of the containsAll function . Change the code and ask the
?innkeeper if Are Sophie and Mordoc present at the same time
...
!Madrigal exclaims Ah, d3l1c10 | _ | s Dr4g0n's Br34th
[Eli, Mordoc, Sophie]
[Mordoc, Sophie, Alex]
The ability to change the list directly depends on its type , which is
shares the ability to modify items in the list. If you need to change an item
you are in the list, use MutableList . Otherwise a good solution
.will disallow mutability using a regular list
Note that a new item has been added to the end of the list. Can
add a visitor to a specific place in the list. For example, if a VIP guest
.comes to the tavern, the innkeeper can give him preference in the queue
Add a VIP guest - let it be a visitor with the same name Alex
Alex) - to the beginning of the list of visitors. (Alex is well known in the)
city and pol- privileges like buying a pint of Dragon's Breath before anyone
else
the rest, which, of course, the other Alex does not like.) The list may
contain
multiple elements with the same value, such as two visitors
with the same names, so adding another Alex is not a problem
.problem for the list
...
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
} (<fun main (args: Array <String
...
("placeOrder ("shandy, Dragon's Breath, 5.91
(println (patronList
("patronList.remove ("Eli
("patronList.add ("Alex
("patronList.add (0, "Alex
(println (patronList
{
...
Let's say the popular Alex decided to change his name to Alexis. Execute it
as
= [] ) This can be done by changing the patronList using the assignment operator
,(
.that is, by assigning a new value to the string with the first index
Listing 10.12. Modifying a mutable list using an operator
(assignments (Tavern.kt
...
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
} (<fun main (args: Array <String
...
("placeOrder ("shandy, Dragon's Breath, 5.91
(println (patronList
("patronList.remove ("Eli
("patronList.add ("Alex
("patronList.add (0, "Alex
"patronList [0] = "Alexis
(println (patronList
{
...
Start Tavern.kt . You will see that patronList has been updated and contains the
new one
.the name is Alexis
...
[Eli, Mordoc, Sophie]
[Alexis, Mordoc, Sophie, Alex]
Functions that change the contents of mutable lists are called
mutators
Iteration
The innkeeper wants to greet every visitor because it is
will have a significant impact on the reputation of the establishment. Lists
support many different functions to perform some action for each
. list item. This idea is called iteration
. One way to iterate over a list is to use a for loop
Its logic is: "For each item in the list, do this and that." You should
determine the element name and the Kotlin compiler will automatically
.determine its type
Change the Tavern.kt code and display a welcome message for each
, settler. (Also remove the code that modifies and outputs the patronList
(.to get rid of unnecessary garbage in the console
(Listing 10.13. PatronList traversing list items using for (Tavern.kt
...
} (<fun main (args: Array <String
...
("placeOrder ("shandy, Dragon's Breath, 5.91
(println (patronList
("patronList.remove ("Eli
("patronList.add ("Alex
("patronList.add (0, "Alex
"patronList [0] = "Alexis
(println (patronList
} (for (patron in patronList
("println ("Good evening, $ patron
{
{
...
:Start Tavern.kt and the innkeeper will greet each visitor by name
...
Good evening, Eli
Good evening, Mordoc
Good evening, Sophie
...
} (<fun main (args: Array <String
...
("placeOrder ("shandy, Dragon's Breath, 5.91
} (for (patron in patronList
("println ("Good evening, $ patron
{
<- patronList.forEach {patron
("println ("Good evening, $ patron
{
{
...
Run Tavern.kt and you will see the same output as before. Cycle for and
function
.tion forEach functionally equivalent
The for loop and the forEach function handle indexes implicitly. If you need
. If you want to get the index of each item in the list, use forEachIndexed
Modify the Tavern.kt code and use the forEachIndexed function to output
.the place of each visitor in the queue
(Listing 10.15. Displaying the position of a string in a list using forEachIndexed (Tavern.kt
...
} (<fun main (args: Array <String
...
("placeOrder ("shandy, Dragon's Breath, 5.91
<- patronList.forEachIndexed {index, patron
(".println ("Good evening, $ patron - you're # $ {index + 1} in line
{
{
...
The forEach and forEachIndexed functions are available for some other types as
.well
in Kotlin. This category of types is called Iterable and includes
(em List , Set , Map , IntRange (ranges like 0 ... 9 that you saw in chapter 3
and other types of collections. Iterable types support iteration - other
in other words, they allow you to traverse the stored items and perform
.thread some actions with each one
It's time to go back to the imitation tavern. Now every visitor wants to
even order Dragon's Breath. To do this, move the call to placeOrder inside
a lambda that is passed to the forEachIndexed function for it to be called
for each visitor on the list. Now that everyone can order a drink
visitors, not just the player, change the placeOrder to accept the name
.the visitor who made the order
Also comment out the performPurchase call in placeOrder . (He will need
(.us in the next chapter
(Listing 10.16. Simulation of multiple orders (Tavern.kt
...
} (<fun main (args: Array <String
...
("placeOrder ("shandy, Dragon's Breath, 5.91
<- patronList.forEachIndexed {index, patron
(".println ("Good evening, $ patron - you're # $ {index + 1} in line
("placeOrder (patron, "shandy, Dragon's Breath, 5.91
{
{
...
} (private fun placeOrder (patronName: String, menuData: String
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
(".println ("$ patronName speaks with $ tavernMaster about their order
(',') val (type, name, price) = menuData.split
".val message = "Madrigal buys a $ name ($ type) for $ price
".val message = "$ patronName buys a $ name ($ type) for $ price
(println (message
(() performPurchase (price.toDouble //
(() performPurchase (price.toDouble
} ("val phrase = if (name == "Dragon's Breath
"{(" !Madrigal exclaims: $ {toDragonSpeak (" Ah, delicious $ name"
"{(" !patronName exclaims: $ {toDragonSpeak (" Ah, delicious $ name $"
} else {
".Madrigal says: Thanks for the $ name"
".patronName says: Thanks for the $ name $"
{
(println (phrase
{
.Launch Tavern.kt and watch the tavern come to life with three visits
:tellers ordering Dragon's Breath
.The tavern master says: Eli's in the back playing cards
.The tavern master says: Yea, they're seated by the stew kettle
.Good evening, Eli - you're # 1 in line
.Eli speaks with Taernyl about their order
.Eli buys a Dragon's Breath (shandy) for 5.91
!Eli exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Good evening, Mordoc - you're # 2 in line
.Mordoc speaks with Taernyl about their order
.Mordoc buys a Dragon's Breath (shandy) for 5.91
!Mordoc exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Good evening, Sophie - you're # 3 in line
.Sophie speaks with Taernyl about their order
.Sophie buys a Dragon's Breath (shandy) for 5.91
!Sophie exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
import java.io.File
...
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
("val menuList = File ("data / tavern-menu-items.txt
() readText.
("split ("\ n.
...
You used the java.io.File type to work with a specific file as specified
path. The readText function in File returns the contents of the file as a string.
Then the split function is called (as in chapter 7) to split the contents of the
file by line feed character (represented by the escaped sequence '\ n' ) and
.return it as a list
...
} (<fun main (args: Array <String
...
<- patronList.forEachIndexed {index, patron
(".println ("Good evening, $ patron - you're # $ {index + 1} in line
("placeOrder (patron, "shandy, Dragon's Breath, 5.91
{
<- menuList.forEachIndexed {index, data
("println ("$ index: $ data
{
{
...
:Start Tavern.kt . You will see the menu content loaded into the list
...
shandy, Dragon's Breath, 5.91 :0
elixir, Shirley's Temple, 4.12 :1
meal, goblet of LaCroix, 1.22 :2
desert dessert, pickled camel hump, 7.33 :3
elixir, iced boilermaker, 11.22 :4
Now that you 've got the menuList , make every visitor do something
.ordered by choosing a random dish from the menu
...
} (<fun main (args: Array <String
...
<- patronList.forEachIndexed {index, patron
(".println ("Good evening, $ patron - you're # $ {index + 1} in line
("placeOrder (patron, "shandy, Dragon's Breath, 5.91
(() placeOrder (patron, menuList.shuffled (). first
{
<- menuList.forEachIndexed {index, data
("println ("$ index: $ data
{
{
...
Start Tavern.kt . You will see that each visitor has made an order for a service
.tea item from the menu
Destructuring
The list also offers the possibility of destructuring up to the first five
elements. Destructuring, as you saw in Chapter 7, allows you to declare
multiple variables and assign values to them in one expression. You use
:use the destructuring technique to split the order into components
(',') val (type, name, price) = menuData.split
This declaration assigns the first three elements of the list returned
. the split function , the string values type , name and price
By the way, you can selectively destructure items from a list by using
using the _ character to skip unwanted items. For example, a tavern
a boxer may want to issue medals to the best sword jugglers in the
,kingdom
but he lacks a silver medal. If you only want to destructure
:the first and third values from the list of visitors, you can do it like this
The sets
Lists, as you've seen, can store duplicate items (and order so repeating
.(elements can be easily identified by their positions
But sometimes you need a collection that guarantees the uniqueness of its
.elements. You can use sets for this
Sets are a lot like lists. They use the same iterative
.functions and can be editable or read-only
But there are two important differences between lists and sets: guaranteed
.sets
They state the uniqueness of elements and do not support the ability to
change them by indices, because they do not provide for any strictly defined
the order of placement. (However, you can still read elements by
.(specific index, which we will return to shortly
Create set
The list is created using the listOf function . You can create a set with a
.by the power of the setOf function . Try creating a set in the REPL
If you try to add the same planet to a set twice, it contains only one will
.remain
("planets.contains ("Earth
true
("planets.contains ("Pluto
false
The set does not index its content - this means that it is not
holds the inline [] operator for accessing elements by index. However
less it is possible to query an element at a specific index using the function
an iteration that uses iterations to solve its problem. To obtain
access the third planet in the set with the elementAt function , type
.in the REPL the following
(Listing 10.23. Search for the Third Planet (REPL
.But keep in mind that index access in a set works by an order of magnitude
lazier than access by index in the list. This is due to the internal structure
elementAt . When elementAt is called on a set, it sequentially
iterates over its elements carefully until it reaches the specified index. it
means that in a large set, access to the element with a large index
-will be slower than accessing by index in the list. For this reason
.rus, if you need index access, use a list, not a set
The set also has a mutable version (as you will see shortly), but not
,has mutator functions using an index (like the add (index
.( element) in type List
.However, sets have a very valuable property of eliminating repetition
moving elements. But what if the programmer needs to provide
uniqueness of elements and high speed of access by index? You can use
,use the following trick: create a set to remove duplicates
and then convert it to a list when you need index access
.or mutator functions
.This is how we implement the diner list for a tavern
...
("val lastName = listOf ("Ironfoot", "Fernsworth", "Baggins
() <val uniquePatrons = mutableSetOf <String
("val menuList = File ("data / tavern-menu-items.txt
() readText.
("split ("\ n.
} (<fun main (args: Array <String
...
} forEach. (9..0)
() val first = patronList.shuffled (). first
() val last = lastName.shuffled (). first
"val name = "$ first $ last
(println (name
uniquePatrons + = name
{
(println (uniquePatrons
{
...
Note that break does not end the program itself - it just
.interrupts the loop in which it is called, and the program continues
.break can be used to exit any loop, which is extremely useful
Converting collections
,In NyetHack, you have created a mutable set of unique visitor names
passing items from the list to the set one by one. Also convert
a list to a set and vice versa using the toSet and toList functions (or
their mutable options: toMutableSet and toMutableList ). Common
-the trick is to call toSet to discard non-unique items in the list. (By
(.experiment in the REPL
The need to remove duplicates and use index access is very is widespread,
so Kotlin provides a function named distinct , which internally calls toSet and
. toList
Listing 10.29. Distinct (REPL) call
() val patrons = listOf ("Eli Baggins", "Eli Baggins", "Eli Ironfoot"). distinct
[Eli Baggins, Eli Ironfoot]
[patrons [0
Eli baggins
Notice the declaration of the IntArray type and the call to the intArrayOf
. function
Like List , IntArray represents a sequence of elements namely integers. Unlike
List , IntArray converts to elementary array at compile time. After compilation,
the resulting bytecode will be match exactly the expected primitive int array
. needed for call the displayPlayerAges Java function
Convert Kotlin collection to corresponding elementary Java
an array can be done using built-in functions. For example, a list of integers
can be converted to IntArray using toIntArray function , support
set by the List type . This will allow you to convert the collection to an int
, array
:when you need to pass an elementary array to a Java function
(val playerAges: List <Int> = listOf (34, 27, 14, 52, 101
(() displayPlayerAges (playerAges.toIntArray
The general rule of thumb is to stick with collection types like List if
you have no compelling reason to do otherwise, such as to ensure that
compatibility with Java code. Kotlin collections are good in most cases
choice, because they support the ability to restrict access only for
.reading and ample opportunities
(Tavern.kt)
...
() <var uniquePatrons = mutableSetOf <String
("val menuList = File ("data / tavern-menu-items.txt
() readText.
("split ("\ n.
(val patronGold = mapOf ("Eli" to 10.5, "Mordoc" to 8.0, "Sophie" to 5.5
} (<fun main (args: Array <String
...
(println (uniquePatrons
var orderCount = 0
} (while (orderCount <= 9
,() placeOrder (uniquePatrons.shuffled (). first
(() menuList.shuffled (). first
++ orderCount
{
(println (patronGold
{
...
Associative array keys must be of the same type and values must
be of the same type, but the keys and values themselves can be of different
types. Here an associative array is created with string keys and fractional
values
niyami. We relied on automatic type detection, but if desired
explicitly show the types of keys and values we could declare associative
. <an array like this: val patronGold: Map <String, Double
Run Tavern.kt to see the resulting array. Note, that when outputting an
associative array is enclosed in curly braces, then like lists and sets - into
.squares
.The tavern master says: Eli's in the back playing cards
.The tavern master says: Yea, they're seated back by the stew kettle
...
{Eli = 10.5, Mordoc = 8.0, Sophie = 5.5}
(Listing 11.2. Declaring an associative array using the Pair type (REPL
...
} (<fun main (args: Array <String
...
(println (uniquePatrons
var orderCount = 0
} (while (orderCount <= 9
,() placeOrder (uniquePatrons.shuffled (). first
(() menuList.shuffled (). first
++ orderCount
{
(println (patronGold
(["println (patronGold ["Eli
(["println (patronGold ["Mordoc
(["println (patronGold ["Sophie
{
Import java.io.File
import kotlin.math.roundToInt
"const val TAVERN_NAME: String = "Taernyl's Folly
var playerGold = 10
var playerSilver = 10
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
("val lastName = listOf ("Ironfoot", "Fernsworth", "Baggins
() <val uniquePatrons = mutableSetOf <String
("val menuList = File ("data / tavern-menu-items.txt
() readText.
("split ("\ n.
(val patronGold = mapOf ("Eli" to 10.5, "Mordoc" to 8.0, "Sophie" to 5.5
() <val patronGold = mutableMapOf <String, Double
} (<fun main (args: Array <String
...
(println (uniquePatrons
} uniquePatrons.forEach
patronGold [it] = 6.0
{
var orderCount = 0
} (while (orderCount <= 9
,() placeOrder (uniquePatrons.shuffled (). first
(() menuList.shuffled (). first
++ orderCount
{
(println (patronGold
(["println (patronGold ["Eli
(["println (patronGold ["Mordoc
(["println (patronGold ["Sophie
{
...
You have added an element to the associative array for each unique
.position
of the 6.0 gold net by traversing uniquePatrons . (Remember
(. keyword it ? Here it refers to an item in uniquePatrons
import java.io.File
import kotlin.math.roundToInt
"const val TAVERN_NAME: String = "Taernyl's Folly
var playerGold = 10
var playerSilver = 10
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
...
} (fun performPurchase (price: Double
() displayBalance
(val totalPurse = playerGold + (playerSilver / 100.0
("println ("Total purse: $ totalPurse
("println ("Purchasing item for $ price
val remainingBalance = totalPurse - price
("{(println ("Remaining balance: $ {"%. 2f ".format (remainingBalance
() val remainingGold = remainingBalance.toInt
() val remainingSilver = (remainingBalance% 1 * 100) .roundToInt
playerGold = remainingGold
playerSilver = remainingSilver
() displayBalance
{
} () private fun displayBalance
("println ("Player's purse balance: Gold: $ playerGold, Silver: $ playerSilver
{
} (fun performPurchase (price: Double, patronName: String
(val totalPurse = patronGold.getValue (patronName
patronGold [patronName] = totalPurse - price
{
= (private fun toDragonSpeak (phrase: String
...
{
} (private fun placeOrder (patronName: String, menuData: String
...
(println (message
(performPurchase (price.toDouble (), patronName //
} ("val phrase = if (name == "Dragon's Breath
...
{
...
:Start Tavern.kt . You will see arbitrary orders among the lines
.The tavern master says: Eli's in the back playing cards
.The tavern master says: Yea, they're seated by the stew kettle
.Mordoc Fernsworth speaks with Taernyl about their order
.Mordoc Fernsworth buys a goblet of LaCroix (meal) for 1.22
.Mordoc Fernsworth says: Thanks for the goblet of LaCroix
...
You have updated the balance of money for visitors, and there is only one
task left - you
keep a record after purchase. This can be accomplished by traversing the
association
. an array using the forEach function
-Add the Tavern.kt new feature called displayPatronBalances , koto
paradise goes through the elements of the associative array and displays the
balance in gold
coins (rounded to the second decimal place, as in Chapter 8) for each
. visitor. Call it at the end of the main function
In the last two chapters, you learned how to work in Kotlin with different
types
.Kotlin collections: with lists, sets and associative arrays
Class declaration 12
The object-oriented programming paradigm was born in the 1960s and
remains in demand, as it provides a set of useful tools to simplify the
structure of programs. The basis of object-oriented oriented style are classes
that define unique categories "Objects" in the code. Classes define what
.data these objects will store
projects and what work to do.To make NyetHack object-oriented, let's start
by defining unique types of objects that will exist in our game
in the world, and declare classes for them. In this chapter, we will add the
Player class in NyetHack, which will express the specific characteristics of
.the player
Class declaration
,The class can be declared in a separate file or next to other elements
such as functions and variables. Class declaration in a separate file
gives space to expand with the growth of the program, and just like that
we will do at NyetHack. Create Player.kt file and declare your first class
. using the class keyword
class Player
The file with the class declaration is usually called the same name, but it is
not required it is best to do just that. You can declare several classes in one
,file
and you will most likely want to do this if they all serve the same
.a similar goal
.So, the class is declared. Now we need to make it work
Creating instances
The declared class looks like a blueprint. The drawing contains information
about how to build a building, but he himself is not a building. Player class
declaration works similarly: until now the player has not been created - you
.have created only his project
When you start a new game in NyetHack, the main function is called and
one
from the first objects to be created. In this case, this is the game first
,character. To design a hero that can be used in NyetHack
you first need to instantiate it by calling the constructor . In Game.kt , where
-re
the variables are declared in the main function , create an instance of the
.Player as shown below
(Listing 12.2. Instantiating Player (Game.kt
Class functions
. A class declaration can include two kinds of content: behavior and data
In NyetHack, the player can perform different actions, for example,
participate
-in battle, move, cast a spell that generates a glass of dope
,drink, or check inventory. You define the behavior of the class
by adding function declarations to the class body. Declared inside the class
. functions are called class functions
You have already identified some of the player's actions in Game.kt . Now
.let's do refactoring the code to incorporate class-specific elements
. Let's start by adding the castFireball function to the Player
(Listing 12.3. Class function declaration (Player.kt
} class Player
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
Note that the castFireball implementation does not include the keyword)
(.private . The explanation will be further
Here you have declared the body of the Player class by adding curly braces.
Class body contains definitions of behavior and class data by analogy with
how
.the conduct of a function is defined in its body
In Game.kt remove the definition of castFireball and add a class function call
. in main
(Listing 12.4. Class function call (Game.kt
Class properties
Class functions describe its behavior. Data definitions are often
called properties of the class are the attributes required to express
a certain state or characteristics of a class. For example the properties
Player class can represent the player's name, health status, race, world
.view, gender, etc
At this point, the player name is declared in the main function , but your
class is best She is suitable for storing such information. Update the Player.kt
code by adding name property . (The name value looks sloppy, but we have an
(.explanation this madness. Just enter what is shown below
(Listing 12.5. Defining the name property (Player.kt
} class Player
"val name = "madrigal
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
You have added the name property to the body of the Player class , including
it as actual value for the Player instance . Note that name is declared as
val . Like variables, properties can be declared with the var keywords
and val to allow or disallow changes to the information stored
.in them. We'll talk about property mutability later in this chapter
. Now remove the classified name of Game.kt
(Listing 12.6. Removing name from main (Game.kt
} (<fun main (args: Array <String
"val name = "Madrigal
var healthPoints = 89
...
{
...
You may have noticed that IntelliJ is now warning about an issue with
Game.kt
.(fig.12.1)
(Listing 12.7. Getting a reference to the name property of the Player name class (Game.kt
Start Game.kt . The player's state, including the name, is displayed in the
same way as as before, but now you have accessed the name property
. through the instance of the Player class instead of a local variable in main
.When a class is instantiated, all of its properties must be assigned values
This means that, unlike variables, class properties must
,initial values are assigned. For example, the following code is not valid
:since name is declared without an initial value
} class Player
var name: String
{
Property methods
Properties model the characteristics of each instance of a class. They also
allow other objects to interact with data in the class using the name of a
simple and compact syntax. Such interaction will provide printed with
.property methods
-For each property declared, Kotlin will generate a field , the method read
Nia ( getter ) and, if necessary, the method of recording ( setter ). The field
is where the data for the property is stored. You cannot directly declare a
field in a class. Kotlin encapsulates fields, protecting the data in the field
and making it accessible through property methods. The property read
method defines the rules for reading it. Me- Reading modes are created for
all properties. Writing method defines rules assigning a value to a property,
so it is generated only to change nimable properties - in other words, if the
property is declared with the
. the word var
Imagine that you have come to a restaurant and on the menu, among other
things, there is a spa getty. You order them and the waiter brings you
.spaghetti with cheese and sauce
You do not need access to the kitchen, the waiter solves all issues himself,
including whether to add cheese and sauce to the ordered spaghetti. You are
.the calling code and the waiter is the reading method
You are a restaurant visitor and do not want to boil water for spaghetti. Do
you want to place an order and receive it. And the restaurant doesn't need
you to wander around kitchen, where you would shift the ingredients and
.dishes as you please
.This is how encapsulation works
Even though property methods are generated by the Kotlin language by
default, you can declare your methods if you want to concretize as you
.should read and write data. This is called overriding property methods
To see how to override the read method, add a get () method to the definition
division of the name property , which will ensure that when accessed
.a string starting with an uppercase letter was returned to this property
(Listing 12.8. Reading methods (Player.kt
} class Player
"val name = "madrigal
() get () = field.capitalize
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
By declaring your own read method for a property, you change its behavior
when tortured to read the meaning. Since name contains a proper name, then
for
accessing it should return a string starting with an uppercase
.letters. Our reading method provides this
Start Game.kt and make sure Madrigal is capitalized M. The field keyword in the
example refers to a field with a value that Kotlin automatically creates for
the property. Field is where methods come from properties read and write
data representing the property. What is it like ingredients in the restaurant
kitchen - the caller never sees the original data (ingredients), only what the
read method will return to it. Moreover, before- Stupas to the field can be
.obtained only inside the methods of this property
When a capitalized version of a name is returned, the contents of the
the field does not change. If the value assigned to name starts with a
lowercase
letters, as we have in the code, it will remain so after the call to the read
.method
In contrast, the write method modifies the property field. Add write method
in the definition of the name property and use the trim function in it to
remove
.leading and trailing spaces from the passed value
(Listing 12.9. Custom setter declaration (Player.kt
} class Player
"val name = "madrigal
() get () = field.capitalize
} (set (value
() field = value.trim
{
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
Adding a write method to this property gave rise to a problem that was
.(warned about IntelliJ prefix (fig 12.2
The name property is declared as val , so it is read-only
and cannot be changed even by writing. This protects the val values
.from changes without your consent
Figure: 12.2. Val properties are read-only
} class Player
"val var name = "madrigal
() get () = field.capitalize
} (set (value
() field = value.trim
{
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
Now name can be changed according to the rules specified in the recording
.method, and the interruptions from IntelliJ are gone as well
Property reader methods are called using the same syntax as
and other variables that you are already familiar with. Methods for writing
to properties are called automatically when trying to assign a new value to a
property using the assignment operator. Try to change the player's name out
.the Player class in the Kotlin REPL
(Listing 12.11. Change the player name (REPL
Assigning new values to properties changes the state of the class to which
,they belong. If name were still declared as val , then an example
:which you entered into the REPL would give the following error message
error: val cannot be reassigned
If you want to reproduce this phrase, you will need to restart the REPL)
using the Build and restart button on the left, otherwise there will be no changes
(.in the Player noticed
Property visibility
.Properties differ from local variables declared inside a function
Properties are defined at the class level. Therefore, they can be accessed
other classes, if the scope allows. Access too wide
, property can cause problems: having access to the data of the Player class
. other classes will be able to make changes to the Player instance
Properties control reading and writing data through property methods. All
properties have read methods, and all var properties have write methods, -
outside depending on whether you declare your behavior to them or not. By
default property methods have the same scope as
the properties themselves. That is, if a property is declared public, its
methods
.will also be publicly available
What if you decide to make the property readable and close for
write si? Declare the scope of the write method separately. Make a method
.the entries for the name property are private
} class Player
"var name = "madrigal
() get () = field.capitalize
} (private set (value
() field = value.trim
{
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
Now name will be readable from any part of NyetHack, but change
only the Player instance itself can do it . This technique is very useful when
you want to prevent other parts of your application from changing the
.property
The scope of property methods cannot be wider than the scope
the property itself. You can restrict access to a property by defining a
narrower
the scope of the read methods, but the methods cannot be assigned
.properties more than a wider scope than is declared for the property itself
Remember that properties must be initialized when declared. it
the rule is especially important for a class with public properties. If on
an instance of the Player class is referenced by some code in the application,
this code should be sure that when accessing Player.name this property will
store
.real value
Computed properties
We said earlier that when you declare a property, a field is always created
that
the second stores the actual value. This is of course true ... Except
so-called computed properties . A computed property is a property
.for which the get and / or set method is overridden and does not use the field
.In such cases, Kotlin does not generate the field
In the REPL create a Dice class with a computed property
. rolledValue
} () class Dice
val rolledValue
() get () = (1..6) .shuffled (). first
{
Each time the rolledValue property is called, its value changes. This is
This is because it is evaluated every time it is accessed. He has no
neither an initial value nor a default value, and it has no field that
.could store a value
For the Curious: A Closer Look at the Var and Val Properties
at the end of this chapter, we'll take a closer look at how properties are
.implemented
values var and val and what bytecode is produced by the compiler when you
.define give them
Refactoring NyetHack
You learned about class functions, properties, encapsulation and have
already done some work applying these ideas at NyetHack. It's time to end
.work and make the NyetHack code even cleaner
We will now move code snippets from one file to another. it
will help us look at two files side by side. Fortunately, IntelliJ already has
.such opportunity
With Game.kt open , right click on the Player.kt tab at the top
.(part of the editor window and select Split Vertically (Fig. 12.3
Another editor panel will appear (Fig. 12.4). (You can drag tabs
(.between editor panels to customize it
Figure: 12.4. Two panels
This is a tricky refactoring, but by the end of this section we will have a
. Player class
reveals access to its functions and properties and hides details
,implementations that other components don't need to know about. In short
.stealing, this is for a good cause
,Find the variables declared in the main Game.kt function , which, logically
-owned by Player . These include heathPoints , isBlessed, and isImmortal . Pre
. turn them into the Player properties
(Listing 12.15. Removing variables from main (Game.kt
After adding them to Player.kt, make sure the variables are declared inside
. the body of the Player class
(Listing 12.16. Adding Properties to Player (Player.kt
} class Player
"var name = "madrigal
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = 89
val isBlessed = true
val isImmortal = false
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
} class Player
"var name = "madrigal
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = 89
val isBlessed = true
private val isImmortal = false
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
.Make sure again that the function declarations are in the body of the class
(Listing 12.19. Adding class functions to Player (Player.kt
} class Player
"var name = "madrigal
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = 89
val isBlessed = true
private val isImmortal = false
,private fun auraColor (isBlessed: Boolean
,healthPoints: Int
} isImmortal: Boolean): String
val auraVisible = isBlessed && healthPoints> 50 || isImmortal
"val auraColor = if (auraVisible) "GREEN" else "NONE
return auraColor
{
= (private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean
} (when (healthPoints
"!is in excellent condition" <- 100
".in 90..99 -> "has a few scratches
} (in 75..89 -> if (isBlessed
"!has some minor wounds, but is healing quite quickly"
} else {
".has some minor wounds"
{
".in 15..74 -> "looks pretty hurt
"!else -> "is in awful condition
{
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
We finished copying the code, but there is still some work left to do in
Player.kt
. and Game.kt . For now, let's turn our attention to the Player
If you split the editor window vertically, undo the split by closing)
all files in the panel. Close the files by clicking on the X in the tab (Figure
(.[ 12.5) or by pressing Command-W [ Ctrl-W
Figure: 12.5. Closing a tab in IntelliJ
-In Player.kt, note that the features previously declared in Game.kt and re
placed in the Player - auraColor and formatHeathStatus - now extract the required
Valid values from the Player properties are isBlessed , heathPoints, and isImmortal .
When functions were declared in Game.kt , they were out of scope
of the Player class . But now they have become functions of the Player class
. and automatically got access to all properties declared in Player
,This means that the class functions in the Player no longer need parameters
. since all data is available inside the Player class
.Modify the function headers to remove parameters from them
(Listing 12.20. Removing unnecessary parameters from class functions (Player.kt
} class Player
"var name = "madrigal
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = 89
val isBlessed = true
private val isImmortal = false
,private fun auraColor (isBlessed: Boolean
,healthPoints: Int
} isImmortal: Boolean ): String
val auraVisible = isBlessed && healthPoints> 50 || isImmortal
"val auraColor = if (auraVisible) "GREEN" else "NONE
return auraColor
{
= (private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean
} (when (healthPoints
"!is in excellent condition" <- 100
".in 90..99 -> "has a few scratches
} (in 75..89 -> if (isBlessed
"!has some minor wounds, but is healing quite quickly"
} else {
".has some minor wounds"
{
".in 15..74 -> "looks pretty hurt
"!else -> "is in awful condition
{
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
} class Player
"var name = "madrigal
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = 89
val isBlessed = true
private val isImmortal = false
} private fun auraColor (): String
...
{
} (private fun formatHealthStatus () = when (healthPoints
...
{
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{
Now all properties and functions are declared in the correct places, but for
:them you The call in Game.kt uses incorrect syntax for three reasons
printPlayerStatus no longer has access to the variables that are needed .1
. it to work, since these variables are now properties of the Player
Now that functions of type auraColor have become functions of the class .2
. in Player , they should be called relative to the Player instance
The functions of the Player class must be called according to their new .3
.signatures, having no parameters
-Modify printPlayerStatus to accept Player instance as argument
cop and could access the required properties and call new versions
.auraColor and formatHeathStatus with no parameters
(Listing 12.22. Calling class functions (Game.kt
Using packages
A package is like a folder for similar objects, which helps logically
group files in a project. For example, the kotlin.collections package contains
There are classes for creating and manipulating lists and sets. As
complication packages allow you to organize your project and also prevent
.collisions of names
→ Create a package by right-clicking on the directory src and select the New
Package . When prompted to give a name to the package, name it
com.bignerdranch.nyethack . (You are free to give the package any name, but we
prefer read reverse DNS style that scales well with quantity
(.applications you write
The generated package com.bignerdranch.nyethack is the top level package
for NyetHack. Including your files in a top-level package will prevent
any name collisions between the types you declared and the types you
declare
.manifested elsewhere, such as in external libraries or modules
As you create new files, you can also create new packages to organize
.store files
Note that the new com.bignerdranch.nyethack package (which is
puts the folder) is displayed in the project tool window. Add your
files ( Game.kt , Player.kt , SwordJuggler.kt , Tavern.kt ) into the new package by
dragging and dropping them
.(there (fig.12.6
Organizing your code using classes, files and packages will help you
.keep the code in order as it grows in complexity
For the curious: a closer look on
properties var and val
In this chapter, you learned that the keywords val and var are used to define
,class properties: val indicates that the property is read-only
.var is for writing
You might be wondering how the Kotlin class property works internally in
.the JVM
To understand how class properties are implemented it is helpful to take a
look at compiled JVM bytecode, and more specifically - compare bytecode,
-sge
.unregulated for each property depending on the form of the definition
Create a new file named Student.kt . (After the exercise, the file needs
(.delete
First, declare a class with a var -property (so that it is available to
.(read and write
(Listing 12.23. Student class declaration (Student.kt
The name property in this example is declared in the main constructor Student
.
You will learn more about constructors in Chapter 13, but for now, just
consider
constructor as a way to help you change the order in the future
creating instances of the class. In this example, the constructor allows us to
.give the name of the student
:( Let's look at the final bytecode ( the Tools → Kotlin → Show Kotlin of Bytecode
After declaring the var name property in the bytecode of the Student class, it
was generated there are four elements: the name field (where the name value
will be stored ), the method
reads, a write method, and finally an instruction to assign a value to the name
. field the name argument
. Now try changing the property by replacing var with val
Let's look at the resulting bytecode (pay special attention to the disappeared
.(new code
.After changing the var keyword to val, the property lost its write method
In this chapter, you also learned that you can declare your own method for a
property reading and / or writing. What will change in bytecode when you
declare compute- a property with a read method and no field for storing
. data? Try do it on the already declared classes Student
(Listing 12.25. Making name a computed property (Student.kt
The result of reading the rolledValue property from Dice is a random value in
the in- range from 1 to 6, defined each time the property is accessed. Is not
."especially corresponds to the definition of "variable
When you 're done examining the bytecode, close Student.kt and delete it by
right clicking by clicking on the file name in the project tool window and
. choosing Delete
You will be surprised, but this code does not compile. Look at the error
,below
.(to understand why (Figure 12.7
Figure: 12.7. Smart casting is not applicable to Weapon
If you are already familiar with Java, you may have noticed that the default
access level is it differs from Kotlin: by default Java is assigned a visibility
level, limited by package scope, i.e. methods, fields and classes without a
modifier visibility can only be used by classes from the same package.
Kotlin
abandoned this approach because it is of little use. In practice, this
the limitation is easy to work around by creating an appropriate package
and copying the class there. On the other hand, Kotlin provides what Java
does not provide - an internal visibility level internal . Functions, classes or
properties with this level visibility is available for other functions, classes
and properties within the same module . A module is a separate functional
unit that can
.run, test, and debug independently
-Modules include: source code, build scripts, unit tests, descrip
,deployment tori etc. NyetHack is one module inside your project
and an IntelliJ project can contain multiple modules. Modules may depend
on
.source files and resources of other modules
.The internal visibility level helps you organize the sharing of a class
itself inside the module and make them inaccessible from other modules.
therefore
.internal is a good choice for building Kotlin libraries
Initialization 13
In the previous chapter, you learned how to declare classes for a view
objects of the real world. In NyetHack, the player is determined by
properties and behavior denier. Despite all the complexity that can be
passed through properties and class functions, you have seen only a small
.part of the ways to create instances of the class
. Recall how Player was announced in the last chapter
} class Player
...
{
That is, calling the class constructor creates an instance of the class - this
process also known as instantiation . This chapter explains how to
initialization of classes and their properties. By initializing a variable,
property or an instance of the class, you are assigning an initial value usable
vania. Next, we will get acquainted with different types of constructors,
techniques property initialization and even learn how to change the rules
.using a later and lazy initialization
Terminology note: technically an instance of a class
created when memory is allocated for it, and initialized when
value is assigned. But in practice, these terms are usually used
otherwise. Often, initialization means "everything you need to
," prepare a variable, property or class instance for use
."whereas instantiation is limited to "creating an instance of the class
.In this book, we stick to the more common meaning
Constructors
Player now contains the behavior and data that you have defined. For
example
: measures, you specified the isImmortal property
val isImmortal = false
You used val because after the player was created, his immortality should
not be
but change. But such a property declaration means that at the moment
neither
one player cannot be immortal: now there is simply no way
. initialize isImmortal with a value other than false
This is where the chief designer comes into play . Constructor
allows, when calling it, to determine the initial values required for
creating an instance of the class. These values can then be used to
.initializing properties declared inside the class
Chief designer
Like a function, the constructor declares expected parameters, which
must be passed as arguments. In order to determine what is needed
given the Player instance to work with, declare the main constructor in the
header Player . Change the code in Player.kt and add the ability to transfer the
.initial values for all Player properties using temporary variables
(Listing 13.1. Chief Designer Declaration (Player.kt
For each constructor parameter, you specify whether it will only be for
reading or not. By defining parameters in the constructor using key
, words val and var , you declare the corresponding class properties val or var
as well as the parameters for which the constructor will expect arguments.
You
also implicitly assign each property the value that is passed
.as an argument
Duplicate code makes it harder to make changes. We usually prefer
it is this way of declaring class properties because it leads to less
, duplication. This technique cannot be used to declare the name property
because it has non-standard read and write methods, but in other cases
.Typically, declaring a property in the primary constructor is a good choice
Helper Constructors
There are two types of constructors: main and auxiliary. Announcing
chief designer, you say: “These parameters are needed for any instance
play of this class ". By declaring a helper constructor, you define
an alternative way to create a class (which meets the requirements
.(chief designer
Default arguments
By declaring a constructor, you can also specify default values, which
get parameters in the absence of their corresponding arguments. You
already
have seen default arguments in the context of functions, in the case of main
and
,they work exactly the same with helper constructors. For instance
set default value 100 for healthPoints in main declaration
.constructor as in the following example
-By adding a default value for the healthPoints parameter to the main con
constructor, you can remove from the declaration of the auxiliary
constructor the soap has an argument healthPoints to chief designer. This gives
another option ant instantiating Player : with and without argument for
. healthPoints
Player is created with 64 health points by calling the main constructor //
(Player ("Madrigal", 64, true, false
Player is created with 100 health points by calling the main constructor //
(Player ("Madrigal", true, false
Player is created with 100 health points by calling the auxiliary //
constructor //
("Player ("Madrigal
Named arguments
The more default arguments are used, the more appears
-options for calling the constructor. The more options, the more two
meaningfulness, so Kotlin allows you to use names in a constructor call
.named arguments, which are similar to named arguments in function calls
: Compare the two options for instantiating Player
,"val player = Player (name = "Madrigal
,healthPoints = 100
,isBlessed = true
(isImmortal = false
(val player = Player ("Madrigal", 100, true, false
Which option do you think is clearer? If the first one, then we are in
.solidarity with you
Named argument syntax allows you to specify a parameter name for each
the same argument to provide additional clarity. This is especially useful
when there are several parameters of the same type: if in a constructor call
pass values true and false to Player , named arguments will help you
recognize which value belongs to which parameter. Besides eliminating
ambiguity, named arguments give another advantage: they
allow you to pass arguments to a function or constructor in an arbitrary
okay. Unnamed arguments must be passed in the order in which they are
.com they are specified in the constructor
You may have noticed that the helper constructor you wrote
for Player , uses named arguments similar to those already
.used in chapter 4
,constructor (name: String): this (name
,healthPoints = 100
,isBlessed = true
(isImmortal = false
Initialization block
In addition to the main and secondary constructors, in Kotlin you can
specify
initialization block for the class. Initialization block is a way to customize
variables or values, and also check them, that is, make sure
that the constructor has been passed valid arguments. Code in the
.initialization block executed immediately after the class is instantiated
For example, when creating a player, a number of requirements are imposed
on him: the player must
wives start the game with at least one health point, and the name must not
.be empty
, Use the init block indicated by the init keyword
.to verify these requirements
Property initialization
Up to this point, you could see that the property can be initialized
in two ways - by passing an argument or declaring a parameter
.chief designer
A property can (and should) be initialized with any value of its
.type, even with the return value of the function. Let's look at an example
,Your character can be native to a large number of exotic locations
thrown around the world of NyetHack. Declare a new property of type String
.named hometown to store the name of the city where the player was born
You declared hometown , but the Kotlin compiler is not enough. Name
announcements
and the type of the property is not enough, as it is also necessary to assign
an initial value when declaring a property. What for? It's about the null
protection rules. Without the initial value of the property may be null,
.which is invalid if the property is of a non-nullable type
To solve this problem, you can "attach plantain", that is, initiate
:Alize hometown with an empty line
"" = val hometown
This code will compile, but it is not a perfect solution because the empty
the string "" is not the name of a city in NyetHack. To solve the problem
-add a new function named selectHometown that returns a case
city from the list. We use this function to assign an initial
. hometown value
(Listing 13.10. SelectHometown function declaration (Player.kt
import java.io.File
Start Game.kt . Now all your heroes with the same names can be
:distinguish by city of birth
(A glass of Fireball springs into existence. Delicious! (x2
(Aura: GREEN) (Blessed: YES)
!Madrigal of Tampa is in excellent condition
If complex logic is required to initialize a property, including
multiple expressions, it can be moved to a function or initial block
.zation
The rule that states that when declaring properties should be assigned
,value, does not apply to variables in a narrower scope
:such as function scope. For instance
} class JazzPlayer
} () fun acquireMusicalInstrument
val instrumentName: String
"instrumentName = "Oboe
{
{
Initialization order
You learned how to initialize properties and add initialization logic
in different ways: declare parameters in the main constructor, initialize
lyse on declaration, in a helper constructor, or in a block
initialization. The same property can be used in several
.initializations, so the order in which they are executed is very important
To deal with this, let's examine in detail the order of initialization of the
total
field and method calls in compiled Java bytecode. Consider
:the following code, which declares the Player class and instantiates it
Note that an instance of the Player class is created by calling the helper
. ("gogo constructor Player ("Madrigal
In fig. 13.1 on the left is the Player class , and on the right is the decompiled
.byte- Java code that reflects the final initialization order
(Figure: 13.1. Player initialization order (compiled bytecode
Initialization delay
Regardless of the way it is declared, the class property must be initialized
at the time of creating an instance of the class. This rule is important
part of Kotlin's null protection system and guarantees initialization of
actions
the valid values of all non-nullable properties when called
class constructor. After creating an object, you can immediately link to
.any of its properties inside or outside the class
Despite its importance, this rule can be circumvented. What for? You don't
always
control how and when the constructor is called. One of these
.cases - Android Framework
Late initialization
The Activity class in Android represents the screen of your application. You
are not con- troll the moment when the Activity constructor will be called .
But from- the earliest point of execution is known to be a function named
. onCreate
If properties cannot be initialized at instantiation time then
?when can it be done
, This is a situation where late initialization becomes important
and this is more than just breaking the compiler initialization rules
.Kotlin
. You can add the lateinit keyword to any var property declaration
Then the Kotlin compiler will allow deferring property initialization until
.moment when such an opportunity appears
} class Player
lateinit var alignment: String
} () fun determineFate
"alignment = "Good
{
} () fun proclaimFate
(if (:: alignment.isInitialized) println (alignment
{
{
Lazy initialization
.Late initialization is not the only way to delay initialization
.You can delay initialization of a variable until the first call to it
,This idea is known as deferred initialization , and despite its name
.it can make your code more efficient
-Most of the properties you initialized in this chapter were pre
pretty simple. For example, single String objects . However, many classes
form a more complex system. They may require the creation of several
objects or perform complex calculations on initialization, for example
read data from a file. If initializing your property requires
perform such calculations or the class does not require immediate readiness
.properties, use lazy initialization
Lazy initialization is implemented in Kotlin through the delegate
. mechanism
.The delegate defines the property initialization template
-Defining a delegate by using the keyword by . Mill
The Kotlin free library includes several predefined delegates, for example
lazy . The lazy delegate accepts a lambda expression with the code you would
.like execute to initialize the property
The initial value for the hometown property in the Player is retrieved from the
file. You do not you need to access hometown right away, so you should
initialize it when first contact. Let's implement lazy initialization of hometown
in Player . (Some of these changes are hard to notice. Remove = , add by
( .() lazy and curly braces around selectHometown
(Listing 13.12. Lazy initialization hometown (Player.kt
There are a couple of similar, but more insidious scenarios for the
development of events that can happen to an uninformed programmer. For
example, in the following
the code declares the name property and then the firstLetter function reads the
first
:property symbol
} () class Player
val name: String
[private fun firstLetter () = name [0
} init
(() println (firstLetter
"name = "Madrigal
{
{
} (<fun main (args: Array <String
() Player
{
This code will compile because the compiler sees that the name property is
.initialized
initialized in the init block - a suitable place to assign the initial
, values. But by running this code, you will get a NullPointerException
-because the firstLetter function (which uses the name property ) is called
. occurs before the name property is initialized in the init block
The compiler does not check the initialization order of properties and
function calls, who use them in the init block . Before declaring the init block
which
calls functions that access properties, make sure to initialize
set properties before function calls. If name is initialized before calling
:firstLetter , the code will compile and execute without error
} () class Player
val name: String
[private fun firstLetter () = name [0
} init
"name = "Madrigal
(() println (firstLetter
{
{
} (<fun main (args: Array <String
() Player
{
The code will be compiled again, since the compiler sees that all properties
.are initialized
lysed. But running the code will result in a null reference exception being
. thrown
What is the problem here? When playerName is initialized with the function
initPlayerName , the compiler assumes name is initialized, but
when initPlayerName is called , it turns out that name has not yet initiated
.lysed
The following example shows once again the importance of the
.initialization order
The initialization of the two properties must be reversed. Having done this,
you can get the Player class to compile and access the name property
:will return a valid value
} (class Player (_name: String
val name: String = _name
() val playerName: String = initPlayerName
private fun initPlayerName () = name
{
} (<fun main (args: Array <String
(println (Player ("Madrigal"). playerName
{
Which is the output of the following code, which instantiates Sword and
(.accesses to the name property ? (Try to answer before checking the REPL
Which will output the following code now, after creating an instance of
Sword and processing
? extension to name
(Listing 13.17. Referencing name (REPL
("val sword = Sword ("Excalibur
(println (sword.name
This assignment will test your knowledge of initialization and custom
.methods for reading and writing properties
Inheritance 14
,Inheritance is an object oriented programming principle
helping to define hierarchical relationships between types. In this chapter
.we use inheritance to organize sharing data and behavior related classes
To get an idea of inheritance, consider an example that does not have
directly related to programming. For cars and trucks
cars have a lot in common: wheels, engine, etc. But there are also
.differences
Using inheritance allows you to combine the same traits into a common
(class Venicle , that will make it possible not to implement re- Wheel (wheel
and Engine for cars and trucks. Cars and trucks new cars will inherit these
common characteristics, and then each of them will identify features that
are unique to itself. In NyetHack, you have already defined what it means to
be a player in the game. In this chapter we use inheritance to create a set of
.rooms in NyetHack. This will give the player the ability to move
.To try out the new Room class , create a Room instance in main at startup
. game and output the result of the description function
?So far so good ... But boring. Who wants to spend time in the foyer
!It's time to travel
Subclassing
A subclass has all the features of an inherited class, which is also called
. are the parent class, or superclass
For example, the residents of NyetHack will benefit from the city square.
Urban
-area is a kind of Room with its own characteristics, ha
typical only for areas. For example, displaying a special message when
player input. The TownSquare class can be declared a child of
Room , as they have much in common, and then describe what TownSquare
. The relative differs from Room
But before declaring the TownSquare class , you first need to change the Room
.class , so that it can be inherited
Not every class you write can become part of the hierarchy, and even
.moreover, they have a restriction that prohibits inheritance by default
chania. For a class to be inherited, it must be marked with a key
. the word open
.Add the open keyword to Room so you can subclass
(Listing 14.3. Making the Room class available for inheritance (Room.kt
} (open class Room (val name: String
"fun description () = "Room: $ name
"... fun load () = "Nothing much to see here
{
The TownSquare class declaration contains the class name to the left of the
operator : and the call the constructor on the right. The constructor call
indicates which parent the constructor needs to be called to create an
instance of TownSquare and what arguments to transfer the cops to him. In
this case, TownSquare is the Room version with the name
. "Town Square"
But you want the town square to have more than just a name. Other
the way to add differences between a subclass and an ancestor is by
overriding . In gla- in 12 we said that properties represent class data, and
functions represent it behavior. Subclasses can override or define their own
implementations
.for data and functions
Room has two functions - description and load . In TownSquare must be their own
load implementation to express joy when the hero goes out to the city
.area
. Override load in TownSquare using the override keyword
(Listing 14.5. TownSquare class declaration (Room.kt
Now instead of the default message ( Nothing much to see here ... ) an instance
TownSquare will display the glee of residents when a hero arrives and
. the call to load comes out
In Chapter 12, you learned how to control the visibility of properties and
functions using visibility modifiers. Properties and functions are public by
.default
You can also make them available only inside the class in which they are
. declared with the visibility level private
The protected access modifier is the third option to restrict the
.The scope of the class member by the class itself and any of its subclasses
. Add a protected property named dangerLevel to Room
(Listing 14.7. Declaring a property with the protected modifier (Room.kt
dangerLevel determines the danger level of the room on a scale from 1 to 10. It
is
installed in the console so that the player can see what level of danger will
be expected him in every room. The average hazard rating is 5, so
. this value is assigned by default in the Room class
Room subclasses can change dangerLevel to express how dangerous
or not) a specific room, but in general the dangerLevel property should)
only available in Room and its subclasses. This is the perfect case for using
using the protected keyword : you want to grant access to your
.only the class in which it is declared and its subclasses
To override the dangerLevel property in TownSquare , use
. the override keyword , as is the case with the load function
NyetHack city square hazard level three points lower
average. To express this logic, you need the ability to refer to the middle
the level of danger in the Room . It is possible to reference the superclass
class using the super keyword . It opens access to all functions and properties
. with the visibility level public or protected , and in our case - to dangerLevel
Override dangerLevel in TownSquare to make the threat level to
.urban area is three points below average
(Listing 14.8. Overriding dangerLevel (Room.kt
, Now any TownSquare subclass will be able to override the description function
. but not load , because it is preceded by the final keyword
, As you may have noticed the first time you tried to override load
functions by default are not available to be overridden unless declared
them open by adding the open modifier . Adding the final keyword
in the override function declaration ensures that it will not override
. is defined even if the class in which it is declared has the open modifier
So you've seen how to use inheritance to provide
.Share data and behavior among related classes
Also you saw how to use the open , final and override keywords for
override control. Requiring explicit use
open and override keywords , Kotlin encourages people to agree with
inheritance
eat. This reduces the chances of interfering with the operation of classes not
intended for subclassing, and prevents you or someone else from overriding
.functions where not offered
Type checking
This is not to say that NyetHack is an incredibly complex program. But
finished- nay version can include many classes and subclasses. You can
very
choose the names for your variables carefully, but it will still sometimes be
there is uncertainty about what type this or that variable gets
during program execution. Get rid of doubts about the type of object you
. the is operator will help
Try it out in the Kotlin REPL. Create an instance of Room (you will need
.(import Room to REPL
(Listing 14.12. Instantiating Room (REPL
. Find out if room is an instance of the Room class by using the is operator
Listing 14.13. Room is Room (REPL) check
room is Room
true
The type of the object to the left of the is operator is compared to the type
.specified to the right
.The expression returns true if the types are the same, and false if not
. Now check if room is an instance of the TownSquare class
Listing 14.14. Room is TownSquare (REPL) check
room is TownSquare
false
room is of type Room , which is the ancestor of TownSquare . But the room object
. is not an instance of TownSquare
. Try another variable, this time with the TownSquare type
Listing 14.15. TownSquare is TownSquare (REPL) check
Run the code. Now the line Room will appear in the console , since the first
condition
.it is defined as true
.If branching depends on the type of object, the order of execution matters
Type hierarchy in Kotlin language
All classes in Kotlin inherit a common superclass known as Any , with no
-non
.(the need to explicitly indicate this in your code (Figure 14.2
Figure: 14.2. TownSquare type hierarchy
For example, both Room and Player are indirect descendants of Any . That's
why
you can declare functions that will accept instances of any of
of these types as arguments. In this, Kotlin reminds Java, where all classes
. implicitly inherit from the java.lang.object class
. Consider the following example of a function named printIsSourceOfBlesings
printIsSourceOfBlesings takes an argument of type Any and uses a conditional
operator to check the type of the passed argument. At the end, she outputs
verification results. Several new ideas are used here that we
.we will consider a little later
} (fun printIsSourceOfBlessings (any: Any
} (val isSourceOfBlessings = if (any is Player
any.isBlessed
} else {
"any as Room) .name == "Fount of Blessings)
{
("println ("$ any is a source of blessings: $ isSourceOfBlessings
{
There are only two sources of blessings in NyetHack: the blessed player
.or a room called Fount of Blessings
Since every object is a subclass of Any , you can pass arguments to any
bogo type in printIsSourceOfBlesings . This flexibility is useful, but sometimes
.it doesn't
lets you start working with the argument right away. This example uses a
-pri
. type inference to deal with the unreliable Any argument
Type casting
Type checking does not always return a useful answer. For example, the any
parameter in the printIsSourceOfBlesings function tells us that the passed
the argument will be of type Any , but the type Any does not specify what is
.with this argument you can do
.Type casting allows you to refer to an object as if it were an instance
Lar of a different type. This allows the object to be manipulated as if it were
.(the type was declared explicitly (for example, call its functions
In the printIsSourceOfBlesings function , the conditional statement checks the
type of the arguments that any , comparing it to the type Player . If the
. condition is not met, the control passed to the else branch
: The code in the else branch refers to the name variable
} (fun printIsSourceOfBlessings (any: Any
} (val isSourceOfBlessings = if (any is Player
any.isBlessed
} else {
"any as Room) .name == "Fount of Blessings)
{
("println ("$ any is a source of blessings: $ isSourceOfBlessings
{
According to the condition, for this branch to be executed, the any argument
must have type Player . Inside the code refers to the branch property isBlessed
. instance the any
isBlessed is a property of the class in Player , not Any , how could such a
?expansion without type casting
In fact, a cast is being performed here - a clever cast. You already
.saw the clever ghost in chapter 6
The Kotlin compiler is smart enough to determine that if type checking
any is Player in the conditional statement was successful, then inside the
branch any can be thought of as an instance of Player . Knowing that in this
branch the casting any to Player will always succeed, the compiler allows you
to drop the syntax casts and just reference the isBlessed property of the Player
. class
Smart casting is an example of how the intelligence of the Kotlin compiler
.allows for more concise syntax
In this chapter, we have seen how to create subclasses that share common
.traits
mi. In the next chapter, you will become familiar with other types of
classes, including data classes, enumerations and object (class with one
instance) when
.will add a game loop to NyetHack
The main function in Game.kt should only serve to run the game
, process. All game logic will be concentrated inside the Game object
.in a single copy
Since the singleton is automatically instantiated, there is no need to add
your constructor with initialization code. Instead, it suffices to define
an initialization block that does everything needed to initialize
of your object. Add one such block to the Game object , which will display
.when responsibility when creating it
(Listing 15.2. Adding an init block to Game (Game.kt
The Game object will not only store the state of the game, but will also loop
games, accepting and executing the player's commands. Your game loop
will take a form of while loop , which will make the NyetHack game more
interactive. He will have a simple true condition keeping the loop running
.while the application will not be closed
So far, play does nothing. Soon she will smash NyetHack gameplay
for rounds: in each round, the player's state will be displayed in the console
and other information about the world, and then user input from
. using the readLine function
Look at the main game logic and think about where it should go
in Game . For example, it makes no sense to create a new Player instance or a
new one the currentRoom at the start of each round, so these parts of the game
lo
geeks should be in Game , not in play . Declare player and currentRoom as
. private -properties Game
Next, move the call to castFireball into the Game initialization block to
make the beginning of every game in NyetHack more fun. Also move your
ad
Lenie printPlayerStatus in the Game . Add the private modifier to your declaration
printPlayerStatus as well as player and currentRoom to encapsulate them
. inside Game
Listing 15.4. Encapsulating properties and functions inside an object declaration
(Game.kt)
} (<fun main (args: Array <String
("val player = Player ("Madrigal
() player.castFireball
() var currentRoom: Room = TownSquare
(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
() Game.play
{
} (private fun printPlayerStatus (player: Player
+ "({() println ("(Aura: $ {player.auraColor
("({" Blessed: $ {if (player.isBlessed)" YES "else" NO)"
("{() println ("$ {player.name} $ {player.formatHealthStatus
{
} object Game
("private val player = Player ("Madrigal
() private var currentRoom: Room = TownSquare
} init
(".println ("Welcome, adventurer
() player.castFireball
{
} () fun play
} (while (true
Play NyetHack //
{
{
} (private fun printPlayerStatus (player: Player
+ "({() println ("(Aura: $ {player.auraColor
("({" Blessed: $ {if (player.isBlessed)" YES "else" NO)"
("{() println ("$ {player.name} $ {player.formatHealthStatus
{
{
By moving the code from the main function to the play function , you will
. save everything you need to customize the game loop in Game
What's left in main ? Displaying a description of the current room, greeting
at the entrance into the room and player states. All this should be output at
the beginning of each round of gameplay, so move these instructions into
.the game loop
. Leave the call to Game.play in main
(Listing 15.5. Status output in the game loop (Game.kt
Have you noticed that the entered text is also displayed? Excellent! Now
.the game can receive user input
Anonymous objects
Declaring classes using the class keyword is useful for those
which allows
you to introduce new concepts into the code. Having defined a class named
, Room
you reported that rooms exist in NyetHack. And by defining a subclass
TownSquare , you indicated that there is a special kind of rooms - city
.area
-But sometimes, declaring a new named class is overkill. On
example, in some cases you need an instance of a class that is slightly
different
.from an existing one, and this copy will be used only once
.Moreover, it will be so temporary that it doesn't even need a name
.Another use case for the object keyword is anonymous objects
:Consider the following example
} () val abandonedTownSquare = object: TownSquare
"... override fun load () = "You anticipate applause, but no one is here
{
Auxiliary objects
If you want to bind the initialization of an object to an instance of a class,
this is
can be organized by defining a helper object. Auxiliary
the object is declared inside the class using the companion modifier . At the
class
.there can be no more than one auxiliary object
The auxiliary object is initialized in two cases: when
initialization of the enclosing class, which makes it a good place to store
data in a single copy, with contextual connection with the declaration
.class, and when directly accessing one of its properties or functions
Since the helper is actually an object declaration, you don't
you will have to instantiate the class to use any declared
it contains functions or properties. Let's take a look at the following
example to help you a powerful object inside a class named PremadeWorldMap
:
} class PremadeWorldMap
...
} companion object
"private const val MAPS_FILEPATH = "nyethack.maps
() fun load () = File (MAPS_FILEPATH) .readBytes
{
{
. In PremadeWorldMap have a helper object for the sole function of the load
If you need to call load elsewhere in your code, you can do this
:without instantiating PremadeWorldMap like below
PremadeWorldMap.load () File content will be loaded by helper object only one
times, during initialization of the PremadeWorldMap instance or the first time
you call the load function . It doesn't matter how many instances of
PremadeWorldMap there will be created because the helper always exists in a
.single copy
Understanding the goals and objectives of singletons, anonymous and
auxiliary objects projects are the key to using them effectively. And their
effective
-use will allow you to write well-organized and scale
.runable code
Nested classes
.Not all classes defined inside other classes are declared without a name
You can also use the class keyword to declare name- a bathroom class nested
in another class. In this section you will declare a new GameInput class ,
. nested within Game object
Now that we have defined the game loop, we can analyze the commands
that
the user drives. NyetHack is a command driven text game
user who read the function readLine . Analyzing the team, introduce
given by the user, it is important, first, to check the validity of the command
,and secondly, to correctly process a command consisting of several parts
for example "go east". The word "go" ( move ) in this case should be
transformed develop into a call to the move function , and the words "to the
east" are passed into a call move as an argument specifying the direction of
.movement
Let's consider these two requirements, starting with an analysis of
commands consisting of non- how many parts. We will put the logic of
. separating the command from its arguments to the GameInput class
.Create a private class inside Game to implement this abstraction
(Listing 15.7. Nested class declaration (Game.kt
...
} object Game
...
} (?private class GameInput (arg: String
"" :?private val input = arg
[val command = input.split ("") [0
({""} ,val argument = input.split ("") .getOrElse (1
{
{
Why is GameInput declared private and nested in Game ? The point is that the
class GameInput is directly related to Game object only and should not be
available from somewhere else. By declaring the nested GameInput class
private, you how would you say that GameInput can only be used inside Game
and not
.should clutter up its public API
You have declared two properties in the GameInput class , the first for the
command and the second for argument. To do this, you call split , which
splits the input string by a space character and then getOrElse to get the
second element from the list, created split . If the element at the specified
.index does not exist, getOrElse will return an empty string
Now it is possible to split commands into parts. Remained
.believe the validity of the command entered
,To do this, we will use the when clause and define a whitelist of commands
-valid for Game . Any quality whitelist starts with re
lize handling invalid input. Add the commandNotFound function
in GameInput , which will return a string for output to the console in the
.event that if the command entered is invalid
(Listing 15.8. Function declaration in nested class (Game.kt
...
} object Game
...
} (?private class GameInput (arg: String
"" :?private val input = arg
[val command = input.split ("") [0
({""} ,val argument = input.split ("") .getOrElse (1
private fun commandNotFound () = "I'm not quite sure what you're trying
" !to do
{
{
Data classes
The first step towards building a world for your hero is creating a system
coordinates to move. The coordinate system will use the primary
directions of travel, as well as a class named Coordinate to represent
.direction changes
Coordinate is a simple type and a potential candidate for a role declaration
data class . As the name suggests, data classes are designed
we are dedicated to data storage and offer ample opportunities for
.data manipulation, as you'll see shortly
Create a new Navigation.kt file and add Coordinate as a class to it
data using the data keyword . At Coordinate shall be three
:properties
In Chapter 14, you learned that all classes in Kotlin are descendants of the
.same class
.sa - Any . The functions declared in Any can be called for any instance
These include toString , equals and hashCode which increase speed
.getting a value by key when using an associative array
Any provides a default implementation of these functions, but as you could
already notice they are not always convenient. Data classes offer their own
implementations these functions, which can better suit the needs of your
project. In this section, we will talk about two of them, as well as some
other
their benefits of using data classes to represent data
.in your code
The default implementation of toString returns an obscure for the class
string. Take the Coordinate class as an example . If you declare Coordinate as
:a normal class, then calling toString on Coordinate will return something like
Coordinate @ 3527c201
You can see a link to the location of the Coordinate instance in memory.
Arose
raises a legitimate question: why do we need information with location
details
.Coordinate in memory? In most cases, you don't care
-In your class, you can override toString like any other from
covered function. But data classes get rid of this work by suggesting
its default implementation. For Coordinate, this implementation will return
:the string
(Coordinate (x = 1, y = 0
equals
The next function implemented in data classes is equals . If
?Coordinate was a normal class, what would be the result of this expression
(Coordinate (1, 0) == Coordinate (1, 0
?It may sound unexpected, but it will return false . Why
By default, objects are compared by reference, since this is an
implementation by the default equals function in Any . Since these
coordinates are independent ec- instances, then they will have different
.links and they are not equal
You might want to assume that two players with the same name are
the same player. Implement equality check by overriding equals
in your class, and compare properties, not memory references. You already
.have See how classes like String use this approach to compare by value
.cheniya
Again, the data classes will do the job for you using their own
.implementation
.equals , which compares the properties declared in the main constructor
== (If you declare Coordinate as a data class, the expression Coordinate (1, 0
Coordinate (1, 0) will return true since the values of the x and y properties are
two
.copies are equal
copy
In addition to more convenient implementations of functions from Any , data
.classes also have a function that makes it easy to create a copy of an object
For example, you want to create a Player instance that has the same
,properties, with the exception of isImmortal . If Player were a data class
then copying the Player instance could have been a simple call to copy
.with arguments for all the properties you want to change
(val mortalPlayer = player.copy (isImmortal = false
-Data Classes will relieve you of the need to implement a function copy self
.worthwhile
Destructuring ads
-Another advantage of data classes is support for automatic destruc
.data touring
Earlier, you have already seen an example of destructuring the data returned
.by functions
tion the split . Underneath the outer shell, destructuring relies on functions
named component1 , component2 , etc. Each function is designed to
retrieving the piece of data that you want to return. Data classes are
automatic
add these functions for every property declared in the main
.constructor
There is nothing magic about destructuring: the data class simply does
this work myself to make the class "destructured". Can be done
any class destructured by adding a component statement function like
:in the example
} (class PlayerScore (val experience: Int, val level: Int
operator fun component1 () = experience
operator fun component2 () = level
{
(val (experience, level) = PlayerScore (1250, 5
By declaring Coordinate as a data class, you can return properties that are
: declared specified in the main constructor Coordinate
(val (x, y) = Coordinate (1, 0
These operators can be overloaded in any class, but it's worth doing
only when it makes sense. Going to change operator logic
addition in the Player class , ask yourself first the question: “What is meant
under the concept of “player plus player”? " Try to answer it before
.overload the operator
By the way, if you decide to override equals , it is also worth overriding
hashCode function . An example of overriding these functions through a
special
the IntelliJ command is shown in For the Curious: Algebraic Types
data ”at the end of the chapter. Detailed explanation of why you need to
override hashCode is outside the scope of this book. If you are interested in
this question, go to babe link kotlinlang.org/api/latest/jvm/stdlib/kotlin/-any/hashcode.html
.
:Launch Game.kt and try moving. You will see the following
.Welcome, adventurer
(A glass of Fireball springs into existence. Delicious! (x2
Room: Town Square
Danger level: 2
!The villagers rally and cheer as you enter
The bell tower announces your arrival. GWONG
(Aura: GREEN) (Blessed: YES)
!Madrigal of Tampa is in excellent condition
Enter your command: move east <
.OK, you move EAST to the Tavern
... Nothing much to see here
Room: Tavern
Danger level: 5
... Nothing much to see here
(Aura: GREEN) (Blessed: YES)
!Madrigal of Tampa is in excellent condition
:Enter your command <
That's all. Now you can navigate the world of NyetHack. In this chapter,
you
learned to use different types of classes. Besides the keyword
class , you can use object declarations to represent data
tov (singletons), data classes and enumerations. Using the correct
.tools makes the relationship between objects more straightforward
.In the next chapter, you will learn about interfaces and abstract classes, i.e
mechanisms for declaring protocols that your classes will need to follow
.mending when you add a battle mode to NyetHack
In this chapter, you learned that data classes can solve this problem. For
this requires an equals implementation that compares all properties declared
,in the main constructor. But Weapon cannot (and will not be) a data class
because it is the base for the weapons ( open modifier ). Classes
.data is not allowed to be superclasses
However, in the section on Operator Overloading, we saw that you can
implement provide your own equals and hashCode versions for structural
.comparison of instances class
This is a common task, which is why IntelliJ has a Generate command
-adding
function overrides, available in the menu as Code → Generate . If
.(select this command, a dialog box will be displayed (Fig. 15.2
You can also write a function that displays a message about the status of the
:student denta
} fun studentMessage (status: StudentStatus): String
} (return when (status
".StudentStatus.NOT_ENROLLED -> "Please choose a course
{
{
One of the advantages of enums and other ADTs is that the compiler
can check if all possible choices have been processed because ADT is
it is a closed set of all possible types. The studentMessage implementation is
not
handles ACTIVE and GRADUATED types , so the compiler will throw an error