0% found this document useful (0 votes)
536 views

Programming Kotlin Enhance Your Skills For Android Development Using Kotlin by Alexander Aronowitz PDF

Uploaded by

Pawan Kumar
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
536 views

Programming Kotlin Enhance Your Skills For Android Development Using Kotlin by Alexander Aronowitz PDF

Uploaded by

Pawan Kumar
Copyright
© © All Rights Reserved
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 299

............................................ ................................................

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 1 . Your first Kotlin application


.................................... ............................................... Installing IntelliJ IDEA
..................................... ............................................. Your first Kotlin project
.................................. ............................................. Your first Kotlin file
.................... ............................................ Running your file in Kotlin language
........... .................................................. ................................................ Kotlin REPL
.... .................................................. ?For the curious: why use IntelliJ
.......... ............................................ For the curious: programming for the JVM
...................................... .............................................. Assignment: REPL arithmetic

...................... ............................................. Chapte r 2 . Variables, constants and types


.................. .................................................. ................................................. Types
....................................... ................................................ Variable declaration
.................................. .............................................. Kotlin built-in types
............... ............................................ Read-only variables
....................... ............................................... Automatic type detection
............................. ............................................... Compile Time Constants
............................................ ............................................. Exploring Kotlin Bytecode
.............. .......................................... For the curious: Java simple types in Kotlin
.................................................. ............................................... Quest: hasSteed
......................................... ............................................ Quest: Unicorn Horn
................................... .............................................. Quest: Magic Mirror

................................. ................................................ Chapte r 3 . Conditional constructions


... .................................................. .............................................. If / else statements
........................................ ................................................ Adding conditions
.............................. ............................................. Nested if / else statements
............. .............................................. More elegant conditionals
.......... .................................................. ................................................. Intervals
.................................... ............................................... When conditional expression
................................................ ................................................ Template strings
.................................. .............................................. Assignment: trying intervals
.... ........................................... Task: displaying extended information about the aura
... ............................................ Assignment: Customizable Status Bar Format
Chapte r
... .................................................. ................................................. 4 . Functions
..................................... .............................................. Highlighting code in a function
................................................ ................................................ Anatomy of function
.......................................... ................................................ Function header
. .................................................. ................................................ Function body
............................. ............................................... Function scope
.... .................................................. ................................................ Function call
........................................... ................................................ Function refactoring
...................................... ............................................... We write our functions
....................................... ............................................... Default arguments
.................... .............................................. Functions with a single expression
....................... ............................................. Functions with the return type Unit
.......................... ............................................... Named function arguments
.................................. ............................................. For the curious: the Nothing type
......... .......................................... For the curious: file-level functions in Java
..................... ............................................. For the curious: function overloading
........................................ For the curious: function names in backticks
..... ............................................ Assignment: functions with a single expression
..................... ............................................. Quest: intoxicating effect of fireball
....................... .............................................. Assignment: intoxicated state

........................................... Chapte r 5 . Anonymous functions and functional types


............................................ ................................................ Anonymous functions
................................... ................................................ Functional types
................................................. ................................................ Implicit return
................................ ................................................ Functional arguments
................................................. ............................................... Keyword it
....................... ............................................... Taking multiple arguments
... .............................................. Support for automatic type detection
............................................ Function declaration that takes a function
...................................... ................................................ Abbreviated syntax
........................................... ................................................ Built-in functions
.............................................. ............................................... Function link
...................... ............................................. Function type as return type
..... .......................................... For the curious: Kotlin lambdas are closures
...................... For the curious: lambdas vs. anonymous inner classes

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

.... .................................................. ................................................. Chapte r 7 . Strings


......................................... ................................................ Extracting a substring
...... .................................................. ................................................. substring
............. .................................................. ................................................. split
.............................................. ............................................... Working with strings
...................................... ................................................ Strings are immutable
.................................................. ................................................ String comparison
..................................... .............................................. For the curious: Unicode
.............. ........................................... For the curious: traversing characters in a string
......................... ............................................. Quest: Improve Draconic Language

..... .................................................. ................................................. Chapte r 8 . Numbers


.. .................................................. ................................................ Numeric types
.................................... ................................................ Integer values
.. .................................................. ................................................ Fractional numbers
............................ .............................................. Convert string to number
................................. .............................................. Converting Int to Double
................. .............................................. Formatting Double Values
................................. .............................................. Converting Double to Int
................ ............................................ For the curious: bit manipulation
............................. ............................................. Assignment: How Many Pints Remain
......... ............................................. Task: Handling Negative Balance
.................................... .............................................. Quest: Dragon Coins

.............................. ................................................ Chapte r 9 . Standard functions


................. .................................................. ................................................. apply
..................... .................................................. ................................................. let
................... .................................................. ................................................. run
.................. .................................................. ................................................. with
................... .................................................. ................................................. also
................ .................................................. ................................................. takeIf
......... .................................................. ................................................. takeUnless
................... ............................................... Using standard functions

................................. ............................................... Chapte r 10. Lists and Sets


.............. .................................................. ................................................. Lists
................................... .............................................. Access to list items
....................................... Index Boundaries and Secure Index Access
............................. ............................................... Checking the contents of the list
.................................. ............................................... Changing the contents of the list
.......... .................................................. ................................................. Iteration
.......................................... .............................................. Reading a file into a list
............................................... ................................................. Destructuring
....... .................................................. ................................................. Sets
...................................... ................................................ Creating a set
................. .............................................. Adding items to a set
......... .................................................. ................................................ While loop
.. .................................................. ................................................ The break statement
................................. ................................................ Converting Collections
........................... ............................................. For the curious: array types
....................... "For the curious: "read-only" instead of "immutable
... ............................................ Assignment: Formatted Display of the Tavern Menu
........................................ Quest: Improved Tavern Menu Formatting

.......................... ................................................ Chapte r 11. Associative arrays


........................ ............................................... Creating an associative array
......... ............................................ Accessing values in an associative array
............ ............................................. Add entries to the associative array
........ ............................................. Changing the values in the associative array
................................ ............................................. Quest: Tavern Bouncer

................................... ............................................. Chapte r 12. Declaring Classes


.............................................. ................................................ Class declaration
........................................... ................................................ Creating instances
. .................................................. ................................................ Class functions
................................ ............................................... Availability and Encapsulation
.................................................. ................................................ Class properties
. .................................................. ................................................ Property methods
.............................................. ................................................ Property Visibility
........................................ ................................................ Computed Properties
......................................... ................................................ Refactoring NyetHack
....................................... ................................................ Using packages
.................... For the curious: a closer look at the var and val properties
.......... ........................................... For the curious: protection against race conditions
................................. For the curious: limiting visibility to the scope of the package

........................................ .............................................. Chapte r 13. Initialization


... .................................................. ................................................. Constructors
........................................... ................................................ Chief Designer
......... ............................................. Declaring properties in the main constructor
........................... ................................................ Helper Constructors
..................................... ............................................... Default arguments
..................................... ................................................ Named arguments
........................................... ................................................ Initialization block
....................................... ................................................ Property initialization
...................................... ................................................ Initialization order
.................................... ................................................ Initialization delay
...................................... ................................................ Late initialization
............................... ................................................ Delayed initialization
........................................... For the curious: initialization pitfalls
............................... .............................................. Quest: the riddle of Excalibur

......................................... .................................................Chapte r 14. Inheritance


..................................... ............................................... Room class declaration
............................................ ................................................ Subclassing
. .................................................. ................................................ Type check
................................ ............................................. Type hierarchy in Kotlin language
................................................. ................................................ Type casting
........................................ ............................................... Clever type casting
........................................... .............................................. For the curious: Any

................................................. ................................................. Chapte r 15. Objects


.......................................... ............................................... The object keyword
.......................................... ................................................ Object declarations
............................................ ................................................ Anonymous objects
................................... ................................................ Auxiliary facilities
.............................................. ................................................ Nested classes
.. .................................................. ................................................ Data classes
........ .................................................. ................................................. toString
.......... .................................................. ................................................. equals
............ .................................................. ................................................. copy
....................... ................................................ Destructuring ads
... .................................................. ................................................. Transfers
....................................... ................................................ Operator overloading
....................................... ............................................... Explore the world of NyetHack
....................................... For the curious: declaring a structural comparison
...... ............................................ For the curious: algebraic data types
......................................... ............................................ "Assignment: command "Quit
........................... ............................................. Assignment: Implementation of the World Map
................................ ............................................. Assignment: ring the bell
Introducing Kotlin
In 2011, JetBrains announced the development of a programming language
Kotlin as an alternative to Java and Scala, which also works
running a Java Virtual Machine. Six years later, Google announced the
beginning of official support for Kotlin, as a development language for the
.Android operating system
Kotlin has grown rapidly from a bright future language to application
.support language of the world's leading operating system
Today big companies like Google, Uber, Netflix, Capital One, Amazon and
others have officially adopted Kotlin for its convenience, understandable
.syntax, modern features and full Java compatibility

?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

?Who is this book for


We've written this book for developers of all sizes: seasoned Android
developers who lack Java capabilities, server developers good code
interested in Kotlin features, as well as for beginners, decided to learn an
.efficient compiled language
This book may interest you in Android support, but it is not limited to
It is done in Kotlin programming for Android. Moreover, in this book only
one chapter - chapter 21 - covers programming techniques in Kotlin
for Android. Nevertheless, if you are interested in the topic of using Kotlin
for
Android Application Development, this book will introduce you to the basic
Themes that will simplify the process of writing Android applications in
.Kotlin
Even though Kotlin has been influenced by several other languages, you
there is no need to know how they work in order to work successfully with
Kotlin. Time from time to time we will compare Java and Kotlin code. If
you have experience development in Java, it will help you understand the
.relationship between the two languages
And if you don't have such experience, examples of solving the same
.problems in another language help you understand the ideas behind Kotlin

How to use this book


This book is not intended to be a reference. Our goal is consistent learning
the Kotlin language. You will work on projects and learn the language as
.you work
For more effect, we recommend trying out all the code examples along the
.way
reading a book. Working with examples will help you develop muscle
memory
and will provide an understanding that allows you to move from one
.chapter to another
Each next chapter builds on the previous one. We recommend not
skip chapters. Even if you have studied the topic while working with other
languages, we suggest at least reading about it here: a lot is implemented in
Kotlin otherwise. We'll start with introductory topics like variables and lists
and then let's move on to the techniques of object-oriented and functional
pro
grammar to give you an idea of what makes Kotlin so powerful tool. By the
.end of the book, you will go from beginner to advanced
.developer in Kotlin
We want to add that there is no need to rush: develop, use documentary See
the Kotlin reference at kotlinlang.org/docs/reference , where there are answers to
many
.questions arising during the experiments

For the curious


Most of the chapters have a For the Curious section. There are revealed
principles of the Kotlin language. Examples in chapters are not directly
related to this information. These sections provide additional information
that
.can be useful to you

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

Kotlin is a relatively young language, so many conventions about


the design of the code is still being formed. As time goes by, you are more
likely all, develop your own style, but first read the JetBrains requirements
:and google

. JetBrains Agreement: kotlinlang.org/docs/reference/coding-conventions.html ❍

Google Style Guide containing styling conventions ❍


. and android code compatibility: android.github.io/kotlin-guides/style.html

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

From the publisher


Send your comments, suggestions, questions to [email protected]
.(publishing house "Peter", computer edition)
!We'd love to hear from you
On the publisher's website www.piter.com you will find detailed information
.about our books

Your first application 1


on Kotlin
In this chapter, you will write your first Kotlin program using
IntelliJ IDEA. By going through this rite of passage into programming, you
learn
chat with the development environment, create a new Kotlin project in it,
write
.and run the code, and you will also see the results of its execution

Installing IntelliJ IDEA


IntelliJ IDEA is an integrated development environment environment, IDE)
for the Kotlin language, created by the JetBrains team (which also created
by Kotlin itself). Download IntelliJ IDEA to get started Community Edition
.(from JetBrains at jetbrains.com/idea/download (Figure 1.1
Figure: 1.1. Download IntelliJ IDEA Community Edition

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

IntelliJ IDEA, or just IntelliJ, helps you write well-structured


code in Kotlin. In addition, it simplifies the development process by using
built-in tools for launching, debugging, exploring and refactoring
ring code. Find out why we recommend IntelliJ for writing code in
?Kotlin, see For the Curious: Why Use IntelliJ

Your first Kotlin project


Congratulations, you now have the Kotlin programming language and
powerful environment development for writing it. It remains to solve the
last problem: to learn freely "speak" it. The agenda is to create a Kotlin
.project
.(Start IntelliJ. The Welcome to IntelliJ IDEA window will open (Figure 1.2
Figure: 1.2. Welcome dialog
If you have already run IntelliJ, after installation it may display the)
the last project to open. To return to the welcome window, you need
(. close the project by selecting File → Close Project
Click on Create New Project . IntelliJ will display a new New project window like
.shown in Fig. 1.3
Figure: 1.3. New project creation window

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

Figure: 1.5. Project name

,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

Figure: 1.7. Project view


the project contains only one module, which is more than enough for your
.the first project
The Sandbox.iml file contains the configuration information of your only
module. .Idea folder contains files with settings for the whole project and
files
with settings for working with a specific project in the IDE (for example, a
list
files opened in the editor). Leave these automatically generated
.bath files in their original form
The External Libraries catalog contains information about external libraries
from which which project depends. If you expand this directory, you will
see that IntelliJ

automatically added Java 1.8 and KotlinJavaRuntime to the dependency list


project. (You can learn more about organizing projects in IntelliJ at
(. JetBrains: jetbrains.org/intellij/sdk/docs/basics/project_sctructure.html
The src folder is where you will put all the files created for
your Sandbox project. So, let's move on to creating and editing your
.the first Kotlin file

Your first Kotlin file


Right click on the src folder in the project tools window
.(and in the context menu, first select New and then File / Class (Fig. 1.8
Figure: 1.8. Creating a new Kotlin file
In the New Kotlin File / Class dialog box, enter “Hello” in the Name field , and in
the Kind field
.(leave File (fig. 1.9
Figure: 1.9. Naming a file

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

} (<fun main (args: Array <String


("!println ("Hello, world
{

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

Running your file in Kotlin language


When you finish entering the code from Listing 1.1, IntelliJ displays a
green
an arrow, known as "Program Start", to the left of the first line
fig. 1.11). (If the icon does not appear or you see a red line under the name)
file in a tab or somewhere in the code that you entered, it means that
there is a mistake in the code. Double-check the entered code: it must match
with the code in Listing 1.1. However, if you see the red-blue Kotlin K flag,
(.it is the same as Launching a Program
It's time for the program to come alive and greet the world. Click the button
launch. In the menu that appears, select Run 'HelloKt' (Figure 1.12). This will
.prompt IntelliJ, what do you want to see the program in action
Once launched, IntelliJ will execute the code inside the curly braces ( {} ),
line by string and exits. Also at the bottom there will be two new
instrumental
.ny windows
Figure: 1.11. Program start button

Figure: 1.12. Launching Hello.kt

"Figure: 1.13. Tool windows "Run" and "Event log

On the left is the run tool window , and


known as the console (hereinafter we will call it that). It is selected
displays information about what is happening after the start of the program,
as well as ki that the program brought out. The console should display Hello,
... !world
Also you will see Process finished with exit code 0 , which means successful
completion of the program. This line will appear in the console most
,recently
if there are no errors, and from now on we will no longer show
it (macOS users may also see red text telling about an error related to
JavaLaucherHelper , it is shown in Fig. 1.13). Not without
rest about it. This is a side effect of installing Java Runtime
Environment (Java runtime) on macOS. Eliminating the error requires
significant effort, however, it has no effect and it simply
.can be ignored
On the right is the event log tool window , in which
.IntelliJ displays information about the work done to run the program
We will not pay attention to the event log, since more interest is
for us is the information in the console. (For this reason, if the log events
(.did not appear initially, then you shouldn't even pay attention to this
You can close it with the "hide" button on the right at the top of the window,
. :which looks like that

Compiling and executing Kotlin / JVM


code
!From the moment you click the 'Hello.kt' start button until the Hello, World
.a lot of events happen to the console
-First of all, IntelliJ compiles Kotlin code using the kotlinc compiler
,jvm . This means that IntelliJ translates Kotlin code into bytecode , language
which the JVM "talks" about. If kotlinc-jvm has problems
with translation, it will display an error (s) message that will prompt
,what exactly needs to be fixed. However, if the compilation went smoothly
.IntelliJ will move to the execution phase
During the execution phase, the bytecode generated by kotlinc-jvm is executed
by the JVM. The console displays everything that the program outputs
during execution, to example the text specified in the call to the println ()
. function
After executing all instructions in bytecode, JVM will stop working and
IntelliJ
will print the exit code to the console, informing you that the work has been
.completed successfully or with error
You don't need to have a complete understanding of the process to read this
.book
compilation in Kotlin. However, we will look at the bytecode in more
.detail
.in chapter 2

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

Figure: 1.15. Kotlin REPL tool window


You can enter code into the REPL in the same way as into the editor. The
difference is that you can
.quickly compute its result without compiling the whole project
.Let's enter the following code into the REPL
(Listing 1.2. "Hello, Kotlin!" (REPL
("!println ("Hello, Kotlin
After typing, press Command-Return ( Ctrl-Return ) to execute the code in the
.REPL
In a moment, you will see the result under the entered line, which should
.(look like Hello, Kotlin! (fig. 1.16
Figure: 1.16. Computing the code
REPL is an abbreviation for "read, execute, output, repeat" ("read, evaluate,
print, loop "). You put a piece of code in the request and send it into
processing by clicking on the green start button on the left in the REPL
window or Command- Return ( Ctrl-Return ). The REPL then reads the code,
.executes it, and outputs the result
Upon completion of execution, the REPL returns control to the user and
gives
.the ability to repeat the process
Your journey through the Kotlin world has begun! You have done a great
job
in this chapter, laying the foundation for the growth of your programming
knowledge- research on Kotlin. In the next chapter, you will begin to dive
into the details of the language and learn how to use variables, constants
.and types to represent data

For the curious: why


?to use IntelliJ
Any text editor is fine to use the Kotlin language. but
we recommend using IntelliJ, especially while you are learning. As well as
text editing software offers verification
spelling to make it easier to write a good novel, IntelliJ suggests
.tools to help you write well-structured Kotlin code
:IntelliJ will help you

write syntactically and semantically correct code using such ❍


functions like syntax highlighting, context hints, autocomplete
;nenie

run and debug code using functions such as ❍


;stop and step through the program

restructure existing code using refactoring techniques ❍


such as renaming or extracting constants) and formatting)
.code for correct indentation
Since Kotlin was developed by the JetBrains team, the integration between
Kotlin and IntelliJ is done very carefully, which makes the job easy and
.enjoyable
It's also worth mentioning that IntelliJ serves as the basis for Android
Studio, so
the keyboard shortcuts and tools described in this book will help you use
.there too, if, of course, you need it

For the curious: programming


for JVM
JVM is a program that knows how to execute a set of instructions
.called bytecode
Programming for the JVM" means that your Kotlin source code will be"
be compiled, or translated, into Java bytecode and executed under
.(JVM management (Figure 1.17
Kotlin source code
JVM Bytecode
Console output
The compiler translates
Kotlin code to bytecode
JVM execute bytecode

Figure: 1.17. Compilation and execution flow

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

Challenge: REPL arithmetic


.Most of the chapters in this book end with one or more chapters
Denmark. The assignments will allow you to independently deepen your
.understanding of the language Kotlin and gain experience
:Use the REPL to learn how arithmetic operations work in Kotlin
For example, enter (9 + 12) * 2 in the REPL. Does the output . % , / , * , - , +
?match your expectations
-If you want to know more, take a look at the math functions that offer
/provided by the Kotlin standard library at kotlinlang.org/api/latest/jvs/stdlib
kotlin.math / index.html and try them in the REPL. For example, min (94, -99) will
.print The smallest of the two numbers in parentheses
Variables, constants 2
and types
In this chapter, we will introduce you to variables, constants and basic
.data types in Kotlin - the fundamental units of any program
Variables and constants are used to store values or pass
data inside the application. Types describe the specific data stored
.variable or constant
There are important differences between data types and also between
variables
.and constants that define the order in which they are used

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

} (<fun main (args: Array <String


{

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

} (<fun main (args: Array <String


var experiencePoints: Int = 5
(println (experiencePoints
{

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

} (<fun main (args: Array <String


var experiencePoints: Int = 5
"var experiencePoints: Int = "thirty-two
(println (experiencePoints
{

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

Figure: 2.2. Type mismatch detected

To clear the error, replace the "thirty-two" value assigned to experiencePoints


variable with another value corresponding to type Int , for example an
.integer 5
(Listing 2.4. Fix type error (TypeIntro.kt

} (<fun main (args: Array <String


"var experiencePoints: Int = "thirty-two
var experiencePoints: Int = 5
(println (experiencePoints
{

.While the program is running, a variable can be assigned a different value


reading. If the player has earned additional points, for example, then the
variable experiencePoints is assigned a new value. Add 5 to the variable
.experiencePoints as shown below

(Listing 2.5. Adding 5 to experiencePoints (TypeIntro.kt

} (<fun main (args: Array <String


var experiencePoints: Int = 5
experiencePoints + = 5
(println (experiencePoints
{

After setting experiencePoints to 5, use the opera- add-assignment ( + = ) generator


.to increment the variable by 5
run the program again. As a result, the number 10 will appear in the
.console

Kotlin built-in types


You have already seen variables of type String and variables of type Int .
Kotlin also supports types for working with values like True / False, lists,
-pas
rami key-value. Table 2.1 lists the most commonly used types available in
.Kotlin

Table 2.1. Commonly used built-in types


A type Description Example
"String (line) Text information "Estragon" "happy meal
(Char (character
One character
'X'
Unicode character U + 0041
-Boolean (log
(Česky
True / false
Well no
true
false
Int
-integer)
(ny
Integer
Estragon" .length"
five
Double (floating
(decimal point
Fractional numbers
3.14
2.718
(A List (list
Collection of elements
3 ,4 ,2 ,1 ,3
" root beer", мclub soda "," coke"
Set (plural
(state
Collection of unique
values
"Larry", "Moe", "Curly"
,"Mercury", "Venus", "Earth", "Mars"
,"Jupiter", "Saturn", "Uranus"
"Neptune"
-Map (associa
(an array
-Collection of pairs "key
"value
,small" to 5.99, "medium" to 7.99"
large" to 10.99"
If you are not familiar with all of these types, do not worry - you will
Be with them as you read this book. In particular, about the lines of the
story
in Chapter 7, about numbers in Chapter 8, and about lists, sets, associative
.arrays, collectively called collections , are found in Chapters 10 and 11

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

} (<fun main (args: Array <String


"val playerName: String = "Estragon
var experiencePoints: Int = 5
experiencePoints + = 5
(println (experiencePoints
(println (playerName
{

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

Next, try changing the playerName value by trying to assign


.another string value, and run the program again

(Listing 2.7. Changing the value of playerName (TypeIntro.kt

} (<fun main (args: Array <String


"val playerName: String = "Estragon
"playerName = "Madrigal
var experiencePoints: Int = 5
experiencePoints + = 5
(println (experiencePoints
(println (playerName
{

:The console displays the following error message


Error: (3, 5) Kotlin: Val cannot be reassigned The compiler reported trying to change
.val . After the initial assignment value val can not be changed
.Remove the second assignment statement to correct this error

Listing 2.8. Bug fix with re-assignment of val


(TypeIntro.kt)

} (<fun main (args: Array <String


"val playerName: String = "Estragon
"playerName = "Madrigal
var experiencePoints: Int = 5
experiencePoints + = 5
(println (experiencePoints
(println (playerName
{

valsare useful for protecting against accidentally changing the values of


variables, which are read-only. For this reason, we recommend use val
. whenever var is not required
IntelliJ is able to determine when var can be preconfigured during code
.analysis
turn into val . If var does not change as the program progresses, IntelliJ will
,suggest convert it to val . We recommend following these guidelines
. unless, of course, you intend to write code to change var values
. To see what IntelliJ's proposal looks like, change playerName to var
(Listing 2.9. Replacing playerName with var (TypeIntro.kt

} (<fun main (args: Array <String


"val playerName: String = "Estragon
"var playerName: String = "Estragon
var experiencePoints: Int = 5
experiencePoints + = 5
(println (experiencePoints
(println (playerName
{

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

Fig 2.3. Variable doesn't change anywhere

As expected, IntelliJ suggests converting playerName to val . To


to confirm the change, click on the var keyword next to playerName
and press Option-Return ( Alt-Enter ). In the menu that appears, select Make variable
.(immutable (Figure 2.4
Figure: 2.4. Making a variable immutable

.(IntelliJ will automatically replace var with val (Figure 2.5


Figure: 2.5. Immutable playerName
,As stated, we recommend using val whenever possible
so that Kotlin can warn of accidental unintentional attempts to
take a different meaning. We also recommend paying attention to the
suggestions IntelliJ's suggestions for possible code improvements. You
.don't need to use them, but worth a look

Automatic type detection


Note that the types you have specified for experi- encePoints and playerName are
greyed out in IntelliJ. Elements highlighted grayed out are optional. Hover
over definition of type String , and IntelliJ will explain why these elements
.(are optional (Figure 2.6
Figure: 2.6. Redundant type information

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

keeps track of types. In this book, we use the automatic


type definitions if this does not create ambiguity. It helps to give
.the code looks neat and makes it easier to change in the future
-Note that IntelliJ will show you the type of any variable you
request, even if its type has not been explicitly declared. To find out the
type of re- name, click on its name and press Control-Shift-P . IntelliJ will show
her
.(type (fig. 2.8
Figure: 2.8. Displaying type information
Compile time constants
.Earlier we talked about how vars can change their values, but vals can not
We lied a little, but ... with good intentions. In fact, sometimes val
can return different values, and we will discuss these cases in Chapter 12. If
,there are values that you absolutely do not want to change in any way
. see compile time constants
A compile-time constant is declared outside of any function, even
not within the function main , because its value is assigned to at the time
compilation (at the moment when the program is compiled) - hence this
name. main and other functions are called at runtime (when the gram
started), and the variables inside the functions get their values in this period.
.The compile-time constant already exists at this point
Compile-time constants can only have values of one of the following
basic types - and using more complex types can make compilation is
.impossible. You will learn more about type structure in Chapter 13
The following are the supported base types for time constants
:compilation

String❍
Int ❍
Double ❍
Float ❍
Long ❍
Short ❍
Byte ❍
Char ❍
Boolean ❍

Add a compile time constant to TypeIntro.kt before the declaration


. function main using the const modifier

(Listing 2.10. Compile-time constant declaration (TypeIntro.kt

const val MAX_EXPERIENCE: Int = 5000


} (<fun main (args: Array <String
...
{

The const modifier preceding val warns the compiler that


care must be taken to ensure that this val is not changed anywhere. In this
case
the MAX_EXPERIENCE constant is guaranteed to have an integer value
and nothing can change it. This will help the compiler to apply 5000
.additional optimizations
Want to know why the name for const val is spelled like this: MAX_EXPERIENCE
?
This format is optional, however we prefer to allocate const val , using
using only uppercase letters and replacing spaces with underscores. How
are you might have noticed when specifying names for vals or vars , we are
using camel case, as well as the first lowercase letter. Similar agreements
for
.formatting help you write clear and easy-to-read code

Exploring Kotlin Bytecode


In Chapter 1, you learned that Kotlin is an alternative to Java, and in it
-programs are written for the JVM virtual machine that executes byte
Java code. It is often helpful to look at the Java bytecode that is generated
It is compiled by the Kotlin language compiler and run under the JVM.
Somewhere in this book we will look at bytecode to understand how
.specific language features in the JVM
Knowing how to parse the Java equivalent of Kotlin code will help you
understand how Kotlin works, especially if you have experience with Java.
If you have no experience with Java, you can still see familiar features
language with which you had to work, so refer to the bytecode
as pseudo code to make it easier to understand. Well, if you are a beginner
in programming - congratulations! Choosing Kotlin will allow you to
.express that the same logic as Java, but with much less code
For example, you might want to know how automatic type detection
variables in Kotlin affects the bytecode generated to run in the JVM. To
.to find out, use the Kotlin Bytecode Toolbox
In TypeIntro.kt press Shift twice to open the Search dialog Everywhere (search
everywhere). Start typing: "show Kotlin bytecode" Kotlin bytecode ) and select
Show Kotlin bytecode from the list of available actions , as soon as you see it
.((fig. 2.9

Figure: 2.9. Show Kotlin Bytecode

The Kotlin bytecode tool window will open (Figure 2.10). (You can also
(. open it by choosing the Tools → Kotlin → Show Kotlin of Bytecode

Figure: 2.10. Kotlin Bytecode Tool Window


If bytecode is not your native language, fear not! Translate the bytecode
back
in Java to see it in a more familiar form. In the bytecode window, click
.Decompile button at the top left
A new TypeIntro.decompiled.java tab will open with the Java version of the
bytecode, cg- not compiled by the Kotlin compiler for the JVM (Figure
.(2.11
In this case, the red underline is due to interaction quirks. between Kotlin)
(.and Java, rather than reporting an error
: Notice the declaration of the playerName and experiencePoints variables
;"String playerName = "Estragon
;int experiencePoints = 5

-Even though you omitted the type declaration when declaring both re
variables in Kotlin, the generated bytecode contains an explicit type
.declaration

Figure: 2.11. Compiled bytecode

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

:For the curious


Java simple types in Kotlin
There are two kinds of types in Java: reference and simple. Reference types
define- in the source code when some source code defines a type. Java also
offers simple types (often referred to simply as "primitives") that are not
.defined in the source code and are represented by keywords
A reference type in Java always starts with an uppercase letter to indicate
that somewhere there is source code that defines this type. This is what an
object looks like experiencePoints phenomenon with reference type in Java
:language
;Integer experiencePoints = 5

:Simple type names in Java start with a lowercase letter


;int experiencePoints = 5

All primitives in Java have a corresponding reference type (but not


all reference types have a corresponding simple type). Why oppose
?put one another
-Often, reference types are chosen simply because some are possible
,The Java language is only available using reference types. For instance
the generalization of types, which you will learn about in Chapter 17, does
not work with mitivas. Reference types also make it easier to use object-
oriented
tied Java features. (About object-oriented programming
(.development and its features in Kotlin will be discussed in Chapter 12
On the other hand, primitives have better performance and some
.other features
Unlike Java, Kotlin only provides one kind of types: reference
.types

var experiencePoints: Int = 5

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

Quest: Horn of the Unicorn


.Imagine the following game scene
The hero Tarragon has arrived at the Unicorn Horn tavern. The innkeeper
:asks
Do you need a stable?" “No,” says Tarragon, “I don’t have a horse. But I"
have fifty coins and I want to drink. " "Wonderful! Says the innkeeper. - I
have honey, wine and beer. What Do you want to?" And now the task itself:
add additional hasSteed below the variable the variables required for the
unicorn horn tavern scene used By examining types and assigning values to
variables as needed. Add variables for the name of the tavern, the name of
.the innkeeper and the number of coins from the player
Please note that the tavern has a drinks menu from which you can
make a choice. What type can help you display the menu? Hint on
.see table. 2.1

Quest: magic mirror


?After resting, Tarragon is ready to go in search of adventure. And you
-The hero has discovered a magic mirror that shows his playerName on
"turnover. Using String magic , turn the playerName "Estragon
.in "nogartsE" , mirroring the value of this variable
.To solve this problem, see the description of the String type at kotlinlang
org / api / latest / jvm / stdlib / kotlin / -string / index.html . There you will learn that,
fortunately, properties supported by a particular type usually have obvious
.(titles (meaning the Reversed team
Conditional constructions 3
In this chapter, you will learn how to determine the rules by which you
must follow code. This language feature is called a thread of execution and
allows
specify the conditions under which different parts of the program should be
.executed
. We'll cover: conditional statement and if / else statement , when statement
You will learn how to write true / false checks using the operators
comparison and logic. Along the way, we will look at one more Kotlin
- feature
.template strings
,To try it all out in practice, let's start by creating the NyetHack project
.which we will be working on throughout most of this book
Why NyetHack? We're glad you asked. You may remember the game
- NetHack, released in 1987 by The NetHack DevTeam. NetHack
is a single-player text game set in a fictional mann world using ASCII
graphics. We will create a similar text game (albeit without such awesome
ASCII graphics). JetBrains team, the creators of the Kotlin language, has its
own representative office in Russia; if in text a game like NetHack add
.Russian flavor to Kotlin, you get NyetHack

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
{

In NyetHack, the player's condition depends on the remaining health points


in the range zone from 0 to 100. During the game the player can get injured
in battles. With another hand, the player can be completely healthy. You
must define the rules displaying the player's current health status: if ( if ) the
player's health equals 100, it should render completely healthy, otherwise (
else ) you
must show how wounded he is. You can solve this problem using
.(operator if / else (if / otherwise
.Inside the main function, write the first if / else statement as shown below
.There is a lot going on in this code, and we'll break it down after it's done
.written
(Listing 3.1. Displaying the player's health status (Game.kt

} (<fun main (args: Array <String


"val name = "Madrigal
var healthPoints = 100
} (if (healthPoints == 100
("!println (name + "is in excellent condition
} else {
("!println (name + "is in awful condition
{
{

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
{

The statement in curly braces is the familiar println function that


,we used to output something to the console. It is indicated in brackets
what exactly needs to be displayed: the expression consists of the name value
and the string "I am in excellent condition!" ( «in excellent condition"). (Note
a space before the line - without it, it will output: "Madrigalis in excellent
condition! " .) Simply put, if the if / else statement detects that the player has
health points, the program will print that our hero's health is 100
.("personal condition ("the hero is in excellent condition
Our if statement contains just one statement in curly braces, but in general,)
you can include several instructions if you need to take more than one
action when the if condition is true.) Using the addition operator ( + ) to add
to the value of a variable string, we are doing string concatenation
operation . This is an easy way to change what is output to the console
.depending on the value of the variable
Later in this chapter, you will see another, more preferred way to insert
.values to strings
What if healtPoints has a value other than 100? In this case, the operator
if will determine that the condition is false, it will skip the expression in
curly braces- after the if and will go to the else statement . Interpret else as
:""otherwise
if the if condition is true, one will be executed , otherwise the other will be
executed. In from- Unlike if , the else statement does not need a condition. It
just happens if the if fails, so the keyword is immediately followed by curly
:parentheses
} else
("!println (name + "is in awful condition
{
The only difference in the printin function call is the line after the name
hero. Instead of announcing that the hero is in "excellent condition!" ( is in
"excellent condition! ), we must report that the hero is in "bad shape
- is in awful condition! ). (So far, you have only seen one function )
a function to output a string to the console. You will learn more about the
(.functions and how declare your functions, in Chapter 4
The code seems to tell the compiler: “If ( if ) the hero has exactly 100 health
points, vya, output to the console: "Madrigal is in excellent condition!" ... If the hero
does not 100 health points, then output to the console: "Madrigal is in awful
." "!condition
The structural equality operator == is one of several operators comparisons
.of Kotlin. Table 3.1 lists the comparison operators of the Kotlin language
You don't need to know all the operators now, as you will still be familiar
with
check with them later. Refer back to this table when deciding which
.the operator is better to express the condition

Table 3.1. Comparison Operators

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

} (<fun main (args: Array <String


"val name = "Madrigal
var healthPoints = 100
var healthPoints = 89
} (if (healthPoints == 100
("!println (name + "is in excellent condition
} else {
("!println (name + "is in awful condition
{
{

:Run the program again and you will see


!Madrigal is in awful condition

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

} (<fun main (args: Array <String


"val name = "Madrigal
var healthPoints = 89
} (if (healthPoints == 100
("!println (name + "is in excellent condition
} (else if (healthPoints> = 90 {
(".println (name + "has a few scratches
} (else if (healthPoints> = 75 {
(".println (name + "has some minor wounds
} (else if (healthPoints> = 15 {
(".println (name + "looks pretty hurt
} else {
("!println (name + "is in awful condition
{
{

:What the new logic looks like


If the hero has so many points
... health
:the following message is displayed ...
100
!Madrigal is in excellent condition
(!Madrigal is in excellent condition)
90-99
.Madrigal has a few scratches
(.Madrigal has a couple of scratches)
89–75
.Madrigal has some minor wounds
(.Madrigal is slightly injured)
74–15
.Madrigal looks pretty hurt
(.Madrigal looks badly wounded)
0-14
!Madrigal is in awful condition
(!Madrigal is in a terrible state)

Run the program again. Since Madrigal's healtPoints value is


-the conditions in the if and in the first else if statement are not true. One ,89
to the condition in else if (healtPoints> = 75) turns out to be true, so you
. you will see in the console: Madrigal has some minor wounds
Note that if / else conditions are checked from top to bottom
and ends as soon as the first true condition is found. If everyone
. the conditions will be false, the else branch will be executed
This means that the order of the conditions matters: if we placed
conditions if and else if in ascending order, none of the branches else if not You
are a would never be full. Any healthPoints value of 15 and above matched
with the first condition, and any value less than 15 would cause the
execution
else branches . (Don't change your code, the following snippet is just
(.for example
} (<fun main (args: Array <String
"val name = "Madrigal
var healthPoints = 89
if (healthPoints> = 15) {// will be executed for any value above 15
(".println (name + "looks pretty hurt
} (else if (healthPoints> = 75 {
(".println (name + "has some minor wounds
} (else if (healthPoints> = 90 {
(".println (name + "has a few scratches
} (else if (healthPoints == 100 {
("!println (name + "is in excellent condition
else {// will be executed if the value is 0-14 {
("!println (name + "is in awful condition
{
{

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

Nested if / else statements


In NyetHack, a player can be "blessed", which means that when
good health, light wounds will heal faster. The next step is to add
a variable to store the player's blessing attribute (do you think
.which data type is appropriate?) and modify the status output to reflect this
To do this, you can nest one more if / else statement in one of the existing ones
branches, for example, corresponding to a health level greater or equal to
go 75. (When you change the code, do not forget the additional } before and
(. after the last else if

(Listing 3.4. Blessing check (Game.kt


} (<fun main (args: Array <String
"val name = "Madrigal
var healthPoints = 89
val isBlessed = true
} (if (healthPoints == 100
("!println (name + "is in excellent condition
} (else if (healthPoints> = 90 {
(".println (name + "has a few scratches
} (else if (healthPoints> = 75 {
} (if (isBlessed
("!println (name + "has some minor wounds but is healing quite quickly
} else {
(".println (name + "has some minor wounds
{
} (else if (healthPoints> = 15 {
(".println (name + "looks pretty hurt
} else {
("!println (name + "is in awful condition
{
{

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

More elegant conditionals


Expressions need an eye and an eye, otherwise they will fill the entire space
of
circle like locusts. Fortunately, Kotlin allows you to take advantage of
conditional expressions and still keep them in check. Let's look at a couple
.of examples
Logical operators
NyetHack may need to check more complex conditions. For example
measures if the player has a blessing and his health is above 50 or if the
player
is immortal, then an aura glows around him. Otherwise, the aura is
.invisible
A set of if / else statements can be used to determine if the gamer has
whether the aura is visible or not, this will lead to a lot of repetitive
code and lack of clearly traceable logic of conditions. There are more
.simple and straightforward way: use logical operators in the condition
.Add a new variable and an if / else statement to print information to the console
.about the presence of an aura
(Listing 3.5. Using boolean operators in a condition (Game.kt

} (<fun main (args: Array <String


"val name = "Madrigal
var healthPoints = 89
val isBlessed = true
val isImmortal = false
Aura //
} (if (isBlessed && healthPoints> 50 || isImmortal
("println ("GREEN
} else {
("println ("NONE
{
} (if (healthPoints == 100
...
{
{

You added a val called isImmortal to store the trait without


death of the player (read-only, since the state of death invariably). This part
is familiar to you, but something new has appeared. Primarily, added a
. comment in the code starting with a pair of // characters
Everything to the right of // is a comment and is ignored by the compiler, so
in the comments you can write anything you want. Comments are very
helpful
to organize and add information about the code, making it more readable
useful for others (or for you in the future, as you might forget some
.(code details
-Further, two logical operators are used inside if . Logical opera
.Ratiors allow you to combine comparison operators into one long condition
logical "AND" operator ( "and" ). Requires both conditions to be on - &&
the left and to the right of it - were true, then the whole expression will be
- || .true
logical operator "OR" ( "or" ). The whole expression with this operator will
be
be considered true if at least one of the conditions (or both) is right or left
.from him is true

.Table 3.2 lists the logical operators of the Kotlin language

Table 3.2. Logical operators

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

Note : all operators have priority, which determines the order


evaluating them when combined into an expression. Operators with the
same
The priority is calculated from left to right. Operators can be grouped by
including in parentheses. The following operators are listed in descending
order
:priority - high to low

("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

Just think how many nested conditionals it took


would be to express this logic without boolean operators. With these
.operators can express very complex logic
The code that detects the presence of an aura turned out to be simpler than
.if we used a set of if / else statements , but it can be made even easier
,Logical operators can be used not only in conditional statements
but also in regular expressions and even in variable declarations. Add
,a new boolean variable that encapsulates the aura visibility condition
and refactor (that is, rewrite) the conditional statement, use
.using a new variable

Listing 3.6. Using Boolean Operators in Variable Declaration

(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

} (<fun main (args: Array <String


...
} (if (healthPoints == 100
} (val healthStatus = if (healthPoints == 100
("!println (name + "is in excellent condition
"!is in excellent condition"
} (else if (healthPoints> = 90 {
(".println (name + "has a few scratches
".has a few scratches"
} (else if (healthPoints> = 75 {
} (if (isBlessed
println (name + "has some minor wounds but is healing quite
(" !quickly
"!has some minor wounds but is healing quite quickly"
} else {
(".println (name + "has some minor wounds
".has some minor wounds"
{
} (else if (healthPoints> = 15 {
(".println (name + "looks pretty hurt
".looks pretty hurt"
} else {
("!println (name + "is in awful condition
"!is in awful condition"
{
Player state //
(println (name + "" + healthStatus
{

If you're tired of indenting your code after every change, IntelliJ)


-may I help. Choose Code → Auto-Indent Lines and enjoy clear indent
stupas.) The if / else statement sets the new healthStatus variable to
the string value "is in excellent condition!" etc., depending on the value
reading healthPoints . This is the beauty of the conventional expression. Since
now the state of the player is written to the healthStatus variable , you can
. remove six identical in meaning println instructions
If a variable needs to get a value depending on a condition, it is better
just use a conditional expression. Remember: Conditional Expressions
,look simpler if all branches return values of the same type (for example
.( lines for healthStatus
The code describing the aura can also be simplified by using a conditional
.excitement. So let's do it

Listing 3.8. Improving the aura code with a conditional expression


...
Aura //
val auraVisible = isBlessed && healthPoints> 50 || isImmortal
} (if (auraVisible
("println ("GREEN
} else {
("println ("NONE
{
"val auraColor = if (auraVisible) "GREEN" else "NONE
(println (auraColor
...

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

Removing parentheses in if / else statements

When a given condition requires a single value to be returned, it is


acceptable (by at least syntactically; see below for details) omit curly braces
around the expression. The curly braces {} can be omitted only if the branch
contains only one expression - removing the parentheses surrounding the
branch with no how many expressions will affect the order in which the
.code is executed
:Consider a version of healthStatus without parentheses
"!val healthStatus = if (healthPoints == 100) "is in excellent condition
".else if (healthPoints> = 90) "has a few scratches
(else if (healthPoints> = 75
"!if (isBlessed) "has some minor wounds but is healing quite quickly
".else "has some minor wounds
".else if (healthPoints> = 15) "looks pretty hurt
"!else "is in awful condition

-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

Without parentheses, the second println statement is no longer part of the if


. branch
Plenty of arrows" will be printed if arrowsInQuiver is greater than or equal"
but "Cannot hold any more arrows" will always be displayed regardless of ,5
.the number of arrows the hero has
-For expressions that fit on one line, you should follow the rule: “Ka
Which way of writing the expression will be easier and more
understandable for the reader? " Often
writing single-line expressions without curly braces makes the code easier
to
reading. For example, code describing an aura with a simple one-line
conditional expression without curly braces looks simpler. Here's another
:one example
val healthSummary = if (healthPoints! = 100) "Need healing!" else "Looking
good. "// Need healing = healing required, Looking good = all is well

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

} (<fun main (args: Array <String


...
} (val healthStatus = if (healthPoints == 100
"!is in excellent condition"
} (else if (healthPoints> = 90 {
} (else if (healthPoints in 90..99 {
".has a few scratches"
} (else if (healthPoints> = 75 {
} (else if (healthPoints in 75..89 {
} (if (isBlessed
"!has some minor wounds but is healing quite quickly"
} else {
".has some minor wounds"
{
} (else if (healthPoints> = 15 {
} (else if (healthPoints in 15..74 {
".looks pretty hurt"
} else {
"!is in awful condition"
{
{

,Bonus : using spacing in conditionals as shown above


solves the problem with the else if execution order that we saw earlier
,in this chapter. At intervals, your branches can be in any order
.and the code will still work the same
In addition to the .. operator, there are several other functions for creating
.intervals
The downTo function creates a descending interval. The until function creates
an inter- a shaft that does not include the upper limit of the selected range.
You will see the application of these functions in the "Assignments" at the
end of the chapter, and more information you will learn about intervals in
.Chapter 10

When conditional expression


The when conditional is another way to control the flow of execution
in Kotlin. Like if / else , the when clause allows you to write conditions and
execute the code that matches the true condition. when provides a short
syntax
.sis and is especially suitable for conditions with three or more branches
Suppose that in NyetHack a player can be a representative of one of the
,thought races (race), for example, an orc or gnome, and these races, in turn
represent different factions. The when statement takes the selected race
:and returns the corresponding fraction
"val race = "gnome
} (val faction = when (race
"dwarf" -> "Keepers of the Mines"
"gnome" -> "Keepers of the Mines"
"orc" -> "Free People of the Rolling Hills"
"human" -> "Free People of the Rolling Hills"
{

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

} (<fun main (args: Array <String


...
} (val healthStatus = if (healthPoints == 100
"!is in excellent condition"
} (else if (healthPoints in 90..99 {
".has a few scratches"
} (else if (healthPoints in 75..89 {
} (if (isBlessed
"!has some minor wounds but is healing quite quickly"
} else {
".has some minor wounds"
{
} (else if (healthPoints in 15..74 {
".looks pretty hurt"
} else {
"!is in awful condition"
{
} (val healthStatus = 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
{
{

, The when conditional works the same as the if / else conditional


defining conditions and fulfilling branches where these conditions are true.
when is different by automatically selecting the condition on the left that
matches the value argument in scope . We will talk in more detail about the
-area of view
See Chapter 4 and Chapter 12. Consider briefly the conditional expression
. in 90..99
You've already seen how to use the keyword in to check belong
the value is within the interval, and the same happens here: we check the
value
-healthPoints , although we don't explicitly mention it. In the scope of inter
shaft to the left of -> is the healthPoints variable , so the compiler
will evaluate the when clause as if healthPoints were included in each condition
.branching
When often conveys the logic of your code better. In this case, to get the
same
result using the if / else statement , you need to add three else branches
.if . The when clause does this in an easier way
/ The when conditional provides more flexibility than the if
else , when matched against the specified conditions. Most conditions on the
left
are calculated as true or false, others are limited to simple comparison
as in the example with condition 100. The when clause can express
.any of them, as shown in the example above
By the way, is it possible to replace the nested if / else statement in one of the
branches of the the when expression ? This design is not common, but
flexibility
.when lets you implement it
Run NyetHack to verify that the refactoring results in
healthStatus using the when clause program execution logic
.has not changed

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

(Listing 3.11. Using Template String (Game.kt

} (<fun main (args: Array <String


...
Player state //
(println (name + "" + healthStatus
("println ("$ name $ healthStatus
{

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"
{

The extra curly braces don't do anything new, so it's better to


get rid of them. In either case, the status assessment will be included in the
.string
Please double-check your fix before running the program. How are you
do you think what will be the result? Run the program to confirm your
.assumptions
The main job of programs is to respond to states or
actions. In this chapter, you learned how to add code execution rules
using conditional statements if / else and when . They saw how to express
sequences of numbers using intervals. Finally looked at an example
.interpolation of variables and values into strings
Remember to keep NyetHack, because we will be using again
its in the next chapter, where we will get acquainted with functions - in a
convenient way
.grouping and reusing expressions in a program

Challenge: trying intervals


Spacing is a powerful tool, and with a little practice you will find
their syntax is simple and straightforward. To complete this simple task
REPL open Kotlin ( Tools → Kotlin → REPL ) and examine syntax interval
fishing, including functions toList () , downTo and until . Enter the following
intervals in sequence. Before hitting Command-Retu r ( Ctrl-Return ) to see
.the result, try to imagine it

(Listing 3.13. Learning Intervals (REPL

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'

Task: Displaying Extended


Information about aura
Before starting this or the next task, close NyetHack and create a copy of it.
You will make changes to your program that are not needed in future
versions. Name the copy NyetHack_ConditionalIsChallenges or as- something
different. We advise you to create a copy of the program before each task in
.all the following chapters
At the moment, if the aura is displayed, then it will always be green. In that
.In the task, we will make the aura color reflect the player's current karma
Karma is a numerical value from 0 to 20. To determine the player's karma
:we use the following formula

* (val karma = (Math.pow (Math.random (), (110 - healthPoints) / 100.0


() toInt. (20

:The color of the aura is determined according to the following rules

The meaning of karma


Aura color
0-5
(red (red
6-10
(orange (orange
11-15
(purple (magenta
20–16
(green (green

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

Assignment: Custom Format status bars


At the moment, the player's state is displayed by two println functions . But
.there is no variable storing complete information as one line
:The code looks like this

Player state //
+ "(println ("(Aura: $ auraColor
("({" Blessed: $ {if (isBlessed)" YES "else" NO)"
("println ("$ name $ healthStatus

:And creates the following output


(Aura: GREEN) (Blessed: YES)
!Madrigal has some minor wounds but is healing quite quickly

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

:should output the state of the player as


!HP: 100) (Aura: Green) -> Madrigal is in excellent condition)
Functions 4
A function is a piece of code that performs a specific task and can be reused.
Functions are a very important part of the gramming. Moreover, the
program is, in fact, a sequence functions that are interconnected to perform
.a more complex task
Above, you have already used the println function from the Kotlin standard
library to output data to the console. However, you can declare your own
.functions
tions in your code. Some functions need to receive the data needed
to solve the problem. Some return values and create weekends
.data to use elsewhere after the function has finished running
Let's start by using the functions to organize an existing
NyetHack code. To do this, we define our function and add a new
possibility
.NyetHack: a spell that spawns a glass of intoxicating drink

Highlighting code in a function


The logic you wrote for NyetHack in Chapter 3 was reasonable, but better
it would be to organize the code into a function. Your first assignment:
reorganize project and encapsulate most of the logic in a function. This will
.prepare the basis for adding new features to NyetHack
Does this mean that you have to delete all the code and rewrite the entire
?program
.God forbid. IntelliJ helps you group your logic into functions
Let's start by opening the NyetHack project. Make sure the file Game.kt The
relative covered in the editor. Next, highlight the conditional expression that
writes player status message in healthStatus . Press the left mouse button
and while holding it, drag the mouse pointer from the first line with the
declaration healthStatus , to the end of the expression, including the closing
.curly brace
:More or less like this
...
} (val healthStatus = 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
{
...

-Right-click ( Control-click ) the selection. You


.(select the Refractor → Extract → Function… item in the context menu (Fig. 4.1
Figure: 4.1. Highlighting code into a function
.The Select Function dialog box appears , as shown in Fig. 4.2
Figure: 4.2. Feature highlighting dialog
We will consider the elements of this window below. For now, enter
-“formatHealth
Status ”in the Name field as shown above, and leave the rest as is. After
then click OK . IntelliJ will add the function definition to the end of the
, Game.kt file
:as below
:(private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean
} String
} (val healthStatus = 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
{
return healthStatus
{

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

Figure: 4.3. The function consists of a header and a body

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

Figure: 4.4. Anatomy of a function header

.Let's take a closer look at these parts

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

Figure: 4.5. Visibility modifier

By default, the function gets global visibility (public) - this means


assumes that all other functions (even functions declared in other files
project) can use this feature. In other words, if you do not indicate
.If a modifier is inserted, the "public" modifier will be considered
In this case, IntelliJ chose the "private" modifier because the function
-formatHealthStatus is only used in Game.kt file . More about modifi
accessors and how to use them to control visibility functions, you will learn
.in chapter 12

Function name declaration

The visibility modifier (if present) is followed by the keyword


.(fun followed by the name of the function (Figure 4.6

Figure: 4.6. Fun keyword and name declaration

You have specified formatHealthStatus as the function name in the dialog


. highlighting the function, so IntelliJ added that name after fun
Please note that the name chosen for the function formatHealthStatus , HA
begins with a lowercase letter and uses camel case without an underscore
nods. Try to name all your functions in such an official way recognized
.style

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
{

For each parameter, its type is also determined. healthPoints should be


.an integer and isBlessed a boolean value
Please note that function parameters are always available only for
readings, that is, they cannot change their values in the function body.
Others
. in other words, in the body of the function, the parameters are val , not var

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

} private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean): String


} (val healthStatus = when (healthPoints
...
{
return healthStatus
{

The healthStatus variable is a local variable as it exists


-only in the body of the formatHealthStatus function . You can also say that ne
the healthStatus variable only exists in the function scope formatHealthStatus .
.Think of the scope as a continuous life is variable
Since it only exists in the scope of the function, the variable healthStatus will
. cease to exist after the function finishes formatHealthStatus
The same is true for function parameters: the healthPoints and isBlessed variables
exist only within the scope of the function and disappear after
.completing the function of its main task
In Chapter 2, you saw an example of a variable that was not local to a
.function
. tion or class, is a file-level variable
const val MAX_EXPERIENCE: Int = 5000
} (<fun main (args: Array <String
...
{

,A file-level variable is accessible from anywhere in the project (however


- phenomenon, you can add a visibility modifier and change the scope
variable). File level variables exist until exits execution of the entire
.program
Due to the difference between local variables and file level variables
the compiler makes different demands on when they should be assigned
. initial value, or, in other words, when they should be initialized to get out
Values for file-level variables should be assigned immediately upon
declaration
otherwise the code will not compile. (There are exceptions and they are
described in chapter 15.) This requirement will protect you from
.unexpected and unwanted behavior
.for example, when trying to use a variable before initializing it
Since a local variable has a more limited scope, - inside the function in
which it is declared - the compiler is more forgiving to where it should be
initialized, so long as it initializes before using it. This means that the
:following definition is correct
} (<fun main (args: Array <String
val name: String
"name = "Madrigal
var healthPoints: Int
healthPoints = 89
healthPoints + = 3
...
{

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
...
{

This is a function call line that activates a function to execute


of actions given in her body. To call a function, you need to specify its
name
.and data corresponding to the parameters as defined in the header
:Compare the header of the formatHealthStatus function with its call
formatHealthStatus (healthPoints: Int, isBlessed: Boolean): String // Title
formatHealthStatus (healthPoints, isBlessed) // Call

The formatHealthStatus declaration shows that the function requires two


passwords dimensions. By calling formatHealthStatus you must list the input
for these parameters by enclosing them in parentheses. The input is called
. arguments , and passing them to a function is called argument passing
-Terminology reference: a parameter is what the function requires, and ar)
a goment is what is passed when a function is called to do this
(.demand. You will see that these terms are used interchangeably
Here, as stated in the function declaration, you are passing the healthPoints
value
. which should be a value of type Int ) and a boolean value isBlessed)
:Launch NyetHack, and voila! You will see the same output as before
(Aura: GREEN) (Blessed: YES)
!Madrigal has some minor wounds, but is healing quite quickly

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
...

. Extract them into a function named printPlayerStatus


:Game.kt file now looks like this
} (<fun main (args: Array <String
"val name = "Madrigal
var healthPoints = 89
var isBlessed = true
val isImmortal = false
Aura //
(val auraColor = auraColor (isBlessed, healthPoints, isImmortal
(val healthStatus = formatHealthStatus (healthPoints, isBlessed
Player state //
(printPlayerStatus (auraColor, isBlessed, name, healthStatus
{
} private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean): String
} (val healthStatus = 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
{
return healthStatus
{
,private fun printPlayerStatus (auraColor: String
,isBlessed: Boolean
,name: String
} (healthStatus: String
+ "(println ("(Aura: $ auraColor
("({" Blessed: $ {if (isBlessed)" YES "else" NO)"
("println ("$ name $ healthStatus
{
,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
{

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

We write our functions


Now that we have organized the NyetHack logic into functions, we can start
to the implementation of a spell that generates a glass of intoxicating drink.
In con- tse Game.kt declare a new function named castFureball with no
.parameters
Add the private visibility modifier. castFireball should not be able to rotated
.values, but should deduce the effect of the spell
(Listing 4.1. Adding the castFireball function (Game.kt
...
,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 castFireball
(".println ("A glass of Fireball springs into existence
{

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

} (<fun main (args: Array <String


...
Player state //
(printPlayerStatus (auraColor, isBlessed, name, healthStatus
() castFireball
{
...

:Start NyetHack and admire the new output


(Aura: GREEN) (Blessed: YES)
!Madrigal has some minor wounds, but is healing quite quickly
.A glass of Fireball springs into existence

-Excellent! The spell appears to be working as intended. You can from


celebrate with a glass of magic. (But you better wait until the end of this
chapter.) One glass of drink is good, but more than two is already a party.
.The player must wives be able to conjure more than one glass at a time
Update the castFireball function to accept a named Int parameter
.numFireballs . When you call castFireball, pass argument 5 to it
.end, note the number of glasses in the output

(Listing 4.3. Adding numFireballs parameter (Game.kt

} (<fun main (args: Array <String


...
Player state //
(printPlayerStatus (auraColor, isBlessed, name, healthStatus
() castFireball
(castFireball (5
{
...
} () private fun castFireball
} (private fun castFireball (numFireballs: Int
(".println ("A glass of Fireball springs into existence
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{

:Run NyetHack again. You will see the following message


(Aura: GREEN) (Blessed: YES)
!Madrigal has some minor wounds, but is healing quite quickly
(A glass of Fireball springs into existence. (x5

Functions with parameters allow you to pass arguments with input


data. You can use them in function logic or just output them as part of the
.template string, as was done with the value 5

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

} (<fun main (args: Array <String


...
Player state //
(printPlayerStatus (auraColor, isBlessed, name, healthStatus
(castFireball (5
{
...
} (private fun castFireball (numFireballs: Int
} (private fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{

Now, by default, numFireballs will get a value of 2 if you call


castFireball with no argument. Update the main function by removing the
argument from the call
. castFireball
(Listing 4.5. Call castFireball with default argument (Game.kt

} (<fun main (args: Array <String


...
Player state //
(printPlayerStatus (auraColor, isBlessed, name, healthStatus
(castFireball (5
() castFireball
{
...

Start NyetHack again. With the castFireball call with no argument, you will see
:following message

(Aura: GREEN) (Blessed: YES)


!Madrigal has some minor wounds, but is healing quite quickly
(A glass of Fireball springs into existence. (x2

Since you did not pass an argument for the numFireballs parameter , it will get
.default value 2

Functions with a single expression


The Kotlin language allows you to shorten the declarations of functions
such as castFireball or formatHealthStatus , which consist of a single expression,
then
there is only one operator. For functions with a single expression
Niemi can not specify a return type, braces and the return statement . Change
.castFireball and formatHealthStatus as shown below
Listing 4.6. Alternative syntax for defining functions with a single
(expression (Game.kt
...
} private fun formatHealthStatus (healthPoints: Int, isBlessed: Boolean): String
} (val healthStatus = when (healthPoints
= (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
{
return healthStatus
{
...
} (private fun castFireball (numFireballs: Int = 2
= (private fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{

Note that to define a function body with a single


expression, you can use the syntax with the assignment operator ( = ), for
.followed by an expression
An alternative syntax will allow you to shorten function declarations with a
single the actual expression that must be evaluated in order to execute
work. If a function needs to evaluate multiple expressions, just use
.use the old syntax described above
From now on, we will apply the syntax for functions with a single
.expression wherever you can make your code more concise

Functions with Unit Return Type


Not all functions return a value. Some do other work
,for example, change the state of a variable or call other functions
which provide data output. For example, the code that displays the state
and the player's aura, or the castFireball function . They are declared with no
. returnable type or value and use println in their work
= (private fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs

.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

:Another way to call this same function


,"printPlayerStatus (auraColor = "NONE
,isBlessed = true
,"name = "Madrigal
(healthStatus = status

This optional syntax uses named function arguments and is an alternative to


.simply passing arguments. This method has a number of advantages
For example, named arguments can be passed to a function in any row. That
:is, you can call printPlayerStatus like this
,printPlayerStatus (healthStatus = status
,"auraColor = "NONE
,"name = "Madrigal
(isBlessed = true

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

For the curious: the Nothing type


In this chapter, you got acquainted with the Unit type and learned that
.functions with such do not return anything by type
There is another type, similar to Unit . It is of type Nothing . Like Unit , type
Nothing indicates that the function does not return anything, but this is their
similarity
ends. Nothing tells the compiler that the function is guaranteed not to
succeeds; the function will either throw an exception, or by some
.otherwise, it will never return the call
Why is the Nothing type needed ? One example of using this type might be
serve the function TODO (to be done), which is included in the standard
library
.flow of the Kotlin language
Get to know TODO by pressing Shift twice to bring up a dialog box
.Search Everywhere , and enter the function name
** /
Always raises [NotImplementedError], signaling that the operation is not *
.implemented
/*
() public inline fun TODO (): Nothing = throw NotImplementedError

TODO raises an exception - in other words, the function is guaranteed to


. terminate is an error — it returns Nothing
When should you use TODO ? The answer lies in its name: it tells you
what "to do". For example, here is the following function, which is still
: to be implemented, but for now it just calls TODO
} fun shouldReturnAString (): String
("TODO ("implement the string building functionality here to return a string
{

The developer knows that the shouldReturnAString function should return a


.string ( String ), but there are still no other features required to implement it
Note that the return value for shouldReturnAString is String , but the function
doesn't actually return anything. Because of the returned TODO values are
.completely normal
The return type Nothing of TODO tells the compiler that the function is
guaranteed will always cause an error, so check the return value after
TODO doesn't make sense since shouldReturnAString will return nothing.
Compile- the torus is happy, and the developer can continue development,
. postponing until later implementation of the shouldReturnAString function
Another useful design feature of Nothing is the ability to
You should add code after calling TODO . In this case, the compiler will
print
.(the caveat that this code is unreachable (Figure 4.10
Figure: 4.10. Unreachable code
With the return type Nothing, the compiler can do the following conclusion: it
knows that the TODO will fail, so all the code after the TODO
.will not be executed

For the curious: level functions


file in Java
.All functions that you wrote up to this point were declared at the file level
la Game.kt . If you are a Java developer, this may surprise you. In Java
functions
and variables can only be declared inside classes. This rule is not
.belongs to Kotlin
Is it possible for Kotlin language code to compile to Java bytecode
?to run on JVM? Shouldn't Kotlin follow the same rules
If you look at the compiled Java bytecode for Game.kt , then all
:it will become clear
} public final class GameKt
} (...) public static final void main
...
{
} (...) private static final String formatHealthStatus
...
{
} (...) private static final void printPlayerStatus
...
{
} (...) private static final String auraColor
...
{
} (...) private static final void castFireball
...
{
FF: synthetic method $ //
FF: bridge method $ //
} (...) static void castFireball $ default
...
{
{

File-level functions are represented in Java as static class methods


with the same name as the file in Kotlin. ( Methods in Java are the same
same as functions.) In this case, the functions and variables declared
. in Game.kt are declared in Java in a class named GameKt
You will learn how to declare functions in classes in Chapter 12, but the
ability to declare expose functions and variables outside of classes gives
freedom of choice when creating functions that are not bound to the class.
(Note how is defined castFireball $ default method in Game.kt - this is how
(.arguments are defined by default. More on this in Chapter 20
For the curious: function overloading
The castFireball function you declared with its default argument
:The numFireballs parameter can be called in two ways
() castFireball
(castFireball (numFireballs

If a function has multiple implementations, such as castFireball , then its


. called congested
.Overloading is not always a consequence of the default argument
It is possible to implement multiple functions with the same name. To
to see how this works, open the REPL Kotlin ( the Tools → Kotlin → Kotlin the
.REPL ) and enter the following code

(Listing 4.7. Overloaded function declaration (REPL

} () 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
{
{

You have declared three implementations of the performCombat function . All


of them are Unit functions with no return value. The first one is not accepted
no arguments. The second takes only one argument - the name of the
.opponent
The latter takes two arguments: the name of the adversary and a boolean
variable, with a sign of the player's blessing. Each function generates a
separate
. message (or messages) via a println call
When you call performCombat , how does the REPL know which one
you need? It will check the passed arguments and find the function that
matches them. Call the REPL implementation of each performCombat
.function , as shown below
(Listing 4.8. Overloaded function call (REPL

() performCombat
("performCombat ("Ulrich
(performCombat ("Hildr", true

:The output will be like this


!You see nothing to fight
.You begin fighting Ulrich
!You begin fighting Hildr. You are blessed with 2X damage
Please note that the choice of this or that implementation is carried out on
.based on the number of arguments

For the curious: function names


in back quotes
.There is a feature in Kotlin that looks a bit strange at first glance
but. It allows you to declare or call a function with a name that contains a
pro
white and other non-standard characters. It's enough to enclose the name
:backticks ` . For example, you can declare a function like this
} () `** ~ !fun `** ~ prolly not a good idea
...
{

: `** ~ !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
...
{

In Kotlin language, is is a reserved keyword. (In standardthe Kotlin library


uses the is operator to check the type of an instance, which will be covered
in Chapter 14.) In Java, however, is can be a method name, because there is
no such key word in this language. Enclosing the function name in back
:quotes, you can call a Java method from Kotlin, for example
} () fun doStuff
ʻIs` () // Calling the ʻis` Java method from Kotlin
{

.In this case, the backquote feature helps maintain consistency


compatibility with a Java method that would otherwise be unavailable due
to
.your name
The second reason is to support more expressive function names, which
:can be used, for example, in tests. For example, a name like this
} () `fun ʻusers should be signed out when they click logout
Run the test //
{
:looks clearer than
} () fun usersShouldBeSignedOutWhenTheyClickLogout
Run the test //
{

Backticks allow you to use more expressive names for


-test functions than the standard naming rules - "camel re
." lowercase at the beginning

Assignment: functions with a single


expression
Earlier we looked at the syntax of a single-expression function as
, a way to write functions more compactly. Can you rewrite auraColor
?using function syntax with a single expression

Quest: intoxicating fireball effect


The spell that creates a goblet of intoxicating drink not only expels
message to the console. NyetHack is gentle and intoxicating effect on the
caster. Make the castFireball function return a value the result of intoxication,
.depending on the number of glasses conjured
,The intoxication level should vary between values from 1 to 50
.where 50 is the maximum value available in the game

Quest: Stupefying State


As the last task, display the state of the player depending on
from the stupefaction level returned by the castFireball according to the
following
:rules

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

() val numLetters = "Mississippi" .count


(print (numLetters
Prints 11 //

. 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 numLetters = "Mississippi" .count ({letter


'letter == 's
({
(print (numLetters
Prints 4 //

In this example, the count function uses an anonymous function to decide


how to count characters in a string. She sequentially transfers an
anonymous function character by character, and if it returns a true value for
some
symbol, the total score is increased by one. Once checked the last character,
.the count function returns the total
Anonymous functions allow the standard library to do its job tu - to provide
a foundation of functions and types to build superior running application in
the Kotlin language - without adding features, too com specific to be called
"standard". They have other applications that will be discussed later in this
.chapter
To understand how count works, let's examine the syntax of the anonymous
.function
the Kotlin language in more detail by declaring your function. We will write
a little Play SimVillage in which you play the role of the mayor of a virtual
.villages
The first anonymous function in Sim Village will print a greeting for
player, recognizing him as the mayor of the village. (Why do this with
anonymous functions? As you read this chapter, you will see that it will
(.allow you to it's easy to pass an anonymous function to other functions
Open your Sandbox project, create a new file named SimVillage.kt and declare
.( the main function as you did earlier (type main and press Tab
Declare an anonymous function inside main function , call it and output
.result
(Listing 5.1. Anonymous hello function declaration (SimVillage.kt

} (<fun main (args: Array <String


}) println
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
(() {
{

Just as you wrote a string, inserting characters between quotes, write


.function by inserting expressions or operators between curly braces
We start by calling the println function . Within parentheses enclosing an
argument println , you add a pair of curly braces in which you define the
body
anonymous function. An anonymous function declares a variable and
returns
:saves a line with a greeting

}
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"
() {

:Run the main function SimVillage.kt . The following output appears


(Welcome to SimVillage, Mayor! (copyright 2020

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

(Listing 5.2. Assigning a variable to an anonymous function (SimVillage.kt

} (<fun main (args: Array <String


}) println
} = val greetingFunction: () -> String
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
{
() ({
(() println (greetingFunction
{

You have declared a variable followed by a colon and type. Volume


the greetingFunction: () -> String event , just like the : Int declaration , says
to the compiler what type of value the variable can store. This is a function
a rational type : () -> String , which tells the compiler what type functions can
.be contained in a variable
:(A functional type declaration consists of two parts (Figure 5.1
.( <- ) function in parentheses and return type following the arrow
Figure: 5.1. Functional type syntax
The type declaration of the greetingFunction variable : () -> String shows that
the compiler can assign greetingFunction to any function that is not takes
arguments (empty brackets) and returns a String . Further, as in the case
With any other variable declarations, the compiler will make sure that a
function assigned to a variable or passed as an argument, belongs to the
.desired type
.Run main . The conclusion shouldn't change
(Welcome to SimVillage, Mayor! (copyright 2020

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

} (<fun main (args: Array <String


} = val greetingFunction: () -> String
<- val greetingFunction: (String) -> String = {playerName
val currentYear = 2018
"(Welcome to SimVillage, Mayor! (Copyright $ currentYear"
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
(() println (greetingFunction
(("println (greetingFunction ("Guyal
{

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

<- val greetingFunction: (String) -> String = {playerName


Start SimVillage.kt again. You will see that the argument passed by anonymous
:function has been added to the line
(Welcome to SimVillage, Guyal! (copyright 2020

Remember the count function ? It takes an anonymous function as an


argument
of type (Char) -> Boolean named predicate . The predicate function takes ap- a Char
and returns a boolean value. You will see that the anonymous functions are
used to implement many of the features in the standard library Kotlin, so try
.to get used to their syntax
It keyword
In an anonymous function that takes exactly one argument, instead of
defining
When changing the parameter name, you can use a convenient alternative -
the key the word it . In anonymous functions with one parameter, you can
use
. and named parameter, and its replacement - the keyword IT
Remove the parameter name and arrow at the beginning of the anonymous
. function and use keyword IT
(Listing 5.4. Using the it keyword (SimVillage.kt

} (<fun main (args: Array <String


<- val greetingFunction: (String) -> String = {playerName
} = val greetingFunction: (String) -> String
val currentYear = 2018
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
"(Welcome to SimVillage, $ it! (Copyright $ currentYear"
{
(("println (greetingFunction ("Guyal
{

.Run SimVillage.kt to make sure everything works as before


The it keyword is nice because it doesn't require a variable name, but note
note that it does not clearly describe the data presented. we
recommended when working with more complex anonymous functions, or
with nested anonymous functions (anonymous functions inside
anonymous functions) use named parameters to save not only your mind,
but the mind of those who will read this. On the other hand us, it is very
good for short expressions like in a function call count , whose logic is clear
:even without the argument name

({'Mississippi" .count ({it == 's"

Taking multiple arguments


The it keyword can be used in an anonymous function that takes
one argument, but not allowed if the number of arguments is more than one.
.However, ano- Partial functions can take multiple named arguments
Now is the time to change the SimVillage program to output something
else besides the greeting. The mayor needs to know if his village is growing
or not. Of- change the anonymous function to accept a numBuildings argument
.with the number of houses built, not just the player's name

(Listing 5.5. Getting second argument (SimVillage.kt

} (<fun main (args: Array <String


} = val greetingFunction: (String) -> String
<- val greetingFunction: (String, Int) -> String = {playerName, numBuildings
val currentYear = 2018
("println ("Adding $ numBuildings houses
"(Welcome to SimVillage, $ it! (Copyright $ currentYear"
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
(("println (greetingFunction ("Guyal
((println (greetingFunction ("Guyal", 2
{

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"
{

Automatic type detection works even when the anonymous function


takes one or more arguments, but to help the compiler determine
divide the type of a variable, you must specify the name and type of each
.parameter in the declaration the phenomenon of anonymous function
-Modify the declaration of the greetingFunction variable to use auto
tomatical typing by specifying the type of each parameter in anonymous
.functions

Listing 5.6. Using auto-typing in greetingFunction

(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
{

.Start SimVillage.kt and make sure everything works as before


Combined with implicit return, automatic function type detection
can make anonymous function difficult to understand. But if anonymous
the function is simple and clear, automatic type detection helps to make
.the code is more concise

A function declaration that takes the


function
You've already seen: Anonymous functions can change the behavior of
functions from stan- dart library. They can also be used to customize
behavior
.your own functions
, By the way, from now on we will call anonymous functions lambdas
and their copies - lambda - expressions . Results returned by anonymous
functions, we will call the result of a lambda . It is common
terminology. (A trivial question: why "lambda"? This term, also
denoted by the Greek letter λ , is the abbreviated form of the name
Lambda calculus" - a system of logic for expressing computations,"
developed
Tanned in 1930 by the mathematician Alonzo Church. By defining
(.anonymous functions, you are using lambda calculus notation
A function parameter can accept arguments of any type, even arguments
you who represent the functions themselves. Functional type parameter
declared like a parameter of any other type - in parentheses after
function name - indicating the corresponding type. To see how it is
works, add a new function to SimVillage that will select the occasion
.the number of buildings built and call the lambda to display the greeting
Add a function called runSimulation that accepts variables
playerName and greetFunction . Also use a couple of functions from the standard
. library to get a random number. Finally, call the function runSimulation

(Listing 5.7. Adding the runSimulation function (SimVillage.kt

} (<fun main (args: Array <String


<- val greetingFunction = {playerName: String, numBuildings: Int
val currentYear = 2018
("println ("Adding $ numBuildings houses
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
((println (greetingFunction ("Guyal", 2
(runSimulation ("Guyal", greetingFunction
{
} (fun runSimulation (playerName: String, greetingFunction: (String, Int) -> String
val numBuildings = (1..3) .shuffled (). last () // Randomly pick 1, 2, or 3
((println (greetingFunction (playerName, numBuildings
{

,runSimulatin takes two parameters, playerName and greetFunction , a function


which takes String and Int and returns String . runSimulation generates
a random number and calls the function passed to greetFunction with a random
a number and the value of playerName (which runSimulation takes as an
.(argument
Start SimVillage several times. You will see that the number of built
buildings are different each time, because runSimulation generates
.new random number for the greeting function
Abbreviated syntax
When a function takes another function in the last parameter, round
the parentheses around the lambda argument can be dropped. That is, an
:example, consider previously discovered
({'Mississippi" .count ({it == 's"
:can also be written without parentheses
{'Mississippi" .count {it == 's"

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
{

Nothing has changed in the implementation of the runSimulation function ; all


changes touched only her challenge. Please note: since now you do not
-assign
you pass the lambda to a variable, and directly pass it to runSimulation ,
specify
.the parameter types in the lambda are no longer required
The shorter syntax allows for cleaner code, and we will use
.change it wherever possible throughout this book

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

(Listing 5.9. Using the inline keyword (SimVillage.kt


...
,inline fun runSimulation (playerName: String
} (greetingFunction: (String, Int) -> String
val numBuildings = (1..3) .shuffled (). last () // Randomly pick 1, 2, or 3
((println (greetingFunction (playerName, numBuildings
{

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

(Listing 5.10. Function declaration printConstructionCost (SimVillage.kt


...
,inline fun runSimulation (playerName: String
} (greetingFunction: (String, Int) -> String
val numBuildings = (1..3) .shuffled (). last () // Randomly pick 1, 2, or 3
((println (greetingFunction (playerName, numBuildings
{
} (fun printConstructionCost (numBuildings: Int
val cost = 500
("{println ("construction cost: $ {cost * numBuildings
{

Now add in runSimulation option costPrinter to the type of function and used
.use it inside runSimulation to display the construction cost of buildings

(Listing 5.11. Adding a costPrinter parameter (SimVillage.kt


...
,inline fun runSimulation (playerName: String
} (greetingFunction: (String, Int) -> String
,inline fun runSimulation (playerName: String
,costPrinter: (Int) -> Unit
} (greetingFunction: (String, Int) -> String
val numBuildings = (1..3) .shuffled (). last () // Randomly pick 1, 2, or 3
(costPrinter (numBuildings
((println (greetingFunction (playerName, numBuildings
{
} (fun printConstructionCost (numBuildings: Int
val cost = 500
("{println ("construction cost: $ {cost * numBuildings
{
To get a reference to a function, use the :: operator with the name of this
functions. Get a reference to the printConstructionCost function and pass it
. as an argument for the new costPrinter parameter in runSimulation

(Listing 5.12. Passing a reference to a function (SimVillage.kt

} (<fun main (args: Array <String


<- runSimulation ("Guyal") {playerName, numBuildings
<- runSimulation ("Guyal", :: printConstructionCost) {playerName, numBuildings
val currentYear = 2018
("println ("Adding $ numBuildings houses
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
{
...

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

Function type as return type


.Like any other type, a functional type can also be returned
.type. This means that you can declare a function that returns a function
-In SimVillage declare function configureGreetingFunction , which nastrai
gets the arguments for the lambda in the greetingFunction variable and then
.generates and returns a lambda ready to use

Listing 5.13. Adding configureGreetingFunction

} (<fun main (args: Array <String


<- runSimulation ("Guyal", :: printContructionCost) {playerName, numBuildings
val currentYear = 2018
("println ("Adding $ numBuildings houses
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
() runSimulation
{
,inline fun runSimulation (playerName: String
,costPrinter: (Int) -> Unit
} (greetingFunction: (String, Int) -> String
val numBuildings = (1..3) .shuffled (). last () // Randomly picks 1, 2 or 3
(costPrinter (numBuildings
((println (greetingFunction (playerName, numBuildings
} () fun runSimulation
() val greetingFunction = configureGreetingFunction
(("println (greetingFunction ("Guyal
{
} fun configureGreetingFunction (): (String) -> String
"val structureType = "hospitals
var numBuildings = 5
<- return {playerName: String
val currentYear = 2018
numBuildings + = 1
("println ("Adding $ numBuildings $ structureType
"(Welcome to SimVillage, $ playerName! (Copyright $ currentYear"
{
{

Imagine that configureGreetingFunction is such a "function factory" which


configures another function. She announces the necessary changes- and
collects them into a lambda, which then returns to the call point, that is into
. the runSimulation function
:Start SimVillage.kt again and you will see how many hospitals have been built
(Adding 6 hospitals Welcome to SimVillage, Guyal! (copyright 2020
numBuildings and structureType are local variables declared internally
configureGreetingFunction and both are used in the lambda returned from
configureGreetingFunction , although they are declared in an external function
that returns a lambda. This is possible because lambdas in Kotlin are closed
cania . They short-circuit variables from the outer scope, in which
were determined. Closures will be discussed in more detail in the section
For the curious: Kotlin lambdas are closures ." A function that takes or "
returns another function is also called is a higher-order function .
Terminology borrowed from the same mathematical area as lambda.
Higher-order functions are used are used in functional programming , which
we will meet
.in chapter 19
In this chapter, you learned what lambdas (anonymous functions) are, how
they are used used to change the behavior of functions from the standard
library
Kotlin and how to declare your own lambdas. Also, you learned that
,functions can be used in the same way as values of any other type
pass them in arguments and return them from other functions. In the next
In this chapter, we'll take a look at how Kotlin helps prevent errors in
.programming
world by maintaining an empty value in the type system. Also we will be
back
.to work on NyetHack and build a tavern

For the curious: Kotlin lambdas are -


these are closures
In Kotlin language, anonymous functions can modify and use variables
outside their scope. This means that the anonymous function has links
to variables declared in scope, where it is created itself, like
.we saw this with the configureGreetingFunction above
To demonstrate this property of anonymous functions, modify runSimulation
so that it calls the function returned from confi- several times
. gureGreetingFunction

(Listing 5.14. Call println twice in runSimulation (SimVillage.kt

...
} () fun runSimulation
() val greetingFunction = configureGreetingFunction
(("println (greetingFunction ("Guyal
(("println (greetingFunction ("Guyal
{
...

:Start SimVillage again. You will see the following output


building 6 hospitals
(Welcome to SimVillage, Guyal! (copyright 2020
building 7 hospitals
(Welcome to SimVillage, Guyal! (copyright 2020
Despite the fact that the numBuildings variable is declared outside the
anonymous function tion, an anonymous function can read and modify it. In
.this case, the value numBuildings increases from 6 to 7

For the curious: lambdas versus


anonymous inner classes
If you haven't used functional types before, then you can ask
the question: why use them at all? Our answer: functional types
offer more flexibility with less code. Consider a language
.which doesn't support functional types like Java 8
Java 8 supports object oriented programming and lambda expressions, but
does not allow you to declare parameters or variables capable of store other
functions. Instead, it suggests using an small inner classes, i.e. unnamed
classes declared internally another class for the sake of implementing a
single method. Anonymous embedded ny classes can be passed as
instances, like lambdas. For instance, in Java 8, to just pass one method,
:you would have to write
} <- (Greeting greeting = (playerName, numBuildings
;int currentYear = 2018
;("System.out.println ("Adding" + numBuildings + "houses
+ return "Welcome to SimVillage," + playerName
;"(" + copyright" + currentYear) !"
;{
} public interface Greeting
;(String greet (String playerName, int numBuildings
{
;(greeting.greet ("Guyal", 6

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
{

A lambda declaration in Java 8 requires an interface declaration. In Kotlin,


.this is no extra effort is required to describe a single abstract method
:The following Kotlin code is equivalent to Java code

{() fun runMyRunnable (runnable: () -> Unit) = {runnable


{("runMyRunnable {println ("hey now

Combine this syntax with the other capabilities learned in this


chapter - built-in functions, the keyword IT , closures - and you
get a better way than declaring inline classes for the sake of
.implementation of a single method
This flexibility is provided by Kotlin by including functions in the number
exemplary citizens, which frees you from routine and allows you to focus
.focus on the main thing - on the performance of work

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

} (<fun main (args: Array <String


"var signatureDrink = "Buttered Ale
signatureDrink = null
{

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

Kotlin did not allow null to be assigned to signatureDrink , because


that this variable is of non-nullable type (string, String ). A type
String does not support null. The current ad guarantees that
.signatureDrink will store a string, not null
If you have previously worked with Java, you will notice that this behavior
differs from
:familiar. In Java, code like this is allowed, for example
;"String signatureDrink = "Buttered Ale
;signatureDrink = null

Re-assigning signatureDrink to null is fine for


-Java. But what do you think will happen if you try to conduct a con
? catenating a string with a signatureDrink variable
;"String signatureDrink = "Buttered Ale
;signatureDrink = null
;"signatureDrink = signatureDrink + ", large

This code will throw a NullPointerException , which in turn will


.will stop the program
Java code crashes because the concatenation operation involves an
irrelevant
next line. This is an invalid operation. (If you don't understand why
,a null value in a string variable is not the same as an empty string
then this example will clarify everything. A null value implies that the
variable
does not exist. An empty string means that the variable exists and has
(. "meaning "" , which can be combined with ", large
:Java and many other languages support this pseudocode instruction
Hey, non-existent string, do string concatenation!" In these languages"
the variable can be null (except for primitives that Kotlin
,do not support). In languages that allow null for any type
.a NullPointerException error is a common cause of application crashes
Kotlin takes the opposite position when dealing with null. If you do not
specify
otherwise, the variable cannot be assigned the null value. This prevents
problems "Hey, non-existent element, do something" else during
.compilation, not at the time of program execution

Explicit null type in Kotlin


should be avoided at all costs . Kotlin protects you from
NullPointerException
,this
not allowing the assignment of null to a variable whose type does not
support
null. But this does not mean that there is no way to assign null in Kotlin.
Here's an example from the header of a function named readLine . The
.readLine function accepts input user to the console and returns it for later use
?public fun readLine (): String
The readLine header looks the same as the rest, except for the return
type String? ... A question mark indicates that the type supports a value
.null. This means readLine will return a String or null
-Remove the experimental signatureDrink code and add a readLine call . Co
.store the value returned from readLine and output it

(Listing 6.2. Declaring a variable with the nullable property. (Tavern.kt

} (<fun main (args: Array <String


"var signatureDrink = "Buttered Ale
signatureDrink = null
() var beverage = readLine
(println (beverage
{

.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

(Listing 6.3. Re-assigning null to a variable (Tavern.kt

} (<fun main (args: Array <String


() var beverage = readLine
beverage = null
(println (beverage
{

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

(Listing 6.4. We restore the service (Tavern.kt

} (<fun main (args: Array <String


() var beverage = readLine
beverage = null
beverage = null //
(println (beverage
{

Compile time and run time


Kotlin is a compiled language . This means that the code is translated into
instructions machine language with a special program - a compiler . At this
step
the compiler needs to make sure your code meets the conditions so that
translate it into machine code. For example, the compiler checks if
assign null to the given type. If you try to assign null to a type, it doesn't
.support living this opportunity, Kotlin will refuse to compile the program
Errors encountered during compilation are called time errors
compilation and are considered one of the advantages when working with
Kotlin. Sounds of course, it is strange that mistakes can be an advantage,
,but if com- the pilaster will check the program before you let others run it
.and finds all errors, this will make it easier to find and fix problems
On the other hand, a runtime error is an error that occurs
after compilation, when the program is already running, because the
compiler is not could find her. For example, since Java lacks a distinction
between
nullable and non-nullable types, the Java compiler cannot
.can detect a null assignment problem
.The code will compile successfully in Java, but will crash at runtime
Simply put, compile-time errors are preferable to time- no fulfillment. It is
better to find the problem while coding rather than later. And to learn about
.it only when the program has already been released - it couldn't be worse

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

} (<fun main (args: Array <String


() var beverage = readLine
() var beverage = readLine (). capitalize
beverage = null //
(println (beverage
{

Start Tavern.kt . You can expect the result to be a trendy name


drink with a capital letter. But by running the code you will see the time
error
:compilation
Only safe (?.) Or non-null asserted (!!.) Calls
?are allowed on a nullable receiver of type String

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

First Option: Safe Call Operator


It happens that you can't do without a null-supported type. For example
using
the value returned by someone else's code can never be sure that it
will not return null. In such cases, you should first use the operator
. safe call ( ?. ) function. Try it out at Tavern.kt

(Listing 6.6. Using a secure call operator (Tavern.kt

} (<fun main (args: Array <String


() var beverage = readLine (). capitalize
() var beverage = readLine () ?. capitalize
beverage = null //
(println (beverage
{

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

} (<fun main (args: Array <String


() var beverage = readLine () ?. capitalize
} var beverage = readLine () ?. let
} (() if (it.isNotBlank
() it.capitalize
} else {
"Buttered Ale"
{
{
beverage = null //
(println (beverage
{

.Here, you are declaring beverage as a nullable variable


. But this time, you are assigning a value as a result of a safe call to let
When beverage is non-null, let is called and executed the body of the
anonymous function passed to let : the input from readLine and determines if
something was entered by the user; if yes, then the first letter becomes
uppercase, and if not, then a spare letter is returned the title of the drink is
"Buttered Ale" . The isNotBlank and capitalize functions require so that the
. argument has a non-nullable type, which the let function will provide
let supports several conventions, two of which you used
, here. Firstly, in the announcement beverage you have used the keyword IT
whom he met in Chapter 5. Inside the let keyword it refers
to the variable for which let is called , and ensures that it is of type, not
supporting null. You call isNotBlank and capitalize with an it - form
.beverage that does not support null
The second let convention is not that obvious: let returns results implicitly
form, so you can (and should) assign that result to a variable
.right after evaluating your expression
Start Tavern.kt by uncommenting the line of code that assigns null
the beverage variable by removing the comment ( // ). When beverage matters
non-null, let is called , capitalized and printed
,result. When beverage is null, let is not called
.and beverage remains null

.!! Option two: operator


To call a function on a variable whose type supports a value
null, you can also use the !! operator . ... But be warned: this is more
Harsh" way than a safe call and should not be used without"
extreme necessity. Visually !!. should stand out in code because
:this is a dangerous option. Use !!. Is like telling the compiler
If I want to perform an operation with a non-existent value, then I“
,REQUIRE
so you throw a NullPointerException ! " (by the way, this operator is also
called the non-null control operator , but more often just the double
.(exclamation mark
We usually oppose the operator !!. but ... wear a protective suit
.and try it in practice

(Listing 6.8. Using the !! operator. (Tavern.kt

} (<fun main (args: Array <String


} var beverage = readLine () ?. let
} (() if (it.isNotBlank
() it.capitalize
} else {
"Buttered Ale"
{
{
() var beverage = readLine () !!. capitalize
beverage = null //
(println (beverage
{

reads like “I don't care if beverage


beverage = readLine () !!. capitalize ()
could be null. Call capitalize ! " However, if beverage is valid will be null, you
. will get a NullPointerException
Sometimes using the operator !!. justified. For example, you are not in
control
You are the type of a variable, but you know for sure that it will never get
.null
If you are absolutely sure that the variable will not become null, then using
operator !!. may be a good solution. Good example of application
.we will show this operator later in this chapter

Option 3: check value for null


The third option for safe working with null values is to check
a variable using an if statement . Remember table. 3.1 from Chapter 3, where
-re
are the comparison operators available in Kotlin. The ! = Operator checks
the inequality of the value on the left to the value on the right, and it can be
.used to checking the value for null. Try it at the tavern
(Listing 6.9. Using! = To check for null (Tavern.kt

} (<fun main (args: Array <String


() var beverage = readLine () !!. capitalize
() var beverage = readLine
beverage = null //
} (if (beverage! = null
() beverage = beverage.capitalize
} else {
("!println ("I can't do that without crashing - beverage was null
{
(println (beverage
{

Now if beverage is null you will get the following message instead of
.error
!I can't do that without crashing - beverage was null

Safe call operator preferred over value! = Null as more flexible


-a tool that solves the problem with less code. For example, without
:the dangerous call is easy to associate with subsequent function calls
("beverage? .capitalize () ?. plus (", large
-Note that you didn't have to use the !! operator . at about
growing to beverage in beverage = beverage.capitalize () . Kotlin recognized that
-in the branches if a variable beverage can not be null and re-pro
checking for null is not needed. This ability of the compiler to keep track of
.conditions inside an if statement is an example of smart casting
When to use the if / else statement to test for null? This option is good
sho is suitable for complex calculations and in cases where you have
previously
want to check a variable for null. The if / else statement allows you to express
.complex logic in a simple and understandable way

:?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

(Listing 6.10. Using the operator?: (Tavern.kt

} (<fun main (args: Array <String


() var beverage = readLine
beverage = null //
} (if (beverage! = null
() beverage = beverage.capitalize
} else {
("!println ("I can't do that without crashing - beverage was null
{

(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

Operator ?: Can be used in conjunction with let function instead of operator


:if / else . Compare this code from Listing 6.9
() var beverage = readLine
} (if (beverage! = null
() beverage = beverage.capitalize
} else {
("!println ("I can't do that without crashing - beverage was null
{

:with this
() var beverage = readLine
} beverage? .let
() beverage = it.capitalize
("!println ("I can't do that without crashing - beverage was null :?{

This option is functionally equivalent to the code in Listing 6.9. If beverage


null, then the console displays "I can't do that without crashing - beverage
was null! " . Otherwise, the name of the beverage in the beverage is displayed
.with a written letters
Does this mean that you should replace the existing if / else using
similar style? We cannot answer this question, because this is the case
.taste. In this case, we choose the if / else statement . We prefer clarity
.If you or your team disagree with us, it's okay, as both styles are acceptable

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

} (<fun main (args: Array <String


var swordsJuggling: Int? = null
val isJugglingProficient = (1..3) .shuffled (). last () == 3
} (if (isJugglingProficient
swordsJuggling = 2
{
("!println ("You juggle $ swordsJuggling swords
{

Start SwordJuggler . One in three, you get the highest grade


your skill; this is not bad for a beginner. If the skill test passes
successfully, you will see the message: You juggle 2 swords! ... Otherwise, a
.message will appear
... !nie: You juggle null swords
.Deriving the value of swordJuggling is inherently not a dangerous operation
The program will print the word null to the console and continue working.
Raise the level
... . the threat. Add another sword with the plus function and the !! operator
(Listing 6.12. Add a third sword (SwordJuggler.kt

} (<fun main (args: Array <String


var swordsJuggling: Int? = null
val isJugglingProficient = (1..3) .shuffled (). last () == 3
} (if (isJugglingProficient
swordsJuggling = 2
{
(swordsJuggling = swordsJuggling !!. plus (1
("!println ("You juggle $ swordsJuggling swords
{

Using the !! operator . with a variable that can have a value


null, is a dangerous operation. In one case out of three, an assessment of
your skill allows you to juggle with the third sword. In the other two cases,
.the program will crash
If an exception happens, you need to deal with it, otherwise the program
will work will be interrupted. An exception that has not been dealt with is
called an inadvertent botched exception , and interrupting program
execution is ugly
word failure . Try your luck by running SwordJuggler a couple of times. If the
application crashes, you will see KotlinNullPointerException and the rest of the
.code (operator println ) will fail
If there is a chance that a variable will get null, there is a likelihood
Nost appearance KotlinNullPointerException . This is one of the reasons why
.Kotlin chooses non-nullable types for variables by default

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

} (<fun main (args: Array <String


var swordsJuggling: Int? = null
val isJugglingProficient = (1..3) .shuffled (). last () == 3
} (if (isJugglingProficient
swordsJuggling = 2
{
(proficiencyCheck (swordsJuggling
(swordsJuggling = swordsJuggling !!. plus (1
("!println ("You juggle $ swordsJuggling swords
{
} (?fun proficiencyCheck (swordsJuggling: Int
("swordsJuggling?: throw IllegalStateException ("Player cannot juggle swords
{

.Run the code several times to see different outcomes


- Here you are signaling that the program has ended up in an invalid state
.nii, - swordsJuggling must not be null, otherwise you are at great risk
This warning indicates that anyone who wants to work with the swordsJuggling
variable , should prepare to handle the exception, arising from the
possibility of a null value appearing in a variable. The warning will not go
unnoticed, which is good, because this is how you most likely you will
notice an exceptional state even at the development stage, that is, before the
user crashes. And since you added in the IllegalStateException error message,
you will know exactly the reason
.program crash
It is not only the exceptions built into Kotlin that can be thrown. You can
-vol
expose your own exceptions to represent errors specific to
.for your application

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

(Listing 6.14. Custom Exception Declaration (SwordJuggler.kt

} (<fun main (args: Array <String


...
{
} (?fun proficiencyCheck (swordsJuggling: Int
("swordsJuggling?: throw IllegalStateException ("Player cannot juggle swords
{
:() class UnskilledSwordJugglerException
("IllegalStateException ("Player cannot juggle swords

UnskilledSwordJugglerExceptionis a custom exception that


.works like IllegalStateException , but with a specific message
-You can raise a new custom exception in the same way as Illegal
StateException using the throw statement . Raise your new exception
. at SwordJuggler.kt

(Listing 6.15. Raising a custom exception (SwordJuggler.kt

} (<fun main (args: Array <String


...
{
} (?fun proficiencyCheck (swordsJuggling: Int
("swordsJuggling?: throw IllegalStateException ("Player cannot juggle swords
() swordsJuggling?: throw UnskilledSwordJugglerException
{
:() class UnskilledSwordJugglerException
("IllegalStateException ("Player cannot juggle swords

is a non-standard error that


UnskilledSwordJugglerException
needs to be reported if swordsJuggling is null. No part of the code
determines when an exception is specifically raised. It's only yours
.a responsibility
Custom exceptions are useful and provide flexibility. They can
use not only to display arbitrary messages, but also to execute
operations in response to their appearance. They also help to reduce
.duplication of code, as they are reused in projects
Exception Handling
Exceptions are destructive as they should be - they represent
an erroneous condition to be corrected. Kotlin allows you to handle
exceptions by placing code where they might occur in a try / catch . The try /
catch syntax is similar to the if / else syntax . To see how try / catch works , we
.will use it in SwordJuggler.kt to protect against dangerous operations

(Listing 6.16. Add try / catch statement (SwordJuggler.kt

} (<fun main (args: Array <String


var swordsJuggling: Int? = null
val isJugglingProficient = (1..3) .shuffled (). last () == 3
} (if (isJugglingProficient
swordsJuggling = 2
{
} try
(proficiencyCheck (swordsJuggling
(swordsJuggling = swordsJuggling !!. plus (1
} (catch (e: Exception {
(println (e
{
("!println ("You juggle $ swordsJuggling swords
{
} (?fun proficiencyCheck (swordsJuggling: Int
() swordsJuggling?: throw UnskilledSwordJugglerException
{
:() class UnskilledSwordJugglerException
("IllegalStateException ("Player cannot juggle swords
By adding a try / catch statement , you have defined what happens if
some value is not null, and what if null. In the try block, you "try" to use
,call a variable. If no exception is thrown, the try statement will be executed
and the catch statement is not. This branching logic is similar to an if / else
condition . In that if you are trying to add another sword to those with which
... . you are already juggling, using the !! operator
In the catch block, you defined what happens if the expression in the try block
calls an exception. The catch block takes an argument with a specific type of
exception from which you need to protect. In this case, you intercept any
. exceptions of type Exception
.The catch block can include any logic, but in our example it's simple
.The catch block just prints out the name of the exception
,Within a try block, lines of code are executed sequentially. In this case
if swordsJuggling is non-null, the plus function will add
to swordsJuggling without any problems, and a message will appear in the 1
:console
!You juggle 3 swords

If you are not good at juggling, the variable


swords Juggling will be null and proficiencyCheck excite
UnskilledSwordJugglerException . But since an exception will be thrown inside
the try / catch statement , the program will continue and execute the catch block ,
:from by editing the following message to the console
UnskilledSwordJugglerException: Player cannot juggle swords
!You juggle null swords

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

} (<fun main (args: Array <String


var swordsJuggling: Int? = null
val isJugglingProficient = (1..3) .shuffled (). last () == 3
} (if (isJugglingProficient
swordsJuggling = 2
{
} try
(proficiencyCheck (swordsJuggling
(swordsJuggling = swordsJuggling !!. plus (1
} (catch (e: Exception {
(println (e
{
("!println ("You juggle $ swordsJuggling swords
{
} (?fun proficiencyCheck (swordsJuggling: Int
() swordsJuggling?: throw UnskilledSwordJugglerException
({"checkNotNull (swordsJuggling, {"Player cannot juggle swords
{
:() class UnskilledSwordJugglerException
("IllegalStateException ("Player cannot juggle swords

checkNotNull checks if swordsJuggling is null after


specific point in the code. If checkNotNull gets null it will raise
.IllegalStateException , indicating that the current state is invalid
checkNotNull takes two arguments: the first is the value to check for
null, the second is an error message that should be printed to the console if
.the value accepted for checking turned out to be null
Condition check functions are a good way to check requirements before
than to execute the code. They are much clearer than manual excitation of
the exception because the checked condition can be seen in the function
name. In that the end result does not change: either swordsJuggling will have
,a value other than null, or an error message appears in the console
but using checkNotNull makes the code clearer than possible excitation of
.UnskilledSwordJugglerException manually
There are five condition-checking functions in the Kotlin standard library;
this is their variety sets them apart from other null checking options. Five
functions
.condition checks are listed in table. 6.1
require is a very useful check. Functions can use
require to define boundary values for the arguments passed to them
cops. Take a look at the function using require to explicitly set
: swordsJuggling parameter requirements
} (fun juggleSwords (swordsJuggling: Int
({".require (swordsJuggling> = 3, {"Juggle at least 3 swords to be exciting
Juggle //
{

Table 6.1. Preconditioned functions

Function Description checkNotNull


Throws IllegalStateException if the argument is null. Otherwise
case returns the resulting value
require
Throws an IllegalArgumentException if the condition is not met
requireNotNull
Throws IllegalArgumentException if the argument is null. Against
otherwise returns the resulting value
error
Throws an IllegalArgumentException with the specified message if
argument is null. Otherwise returns the resulting value
assert
Raises an AssertionError if the condition is not met and at the stage
compilation flag set to enable assertion checking
during execution a

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

?Null: what's good about it


This chapter takes an anti-null position. We have shown that this position is
good, but in the wild world of software engineering, the representation of a
.nonexistent states with null are common
Why? Null is often used in Java and similar languages as an initial
the value of the variable. For example, let's take a variable to store the name
human. There are common names of people, but there is no name assigned
,by default . Null is often used as the initial value of a variable
which has no natural meaning. Moreover, in many languages you can
declare a variable with no initial value, and then it will get the value
.the default is null
This null assignment approach can result in a NullPointerException , which is
common in other languages. One way to get around null is more
careful attention to the initial values. Not every type has
- natural initial value, but for String there is such a value
empty line. An empty string tells us the same thing as null: the value is not
yet
created. Hence, the uninitialized state can be represented without
.checks for null
Null values can be approached from the other side - accept and use
See techniques in this chapter. It doesn't matter if you use the operator for
protection without dangerous call or operator ?: , correct handling of null
.value is not a prerequisite for a Kotlin developer
Nullness is a real-world phenomenon. Be able to It's very important to
recognize the lack of value in Kotlin. If you use such opportunity in your
.code or call someone else's code that can return null then do it wisely
In this chapter, you learned how Kotlin handles the problems associated
with
null. You saw that nullable types must be declared explicitly, because null is
not supported by default. And when is it possible you can, you should give
preference to types that do not support null, because that such values help
.the compiler to prevent errors
Were considered techniques for safe work with types that support
null, when you can't do without them - using the safe operator call, operator
?:, or directly checking for equality with null. Was considered the let
function and how to use it in combination with the operator a safe call
generator for evaluating variable expressions, capable of being null. Finally,
you have become familiar with the keys, learned how to handle them using
try / catch , as well as how define conditions in order to catch exceptional
,conditions earlier
than they will cause the failure. In the next chapter, you will learn how to
work with strings in the Kotlin language and Continue working on the
.tavern at NyetHack

For the curious: testable and


unchecked exceptions
In Kotlin, all exceptions are unchecked . This means that the Kotlin
compiler does not forces to push all the code that can throw exceptions into
the operator
. torus try / catch
Compare, for example, to Java, which mixes a cocktail of verifiables
and unchecked exception types. In the case of checked exceptions
the compiler checks if there is an exception protection by requiring to add
. gram try / catch
This sounds reasonable . But in practice, the idea of checked exceptions is
.not shown
it behaves as well as its creators intended. Frequently checked exceptions
(are detected (the compiler requires to handle checked exceptions
and then simply ignored, as long as the program compiles. This
the process of "consuming an exception" makes the program very difficult
to debug, because it hides information that something went wrong from the
very beginning chala. Most of the time ignoring the problem at compile
time
adds runtime errors. Unchecked exceptions have won out in modern
programming languages. because experience has shown that checked
exceptions lead to large problems than they can solve: duplication of code,
.difficult to understanding logic and engulfed exceptions without error data

For the curious: how is provided null


?support
-Kotlin has strict rules regarding null when compared to other languages
mi such as Java. This is purely a Kotlin feature, but let's see
trim how these rules are implemented. Do Kotlin language rules enforce
when
are you interacting with less strict languages like Java? Remember
:the printPlayerStatus function from Chapter 4
,fun printPlayerStatus (auraColor: String
,isBlessed: Boolean
,name: String
} (healthStatus: String
...
{

. printPlayerStatus accepts parameters with Kotlin String and Boolean types


In Kotlin, the function signature is obvious - the arguments are auraColor ,
name , healthStatus must be of type String , no null support, and the isBlessed
argument must be of type Boolean , which also does not support null.
However, in Java do
.different rules for working with null, Java String may well be null
How does Kotlin maintain a null safe environment? The answer to this
question
:would require diving into compiled Java bytecode
,public static final void printPlayerStatus (@NotNull String auraColor
,boolean isBlessed
,NotNull String name@
} (NotNull String healthStatus@
;("Intrinsics.checkParameterIsNotNull (auraColor, "auraColor
;("Intrinsics.checkParameterIsNotNull (name, "name
;("Intrinsics.checkParameterIsNotNull (healthStatus, "healthStatus
...
{

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

(Listing 7.1. Extracting the name of the innkeeper (Tavern.kt

"const val TAVERN_NAME = "Taernyl's Folly


} (<fun main (args: Array <String
() var beverage = readLine
beverage = null //
} (if (beverage! = null
() beverage = beverage.capitalize
} else {
("!println ("I can't do that without crashing - beverage was null
{
"val beverageServed: String = beverage?: "Buttered Ale
(println (beverageServed
() placeOrder
{
} () private fun placeOrder
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
{

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

substring takes IntRange (a type that represents a range of integers


,values), which defines the indices of the characters to retrieve (remember
.(which until creates an interval excluding the specified upper bound
This will set the variable tavernMaster to a string including characters from the
beginning of the line TAVERN_NAME to the first apostrophe, in other words
. "you, "Taernyl
-Finally, you used a template string (as in Chapter 3) to inter
: $ Modify the tavernMaster variable in the output by prefixing
(".println ("Madrigal speaks with $ tavernMaster about their order

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

Your next task is to add to the placeOrder function the ability to


read data from the tavern menu and display the name, type and price of the
drink, ordered by the visitor. Modify the placeOrder declaration to add a
reception data, and pass it the data from the menu at the point of the call to
placeOrder . (Please note that from now on we will stop strikethrough and
again
introduce lines with changes, but we will simply make changes to the
existing
(.code
(Listing 7.2. Transferring information about the tavern to placeOrder (Tavern.kt

"const val TAVERN_NAME = "Taernyl's Folly


} (<fun main (args: Array <String
("placeOrder ("shandy, Dragon's Breath, 5.91
{
} (private fun placeOrder (menuData: String
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
{

Next, to extract individual menu components, use the function


split , which splits the string into substrings at the specified delimiter.
-Before
. Add split to placeOrder

(Listing 7.3. Dividing Menu Data (Tavern.kt


...
} (private fun placeOrder (menuData: String
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
(',') val data = menuData.split
[val type = data [0
[val name = data [1
[val price = data [2
".val message = "Madrigal buys a $ name ($ type) for $ price
(println (message
{

splittakes a delimiter character and returns a list of substrings without


separator
divider. (The lists we’ll see in Chapter 10 store sequential
elements.) In our case, split returns a list of strings in the order
their succession in the original line. You are using indices in square
parentheses called the indexed access operator to extract
the first, second and third lines from the list and assign them to the type
. variables , name and price
-Finally, as before, you include lines in the message using the inter
.line polarity
.Start Tavern.kt . You will see the order including the drink, its type and price
.Madrigal speaks with Taernyl about their order
.Madrigal buys a Dragon's Breath (shandy) for 5.91
.Since split returns a list, a simplified syntax is supported for it
a taxis called destructuring . Destructuring is the ability to declare and
initialize several variables at once. Per- replace individual assignments in
.placeOrder using the syntax destructuring

(Listing 7.4. Destructuring data from the menu (Tavern.kt


...
} (private fun placeOrder (menuData: String
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
(',') val data = menuData.split
[val type = data [0
[val name = data [1
[val price = data [2
(',') val (type, name, price) = menuData.split
".val message = "Madrigal buys a $ name ($ type) for $ price
(println (message
{

Destructuring often helps to simplify the assignment of values immediately


how many variables. In any case, when the result is a list, one can
use destructuring syntax. In addition to lists (type List ), destructive
touring assignment is also supported for associative arrays
Map type ) and pairs ( Pair type ) (which are discussed in detail in Chapter )
.11) and for data classes
.Run Tavern.kt again. The results should be the same

Working with strings


,Those who drink Dragon's Breath not only enjoy the delicious taste
but also acquires special programming skills and the ability to understand
and speak the dragon language (DragonSpeak), an ancient dialect similar to
.1337Sp34k
:For instance
A word of advice: Don't drink the Dragon's Breath

translates into dragon language as


A w0rd 0f 4dv1c3: D0n't dr1nk th3 Dr4g0n's Br34th

.The String type includes functions for manipulating string values


To add a dragon translator to NyetHack, use the replace function , which, as
its name implies, replaces characters according to the given rules. replace
accepts a regular expression (more details we will talk about them a little
below), which defines the replacement characters, and you- calls an
anonymous function that you have declared that determines which symbols
.than to replace
Add a new function called toDragonSpeak that takes the phrase
and returns it in dragon tongue. Also add the phrase itself to printOrder
.and call toDragonSpeak with it

(Listing 7.5. ToDragonSpeak function declaration (Tavern.kt

"const val TAVERN_NAME = "Taernyl's Folly


} (<fun main (args: Array <String
("placeOrder ("shandy, Dragon's Breath, 5.91
{
= (private fun toDragonSpeak (phrase: String
} (("[phrase.replace (Regex ("[aeiou
} (when (it.value
"a" -> "4"
"e" -> "3"
"i" -> "1"
"o" -> "0"
"| _ |" <- "u"
else -> it.value
{
{
} (private fun placeOrder (menuData: String
...
(println (message
"!val phrase = "Ah, delicious $ name
("{(println ("Madrigal exclaims: $ {toDragonSpeak (phrase
{

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
{
{

Anonymous function receives an argument with a found value that matches


.with a regular expression, and returns the new value to replace

Strings are immutable


: Let's explain what "replace characters" means in terms of toDragonSpeak
if you dump the variable phrase from Listing 7.5 before and after calling
.replace , it will be found that the value of the variable is actually unchanged
The replace function does not actually "replace" parts of a phrase in a
.variable
Instead, it creates a new line. It uses the old line like input data and selects
.characters from it for a new one using an expression, which you specified
.It doesn't matter if var or val , all strings in Kotlin are actually immutable
as in Java). Even though a variable of type String can be assigned a new one)
.value, if declared as var , the string instance never changes
Any function that changes the value of a string (e.g. replace ) to
.actually creates a new line, making all changes to it
String comparison
What if the player wants to order something other than Dragon's Breath?
toDragonSpeak
.will still be called, this is undesirable
, Add a condition to the placeOrder function to skip calling toDragonSpeak
.if the player ordered something other than Dragon's Breath

(Listing 7.6. Compare strings in placeOrder (Tavern.kt

...
} (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

"const val TAVERN_NAME = "Taernyl's Folly


} (<fun main (args: Array <String
("placeOrder ("shandy, Dragon's Breath, 5.91
("placeOrder ("shandy, Dragon's Breath, 5.91 //
("placeOrder ("elixir, Shirley's Temple, 4.12
{
...

:Start Tavern.kt . You will see the following output


.Madrigal speaks with Taernyl about their order
Madrigal buys a Shirley's Temple (elixir) for 4.12
.Madrigal says: Thanks for the Shirley's Temple

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

For the curious: Unicode


As you already know, a string consists of a sequence of characters, and a
character ly are instances of type Char . It should be clarified that Char is a
-Uni
code . The Unicode character encoding system was developed for support
for the "exchange, processing and display of written texts on different
languages and from different technical areas of the modern world ”(
.( unicode.org
This means that the individual characters in the string can be any character
from a wide palette - 136 690 pieces (the number is growing), including
symbols alphabet of any language in the world, icons, figures, emojis,
hieroglyphs, etc. There are two ways to declare a symbol. In both cases, the
definition of sim- the ox is enclosed in single quotes. For characters that can
be entered from the keyboard, the simplest option is used - the symbol itself
:is indicated in single quotes

'val capitalA: Char = 'A

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

'val unicodeCapitalA: Char = '\ u0041

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

} (<fun main (args: Array <String


'val omSymbol = '\ u0950
(print (omSymbol
{

.You will see the symbol in the console


For the curious: traversing characters in a
string
.The String type includes other sequential traversal functions
characters like indexOf and split . For example, characters from a string can be
:lead separately, one after another, using the forEach function . This call

} Dragon's Breath" .forEach"


("println ("$ it \ n
{

:will generate the following output

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

Quest: Improve Draconic Tongue


At the moment toDragonSpeak only works with lowercase letters. For example
:Mer, the following statement will not be translated into Draconic
!DRAGON'S BREATH: IT'S GOT WHAT ADVENTURERS CRAVE

.Improve the toDragonSpeak function to work with uppercase letters


Numbers 8
.Kotlin has a wide variety of types for working with numbers and arithmetic
,technical calculations. For each of the two main varieties
villages - whole and fractional - there are several different types in Kotlin.
In this chapter, you see how Kotlin handles both kinds of numbers,
implement in NyetHack wallet for the player and add the ability to
.exchange money

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

Table 8.1. Frequently used numeric types

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

Figure: 8.1. 42 in binary

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

"const val TAVERN_NAME = "Taernyl's Folly


var playerGold = 10
var playerSilver = 10
} (<fun main (args: Array <String
("placeOrder ("shandy, Dragon's Breath, 5.91 //
("placeOrder ("elixir, Shirley's Temple, 4.12
{
} () fun performPurchase
() displayBalance
{
} () private fun displayBalance
("println ("Player's purse balance: Gold: $ playerGold, Silver: $ playerSilver
{
= (private fun toDragonSpeak (phrase: String
...
{
} (private fun placeOrder (menuData: String
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
(',') val (type, name, price) = menuData.split
".val message = "Madrigal buys a $ name ($ type) for $ price
(println (message
() performPurchase
} ("val phrase = if (name == "Dragon's Breath
"{(" !Madrigal exclaims $ {toDragonSpeak (" Ah, delicious $ name"
} else {
".Madrigal says: Thanks for the $ name"
{
(println (phrase
{

Note: you used Int to pass the number of coins


player. The maximum number of coins in the wallet (and in the known part
universe NyetHack) is significantly less than the maximum value of Int
.at 2147483647
Start Tavern.kt . You have not yet implemented the logic of payment for the
,order by the player
:so this time the hero gets his order from the house

.Madrigal speaks with Taernyl about their order


.Madrigal buys a Dragon's Breath (shandy) for 5.91
Player's purse balance: Gold: 10, Silver: 10
!Madrigal exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th

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

(Listing 8.2. Transfer of price information (Tavern.kt

"const val TAVERN_NAME = "Taernyl's Folly


...
} (fun performPurchase (price: Double
() displayBalance
("println ("Purchasing item for $ price
{
...
} (private fun placeOrder (menuData: String
...
(',') val (type, name, price) = menuData.split
".val message = "Madrigal buys a $ name ($ type) for $ price
(println (message
(performPurchase (price
...
{
Convert string to number
If you run Tavern.kt now, a compilation error will appear. This happens
goes because the price variable that you pass to performPurchase is
is a string, and the function takes a Double . For a person, "5.91" looks like
as a number, but the Kotlin compiler sees it differently because the value
was
. obtained by the split function from the menuData string
However, things are not so bad, because Kotlin has functions for converting
strings to other types, even numbers. Here are the most commonly used
:functions transformations

toFloat❍
toDouble ❍
toDoubleOrNull ❍
toIntOrNull ❍
toLong ❍
toBigDecimal ❍

Attempting to convert a string in the wrong format will throw an exception.


For example measures, calling toInt with the string "5.91" will throw an
exception, because Int is not supports fractional values. Since converting to
different number formats can cause exceptions However, Kotlin offers
toDoubleOrNull safe conversion functions and toIntOrNull . If the number
cannot be converted without error, it returns null instead of an exception.
:Use the ? Operator with toIntOrNull , on- example to return a different value
val gold: Int = "5.91" .toIntOrNull ()?: 0

Modify the placeOrder code to convert the string argument to Double


. for performPurchase
(Listing 8.3. Translate price argument to Double (Tavern.kt
...
} (private fun placeOrder (menuData: String
(' '\') val indexOfApostrophe = TAVERN_NAME.indexOf
(val tavernMaster = TAVERN_NAME.substring (0 until indexOfApostrophe
(".println ("Madrigal speaks with $ tavernMaster about their order
(',') val (type, name, price) = menuData.split
".val message = "Madrigal buys a $ name ($ type) for $ price
(println (message
(() performPurchase (price.toDouble
...
{

Converting Int to Double


It's time to withdraw money from the player's wallet. The wallet stores the
whole the number of gold and silver coins, but the price of the drink is
. expressed in gold as Double
To make a sale, first let's convert gold and silver into one
so that the price can be deducted. Add a variable to performPurchase
to represent the total amount of money in the wallet. One gold coin
costs 100 silver, so divide the player's silver by 100
.and add the result to the number of gold coins to get the total
The variables totalPurse and price are of the same type, Double , so subtract the
.price from the amount in the wallet and assign the result to a new variable

(Listing 8.4. Price deduction (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
{
...

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
{
...

The remaining gold in the wallet is interpolated to a string using $ like


you saw earlier. But after the $ is not just the variable name, but the
expression
in curly braces. Inside the brackets, the format function is called with an
. argument remainingBalance
The format call also defines the format string "% .2f" . Format string
with the help of a special character set determines how to format
upload data. Specifically, this format indicates that a floating point number
must be rounded to 2 decimal places. Then you pass the value
. or the values to be formatted in the arguments to the format function
Kotlin uses standard format strings like Java, C / C ++, Ruby
-and other languages. For details, see the doc
. See the Java API at docs.oracle.com/javase/8/docs/api/java/util/Formatter.html
Start Tavern.kt . See Madrigal paying for Dragon's
:Breath
.Madrigal speaks with Taernyl about their order
.Madrigal buys a Dragon's Breath (shandy) for 5.91
Player's purse balance: Gold: 10, Silver: 10
Total purse: 10.1
Purchasing item for 5.91
Remaining balance: 4.19
!Madrigal exclaims Ah, d3l1c10 | _ | s Dr4g0n's Br34th

Converting Double to Int


Now that we have counted the player's remaining money, it remains to
convert
, balance back to gold and silver coins. Update the performPurchase code
-to convert the player's remaining amount into gold and silver. (Not for
(. please add import kotlin.math.roundToInt at the top of the file)

(Listing 8.6. Converting silver to gold (Tavern.kt

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
{
...

This example used two conversion functions available for


of type Double . The toInt function discards the fractional part of the value.
the effect The action of this function is also called loss of precision . Part of
the original data is lost because you asked to return an integer
representation
.fractional number, and the integer value is not so exact
Note that calling toInt on Double acts differently than calling
toInt for a string like "5.91", which throws an exception. The difference is
that when converting a string to Double it needs to be parsed first
and convert to numeric, while for numeric types such as
.Double or Int , no additional analysis required
In our case, the remainingBalance is 4.18999999999999999995, so the call
.toInt will give the result - 4. This is the player's remaining gold
:Next, you convert the fractional part to the remainder of the silver
() val remainingSilver = (remainingBalance% 1 * 100) .roundToInt
Here you have applied the modulo operator , which finds the remainder
from dividing one number by another. Operation % 1 will discard the integer
part
remainingBalance (the part that is evenly divisible by 1), and will leave us
fractional part. Finally, you multiplied the remainder by 100 to translate into
.edge and called roundToInt for the resulting value 18.99999999999995
roundToInt rounds to the nearest integer, so 19 silver remains
.coins

:Launch Tavern.kt again to see the tavern running smoothly


.Madrigal speaks with Taernyl about their order
.Madrigal buys a Dragon's Breath (shandy) for 5.91
Player's purse balance: Gold: 10, Silver: 10
Total purse: 10.1
Purchasing item for 5.91
Remaining balance: 4.19
Player's purse balance: Gold: 4, Silver: 19
!Madrigal exclaims Ah, d3l1c10 | _ | s Dr4g0n's Br34th

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

For the curious: bit manipulation


You saw earlier that numbers have binary representations. Get binary
representation of the number is possible at any time. For example, get
:binary integer 42 can be represented as follows
(Integer.toBinaryString (42
101010
-Kotlin has functions for performing operations on binary pre
,positions, which are called bitwise operations, including already
perhaps familiar operations from other languages such as Java. Table 8.2
.lists commonly used binary operations available in Kotlin
Table 8.2. Binary operations

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 //

Challenge: How Many Pints Remain


Dragon's Breath is poured from a 5 gallon barrel. Assuming
that one order is a pint (0.125 gallons), count the remaining Dragon's
.Breath
.Output the value of the remainder in pints after the sale of 12 pints

Task: Handling Negative Balance


Now the player can place an order regardless of the number of coins. This is
.not the most profitable business model for Taernyl's Folly. Fix it
Modify the performPurchase code to determine if it is possible to perform
purchase. If this is not possible, money should not change hands, and
instead of the message "Madrigal buys a Dragon's Breath (shandy) for 5.91" a message
should be displayed from the innkeeper that the buyer lacks exactly money.
To simulate multiple orders, call multiple times performPurchase in the
. function 's placeOrder
Quest: Dragon Coins
A new coin has appeared in the kingdom. Dracoin is speed, security
and anonymity when making payments in any tavern. Suppose the current
course is equals 1.43 gold coins for one dracoin. Implement Player
Calculations
not in gold and silver, but in dracoins. The prices in the tavern will continue
to be listed in gold. The player starts the game with 5 Dracoins. After the
purchase
one Dragon's Breath at the price of 5.91 gold coins, the player must have
.Dracoins 0.8671
Standard features 9
Standard functions are generic helper functions from the Kotlin standard
libraries that accept lambda expressions, clarifying their behavior. In this
chapter, you will learn about six of the most commonly used standard
.functions - apply , let , run , with , also and takeIf - and see what exactly they do
-In this chapter, we will not add anything to NyetHack or Sandbox, but pre
.Let's experiment with code examples in the REPL
Here, to denote an instance of a type, we will use the term declaration
ect - the recipient . Kotlin standard functions are actually - functions -
distributed
extensions , the context for which is the recipient object. More details
extension functions that allow you to flexibly define additional
.functions for different types are described in chapter 18

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

:Using apply , the same can be accomplished with less code


} val menuFile = File ("menu-file.txt"). apply
(setReadable (true
(setWritable (true
(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

} val firstItemSquared = listOf (1,2,3) .first (). let


it * it
{

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" :?{
{

Since the variable vipGuest is nullable, it is important to ensure that


it is not null before calling other functions. Safe operator the pass call
ensures that let is executed only if the string is not equal null; and if let is
.executed, then it is also not null
: Compare formatGreeting with the non- let version
} fun formatGreeting (vipGuest: String?): String
} (return if (vipGuest! = null
".Welcome, $ vipGuest. Please, go straight back - your table is ready"
} else {
".Welcome to the tavern. You'll be seated shortly"
{
{

.This version of formatGreeting is functionally equivalent but more verbose


The if / else structure uses the full name of the vipGuest variable twice: first
the first time in the condition and the second time to create the final row. let
, on the other hand, allows for a fluid style, or a chain style that allows
.use a variable name only once
let can be called on any target object and get the result you- filling lambda.
In our example let is called relative to a variable vipGuest with a nullable type.
The lambda passed to let gets destination object as the only argument. So
. refer to the argument You can use the keyword IT
A few differences between let and apply worth mentioning: as you already
.seen let passes a receiver object to a lambda, but apply doesn't pass anything
Also apply returns the current target object as soon as anonymous
the function exits. let , on the other hand, returns the result
.the last line of the lambda
Standard functions like let can also be used to reduce
risk of accidentally changing the variable because the let argument is passed
to the lambda as a read-only parameter to the function. You will see
.See Chapter 12 for an example of this use of standard functions
run
The next standard function on the list is run . The run function is like
apply , similarly limiting the relative scope, but not possible
.rotates the target object
:For example, here's how to check for a specific line in a file
("val menuFile = File ("menu-file.txt
} val servesDragonsBreath = menuFile.run
("readText (). contains ("Dragon's Breath
{

-The readText function is implicitly called relative to the receiver object - eq


File instances - like setReadable , setWriteble, and setExecutable
- in the apply example . But unlike apply , run returns the lambda result
.in our case, true or false
run can also be used to execute a function reference relative to
specifically the destination object. You used function references in chapter
: 5: here example how to do it with run

fun nameIsLong (name: String) = name.length> = 20


Madrigal" .run (:: nameIsLong) // False"
Polarcubis, Supreme Master of NyetHack" .run (:: nameIsLong) // True"

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

} val status = run


"if (healthPoints == 100) "perfect health" else "has injuries
{

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
{

,Instead of calling a string relative, as in the case of calling "Polarcubis


Supreme Master of NyetHack ".run , the string is passed to with in the first (and in
.this case only) argument
This inconsistency with the rest of the standard functions makes with
less preferred than run . Moreover, we recommend avoiding with and using
use run instead . We only included with here so that
when you met her, you knew what she was doing (and you might want to
.( replace her with run

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

("val file = File ("myfile.txt


} (() val fileContents = if (file.canRead () && file.canWrite
() file.readText
} else {
null
{

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

;takeIf - returns a value if the condition is true ❍


.takeUnless - Returns a value if the condition is not true ❍

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 and sets 10


Working with a group of related values is very important for many
programs. On- example, the program can operate with lists of books,
attractions
on the tourist route, dishes on the menu or the balance of money on the
accounts young visitors of the tavern. Collections make it easy to work with
.groups values and pass them as arguments to functions
In the next two chapters, we will look at the most commonly used types
.(collections: List (list), Set (set) and Map (associative array
Like many of the other types of variables studied in Chapter 2, lists, sets
and associative arrays are of two types: mutable and accessible only
.for reading. In this chapter, we'll look at lists and sets
-You will use the collections to upgrade the NyetHack Tavern. In re
As a result, the tavern will acquire a menu with a solid list of dishes - along
with a bunch of
.visitors eager to spend money

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

(Listing 10.1. Creating a list of visitors (Tavern.kt


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
{
...

Up to this point, you have created variables of different types by simply


.declaring them
But, to get a collection, you need to follow two steps: create a collection
tion (in the example, this is a list of visitors) and add content (names
visitors). Kotlin provides functions like listOf that can
.do it at the same time
. Now that you have a list, let's take a closer look at the List type
Although auto-typing recognizes lists, we
included type information - val patronList: List <String> to make it
. <visible for discussion. Notice the angle brackets in List <String
String> is a type parameter and it tells the compiler the type of the values >
that
will be contained in the list - in our case, it is String . Parameter change
type changes the type of values that the compiler will allow to store in the
.list
If you try to write an integer to the patronList the compiler doesn't
.admit. Try adding a number to the advertised list

(Listing 10.2. Adding an integer to a list of strings (Tavern.kt

...
(var patronList: List <String> = listOf ("Eli", "Mordoc", "Sophie", 1
...

IntelliJ will warn that an integer value is not as expected


type String . The type parameter must be used with List because itself
the List type is a generic type . This means that the list can store
data of any type, including text data such as strings (as in the case
with patronList ) or characters, numeric values such as whole and fractional
la, and even data of new user-defined types. (About generalized
(.types, see Chapter 17
Delete the last change using the undo command ( Command-z
.Ctrl-z )) or by removing an integer )

(Listing 10.3. Correction of the contents of the list (Tavern.kt


...
( var patronList: List <String> = listOf ("Eli", "Mordoc", "Sophie" , 1
...

Accessing list items


From Chapter 7, when you looked at the split function , you know that any
. fight a list item can be obtained by its index using the [] operator
The numbering of items in lists starts at 0 , so "Eli" has an index of 0, and
."Sophie" has index 2
Modify the Tavern.kt code so that it only displays the first visitor. Also remove
the type information from the patronList declaration . Now that you see- Do what
type of list is parameterized a List , you can use the mechanism automatic
.type detection and write cleaner code

(Listing 10.4. First visitor appeal (Tavern.kt

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

Index Boundaries and Secure Index


Access
Accessing an element by index requires caution, because how to get an
element with a nonexistent index - in our case 4, since the list contains only
. three elements, it will throw an exception ArrayIndexOutOfBoundsExcep tion
Try doing this in the Kotlin REPL. (You can copy the first line
(. code from Tavern.kt

(Listing 10.5. Accessing a nonexistent index (REPL

("val patronList = listOf ("Eli", "Mordoc", "Sophie


[patronList [4

. As a result, you will see the message: java.lang.ArrayIndexOutOfBoundsException: 4


Since accessing an element by index can throw an exception, Kotlin
provides index-safe functions that will allow approach this problem from
the other side. If the index does not exist, instead of exceptions, they may
return some other result. For example, one of the index-safe access
, functions, getOrElse
takes two arguments: the first is the index being requested (not in square
brackets), the second is a lambda that generates a default value
.instead of throwing out if the requested index does not exist
.Try this in the REPL

(Listing 10.6. GetOrElse Testing (REPL

("val patronList = listOf ("Eli", "Mordoc", "Sophie


{"patronList.getOrElse (4) {"Unknown Patron

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

(Listing 10.7. GetOrNull Testing (REPL

"val fifthPatron = patronList.getOrNull (4)?: "Unknown Patron


fifthPatron

. Again, the result will be Unknown Patron

Checking the contents of the list


The tavern has dark corners and secret rooms. Fortunately, the innkeeper
sharp eye, and he scrupulously records who came or went. If you ask
,if a particular visitor is present, the innkeeper will be able to answer
.looking at the list
Modify the Tavern.kt code and use the contains function to check for presence
.specific visitor's impact

(Listing 10.8. Visitor verification (Tavern.kt

...
} (<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

(Listing 10.9. Checking multiple visitors (Tavern.kt


...
} (<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
{
} ((("if (patronList.containsAll (listOf ("Sophie", "Mordoc
(".println ("The tavern master says: Yea, they're seated by the stew kettle
} else {
(".println ("The tavern master says: Nay, they departed hours ago
{
("placeOrder ("shandy, Dragon's Breath, 5.91
{
...

:Start Tavern.kt . You will see the following output


.The tavern master says: Eli's in the back playing cards
.The tavern master says: Yea, they're seated by the stew kettle
...

Change the contents of the list


If a visitor comes or leaves in the middle of the night, the considerate
innkeeper
should add or remove the name of the visitor from the patronList variable . In
on
.at the present time it is impossible
listOf returns a read-only list, which cannot be
change content: you cannot add, remove, update or replace
data. Read-only lists are a good solution because
they prevent annoying mistakes, for example, the ability to drive crops
.to the street by accidentally removing his name from the list
The read-only nature of the list has nothing to do with the key
.the word val or var used to declare a list variable
By replacing patronListс val in the variable declaration (as now) with var , you do not
make the list editable. You will simply be allowed to svoit variable patronList
.another meaning, that is, create a new list
The ability to change the list directly depends on its type, which is
shares the ability to modify items in the list. Since visitors are constant
janno come and go, we have to change the patronList type to allow
.update. In Kotlin language, a mutable list is known as a mutable list
.list and you must call mutableListOf to create it
Modify the Tavern.kt code and use mutableListOf instead of listOf . Changeable
lists support many functions for adding, deleting and updating
,changing content. Simulate the arrival and departure of multiple visitors
. using the add and remove functions
(Listing 10.10. Create a customizable list of visitors (Tavern.kt
...
("val patronList = listOf ("Eli", "Mordoc", "Sophie
("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
(println (patronList
{
...

:Start Tavern.kt . The following message will appear in the console

...
!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

(Listing 10.11. Adding another Alex (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
(println (patronList
{
...

:Start Tavern.kt again. You will see the following output


...
[Eli, Mordoc, Sophie]
[Alex, Mordoc, Sophie, Alex]

To make the patronList mutable, we had to replace in our


listOf code on mutableListOf . But List has functions to transform
:immutable lists to mutable and back right at runtime
-toList and toMutableList . For example, make the mutable patronList again to
: read-only using toList
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
() val readOnlyPatronList = patronList.toList

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

, <In this case, because patronList is of type MutableList <String


patron will be of type String . In the body of the for loop, any code affecting
. on patron will be applied to all items in patronList
In some languages, including Java, the for loop by default runs from the
-index
mi array or collection. This is often inconvenient, but this approach also has
its advantages. The syntax is verbose and difficult to read, but allows
.more accurately control the iteration process
In Kotlin, all for loops rely on iteration. In Java and C # languages im
. foreach loops are equivalent
For those familiar with Java, it may come as a surprise that the usual Java
expression (int i = 0; i <10; i ++) {...} is not possible in Kotlin. Instead, the for loop
writes is formed like this: for (i in 1..10) {...} . However, at the bytecode level,
the compiler optimizes the for loop of the Kotlin language to the Java
.version, if possible, so that improve productivity
: Note the key word in
{...} (for (patron in patronList

. in defines an object to be traversed in a for loop


The for loop is simple and easy to read, but if you prefer more functionality
. style, use the forEach function
The forEach function iterates over each item in the list, sequentially from
go to the end - and passes each element to the anonymous function in an
.argument
. Replace the for loop with the forEach function

(Listing 10.14. Traversing patronList with forEach (Tavern.kt

...
} (<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
{
{
...

:Run Tavern.kt again to see the queued visitors


...
.Good evening, Eli - you're # 1 in line
.Good evening, Mordoc - you're # 2 in line
.Good evening, Sophie - you're # 3 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

Iterable collections support a variety of functions that allow


determine the actions to be performed for each item collection. Read more
.about Iterable and other iteration functions. is found in chapter 19

Reading a file into a list


Variety brightens life, and the innkeeper knows that visitors want to
the next selection in the menu. At the moment Dragons' Breath is the only
one
what is sold in the tavern. Now is the time to fix it by adding more to the
menu
.meals and drinks
To save time, we have prepared the menu in a text file that
can be downloaded at NyetHack. The file contains several items for the
menu
.in the same format as the description of the drink Dragons' Breath
First create a new folder for data: right click
NyetHack on the project in the Project tool window and select the New →
. Directory (fig.10.1). Name the folder data

Figure: 10.1. Create a new folder


`

Download the menu at bignerdranch.com/solutions/tavern-menu-data.txt and save


. in a data folder named tavern-menu-items.txt
Now you can add code to Tavern.kt to read text from a file into a string and
-you
call split for the resulting string. Don't forget to add to the top of the Tavern.kt
. file import java.io.File statement

(Listing 10.17. Reading a menu from a file (Tavern.kt

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

Now call forEachIndexed on the menuList to list all elements


.the list along with their indices
(Listing 10.18. Displaying a varied menu (Tavern.kt

...
} (<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

(Listing 10.19. Selecting random items from the menu (Tavern.kt

...
} (<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

val (goldMedal, _, bronzeMedal) = patronList

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

(Listing 10.20. Create set (REPL

("val planets = setOf ("Mercury", "Venus", "Earth


planets
["Mercury", "Venus", "Earth"]

If you try to add the same planet to a set twice, it contains only one will
.remain

(Listing 10.21. Trying to create a set with a duplicate (REPL

("val planets = setOf ("Mercury", "Venus", "Earth", "Earth


planets
["Mercury", "Venus", "Earth"]

.The second meaning "Earth" was thrown out of the crowd


Just like a list, a set allows you to check the presence of a specific
element with contains or containsAll . Try the contains function
.in the REPL

(Listing 10.22. Checking planets (REPL

("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

("val planets = setOf ("Mercury", "Venus", "Earth


(planets.elementAt (2
Earth

.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

Adding items to a set


.To add some variety, we will generate the names of the visitors at random
all at once using a list of first and last names. Add a list of surnames to
Tavern.kt
and use forEach to get 10 random combinations of names (from
patronList ) and last names. (Recall that for iterations you can use
(.ranges
-Remove the two calls to forEachIndexed that greeted the visitors and created
their orders from the menu. We'll be replacing them shortly with iterations
over a list of unique
.visitors

(Listing 10.24. Generation of 10 random visitors (Tavern.kt


...
("val patronList = mutableListOf ("Eli", "Mordoc", "Sophie
("val lastName = listOf ("Ironfoot", "Fernsworth", "Baggins
("val menuList = File ("data / tavern-menu-items.txt
() readText.
("split ("\ n.
} (<fun main (args: Array <String
...
<- patronList.forEachIndexed {index, patron
(".println ("Good evening, $ patron - you're # $ {index + 1} in line
(() placeOrder (patron, menuList.shuffled (). first
{
<- menuList.forEachIndexed {index, data
("println ("$ index: $ data
{
} forEach. (9..0)
() val first = patronList.shuffled (). first
() val last = lastName.shuffled (). first
"val name = "$ first $ last
(println (name
{
{
...
Start Tavern.kt . You will see 10 random visitor names in the output. They are
will not necessarily match the ones below, but will be similar - and
:and they will come across the same combination of first and last names
...
Eli baggins
Eli baggins
Eli baggins
Eli ironfoot
Sophie Baggins
Sophie Fernsworth
Sophie Baggins
Eli ironfoot
Eli ironfoot
Sophie Fernsworth

But our tavern model requires unique visitor names because


soon we will link the balance of coins to each visitor. The emergence of two
.names with the same name can be confusing
.To remove duplicate names from the list, add each name to the set
-Any duplicate elements will be discarded, and only uni
.cal values
Declare an empty mutable set and add a randomly generated
.bathroom names of visitors

(Listing 10.25. Uniqueness through many (Tavern.kt

...
("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
{
...

Please note that you cannot count on automatic detection


-division of types for uniquePatrons , because they declared it as an empty set
:state. It is necessary to indicate the type of elements that it can contain
mutableSetOf <String> . In this case, you use the + = operator to add
.name to uniquePatrons and iterate 10 times
,Start Tavern.kt again. The set contains only unique values
.so you will most likely get less than 10 visitor names
...
,Eli Fernsworth, Eli Ironfoot, Sophie Baggins, Mordoc Baggins]
[Sophie Fernsworth
-Although MutableSet supports adding and removing elements, just like Mutable
List , it doesn't support index-accessed mutators
While loop
-Now that you have a list of unique names, let them make the case
new orders from the menu. However, in this section, you must use another
. mechanism for traversing the collection: the while loop
.The for loop is useful when you need to execute code for each element
the sequence. But it is bad for cases where the loop ends upon reaching a
certain state, and not after a certain the number of iterations. A while loop is
. better for such situations
The logic of the while loop is as follows: "While the condition is true, execute
".the code in the block
, Generate exactly 10 orders using var as an order counter and a while loop
.continuing to run until 10 orders are generated
Iterate over the contents of the set in the Tavern.kt code to get
. in total 10 orders. To do this, use a while loop

(Listing 10.26. Unique visitors place random orders (Tavern.kt


...
} (<fun main (args: Array <String
...
(println (uniquePatrons
var orderCount = 0
} (while (orderCount <= 9
,() placeOrder (uniquePatrons.shuffled (). first
(() menuList.shuffled (). first
++ orderCount
{
{
...

.The increment operator ( ++ ) adds 1 to the orderCount value each iteration


.walkie-talkies
:Start Tavern.kt . This time you will see 10 random orders of visitors
.Sophie Ironfoot speaks with Taernyl about their order
.Sophie Ironfoot buys a Dragon's Breath (shandy) for 5.91
!Sophie Ironfoot exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Mordoc Fernsworth speaks with Taernyl about their order
.Mordoc Fernsworth buys a Dragon's Breath (shandy) for 5.91
!Mordoc Fernsworth exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Eli Baggins speaks with Taernyl about their order
.Eli Baggins buys a pickled camel hump (desert dessert) for 7.33
.Eli Baggins says: Thanks for the pickled camel hump
...
The while loop requires you to keep track of orders to determine a
.continuation condition
You start with an orderCount value of 0 and increment it in each iterations.
The while loop is more flexible than the for loop because it can pre- put state
not based on iteration. The example achieves this increasing the orderCount
. counter
You can express more complex states by combining a while loop with others
,forms of flow control such as conditional expressions
:which were covered in Chapter 3. Take a look at this example
var isTavernOpen = true
val isClosingTime = false
} (while (isTavernOpen == true
} (if (isClosingTime
isTavernOpen = false
{
("!println ("Having a grand old time
{

,Here the while loop keeps repeating as long as isTavernOpen is true


and the state is represented by a boolean value. This is a very powerful tool
but it can be dangerous. Think what will happen if isTavernOpen never
won't get false? The while loop will continue indefinitely,and the program will
freeze or continue running indefinitely. Be careful get rid of it using a while
. loop
Break statement
One way to end the while loop is to change the state it is
checks. Another way is the break statement . In the example above, the while
loop repeats as long as isTavernOpen is true. Instead of changing the value of
isTavernOpen to false, you can use the break statement , which will immediately
:break the loop
var isTavernOpen = true
val isClosingTime = false
} (while (isTavernOpen == true
} (if (isClosingTime
break
{
("!println ("Having a grand old time
{
Without break, the line "Having a grand old time!" (The fun continues!) Will
printed again after changing the value of isClosingTime . With break fun
breaks as soon as execution reaches this statement, and the loop here
.the same ends

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

(Listing 10.27. Translating a List into a Set (REPL

() listOf ("Eli Baggins", "Eli Baggins", "Eli Ironfoot"). toSet


[Eli Baggins, Eli Ironfoot]

,To be able to access by index after removing duplicates


.convert set to list again

(Listing 10.28. Convert set to list (REPL

("val patrons = listOf ("Eli Baggins", "Eli Baggins", "Eli Ironfoot


() toSet.
() toList.
[Eli Baggins, Eli Ironfoot]
[patrons [0
Eli baggins

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

.Sets are useful for representing datasets with unique elements


tami. In the next chapter, we will finish the overview of collection types in
.the Kotlin language
.getting familiar with associative arrays

For the curious: array types


If you have worked with Java, you know that it supports arrays of primitive
types other than reference types such as List and Set , with which you worked
in this chapter. Kotlin also includes several reference types with names
including the word Array , which are compiled to elementary Java arrays.
.Array types are mainly used for interactions between Java code and Kotlin
Let's say there is a Java method that you want to call from Kotlin and looks
like
:he's so
} (static void displayPlayerAges (int [] playerAges
} (++ for (int i = 0; i <ages.length; i
;([System.out.println ("age:" + ages [i
{
{

The displayPlayerAges function takes an int [] playerAges parameter , the array is


values of primitive type int . This is how the displayPlayerAges method can be
:called from Kotlin
(val playerAges: IntArray = intArrayOf (34, 27, 14, 52, 101
(displayPlayerAges (playerAges

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

For the curious: read only instead of


""immutable
Throughout the book, we use the term read-only instead of immutable
.Except for a couple of cases, but we don't explain why. The time has come
Immutable" misrepresents the essence of Kotlin collections (and some"
other types) because they are subject to change. Consider several
applications
.measures lists
Here is the declaration of two Lists . Variables referencing lists are available
read-only because they are declared as val . But each of them contains
.mutable list
((val x = listOf (mutableListOf (1,2,3
((val y = listOf (mutableListOf (1,2,3
x == y
true
,Nothing special yet. The variables x and y are assigned the same values
and the List type does not offer any functionality for manipulating the link
itself
.on the list
But variables refer to mutable lists, and their contents can
:to be changed
((val x = listOf (mutableListOf (1,2,3
((val y = listOf (mutableListOf (1,2,3
(x [0] .add (4
x == y
false

Structural comparison of x and y returned false, because the contents of x


,changed- Elk. Should an immutable value behave this way? In our opinion
.shouldn't
:Here's another example
(var myList: List <Int> = listOf (1,2,3
myList as MutableList) [2] = 1000)
myList
[1000 ,2 ,1]

In this example myList has been coerced to type MutableList to inform


to the compiler that it should treat myList as a mutable list, even though it
was created with listOf . (About type casting will be discussed in more detail
in chapters 14 and 16.) Type casting allowed change the third value in myList
. And now we see the wrong behavior again, expected from something
marked as "unchanging." List in Kotlin is not strictly immutable - you
choose, you use
whether to call it in an unchanging manner. The "immutability" of a List in
.Kotlin is just a thin wrapper, and whatever you do, don't forget about it

Task: Formatted Output


tavern menu
First impressions are the most important and one of the first things they see
visitor is the menu. In this task, generate a cleaner version
menu. Capitalize menu items and align to the left
-edge. Include prices in the menu, align them with the decimal point. Form
.Tie all menus into a neat block

:The output should look like this


*** Welcome to Taernyl's Folly ***
Dragon's Breath ............... 5.91
Shirley's Temple .............. 4.12
Goblet of LaCroix ............. 1.22
Pickled Camel Hump ............ 7.33
Iced Boilermaker ............. 11.22

,Hint : to count the number of placeholders for each line


.select the longest line from the list of menu items

Quest: Improved Formatting


tavern menu
Based on the previous formatting code, generate a menu that
also groups the items in the list by type. You should get next
:output
*** Welcome to Taernyl's Folly ***
~ [shandy] ~
Dragon's Breath ............... 5.91
~ [elixir] ~
Iced Boilermaker ............. 11.22
Shirley's Temple .............. 4.12
~ [meal] ~
Goblet of LaCroix ............. 1.22
~ [desert dessert] ~
Pickled Camel Hump ........ 7.33
Associative arrays 11
The third most commonly used collection type in the Kotlin language is
type Map , or associative array. The Map type has a lot in common with the Set
.types
and List : all three group sets of items, by default only available
for reading use a type parameter to tell the compiler about the type
.content and also support iteration
Unlike lists and sets, associative arrays consist of pairs
Key-value", and instead of access by an integer index, they provide"
access by a key of the specified type. Keys are unique and define values
in an associative array: values, on the other hand, do not have to be
unique. From this point of view, associative arrays have one of
properties of sets: they guarantee the uniqueness of keys, like elements
.sets

Creating an associative array


Similar to lists and sets, associative arrays are created using functions:
mapOf or mutableMapOf . In Tavern.kt create an associative array to represent the
amount of gold each visitor has in the wallet (we explain the syntax of the
.(argument below

Listing 11.1. Creating a read-only associative array

(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}

(You used to to define each item (key and value


:in an associative array
...
(mapOf ("Eli" to 10.75, "Mordoc" to 8.25, "Sophie" to 5.50

may look like a keyword, but in fact it is a special kind


to
functions that can be called by dropping the period and parentheses around
.the arguments
You will learn more about this in Chapter 18. The to function converts the
operands to the left and on the right in a pair ( Pair ) - a type representing a
.group of two elements
Associative arrays are built using key-value pairs. So- there is another way
to define the elements of an associative array, as shown below. (Try the
(.REPL

(Listing 11.2. Declaring an associative array using the Pair type (REPL

,(val patronGold = mapOf (Pair ("Eli", 10.75


,(Pair ("Mordoc", 8.00
((Pair ("Sophie", 5.50
However, the syntax for creating an associative array with the function to
look
.neater
.We have already said that the keys in an associative array must be unique
nym. But what happens if you try to re-add an element with the same
.key? Add the key pair " Sophie " to the REPL

(Listing 11.3. Adding a duplicate key (REPL

(val patronGold = mutableMapOf ("Eli" to 5.0, "Sophie" to 1.0


patronGold + = "Sophie" to 6.0
(println (patronGold
{Eli = 5.0, Sophie = 6.0}

You used the addition assignment operator ( + = ) to add


" a duplicate key pair to an associative array. Since the key " Sophie
is already in the array, the existing pair has been overwritten with a new
one. Similar behavior can be observed when you include duplicate values
when
:array initialization
,println (mapOf ("Eli" to 10.75
,Mordoc" to 8.25"
,Sophie" to 5.50"
((Sophie" to 6.25"
{Eli = 10.5, Mordoc = 8.0, Sophie = 6.25}

Accessing values in an associative array


You can access the value in the array by its key. In the case of an array
patronGold we will use a string key to access to the balance of the visitor,
.expressed in gold

(Listing 11.4. Access to personal "gold" accounts (Tavern.kt

...
} (<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
{

Run Tavern.kt to display the balance of the three visitors added


:into an array
...
10.5
8.0
5.5

.Note that the output contains only values, no keys


Similar to other collections, Kotlin provides functions to access
.to the values stored in the associative array

Add entries to associative array


,Your array of visitors' gold presents wallets Eli, Mordoc
Sophie, but does not include the visitors' wallets that were generated
dynamically. Now is the time to fix this by replacing patronGold with
MutableMap . Let's make patronGold a mutable associative array. Then we go
over
in the loop, the elements of the uniquePatrons set and add entries about the
wallets by Settlers by placing 6.0 gold in each. Also delete the already
created entries, because from now on, the keys will contain more than just
.names

(Listing 11.5. Associative array filling (Tavern.kt

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

Change values in an associative array


To complete the purchase, we must subtract the price of the item on the
menu from the content visitor's wallet. The patronGold associative array links
the value of the money balance with the key - the name of the visitor.
Change balance visitor by writing down the new value after completing the
.purchase
The performPurchase and displayBalance functions are associated with the hero's
wallet and sub- show in detail how many coins are there. However, these
details for us you won't need it now. Remove them as well as the playerGold
variables
and playerSilver , used only in these functions. Then declare a new
performPurchasefunction to handle purchases of visitors. (You declare a new
(.function to display visitor balance status Further
To update the value after completing the purchase, the function will receive
it
from the patronGold associative array by the name of the visitor. Call new
- performPurchase function after the visitor has talked to the innkeeper
.(com Taernyl about your order (don't forget to uncomment the challenge
(Listing 11.6. Updating values in patronGold (Tavern.kt

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

(Listing 11.7. Visitor balances display (Tavern.kt


...
} (<fun main (args: Array <String
...
var orderCount = 0
} (while (orderCount <= 9
,() placeOrder (uniquePatrons.shuffled (). first
(() menuList.shuffled (). first
++ orderCount
{
() displayPatronBalances
{
} () private fun displayPatronBalances
<- patronGold.forEach {patron, balance
("{(println ("$ patron, balance: $ {"%. 2f ".format (balance
{
{
...

Run Tavern.kt , sit back and see how visitors


:Taenyl's Folly talk to the innkeeper, place orders and pay for them
.The tavern master says: Eli's in the back playing cards
.The tavern master says: Yea, they're seated by the stew kettle
.Mordoc Ironfoot speaks with Taernyl about their order
.Mordoc Ironfoot buys a iced boilermaker (elixir) for 11.22
.Mordoc Ironfoot says: Thanks for the iced boilermaker
.Sophie Baggins speaks with Taernyl about their order
.Sophie Baggins buys a Dragon's Breath (shandy) for 5.91
!Sophie Baggins exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Sophie Ironfoot speaks with Taernyl about their order
.Sophie Ironfoot buys a pickled camel hump (desert dessert) for 7.33
.Sophie Ironfoot says: Thanks for the pickled camel hump
.Eli Fernsworth speaks with Taernyl about their order
.Eli Fernsworth buys a Dragon's Breath (shandy) for 5.91
!Eli Fernsworth exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Sophie Fernsworth speaks with Taernyl about their order
.Sophie Fernsworth buys a iced boilermaker (elixir) for 11.22
.Sophie Fernsworth says: Thanks for the iced boilermaker
.Sophie Fernsworth speaks with Taernyl about their order
.Sophie Fernsworth buys a Dragon's Breath (shandy) for 5.91
!Sophie Fernsworth exclaims: Ah, d3l1c10 | _ | s Dr4g0n's Br34th
.Sophie Fernsworth speaks with Taernyl about their order
.Sophie Fernsworth buys a pickled camel hump (desert dessert) for 7.33
.Sophie Fernsworth says: Thanks for the pickled camel hump
.Mordoc Fernsworth speaks with Taernyl about their order
.Mordoc Fernsworth buys a Shirley's Temple (elixir) for 4.12
.Mordoc Fernsworth says: Thanks for the Shirley's Temple
.Sophie Baggins speaks with Taernyl about their order
.Sophie Baggins buys a goblet of LaCroix (meal) for 1.22
.Sophie Baggins says: Thanks for the goblet of LaCroix
.Mordoc Fernsworth speaks with Taernyl about their order
.Mordoc Fernsworth buys a iced boilermaker (elixir) for 11.22
.Mordoc Fernsworth says: Thanks for the iced boilermaker
Mordoc Ironfoot, balance: -5.22
Sophie Baggins, balance: -1.13
Eli Fernsworth, balance: 0.09
Sophie Fernsworth, balance: -18.46
Sophie Ironfoot, balance: -1.33
Mordoc Fernsworth, balance: -9.34

In the last two chapters, you learned how to work in Kotlin with different
types
.Kotlin collections: with lists, sets and associative arrays

Since collections are read-only by default, you should


,(create a mutable collection (or convert to a mutable version
.to change its contents. This will prevent accidental damage
.adding or removing items
In the next chapter, you will learn how to apply the principles of object-
.orientation
bathroom programming by the example of declaring your own class
.at NyetHack

Quest: Tavern Bouncer


A visitor without gold should not place orders. Moreover, he should not
scamper around the tavern and the bouncer should spot him right away. If
you have no money, drive him out to the inhospitable street NyetHack by
deleting
. from uniquePatrons and patronGold array

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

(Listing 12.1. Player class declaration (Player.kt

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

} (<fun main (args: Array <String


"val name = "Madrigal
var healthPoints = 89
val isBlessed = true
val isImmortal = false
() val player = Player
Aura //
(val auraColor = auraColor (isBlessed, healthPoints, isImmortal
Player state //
(val healthStatus = formatHealthStatus (healthPoints, isBlessed
(printPlayerStatus (auraColor, isBlessed, name, healthStatus
() castFireball
{
...
You called the main constructor of Player by adding parentheses after the
class name Player . This action creates an instance of the Player class . The
". player variable is now "Contains an instance of the Player class
The name "constructor" speaks for itself: it creates. More specifically, he
created gives a copy and prepares it for use. Constructor Call Syntax
is like a function call: it uses parentheses to pass its arguments
.parameters. We'll look at other ways to create an instance in Chapter 13
?Now that you have a Player instance , what can you do with it

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

} (<fun main (args: Array <String


var healthPoints = 89
val isBlessed = true
val isImmortal = false
() val player = Player
() player.castFireball
Aura //
(val auraColor = auraColor (isBlessed, healthPoints, isImmortal
Player state //
(val healthStatus = formatHealthStatus (healthPoints, isBlessed
(printPlayerStatus (auraColor, isBlessed, player.name, healthStatus
() castFireball
{
...
= (private fun castFireball (numFireballs: Int = 2
.println ("A glass of Fireball springs into existence
(" (x $ numFireballs)

Grouping logic into "objects" using classes helps preserve


thread the code organized as it develops. As NyetHack grows, you will
.Add even more classes, each with their own responsibilities
.Start Game.kt and make sure the player gets a glass of intoxicating drink
Why move castFireball to Player ? In NyetHack, the spell for getting
intoxicating drink is pronounced by the player: this drink cannot be
obtained
drink without a Player instance , and a glass with this drink is created with a
specific by the player who called the castFireball . Declaring castFireball as a
function class, which is called for a specific instance of the class, reflects
this
logic. Later in the chapter, we will move other functions related to the
.player
. in NyetHack, in the Player class

Availability and encapsulation


Adding behavior to a class as class functions (and data as properties
class, as we will see later) creates a description of what a class can be and
what
can do, and this description is available to anyone who has an instance of
the class. By default, any function or property without visibility modifier
will be available to everyone - that is, from any file or function in the
program. So as you didn't specify the visibility modifier for castFireball , it
can be
.called from anywhere
In some cases, as with castFireball , you will want other parts of the code
-accessed class properties or called class functions. But you have so
there may be other class functions or properties that should not be
.available everywhere
As the number of classes in a program grows, so does the complexity of the
code. Concealment implementation details that should not be available to
other parts of the code, helps keep your code logic clear and concise. And
.this will help you limiting the scope
,While the public function can be called from anywhere in the program
the private class function is available only in the class in which it is declared.
This idea restricting the visibility of some properties and / or functions of a
class in object- oriented programming is called encapsulation . According
to this the idea is that the class should selectively provide functions and
properties to other objects to interact with him. Anything not involved,
including implementation details functions and properties should be
.unavailable
For example, the code in Game.kt that calls castFireball is not interested in how
to implement This function is called. For him, it is only important to get a
glass of drink. Therefore as long as the function is available, the
.implementation details should not be important to you calling
Moreover, it can even be dangerous if the Game.kt code can change the sign
the values that castFireball relies on , that is, the number of
.glasses with a drink or on the properties of the drink
.Simply put, when creating classes, leave only what you need available
.We'll discuss the protected keyword in Chapter 14
If you are familiar with Java, please note that there is no level in Kotlin
visibility, limited by the scope of the package. We will explain the reason in
the section "For the curious: limiting visibility to the scope of the package"
.at the end of the chapter

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)

Figure: 12.1. Unresolved link

Now that name is owned by Player , we need to update printPlayerStatus to


,get the name from an instance of the Player class . Use dot syntax
. to pass the name property of the player variable to printPlayerStatus

(Listing 12.7. Getting a reference to the name property of the Player name class (Game.kt

} (<fun main (args: Array <String


...
Player state //
(printPlayerStatus (auraColor, isBlessed, player.name, healthStatus
{
...

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
{

.We'll explore the details of initializing classes and properties in Chapter 13


Later in this chapter, you will refactor NyetHack and move the rest of the
.data belonging to the Player class into the declaration of this class

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

In its tooltip, IntelliJ communicates important information about write


methods: they are are called to assign values to properties. This is not
logical (but in fact
this is an error) - declare a write method for the property val , because if the
.value is read-only, the write method cannot do its job
To be able to change the player's name, you need to replace val with var in
the declaration the phenomenon of the name property . (Note that from now
(.on we will show all changes in the code whenever possible

(Listing 12.10. Make the name changeable (Player.kt

} 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

() val player = Player


"player.name = "estragon
("print (player.name + "TheBrave
EstragonTheBrave

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

(Listing 12.12. Hide setter name (Player.kt

} 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

(Listing 12.13. Computed Property Declaration (REPL

} () class Dice
val rolledValue
() get () = (1..6) .shuffled (). first
{

.Let's make a throw


(Listing 12.14. Computed Property Access (REPL

() val myD6 = Dice


myD6.rolledValue
6
myD6.rolledValue
1
myD6.rolledValue
4

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

Figure: 12.3. Splitting the editor window vertically

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

} (<fun main (args: Array <String


var healthPoints = 89
val isBlessed = true
val isImmortal = false
() val player = Player
() player.castFireball
...
{
...

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
{

:These changes will lead to a lot of errors in Game.kt . Hold on tight


.by the time you are done, all errors have been resolved
-heathPoints and isBlessed should be available in Game.kt . But isImmortal ni
when not accessed outside of the Player class , so the isImmortal property
follows
declare private. Hide the property by adding a private modifier so that
.ensure that it is not available to other classes

(Listing 12.17. Encapsulating isImmortal inside 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
= (fun castFireball (numFireballs: Int = 2
("(println ("A glass of Fireball springs into existence. (x $ numFireballs
{

Next, consider the functions declared in Game.kt . printPlayerStatus outputs


text-based games, so Game.kt it belongs. But auraColor and formatHeathStatus are
player related, not gameplay related. This means that these the two
. functions must belong to the class, not the main function
. Move auraColor and formatHeathStatus to Player
(Listing 12.18. Removing a function from main (Game.kt

} (<fun main (args: Array <String


...
{
= (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
{
,private fun printPlayerStatus (auraColor: String
,isBlessed: Boolean
,name: String
} (healthStatus: String
+ "(println ("(Aura: $ auraColor
("({" Blessed: $ {if (isBlessed)" YES "else" NO)"
("println ("$ name $ healthStatus
{
,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
{

.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
{

Prior to this change, a reference to heathPoints inside the formatHealthStatus


function would be a link to the formatHealthStatus parameter , because that
link was limited by the scope of the function. Without a variable named
heathPoints
in the function scope, the closest scope is at the class level in which the
. heathPoints property is declared
.Next, notice that two of the class's functions are declared private
This was not a problem when they were in the same file from where they
are calling- fox. But now they became private in the Player class and were
inaccessible- for other classes. These functions should be made available,
so
. remove the private keyword from the auraColor and formatHeathStatus declarations
(Listing 12.21. Making a class function public (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 (): 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

} (<fun main (args: Array <String


() val player = Player
() player.castFireball
Aura //
(val auraColor = player.auraColor (isBlessed, healthPoints, isImmortal
Player state //
(val healthStatus = formatHealthStatus (healthPoints, isBlessed
( printPlayerStatus (player auraColor, isBlessed, player.name, healthStatus
Aura //
(player.auraColor (isBlessed, healthPoints, isImmortal
{
,private fun printPlayerStatus (player: PlayerauraColor: String
,isBlessed: Boolean
,name: String
} (healthStatus: String
+ "({() println ("(Aura: $ {player.auraColor
("({" Blessed: $ {if (player.isBlessed)" YES "else" NO)"
("{() println ("$ {player.name} $ {player.formatHealthStatus
{

This change in the printPlayerStatus header helps keep it clean


. from unnecessary details of the Player implementation
:Compare these two signatures
(printPlayerStatus (player: Player
,printPlayerStatus (auraColor: String
,isBlessed: Boolean
,name: String
(healthStatus: String
Which one looks neater? The second signature requires the caller to know
quite a bit about the Player implementation . The first requires only a Player
. instance
In this example, you see one of the benefits of object-oriented
programming: since the data is now part of the Player class , on
.they can be referenced without having to pass them explicitly
-Let's stop and see what we have achieved with this refactoring
ha. The Player class now contains all the data and behavior of a particular
object
player". It exposes three properties and three functions and hides"
implementation details in private elements that should be available
only to the Player class . These functions declare the player's capabilities:
player
.can report their health status, aura color, etc
As the application evolves, it is important to keep the scope manageable
,visibility. Getting started with object-oriented programming
you join the idea that each object should fulfill its own
,responsibilities and open access only to those functions and properties
which are required for other functions and classes to work. Now Player
fully reflects what it means to be a NyetHack player, and Game.kt contains
the game loop in the form of a simpler and more understandable main
. function
Run Game.kt to make sure everything works as before. Praise
yourself for successfully completing the refactoring. In the following
chapters, we will build the foundation for the game NyetHack, we will
make it more complex and add the possibility objects based on the
.paradigm of object-oriented programming rationing
. In the next chapter, you will learn more ways to create a Player instance
getting familiar with additional initialization techniques. But before
than developing the application further, you need to pay attention to
.packages

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

Figure: 12.6. Com.bignerdranch.nyethack package

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

(class Student (var name: String

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

} public final class Student


NotNull@
;private String name
NotNull@
} () public final String getName
;return this.name
{
} (public final void setName (@NotNull String var1
;("<?- Intrinsics.checkParameterIsNotNull (var1, "<set
;this.name = var1
{
} (public Student (@NotNull String name
;("Intrinsics.checkParameterIsNotNull (name, "name
;() super
;this.name = name
{
{

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

(Listing 12.24. Replacing var with val (Student.kt

(class Student ( var val name: String

Let's look at the resulting bytecode (pay special attention to the disappeared
.(new code

} public final class Student


NotNull@
;private String name
NotNull@
} () public final String getName
;return this.name
{
} (public final void setName (@NotNull String var1
;("<?- Intrinsics.checkParameterIsNotNull (var1, "<set
;this.name = var1
{
} (public Student (@NotNull String name
;("Intrinsics.checkParameterIsNotNull (name, "name
;() super
;this.name = name
{
{

.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

} (class Student (val name: String


val name: String
"get () = "Madrigal
{

:Now let's look at the resulting bytecode

} public final class Student


NotNull@
;private String name
NotNull@
} () public final String getName
;return this.name
"return "Madrigal
{
} (public final void setName (@NotNull String var1
;("<?- Intrinsics.checkParameterIsNotNull (var1, "<set
;this.name = var1
{
} (public Student (@NotNull String name
;("Intrinsics.checkParameterIsNotNull (name, "name
;() super
;this.name = name
{
{
This time, only one item was generated - the read method. Compiler
determined that the field is not required, since no data from the field is read
.and do not write into it
.Specifically, this property feature is calculating a value, not just reading
it out of the field is another reason why we use the terms “just for
read and write-only, not mutable and immutable. Look
: rub again on the Dice class you created earlier in the REPL
} () class Dice
val rolledValue
() get () = (1..6) .shuffled (). first
{

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

For the curious: protection from race


condition
If a property of a class is both mutable and has a supported type null, make
sure it's not null before referring to it. For instance, Consider the following
code that checks if the player has a weapon (after all the player can be
:unarmed or disarmed). If there is a weapon, it is displayed his name
(class Weapon (val name: String
} class Player
("var weapon: Weapon? = Weapon ("Ebony Kris
} () fun printWeaponName
} (if (weapon! = null
(println (weapon.name
{
{
{
} (<fun main (args: Array <String
() Player (). PrintWeaponName
{

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

The compiler aborts compiling your code because of the possibility of a


. race condition
Race condition occurs when there is a possibility of concurrent change
states from elsewhere in the program. This can lead to unforeseen
.results
In the above example, the compiler sees that although weapon is checked
not equal to null, it is still possible that the property
weapon of the Player class will be null between the moment of checking and
.output the names of the weapons on the screen
That is, unlike other cases when a smart type conversion is possible
weapon when checked for null, in this situation the compiler refuses to
.continue compilation as I'm not sure weapon will never be null
You can fix this problem and protect yourself from null using the standard
:the also function , which you read about in Chapter 9
} class Player
("var weapon: Weapon? = Weapon ("Ebony Kris
} () fun printWeaponName
} weapon? .also
(println (it.name
{
{
{
This code is compiled thanks to the standard also function . Instead of
reference
to the property of the class, the code uses the argument it of the function also ,
which in this case is a local variable that only exists inside the scope of the
anonymous function. This means that re- the name it is guaranteed not to be
.altered by other parts of the program
We managed to avoid the problem with smart type conversion, because
instead of using a nullable property this code uses the local
a read-only non-nullable variable. This the variable is local because also is
called after the statement
. safe call weapon? .also
For the curious: limiting visibility
package scope
Think back to the beginning of the chapter discussing private and public
.scopes
As you already know, in the Kotlin language, classes, functions or
.properties are by default (no visibility modifier) get the public scope
This means that they can be used by any other classes, functions or
.properties in the project

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
...
{

The Player class header is pretty simple, so create an instance of Player


.was easy too
} (<fun main (args: Array <String
() val player = 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

,class Player (_name: String


,healthPoints: Int_
,isBlessed: Boolean_
} (isImmortal: Boolean_
var name = "Madrigal" _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = 89_healthPoints
val isBlessed = true_isBlessed
private val isImmortal = false_isImmortal
...
{

?Why do variable names start with an underscore)


,variables, including parameters that are used only once
often give names starting with an underscore. This shows that they
(.disposable
Now, in order to create an instance of the Player , pass the arguments
corresponding to
corresponding to the parameters added to the constructor. Thanks to this,
-on
example, you don't have to limit yourself to a hard-coded value for a
property
name , and pass it as an argument to the main constructor of Player . Change
.calling the Player constructor inside main to express this
(Listing 13.2. Calling the main constructor (Game.kt
} (<fun main (args: Array <String
(val player = Player ("Madrigal", 89, true, false
...
{
Think about how many features the chief designer added
in Player : before, a player in NyetHack always received the name Madrigal,
could not become immortal, etc. Now the player can choose any name, and
.also become immortal because no data in the Player class is hardcoded
.Run Game.kt to make sure the output hasn't changed

Declaring properties in the main


constructor
Note the direct relationship of constructor parameters in Player
and class properties: you have a parameter for each class property which
.needs to be initialized when creating a player
For properties with default read and write methods, Kotlin allows
declare parameter and property in one definition, without creating a
temporary
variable. name uses non-standard read and write methods, so
this approach cannot be used for him. But it is possible for other properties
. Player
Change the code of the Player class and declare healthPoints , isBlessed, and
isImmortal as parameters of the main constructor Player . (Remember to
(.remove the bottom underscores in front of variable names
(Listing 13.3. Declaring properties in the primary constructor (Game.kt

,class Player (_name: String


,var _ healthPoints: Int
,val _ isBlessed: Boolean
} (private val _ isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
var healthPoints = _healthPoints
val isBlessed = _isBlessed
private val isImmortal = _isImmortal
...
{

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

-The helper constructor must call the main constructor, re


,giving it all the required arguments, or another helper constructor
which follows the same rule. For example, you know that most
,cases, the player starts the game with 100 health points already available
,blessed and mortal. You can declare a helper constructor
automatically setting these values. Add an auxiliary
. constructor in Player
(Listing 13.4. Helper Constructor Declaration (Player.kt

,class Player (_name: String


var healthPoints: Int
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
,constructor (name: String): this (name
,healthPoints = 100
,isBlessed = true
(isImmortal = false
...
{

Several helper constructors can be declared for different


combinations of parameters. The helper constructor calls the main
a constructor with a full set of parameters. The this keyword in the given
.case refers to an instance of the class for which the constructor is declared
Specifically in this case, this calls another constructor, declared
.inside the class, the main constructor
Since the helper constructor defines default values for
healthPoints , isBlessed and isImmortal , you don't have to pass arguments for
these parameters when it is called. Call the helper constructor for
.Player from Game.kt instead of the main constructor
(Listing 13.5. Calling the Constructor Helper (Game.kt

} (<fun main (args: Array <String


( val player = Player ("Madrigal" , 89, true, false
...
{

In the auxiliary constructor, you can also define init logic


.cialisation is the code that is executed at the time of class instance creation
For example, add an expression that reduces the player's health to 40 if
.his name is Kar

(Listing 13.6. Adding logic to the secondary constructor (Player.kt

,class Player (_name: String


var healthPoints: Int
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
,constructor (name: String): this (name
,healthPoints = 100
,isBlessed = true
} (isImmortal = false
if (name.toLowerCase () == "kar") healthPoints = 40
{
...
{

While it is possible to define alternative logic in the secondary constructor


initialization, in it, unlike the main constructor, it is impossible to declare
properties. Class properties can only be declared in the main constructor, or
.at the class level
Start Game.kt and you will see that the hero is blessed and has 100 points
health; this proves that the Player instance was created by calling the helper
.a solid constructor

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

(Listing 13.7. Default argument declaration in constructor (Player.kt


,class Player (_name: String
var healthPoints: Int = 100
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
,constructor (name: String): this (name
,healthPoints = 100
,isBlessed = true
} (isImmortal = false
if (name.toLowerCase () == "kar") healthPoints = 40
{
...
{

-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

,If you need to pass multiple arguments to a constructor or function


we recommend using named arguments. Thanks to them it is easier
.understand which arguments are passed to which parameter

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

(Listing 13.8. Initialization block declaration (Player.kt

,class Player (_name: String


var healthPoints: Int = 100
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
} init
({".require (healthPoints> 0, {"healthPoints must be greater than zero
({".require (name.isNotBlank (), {"Player must have a name
{
,constructor (name: String): this (name
,isBlessed = true
} (isImmortal = false
if (name.toLowerCase () == "kar") healthPoints = 40
{
...
{

If at least one of the conditions is not met, an exception will be raised


IllegalArgumentException (you can check this in the Kotlin REPL by passing to
.(the Player other parameters
These requirements are difficult to implement in a constructor or
declaration
properties. The code in the initialization block is called immediately after
.the creation of the item
specimen. It doesn't matter which constructor was called - the main one or
.auxiliary

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

(Listing 13.9. Hometown property declaration (Player.kt

,class Player (_name: String


var healthPoints: Int = 100
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
val hometown: String
} init
({".require (healthPoints> 0, {"healthPoints must be greater than zero
({".require (name.isNotBlank (), {"Player must have a name
{
...
{

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

,class Player (_name: String


var healthPoints: Int = 100
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
() get () = field.capitalize
} (private set (value
() field = value.trim
{
() val hometown : String = selectHometown
...
("private fun selectHometown () = File ("data / towns.txt
() readText.
("split ("\ n.
() shuffled.
() first.
{

Don't forget to add import java.io.File to Player.kt to access)


(. to the File class
. You will have to add the towns.txt file with the list of cities to the data folder
. The file itself can be found at bignerdranch.com/solutions/towns.txt
Test the hometown property using the read method of the name property . To
distinguish your hero from all other Madrigals in the world, the hero must
.by name with a reference to his hometown
(Listing 13.11. Using the hometown property (Player.kt
,class Player (_name: String
var healthPoints: Int = 100
val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
"get () = "$ {field.capitalize ()} of $ hometown
} (private set (value
() field = value.trim
{
() val hometown = selectHometown
...
("private fun selectHometown () = File ("data / towns.txt
() readText.
("split ("\ n.
() shuffled.
() first.
{

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
{
{

Since the value is assigned to the variable instrumentName before calls


.to it, the code compiles safely
Properties have stricter initialization rules. This is due to the fact that
they are accessible from other classes if they are declared public. Locale
function variables, on the other hand, only exist in scope
.functions in which they are declared are not accessible from the outside

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

} (class Player (_name: String, val health: Int


"val race = "DWARF
"var town = "Bavaria
val name = _name
val alignment: String
private var age = 0
} init
("println ("initializing player
"alignment = "GOOD
{
} (constructor (_name: String): this (_name, 100
"town = "The Shire
{
{
} (<fun main (args: Array <String
("Player ("Madrigal
{

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

:So, initialization is done in the following order


The properties are created and initialized, embedded in the declaration of .1
. (chap- constructor (val health: Int
,"Class-level assignment operations are performed (val race = "DWARF .2
. (val town = "Bavaria", val name = _name
The init block is executed , assigning values to properties and calling .3
. ( functions (alignment = "GOOD", call println
The body of the auxiliary constructor is executed, in which the .4
assignment is
. ("property values and functions are called (town = "The Shire
The order of execution of the init block (point 3) and assignment operations
at the level class (item 2) depends on the order in which they are listed. If
,the init block place after class-level property assignments
.it will be executed after them
: Pay attention to the lack of initialization code for one of the properties - age
although it is declared and initialized, it is at the property level
class. This is due to the fact that it is assigned the value 0 (the default value
for a simple int type in Java), so you don't need to initialize it
explicitly, and the compiler just optimized the code by omitting unnecessary
.instructions

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
{
{

This is a useful tool, but should be used with care. If


a variable with late initialization will receive an initial value before the first
contacting her, there will be no problems. But if we refer to such a property
before it initialization, you will get a nasty UninitializedPropertyAcces exception
,sException . This problem can be solved by using a nullable type
,but then you have to handle possible null all over your code
which is very tiring. Having received the initial value, the variables from
.the later initialization will work the same way as other variables
The lateinit keyword acts like a tacit agreement: “I undertake to initiate
initialize this variable before the first attempt to access it. " Kotlin
provides a tool to check if such changes are initialized
The isInitialized method shown in the example above. You can call
isInitialized whenever there is any doubt that the variable lateinit initiated
. initialized to avoid UninitializedPropertyAccess Exception
However, isInitialized should be used sparingly - for example, not next
.You can add this check to every variable with late initialization
If you use isInitialized too often, it most likely means
.that it is better to use a nullable type

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

,class Player (_name: String


var healthPoints: Int = 100
val isBlessed: Boolean
} (val isImmortal: Boolean
var name = _name
"get () = "$ {field.capitalize ()} of $ hometown
} (private set (value
() field = value.trim
{
{() val hometown = by lazy {selectHometown
...
("private fun selectHometown () = File ("towns.txt
() readText.
("split ("\ n.
() shuffled.
() first.
{

This lambda expression returns the result of calling selectHometown , which is


. then hometown is assigned
The hometown property remains uninitialized until the first call
to him. At this point, the lambda code in lazy will be executed . note that
this code will be executed only once, on the first call to the delegated
property in the method reading the name property . All further references to
this
property will use the previously obtained result instead of repeated
.calculations
Lazy initialization is useful, but too verbose, so
.use it only when complex calculations are required
To summarize, we note that you've learned all about initializing objects in
.Kotlin
You will most often use the simplest way by calling the constructor
and getting a reference to an instance of the class for further work. But you
have and other options for initializing objects in Kotlin, and understanding
.these options helps you write more beautiful and efficient code
In the next chapter, you will learn about inheritance, that is, the principle
object-oriented programming that allows
.different classes share data and behavior

For the curious: underwater initiation


stones
Earlier in the chapter, you saw how important it is where blocks are
.declared
initialization. All properties used in a block must be initialized
before the declaration of the initialization block. The next example is
.covers this problem
} () class Player
} init
(val healthBonus = health.times (3
{
val health = 100
{
} (<fun main (args: Array <String
() Player
{
This code will not compile because health property is not initialized
before using it in the init block . As noted earlier, when the property
used inside init block , property initialization must happen
before contacting him. If health is declared before the initialization block, the
code
:will compile
} () class Player
val health = 100
} init
(val healthBonus = health.times (3
{
{
} (<fun main (args: Array <String
() Player
{

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
{

Another tricky scenario is shown in the following example, in which the


:initiation Two properties are being analyzed
} (class Player (_name: String
() val playerName: String = initPlayerName
val name: String = _name
private fun initPlayerName () = name
{
} (<fun main (args: Array <String
(println (Player ("Madrigal"). playerName
{

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
{

Quest: the riddle of Excalibur


In Chapter 12, you learned that you can define your own read methods for a
.property
and records. Now that you know how properties and their classes are
,initialized
.we have a riddle for you
Every great sword has a name. Declare a class named Sword in Kotlin
.REPL that confirms this

(Listing 13.13. Sword Declaration (REPL

} (class Sword (_name: String


var name = _name
"get () = "The Legendary $ field
} (set (value
() field = value.toLowerCase (). reversed (). capitalize
{
{

Which is the output of the following code, which instantiates Sword and
(.accesses to the name property ? (Try to answer before checking the REPL

(Listing 13.14. Name reference (REPL


("val sword = Sword ("Excalibur
(println (sword.name
? What will the following code display after changing name

(Listing 13.15. Reassign name (REPL


"sword.name = "Gleipnir
(println (sword.name

Finally, add an initialization block to Sword that initializes


. name
(Listing 13.16. Adding an Initialization Block (REPL

} (class Sword (_name: String


var name = _name
"get () = "The Legendary $ field
} (set (value
() field = value.toLowerCase (). reversed (). capitalize
{
} init
name = _name
{
{

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

Room class declaration


Start by creating a new file with the name NyetHack Room.kt . Room.kt will
-co
keep a new class named Room representing one square in a plane
coordinates of NyetHack. Later, you will declare a specific kind of room in
. the classroom, which inherits the characteristics of Room
The Room class will have one property, name, and two functions, description
and load . description returns a string describing the room. load returns a string
to output to the console after entering the room. These traits should have
.all rooms at NyetHack
.Add a Room class declaration to Room.kt as shown below

(Listing 14.1. Room class declaration (Room.kt

} (class Room (val name: String


"fun description () = "Room: $ name
"... fun load () = "Nothing much to see here
{

.To try out the new Room class , create a Room instance in main at startup
. game and output the result of the description function

(Listing 14.2. Room description output (Game.kt

} (<fun main (args: Array <String


("val player = Player ("Madrigal
() player.castFireball
("var currentRoom = Room ("Foyer
(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
{
...

:Start Game.kt . The following lines will appear in the console


(A glass of Fireball springs into existence. Delicious! (x2
Room: Foyer
... Nothing much to see here
(Aura: GREEN) (Blessed: YES)
!Madrigal of Tampa is in excellent condition

?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
{

Now that Room is open , create a TownSquare class in Room.kt by declaring


.subclassing it Room via the : operator , as in the following example
(Listing 14.4. TownSquare class declaration (Room.kt

} (open class Room (val name: String


"fun description () = "Room: $ name
"... fun load () = "Nothing much to see here
{
("class TownSquare: Room ("Town Square

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

} (open class Room (val name: String


"fun description () = "Room: $ name
"... fun load () = "Nothing much to see here
{
} ("class TownSquare: Room ("Town Square
"!override fun load () = "The villagers rally and cheer as you enter
{
When you override load , IntelliJ will complain by underlining
.(the override keyword (Figure 14.1
Figure: 14.1. load cannot be overridden

IntelliJ is right as always: there is a problem. The open keyword needs to be


mark not only the Room class , but also the load function so that it can be
.override
.Make the load function in the Room class available for override
(Listing 14.6. Open function declaration (Room.kt

} (open class Room (val name: String


"fun description () = "Room: $ name
"... open fun load () = "Nothing much to see here
{
} ("class TownSquare: Room ("Town Square
"!override fun load () = "The villagers rally and cheer as you enter
{

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

} (open class Room (val name: String


protected open val dangerLevel = 5
+ "fun description () = "Room: $ name \ n
"Danger level: $ dangerLevel"
"... open fun load () = "Nothing much to see here
{
} ("class TownSquare: Room ("Town Square
"!override fun load () = "The villagers rally and cheer as you enter
{

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

} (open class Room (val name: String


protected open val dangerLevel = 5
+ "fun description () = "Room: $ name \ n
"Danger level: $ dangerLevel"
"... open fun load () = "Nothing much to see here
{
} ("class TownSquare: Room ("Town Square
override val dangerLevel = super.dangerLevel - 3
"!override fun load () = "The villagers rally and cheer as you enter
{

,Subclasses can not only override properties and functions of superclasses


.but also declare yours
NyetHack's town square, for example, differs from other rooms by
the presence of bells announcing important events. Add to TownSquare
a private function ringBell and a private variable named bellSound . bellSound
contains a string that describes the ringing of bells, and ringBell called
in load , returns this line to announce your arrival at the city
.area
(Listing 14.9. Adding new property and function to subclass (Room.kt

} (open class Room (val name: String


protected open val dangerLevel = 5
+ "fun description () = "Room: $ name \ n
"Danger level: $ dangerLevel"
"... open fun load () = "Nothing much to see here
{
} ("class TownSquare: Room ("Town Square
override val dangerLevel = super.dangerLevel - 3
"private var bellSound = "GWONG
\ !override fun load () = "The villagers rally and cheer as you enter
" {() n $ {ringBell
"private fun ringBell () = "The bell tower announces your arrival. $ bellSound
{

TownSquare includes properties and functions announced in both TownSquare


and Room . However, Room does not contain all the properties and functions
. declared in TownSqua re , and therefore does not have access to ringBell
Test the load function by updating the currentRoom variable in Game.kt to
. create an instance of TownSquare
(Listing 14.10. Call subclass function implementation (Game.kt

} (<fun main (args: Array <String


("val player = Player ("Madrigal
() player.castFireball
() var currentRoom: Room = Room ("Foyer") TownSquare
(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
{
...
:Start Game.kt again. The following lines will appear in the console
(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

Note that the currentRoom variable in Game.kt is still of type


Room , despite the fact that the instance itself is of type TownSquare , and its
function load has changed significantly from the implementation in Room .
You clearly have specified a Room type for currentRoom so it can refer to a
room
-any type, even if you assign currentRoom to the object returned by the con
. the TownSquare structure
.Since TownSquare is a subclass of Room , this is a valid syntax
You can also define a subclass of a subclass by creating a deeper
hierarchy. If you create a Plazza class , inheriting TownSquare , then Plazza will
be TownSquare type and Room type . The depth of inheritance is limited only
(.common sense for organizing your code. (And of course, your imagination
Calling different versions of load , depending on the class of the object, is an
. example of an idea object-oriented programming called polymorphism
Polymorphism is an approach to simplifying the structure of a program. He
allowed reuse functions that describe common behaviors
,(groups of classes (for example, what happens when a player enters a room
,and also change the behavior for the unique needs of the class (for example
cheering crowd in TownSquare ). By defining the TownSquare class as a
, subclass of Room
you have declared a new load implementation that has overridden the Room
. version
Now, when calling the load method of the currentRoom object, the version will
be called load for TownSquare . Accordingly, no changes should be made to
Game.kt
.required
:Consider the following function header
(fun drawBlueprint (room: Room
drawBlueprint takes Room as a parameter. She can also accept
any Room subclass , because any subclass will have all the characteristics
teristics Room . Polymorphism allows you to write functions that matter
.only the capabilities of the class, not their implementation
Opening functions for overriding can be useful, but there is
and a side effect. When you override a function in Kotlin, override
the dividing function in the subclass is open to overriding by default
.( if the subclass itself is marked with the open keyword)
. What if it's undesirable? Let's take an example from TownSquare
Let's say you want any TownSquare subclass to be able to change its
.description
. Saniye description , but no way to download the load
Add the final keyword to disallow overrides
functions. Open TownSquare and add the final keyword to the definition
the load function so that no one can override the glee of residents when
.the hero comes to the town square

(Listing 14.11. Function declaration final (Room.kt

} (open class Room (val name: String


protected open val dangerLevel = 5
+ "fun description () = "Room: $ name \ n
"Danger level: $ dangerLevel"
"... open fun load () = "Nothing much to see here
{
} ("open class TownSquare: Room ("Town Square
override val dangerLevel = super.dangerLevel - 3
"private var bellSound = "GWONG
= () final override fun load
"{() The villagers rally and cheer as you enter! \ N $ {ringBell"
"private fun ringBell () = "The bell tower announces your arrival. $ bellSound
{

, 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

("var room = Room ("Foyer

. 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

() var townSquare = TownSquare


townSquare is TownSquare
true
Listing 14.16. TownSquare is Room (REPL) check
townSquare is Room
true

is of type TownSquare as well as Room type . Recall that it is this


townSquare
.the idea makes polymorphism possible
If you need to know the type of a variable, then type checking is the
simplest
decision. Using type checking and conditional statements, you can organize
branch logic. But don't forget how polymorphism can
.influence this logic
For example, create a when expression in the Kotlin REPL that returns Room
.or TownSquare depending on the type of the variable

(Listing 14.17. Type checking on conditional branch (REPL

() var townSquare = TownSquare


} (var className = when (townSquare
"is TownSquare -> "TownSquare
"is Room -> "Room
() else -> throw IllegalArgumentException
{
(print (className

The first condition in the when expression is determined to be true because


townSquare is a type of TownSquare . The second condition is also true, because
townSquare is also of type Room . But it doesn't matter to you anymore, because
the first condition has already been satisfied and the second will simply not
.be checked
. Run this code and TownSquare line will appear in the console
.Now let's swap the branches
(Listing 14.18. Reversed type checking (REPL

() var townSquare = TownSquare


} (var className = when (townSquare
"is TownSquare -> "TownSquare
"is Room -> "Room
"is TownSquare -> "TownSquare
() else -> throw IllegalArgumentException
{
(print (className

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
{

The as operator denotes a cast. He tells us: "To fulfill


this is an expression, any variable should be considered an instance of the
Room type. " You- The expression in this case is a reference to the name
property of the Room class , the value which is compared to the string "Fount
."of Blessings
:Typecasting is power, and with power comes responsibility
you need to take it carefully. An example of a safe application can be
serve to cast a value of type Int to a more precise numeric type, such as
. example Long
?Casting to printIsSourceOfBlesings works, but it's not safe. Why
Room , Player , TownSquare - these are all classes in NyetHack, so you can
. Assume that if any is of type other than Player , then it is of type Room
At the moment it is. But what happens if we add a new class
?at NyetHack
,Your cast will fail if the type of the instance is incompatible with the type
to which it must be led. For example, String has nothing to do
, with Int , so casting String to Int will throw a ClassCastException
which can cause the program to crash. (Remember that casting and
transforming development is two different things. Some strings can be
converted to integers numbers, but the string type String cannot be cast to a
numeric type, Int ). The cast operator allows you to try to cast any change
to any type, but responsibility for the result of casting is completely
lies with you. If a cast operation might be unsafe, then
,Include it in a try / catch block . However, it is best to avoid type casts
.if you are not sure if it will be successful

Clever type casting


One way to make sure that the cast is successful is to
first check the type of the variable being reduced. Let's go back to the first
: branches of the conditional statement in printIsSourceOfBlesings
} (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

For the curious: Any


When displaying the value of a variable to the console, a function named
toString to determine how the value looks in the console. For some
types it is simple: for example, the value of a string is expressed through the
. value of a String
.But for other types it is not so obvious
Any includes abstract definitions of functions like toString , which are
.held by the target platform where the project is running
:Looking at the source code of the Any class , you can see the following
** /
.The root of the class hierarchy in Kotlin *
.All classes in Kotlin inherit the [Any] superclass *
/*
} public open class Any
public open operator fun equals (other: Any?): Boolean
public open fun hashCode (): Int
public open fun toString (): String
{

.Note that there is no toString function in the class declaration


But where is it then and what does it return when called, for example, for
? a Player instance
Remember the last line in printIsSourceOfBlesings , which outputs
:result to console
} (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
{

The result of calling printIsSourceOfBlesings , if passed to it, is


:foot player looks something like this
Player @ 71efa55d is a source of blessings: true
Player @ 71efa55d is the result of the default toString implementation in the class
Any . Kotlin uses the java.lang.Object.toString implementation from the JVM
because that you chose the JVM to compile. You can override toString
.in your Player class to return something more readable
.The Any type is one way for Kotlin to remain platform agnostic
It serves as an abstraction representing a typical superclass for each
platform specific such as JVM. That is, when the target platform
. is JVM, java.lang.Object implementation is chosen for toString in Any
toString , but when compiled to JavaScript, its implementation will be
different. It abstract explanation means that you do not need to know the
specifics of each
the system on which you will run your code. Instead, you can simply
. rely on type Any
Objects 15
In the last three chapters, you learned how to apply object-oriented
.programming to build meaningful relationships between objects
Despite the variety of initialization options, all classes with which
you worked up to this point were declared with the class keyword . In this
In this chapter we will get acquainted with object declarations , as well as
.with other types
classes: nested classes, data classes, enumerations . How are you
you will see further, each of them has its own declaration syntax and is
unique
.characteristics
By the end of the chapter, your character will be able to move from room to
room and
the world of NyetHack thanks to your contribution to the game. We also
organize sales
gram and prepare it for the improvements that will be made in the next
.chapters

The object keyword


In Chapter 13, you learned how to design classes. Return class constructor
defines an instance of the class, and you can call the constructor any
.number of times,to create any number of instances
For example, NyetHack can have any number of players, since the
competition
The Player structure can be called as many times as you like. For Player it is
makes sense as the world of NyetHack is large enough to accommodate
.multiple players
But imagine that you decide to create a Game class that will monitor
the state of the game. The presence of several instances of the Game class in
the game is not preferably, because everyone can store their own state,
.which, most likely, will result in a lack of synchronization between them
If you need one instance with a consistent state, there is
. for the duration of the program, announce a single tone
An instance of such a class will be created automatically on the first call
to him. This copy will exist for the entire duration of the work
grams, and with each next call, the original
.original copy
There are three ways to use the object keyword : to create ads
objects (singletons), anonymous objects, and helper objects . we
we will outline the boundaries of application of each of them in the
.following sections
Object declarations
-Object declarations (singletons) are useful for organizing and managing co
standing, especially when it is necessary to maintain some state for
. work of the program. To do this, we will declare a Game object
Declaring the Game class as a singleton will also serve as a convenient tool
.for us
stom to define the game loop and remove it from the main function
in Game.kt . Dividing code into classes and declaring objects even more
contributes to your goal of keeping the code organized
.bathroom condition
.Declare a Game object in Game.kt using object declarations
(Listing 15.1. Game object declaration (Game.kt

} (<fun main (args: Array <String


...
{
} (private fun printPlayerStatus (player: Player
+ "({() println ("(Aura: $ {player.auraColor
("({" Blessed: $ {if (player.isBlessed)" YES "else" NO)"
("{() println ("$ {player.name} $ {player.formatHealthStatus
{
} object Game
{

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

} (<fun main (args: Array <String


...
{
} (private fun printPlayerStatus (player: Player
+ "({() println ("(Aura: $ {player.auraColor
("({" Blessed: $ {if (player.isBlessed)" YES "else" NO)"
("{() println ("$ {player.name} $ {player.formatHealthStatus
{
} object Game
} init
(".println ("Welcome, adventurer
{
{

.Start Game.kt . No greeting is displayed because Game was not initialized


socialized. And it is not initialized due to the fact that we have not yet
.applied
To refer to an object, you need to refer to one of its properties, or
functions. To start the initialization of the Game , we declare and call the
.function named play . play will serve as home to the NyetHack game loop
Add play to Game and call it from main . The object function is called with
using the name of the object in which it is declared, and not an instance of
.the class, as is the case with class functions

(Listing 15.3. Calling a function from an object declaration (Game.kt

} (<fun main (args: Array <String


...
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
} init
(".println ("Welcome, adventurer
{
} () fun play
} (while (true
Gameplay NyetHack //
{
{
{

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

} (<fun main (args: Array <String


(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
() Game.play
{
} object Game
("private val player = Player ("Madrigal
() private var currentRoom: Room = TownSquare
} init
(".println ("Welcome, adventurer
() player.castFireball
{
} () fun play
} (while (true
Gameplay NyetHack //
(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
{
{
} (private fun printPlayerStatus (player: Player
+ "({() println ("(Aura: $ {player.auraColor
("({" Blessed: $ {if (player.isBlessed)" YES "else" NO)"
("{() println ("$ {player.name} $ {player.formatHealthStatus
{
{

If you start Game.kt right now, it will repeat endlessly, because


that the cycle is not interrupted by anything. The last step, at least for now,
is
. accepting user input from the console using the readLine function
You may remember this function: in Chapter 6, it stopped execution
and waited for user input. She will continue execution as soon as
.will get a carriage return character
.Add a readLine call to your game loop to accept user input
(Listing 15.6. Receiving user input (Game.kt
...
} object Game
...
} () fun play
} (while (true
(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
(":print ("> Enter your command
("{() println ("Last command: $ {readLine
{
{
...
{

:Try starting Game.kt and entering the following command


.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: fight <
Last command: fight
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 <

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
{

.This anonymous object is a subclass of TownSquare where no one greets you


In the body of this declaration, you can override properties and functions
that are declared introduced in TownSquare , and add new features and
functions to
.define the data and behavior of the anonymous class
This class follows the rules of the object keyword , in the sense that one
only one instance can temporarily exist, but its scope
much narrower than the scope of a named singleton. Weak side
-anonymous class is its dependence on the place of declaration. If ano
,the small class is declared at the file level, it is initialized immediately
.if inside another class - simultaneously with this class

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
{
{

. Next, add another function to GameInput called processCommand


processCommand must return the result of a when expression , which depends
from the command entered by the user. To avoid confusion, remember to
. convert all letters in the command to lowercase by calling toLowerCase
(Listing 15.9. ProcessCommand function 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
} (() fun processCommand () = when (command.toLowerCase
() else -> commandNotFound
{
private fun commandNotFound () = "I'm not quite sure what you're trying to
" !do
{
{
Now is the time to use GameInput . Replace the readLine call in Game.play
. to call the GameInput class

(Listing 15.10. Using GameInput (Game.kt


...
} object Game
...
} () fun play
} (while (true
(() println (currentRoom.description
(() println (currentRoom.load
Player state //
(printPlayerStatus (player
(":print ("> Enter your command
("{() println ("Last command: $ {readLine
(() println (GameInput (readLine ()). processCommand
{
{
...
{

. Start Game.kt . Now commandNot will be called in response to any input


: Found
.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: fight <
!I'm not quite sure what you're trying to do
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 <
This is progress: we have limited the range of allowed commands to a small
,one (for now
stym) white list. Later in this chapter, you add the move command , and then
.GameInput will become a little more useful
But before you can navigate the world of NyetHack, your hero
.you need a world in which there is something else besides the city square

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

x , Int val , is defined in the main constructor and represents the ❍


coordinate
; tu x
y , Int val , is defined in the main constructor and represents the ❍
coordinate
; tu y
isInBounds , Boolean val , a sign that both coordinates are positive ❍
.solid
(Listing 15.11. Data class declaration (Navigation.kt

} (data class Coordinate (val x: Int, val y: Int


val isInBounds = x> = 0 && y> = 0
{
-Coordinates should never have a value less than 0, so you add
add a property to the class that makes sure that their values do not come out
beyond the permissible limits. Later we will check the isInBounds property
before updating currentRoom to make sure Coordinate is
allowed direction to move. For example, if the player, being at
top edge of the map, will try to move up, then check isInBounds
.should prevent it
To track the player's position on the world map, add a property with the
name
. It currentPosition in class Player Get

(Listing 15.12. Player position tracking (Player.kt

,class Player (_name: String


,var healthPoints: Int = 100
,val isBlessed: Boolean
} (private val isImmortal: Boolean
var name = _name
"get () = "$ {field.capitalize ()} of $ hometown
} (private set (value
() field = value.trim
{
{() val hometown by lazy {selectHometown
(var currentPosition = Coordinate (0, 0
...
{

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

, Since x and y are properties declared in the main Coordinate constructor


they are used to represent the Coordinate in text form. ( isInBounds
it is not included here because it was not declared in the main constructor
Coordinate .) The implementation of toString in data classes is more useful
. than the implementation of The default setting is Any

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

In this example, x is set to 1, since component1 returns the value


the first property declared in the main constructor Coordinate . u has
the value 0 because component2 returns the second property declared
. in the main constructor Coordinate
These features add weight to the use of data classes to express
,simple objects containing information, such as Coordinate . Classes
which are often compared or copied and whose content is displayed
.to the screen are especially suitable for creating data classes
:However, there are some limitations for data classes. Data classes

;must have at least one parameter in the main constructor ❍


; require main constructor parameters to be declared as var or val ❍
. cannot have abstract , open , sealed , inner modifiers ❍

, If your class doesn't need toString , copy , equals or harsCode functions


then declaring it as a data class has no benefit. If
you need your own equals implementation using only certain properties
data for comparison, not all, data classes will not work for you, because
they
. include all properties in the automatically generated equals function
You will learn about overriding equals and other functions in your types later
in the same chapter in the section "Operator overloading", and about the
quick method, supplied by IntelliJ to override equals - in the section "For the
:curious
." algebraic data types
Enumerations
Enumerations , or enum, are a special type of class useful for declaring
. Leaving a collection of constants known as enumerated types
In NyetHack, you will use an enum to represent multiple
of the four directions available for the player to move, that is, four
. cardinal points. To do this, add an enumeration named Direction to Navigator.kt

(Listing 15.13. Enum declaration (Navigator.kt

} enum class Direction


,NORTH
,EAST
,SOUTH
WEST
{
} (data class Coordinate (val x: Int, val y: Int
val isInBounds = x> = 0 && y> = 0
{

Enumerations are more descriptive than other types of constants, such as


.strings
Refer to enumerated types using the enumeration class name, period
:and the type name, for example like this
Direction.EAST

.Enumerations can express more than just named constants


To use Direction to represent the direction of movement
in NyetHack, define for each type in Direction the corresponding
.changing the Coordinate when the player takes a step
Movement in the world should change the x and y coordinates of the player
in accordancewith the direction of movement. For example, if a player takes
a step to the east, the ordinate of x should increase by 1 and y by 0. If the
player steps south, the x coordinate should increase by 0 and the y
.coordinate by 1
Add a main constructor to the Direction enumeration that declares property
GUSTs coordinate of . Since you added the parameter to the enum constructor,
you will have to call it in the declaration of each enumerated type in Direction
. and pass the appropriate Coordinate value
(Listing 15.14. Enum constructor declaration (Navigator.kt

} (enum class Direction (private val coordinate: Coordinate


,((NORTH (Coordinate (0, -1
,((EAST (Coordinate (1, 0
,((SOUTH (Coordinate (0, 1
((WEST (Coordinate (-1, 0
{
} (data class Coordinate (val x: Int, val y: Int
val isInBounds = x> = 0 && y> = 0
{

.Enumerations, like other classes, can contain function declarations


Add a function called updateCoordinate to Direction that will change
determine the position of the player depending on his direction of
movement (pay note: you need to add a semicolon separating type
declarations
.(enums from function declaration

(Listing 15.15. Function declaration in enum (Navigator.kt

} (enum class Direction (private val coordinate: Coordinate


,((NORTH (Coordinate (0, -1
,((EAST (Coordinate (1, 0
,((SOUTH (Coordinate (0, 1
;((WEST (Coordinate (-1, 0
= (fun updateCoordinate (playerCoordinate: Coordinate
,Coordinate (playerCoordinate.x + coordinate.x
(playerCoordinate.y + coordinate.y
{
} (data class Coordinate (val x: Int, val y: Int
val isInBounds = x> = 0 && y> = 0
{

Functions should be called on enumerated types, not on the class itself


:enums, so a call to updateCoordinate would look like this
((Direction.EAST.updateCoordinate (Coordinate (1, 0
Operator overloading
You already know that the built-in types of the Kotlin language support
many
operations and some even adapt these operations depending on the
the data they supply. For example, the equals function and its associated
operator torus == . They can be used to check whether two instances of a
numeric type, whether the strings contain the same character set and
whether the values are properties of two instances of the data class declared
in the main constructor re equivalent. Similarly, the plus function and the +
operator add two numbers, add one line to the end of another, and add items
from one
.list to another
When working with custom types, the Kotlin compiler does not always
know how apply built-in operators to them. How, for example, compare two
copies
Play Player ? If you want to use built-in operators with your own
types, override operator functions to tell the compiler
.what it should do with your types. This process is called overloading
. operators
You saw extensive use of operator overloading in Chapter 10 and
ve 11. Instead of calling get to retrieve an item from the list, you use
used the index access operator [] . Kotlin syntax concise
[relies on little conveniences like spellList [3
. [instead of spellList.get [3

is a clear candidate for improvement through operator


Coordinate
overloading. You
move the hero around the world by adding the properties of two instances
of Coordinate
together. Instead of describing this work in Direction , you can overload
. plus operator for Coordinate
. Let's do this in Navigator.kt : add a function with the operator modifier

(Listing 15.16. Plus operator overload (Navigator.kt

} (enum class Direction (private val coordinate: Coordinate


,((NORTH (Coordinate (0, -1
,((EAST (Coordinate (1, 0
,((SOUTH (Coordinate (0, 1
;((WEST (Coordinate (-1, 0
= (fun updateCoordinate (playerCoordinate: Coordinate
,Coordinate (playerCoordinate.x + coordinate.x
(playerCoordinate.y + coordinate.y
{
} (data class Coordinate (val x: Int, val y: Int
val isInBounds = x> = 0 && y> = 0
(operator fun plus (other: Coordinate) = Coordinate (x + other.x, y + other.y
{

-Now just use the addition operator ( + ) to add one eq


. Coordinate instance to another. Let's do it in Direction

(Listing 15.17. Using the overloaded operator (Navigator.kt

} (enum class Direction (private val coordinate: Coordinate


,((NORTH (Coordinate (0, -1
,((EAST (Coordinate (1, 0
,((SOUTH (Coordinate (0, 1
;((WEST (Coordinate (-1, 0
= (fun updateCoordinate (playerCoordinate: Coordinate
,Coordinate (playerCoordinate.x + coordinate.x
(playerCoordinate.y + coordinate.y
coordinate + playerCoordinate
{
} (data class Coordinate (val x: Int, val y: Int
val isInBounds = x> = 0 && y> = 0
(operator fun plus (other: Coordinate) = Coordinate (x + other.x, y + other.y
{

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
.

Explore the world of NyetHack


Now that you have built the game loop and set up the coordinate system,
you need to It's time to apply your knowledge and add more rooms to
explore
.at NyetHack
-To customize the world map, you need a list that includes all the com
no misters. Moreover, since the player can move in two dimensions, you
you need a list containing two other lists inside. First list
,(rooms will include, from east to west, the town square (start of the game
tavern and tavern back room. The second list of rooms will include a
corridor
and just a room. These lists will be stored in a third list named
. worldMap representing the y coordinate
Add the worldMap property to the Game with a set of rooms for the hero to
.explore
(Listing 15.18. Building a world map NyetHack (Game.kt
...
} object Game
("private val player = Player ("Madrigal
() private var currentRoom: Room = TownSquare
) private var worldMap = listOf
,(("listOf (currentRoom, Room ("Tavern"), Room ("Back Room
((("listOf (Room ("Long Corridor"), Room ("Generic Room
...
{
.In fig. 15.1 shows a grid of rooms that can be visited in NyetHack
Figure: 15.1. NyetHack World Map
-Now that the rooms are in place, it's time to add a command to re
premises and give the player the ability to move around the world. Add the
function
. a function named move that takes the direction of travel as a String
A lot happens in move ; we will tell you about it after you write
.send the code
(Listing 15.19. Move function declaration (Game.kt
...
} object Game
() private var currentRoom: Room = TownSquare
("private val player = Player ("Madrigal
) private var worldMap = listOf
,(("listOf (currentRoom, Room ("Tavern"), Room ("Back Room
((("listOf (Room ("Long Corridor"), Room ("Generic Room
...
= (private fun move (directionInput: String
} try
(() val direction = Direction.valueOf (directionInput.toUpperCase
(val newPosition = direction.updateCoordinate (player.currentPosition
} (if (! newPosition.isInBounds
(".throw IllegalStateException ("$ direction is out of bounds
{
[val newRoom = worldMap [newPosition.y] [newPosition.x
player.currentPosition = newPosition
currentRoom = newRoom
"{() OK, you move $ direction to the $ {newRoom.name}. \ N $ {newRoom.load"
} (catch (e: Exception {
".Invalid direction: $ directionInput"
{
{
. move returns a String depending on the result of the try / catch statement
In the try block we call the valueOf function to find a match with the input
user. The valueOf function is available for all enumeration classes and
rotates an enumerated type with a name that matches the passed string
. value. For example, calling Direction.valueOf ("EAST") will return Direction.EAST
,If you pass a value that does not match any of the enumerated types
. an IllegalArgumentException will be thrown
The exception will be handled by the catch block (moreover, this is how it will
be handled
.( any exception thrown in the try block
If the call to valueOf succeeds, then a check will be performed
the player's location within the boundaries of the world. If he went beyond
the boundaries, there will be
. an IllegalStateException is thrown , which will also process the catch block
If the player has moved in the allowed direction, then the next step is
ask worldMap for a room in a new position. You've already seen how to receive
values from the collection by index in chapter 10, and here you just repeat
this
action twice. The first index, worldMap [newPosition.y] , returns a list
from a list named worldMap . The second index, [newPosition.x] , returns the
-com
natu inside the second list. If there is no room with such coordinates, then
an ArrayIndexOutOfBoundsException is thrown and yes, it is handled as well
. with a catch block
If all the code succeeds , then the player's currentPosition property
will update and the text that is part of the interface will be sent to the
console
.NyetHack
The move function should only be called when the player enters the "move"
command, which we are now implementing using the GameInput class we
wrote earlier
.in this chapter

(Listing 15.20. ProcessCommand function 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
} (() fun processCommand () = when (command.toLowerCase
(move" -> move (argument"
() else -> commandNotFound
{
private fun commandNotFound () = "I'm not quite sure what you're
" !trying to do
{
{

: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

For the curious: announcement


structural comparison
: Imagine a Weapon class that has name and type properties
(open class Weapon (val name: String, val type: String
Let's say you want the equality operator ( == ) to count two different
an instance of a weapon is structurally equivalent if the values of their
properties name and type are structurally equal. By default, as stated earlier in
this
chapter, the == operator tests for reference equality, so the following
expression
: will return false
(open class Weapon (val name: String, val type: String
// (("println (Weapon ("ebony kris", "dagger") == Weapon ("ebony kris", "dagger
False

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

For the curious: algebraic


data types
Algebraic data types (Algebraic Data Types, ADT) allows the expression
closed set of possible subtypes that can be associated
.with the given type. Enumerations are the simplest form of ADT
,Imagine a Student class that has three associative states
depending on the enrollment status: NOT_ENROLLED (not enrolled), ACTIVE
.((enrolled) and GRADUATED (released
Using the enumeration class that you learned about in this chapter, you can
:divide these three states for the Student class as follows
} enum class StudentStatus
,NOT_ENROLLED
,ACTIVE
GRADUATED
{
(class Student (var status: StudentStatus
} (<fun main (args: Array <String
(val student = Student (StudentStatus.NOT_ENROLLED
{

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

The compiler will be satisfied if all types are explicitly referenced, or


: they are specified in the else branch

} fun studentMessage (status: StudentStatus): String


} (return when (studentStatus
".StudentStatus.NOT_ENROLLED -> "Please choose a course
"!StudentStatus.ACTIVE -> "Welcome, student
"!StudentStatus.GRADUATED -> "Congratulations
{
{
For more complex ADTs, you can use sealed classes that
some allow for more sophisticated declarations. Isolated
classes allow you to define ADTs, similar to enums, but with a larger
.control over subtypes
.For example, an admitted student must also have a student ID card
It is possible to add a ticket property to an enumeration, but it is only
needed for
case ACTIVE , in other cases it creates two undesirable states
:yaniya null for property
} enum class StudentStatus
,NOT_ENROLLED
,ACTIVE
;GRADUATED
var courseId: String? = null // Used only for the ACTIVE state
{
-The best solution would be to use an isolated class for the model
:the state of the student
} sealed class StudentStatus
() object NotEnrolled: StudentStatus
() class Active (val courseId: String): StudentStatus
() object Graduated: StudentStatus
{
-The isolated StudentStatus class has a limited number of sub
. classes that must be declared in the same file as StudentStatus
Otherwise, it will be unusable for subclassing. Announcement
-an isolated class instead of an enumeration to express possible con
student states allows you to specify a limited set of states that the compiler
,(will be able to check in the when clause (as in the case of an enumeration
.but gives more control over the declaration of subclasses
The object keyword is used for states when a student
there is no ticket, since there will be no variations of these states, and the
class keyword used for class ACTIVE , because it will have different states, so
.how the student ID number will change from student to student
Using the new isolated class in when will allow you to read
:courseID ticket number from ACTIVE class via smart casting
} (<fun main (args: Array <String
(("val student = Student (StudentStatus.Active ("Kotlin101
(studentMessage (student.status
{
} fun studentMessage (status: StudentStatus): String
} (return when (status
"!is StudentStatus.NotEnrolled -> "Please choose a course
"{is StudentStatus.Active -> "You are enrolled in: $ {status.courseId
"!is StudentStatus.Graduated -> "Congratulations
{
{

Assignment: Quit Command


Players will most likely want to complete NyetHack at some point, but
now the game does not allow it. Your job is to fix this. When using
tel enters "quit" or "exit", the game NyetHack should output a farewell
message and shut down. Hint: remember that at the moment
your while loop runs indefinitely. The essence of this task is
.to end the loop under a certain condition
Quest: Implementing a World Map
Do you remember how at the very beginning we said that NyetHack will
!not contain ASCII graphics? After completing this task will be
Players can sometimes get lost in the vast world of NyetHack, but luckily
you have the strength to give them a magic kingdom card. Implement
the "map" command, which displays the player's current position in the
world. For the player
:in a tavern, the output should be like this
Enter your command: map <
OXO
OO
.X represents the room the player is currently in

Assignment: ring the bell


Add the "ring" command to NyetHack so the player can ring the bell on
.city square any number of times
.Hint: the ringBell function must be declared public

You might also like