1. Kotlin Programming Flashcards

1
Q

What is Kotlin?

A

Kotlin is not just a better language to write code to run on the Java Virtual Machine. It is a multiplatform language that aims to be general purpose: Kotlin can be used to write native macOS and Windows applications, JavaScript applications, and, of course, Android applications. Platform independence means that Kotlin has a wide variety of uses.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

How IntelliJ compiles the Kotlin code?

A

IntelliJ compiles the Kotlin code using the kotlinc-jvm compiler. This means IntelliJ translates the Kotlin code you wrote into bytecode, the language the JVM “speaks.”

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

What is a REPL?

A

REPL is short for “read, evaluate, print, loop.”

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

What does mean targeting JVM?

A

The JVM is a piece of software that knows how to execute a set of instructions, called bytecode. “Targeting the JVM” means compiling, or translating, your Kotlin source code into Java bytecode, with the intention of running that bytecode on the JVM

Each platform, such as Windows or macOS, has its own instruction set. The JVM acts as a bridge between the bytecode and the different hardware and software environments the JVM runs on, reading a piece of bytecode and calling the corresponding platform-specific instruction(s) that map to that bytecode. Therefore, there are different versions of the JVM for different platforms. This is what allows Kotlin developers to write platform-independent code that can be written one time and then compiled into bytecode and executed on different devices regardless of their operating systems.

Since Kotlin can be converted to bytecode that the JVM can execute, it is considered a JVM language. Java is perhaps the most well-known JVM language, because it was the first. However, other JVM languages, such as Scala and Kotlin, have emerged to address some shortcomings of Java from the developer perspective.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Is Kotlin limited to JVM?

A

Kotlin is not limited to the JVM, however. At the time of this writing, Kotlin can also be compiled into JavaScript or even into native binaries that run directly on a given platform – such as Windows, Linux, and macOS – negating the need for a virtual machine layer.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Why we specify data types and what that does tell to compiler?

A

Variables and constants have a data type that you specify. The type describes the data that is held by a variable or constant and tells the compiler how type checking will be handled, a feature in Kotlin that prevents the assignment of the wrong kind of data to a variable or constant.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

What is static type system and static type checking?

A

Kotlin uses a static type system – meaning the compiler labels the source code you define with types so that it can ensure the code you wrote is valid. IntelliJ also checks code as you type it and notices when an instance of a particular type is incorrectly assigned to a variable of a different type. This feature is called static type checking, and it tells you about programming mistakes before you even compile the program.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Commonly used built-in types in Kotlin?

A
String - Textual data,
Char - Single character,
Boolean - True/false value,
Int - Whole numbers,
Double - Decimal numbers,
List - Collection of elements,
Set - Collection of unique elements,
Map - Collection of key-value pairs
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

What is a Type Inference?

A

Kotlin includes a feature called type inference that allows you to omit the type definition for variables that are assigned a value when they are declared.

Type inference helps keep code clean, concise, and easier to modify as your program changes.

If you ever have a question about the type of a variable, click on its name and press Control-Shift-P. IntelliJ will display its type

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

What is compile time constants?

A

A compile-time constant must be defined outside of any function, including main, because its value must be assigned at compile time (that is, when the program compiles) – hence the name. main and your other functions are called during runtime (when the program is executed), and the variables within them are assigned their values then.

Compile-time constants also must be of one of the following basic types: String, Int, Double, Float, Long, Short, Byte, Char, Boolean.

Prepending a val with the const modifier tells the compiler that it can be sure that this val will never change.

This gives the compiler the flexibility to perform optimization behind the scenes.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

How you can inspect Kotlin Bytecode?

A

press the Shift key twice to open the Search Everywhere dialog. Begin entering “show kotlin bytecode” in the search box, and select Show Kotlin Bytecode from the list of available actions when it appears.

You can also open tool window with Tools -> Kotlin -> Show Kotlin Bytecode.

You can translate the bytecode back to Java to see it represented in terms you may be more familiar with. In the bytecode tool window, click the Decompile button at the top left.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Java Primitive Types in Kotlin?

A

In Java, there are two kinds of types: reference types and primitive types. Reference types are defined in source code: A matching source code definition corresponds to the type. Java also offers primitive types (often called just “primitives”), which have no source file definition and are represented by special keywords instead.

A reference type in Java always begins with a capital letter, indicating that it is backed by a source definition for its type.

A Java primitive type starts with a lowercase letter.

All primitives in Java have a corresponding reference type. (But not all reference types have a corresponding primitive type.)

One reason for choosing a reference type is that there are certain features of the Java language that are only available when using reference types. Generics, for example, do not work with primitives. Reference types can also work with the object-oriented features of Java more readily than Java primitives.

On the other hand, primitives offer better performance and some other perks.

Unlike Java, Kotlin provides only one kind of type: reference types.

Kotlin made this design decision for several reasons. First, if there is no choice between kinds of types, you cannot code yourself into a corner as easily as you can with multiple kinds to choose from. For example, what if you define an instance of a primitive type, then realize later that you need to use the generic feature, which requires a reference type? Having only reference types in Kotlin means that you will never encounter this problem.

If you are familiar with Java, you may be thinking, “But primitives offer better performance than reference types!” This is true.

The Kotlin compiler will, where possible, use primitives in the Java bytecode, because they do indeed offer better performance.

Kotlin gives you the ease of reference types with the performance of primitives under the hood. In Kotlin you will find a corresponding reference type for the eight primitive types you may be familiar with in Java.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

What is structural equality operator?

A

You express this with the ==.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

What is called string concatenation?

A

Using the addition operator (+) to append a value to a string is called string concatenation.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

How to evaluates whether the two instances point to the same reference?

A

You express this with the ===.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

What is if else statement?

A
if (condition) {
    // expression
} else {
    // expression
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

Kotlin logical operator?

A

Logical operators allow you to combine comparison operators into a larger statement.

&& - Logical ‘and’: true if and only if both are true (false otherwise).

|| - Logical ‘or’: true if either is true (false only if both are false).

! - Logical ‘not’: true becomes false, false becomes true.

One note: When operators are combined, there is an order of precedence that determines what order they are evaluated in. Operators with the same precedence are applied from left to right. You can also group operations by surrounding the operators that should be evaluated as a group in parentheses.

Here is the order of operator precedence, from highest to lowest:
! (logical ‘not’)

< (less than), <= (less than or equal to), > (greater than), >= (greater than or equal to) == (structural equality), != (non-equality)

&& (logical ‘and’)

|| (logical ‘or’)

Logical operators are not only for conditionals. They can be used in many expressions, including in the declaration of a variable.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

What is conditional expressions?

A

A conditional expression is like a conditional statement, except that you assign the if/else to a value that you can use later.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

When is acceptable to removing braces from if/else expressions?

A

In cases where you have a single response for the matching condition, it is valid (at least, syntactically – more on that shortly) to omit the curly braces wrapping the expression. You can only omit the {}s when a branch contains only one expression – omitting them from a branch with more than one expression will affect how the code is evaluated.

We recommend that you do not omit braces for conditional statements or expressions that span more than one line.

Often, for one-line expressions, removing the curly braces is more readable.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

Kotlin Ranges?

A

The .. operator, as in in 1..5, signals a range. A range includes all values from the value on the left of the .. operator to the value on the right, so 1..5 includes 1, 2, 3, 4, and 5. Ranges can also be a sequence of characters.

You use the in keyword to check whether a value is within a range. Refactor your healthStatus conditional expression to use ranges rather than comparison operators.

In addition to the .. operator, several functions exist for creating ranges. The downTo function creates a range that descends rather than ascends, for example. And the until function creates a range that excludes the upper bound of the range specified.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

When expression?

A

The when expression is another control flow mechanism available in Kotlin. Like if/else, the when expression allows you to write conditions to check for and will execute corresponding code when the condition evaluates as true. when provides a more concise syntax and is an especially good fit for conditionals with three or more branches.

By default, a when expression behaves as though there were a == equality operator between the argument you provide in parentheses and the conditions you specify in the curly braces.

A when expression works similarly to an if/else expression in that you define conditions and branches that are executed if a condition is true. when is different in that it scopes the lefthand side of the condition automatically to whatever you provide as an argument to when.

when expressions also support greater flexibility than if/else statements in how they match branches against the conditions you define.

val healthStatus = when (healthPoints) {
    100 -> "excellent"
    in 90..99 -> "has a few scratches"
    else -> "bla bla"
}
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

String templates?

A

Kotlin features string templates to aid in this common need and, again, make your code more readable. Templates allow you to include the value of a variable inside a string’s quotation marks.

You added the values of variable display string by prefixing each with $. This special symbol indicates to Kotlin that you would like to template a val or var within a string you define, and it is provided as a convenience. Note that these templated values appear inside the quotation marks that define the string.

val name = “Pera”
println(“Name is $name”)

Kotlin also allows you to evaluate an expression within a string and interpolate the result – that is, to insert the result into the string. Any expression that you add within the curly braces after a dollar-sign character (${}) will be evaluated as a part of the string.

val numberOne = 1
val numberTwo = 2

println(“Sum is${numberOne + numberTwo}”)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

What is a function?

A

A function is a reusable portion of code that accomplishes a specific task. Functions are a very important part of programming. In fact, programs are fundamentally a series of functions combined to accomplish more complex tasks.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

How to extract code to function?

A

There is two (three) options:

  1. Right click on the code you selected and choose Refactor -> Extract -> Function …
  2. Use shortcut: Control + Command + M
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
25
Q

Anatomy of a Functions & Function Scope?

A

A function consists of a function header and function body.

The first part of a function is the function header. The function header is made up of five parts: the visibility modifier, function declaration keyword, function name, function parameters, and return type.

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

private - Visibility modifier: By default, a function’s visibility is public – meaning that all other functions (including functions defined in other files) can use the function. In other words, if you do not specify a modifier for the function, the function is considered public.

function parameters - specify the name and type. Note that function parameters are always read-only – they do not support reassignment within the function body. In other words, within the body of a function, a function parameter is a val, instead of a var.

Function body is where the acton the function performs takes place. The return keyword indicates to the compiler that the function has finished its work and is ready to return its output data.

{
return healthStatus
}

Function scope

Local variable - exists only in the function’s body. You can think of scope as the lifespan for a variable. The same is true of the function parameters - exists within the scope of the function body and cease to exist once the function completes.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
26
Q

What is a “Calling a Function”?

A

Function call triggers the function to perform whatever actions are defined in its body. You call a function with its name, along with data to satisfy any parameters required by the function header.

The inputs are called arguments, and providing them to the function is called passing in arguments.

(A note about the terminology: While technically a parameter is what a function requires and an argument is what the caller passes in to fulfill the requirement, you will hear the two terms used interchangeably.)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
27
Q

Functions - Default Arguments?

A

Sometimes an argument for a function has a “usual” value. you can assign a default value for a parameter that will be assigned if no argument is specified.

private fun castFireball(numFireballs: Int = 2) { … }

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
28
Q

Single - Expression Functions?

A

Kotlin allows you to reduce the amount of code required to define a function, function that has only one expression – that is, one statement to be evaluated. For single-expression functions, you can omit the return type, curly braces, and return statement.

private fun formatHealthStatus(healthPoints: Int) = if (healthPoints > 50) “Good” else “Bad”

Notice that instead of using the function body to specify the work the function will perform, with single-expression function syntax you use the assignment operator (=), followed by the expression.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
29
Q

Unit Functions?

A

Not all functions return a value. Some use side effects instead to do their work, like modifying the state of a variable or calling other functions that yield system output. They define no return type and have no return statement. For example println() is that kind of function.

In Kotlin, such functions are known as Unit functions, meaning their return type is Unit.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
30
Q

What kind of type is Unit?

A

Kotlin uses the Unit return type to signify exactly this: a function that returns no value. If the return keyword is not used, it is implicit that the return type for that function is Unit.

Prior to Kotlin, many languages faced the problem of describing a function that does not return anything. Some languages opted for a keyword void, which said, “There is no return type; skip it, because it does not apply.”

Unfortunately, this solution fails to account for an important feature found in modern languages: generics.

Languages that use the void keyword have no good way to deal with a generic function that returns nothing. void is not a type – in fact, it says, “Type information is not relevant; skip it.”

Kotlin solves this problem by specifying Unit for the return type instead. Unit indicates a function that does not return anything, but at the same time it is compatible with generic functions that must have some type to work with. This is why Kotlin uses Unit: You get the best of both worlds.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
31
Q

Named Function Arguments?

A

Another way you could call the same function using named function arguments is an alternative way to provide arguments to functions.

When you do not use named function arguments, you must pass arguments in the order they are defined on the function header. With named function arguments, you can pass arguments independent of the function header’s parameter order.

Another benefit of named function arguments is that they can bring clarity to your code. Especially when a function requires a large number of arguments.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
32
Q

The Nothing Type?

A

Another type that is related to Unit is the Nothing type. Like Unit, Nothing indicates that a function returns no value – but there the similarity ends. Nothing lets the compiler know that a function is guaranteed to never successfully complete; the function will either throw an exception or for some other reason never return to where it was called.

What is the use of the Nothing type? One example of Nothing’s use is the TODO function, included with the Kotlin standard library.

TODO’s Nothing return type indicates to the compiler that the function is guaranteed to cause an error, so checking the return type in the function body is not required past TODO because will never return. The compiler is happy, and the developer is able to continue feature development without completing the implementation for function until all the details are ready.

Another feature of Nothing that is useful in development is that if you add code below the TODO function, the compiler will show a warning indicating that the code is unreachable.

Because of the Nothing type, the compiler can make this assertion: It is aware that TODO will not successfully complete; therefore, all code after TODO is unreachable.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
33
Q

How is File-level functions from Kotlin represented in Java?

A

File-level functions are represented in Java as static methods on a class with a name based on the file in which they are declared in Kotlin. (Method is Java for “function.”) In this case, functions and variables defined in Game.kt are defined in Java in a class called GameKt.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
34
Q

Function Overloading?

A

When a function has multiple implementations, it is said to be overloaded. Overloading is not always the result of a default argument. You can define multiple implementations with the same function name.

Notice that the implementation of the overloaded function was selected based on how many arguments you provided.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
35
Q

Function Name in Backticks?

A

Kotlin includes a feature that might, at first glance, seem slightly peculiar: the ability to define or invoke a function named using spaces and other unusual characters, so long as they are surrounded using the backtick symbol, `.

fun ***prolly not a good ide!-**() { … }

Why is this feature included?

The first is to support Java interoperability. Kotlin includes great support for invoking methods from existing Java code within a Kotlin file. Because Kotlin and Java have different reserved keywords, words that are forbidden for use as function names, the function name backticks allow you to dodge any potential conflict when interoperability is important.

For example, Java method name is() in Kotlin is is reserved keyword. But you are able to invoke a Java is() method from Kotlin like so:

fun doStuff() {
    `is()`
}

The second reason for the feature is to support more expressive names of functions that are used in a testing file.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
36
Q

Anonymous Functions?

A

Functions defined without a name, called anonymous functions, are similar, with two major differences:
Anonymous functions have no name as part of their definition, and they interact with the rest of your code a little differently in that they are commonly passed to or returned from other functions. These interactions are made possible by the function type and function arguments.

Anonymous functions are an essential part of Kotlin. One way they are used is to allow you to easily customize how built-in functions from the Kotlin standard library work to meet your particular needs. An anonymous function lets you describe additional rules for a standard library function so that you can customize its behavior.

Just as you write a string by putting characters between opening and closing quotes, you write a function by putting an expression or statements between opening and closing curly braces.

{
val name = “Tom”
“Welcome $name”
}

The anonymous function defines a variable and returns a greeting message string.

Outside the anonymous function’s closing brace, you call the function with a pair of empty parentheses.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
37
Q

Anonymous Functions: The function type?

A

Anonymous functions also have a type, called the function type. Variables of the function type can hold an anonymous function as their value, and the function can then be passed around your code like any other variable.

A function type definition consists of two parts: the function’s parameters, in parentheses, followed by its return type, delimited by the arrow (->).

val greetingFunction: () -> String = {
val name = “Tom”
“Welcome $name”
}

Indicates to the compiler that greetingFunction can be assigned any function that accepts no arguments (indicated by the empty parentheses) and returns a String.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
38
Q

Anonymous Functions: Implicit returns?

A

Unlike a named function, an anonymous function does not require – or even allow, except in rare cases – the return keyword to output data. Anonymous functions implicitly, or automatically, return the last line of their function definition, allowing you to omit the return keyword.

The return keyword is prohibited in an anonymous function because it could be ambiguous to the compiler which function the return is from: the function the anonymous function was invoked within, or the anonymous function itself.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
39
Q

Anonymous Functions: Function arguments?

A

Like a named function, an anonymous function can accept zero, one, or multiple arguments of any type. The parameters an anonymous function accepts are indicated by type in the function type definition and then named in the anonymous function’s definition.

val greetingFunction: (String) -> String = { playerName -> … }

Here you specify that the anonymous function accepts a String. You name the string parameter within the function, right after the opening brace, and follow the name with an arrow.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
40
Q

Anonymous Functions: The it keyword?

A

When defining anonymous functions that accept exactly one argument, the it keyword is available as a convenient alternative to specifying the parameter name. Both it and a named parameter are valid when you have an anonymous function that has only one parameter.

val greetingFunction: (String) -> String = { “Welcome $it” }

it is convenient in that it requires no variable naming, but notice that it is not very descriptive about the data it represents.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
41
Q

Anonymous Functions: Accepting multiple arguments?

A

Anonymous functions can certainly accept multiple named arguments.

val greeting: (String, Int) -> String = { player, year ->
Welcome $player, year $year”
}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
42
Q

Anonymous Functions: Type Inference Support?

A

Kotlin’s type inference rules behave exactly the same with function types as they do with the types. If a variable is given an anonymous function as its value when it is declared, no explicit type definition is needed.

val greeting: () -> String = { “Welcome” }

Could also have been written with no specified type, like this:

val greeting = { “Welcome” }

Type inference is also an option when the anonymous function accepts one or more arguments, but to help the compiler infer the type of the variable, you do need to provide both the name and the type of each parameter in the anonymous function definition.

val greeting = { player: String, year: Int ->
“Welcome $player, year: $year”
}

When combined with an ambiguous implicit return type, type inference may make an anonymous function difficult to read. But when your anonymous function is simple and clear, type inference is an asset for making your code more concise.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
43
Q

Anonymous Functions: Defining a Function That Accepts a Function?

A

By the way, from here on out, we will refer to anonymous functions as lambdas and their definitions as lambda expressions. We will also refer to what an anonymous function returns as a lambda result.

A function parameter can accept arguments of any type, including arguments that are functions. A function type parameter is defined like a parameter of any other type: You list it in the parentheses after the function name and include the type.

Shorthand syntax

When a function accepts a function type for its last parameter, you can also omit the parentheses around the lambda argument.

“Mississippi”.count( { it == ‘s’ } )

Can also be written this way, without the parentheses:

“Mississippi”.count { it == ‘s’}

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
44
Q

Anonymous Functions: Function Inlining

A

Lambdas are useful because they enable a high degree of flexibility in how your programs can be written. However, that flexibility comes at a cost.

When you define a lambda, it is represented as an object instance on the JVM. The JVM also performs memory allocations for all variables accessible to the lambda, and this behavior comes with associated memory costs. As a result, lambdas introduce memory overhead that can in turn cause a performance impact – and such performance impacts are to be avoided. Fortunately, there is an optimization you can enable that removes the overhead when using lambdas as arguments to other functions, called inlining. Inlining removes the need for the JVM to use an object instance and to perform variable memory allocations for the lambda.

To inline a lambda, you mark the function that accepts the lambda using the inline keyword.

inline fun hello(year: Int, nameFunction: (String, Int) -> String) {
println(nameFunction(“Mayor”, year))
}

Now that you have added the inline keyword, instead of invoking hello with a lambda object instance, the compiler “copy and pastes” the function body where the call is made. Take a look at the decompiled Kotlin bytecode.

It is generally a good idea to mark functions that accept lambdas as arguments with the inline keyword. However, in a few limited instances, it is not possible. One situation where inlining is not permitted, for example, is a recursive function that accepts a lambda, since the result of inlining such a function would be an infinite loop of copying and pasting function bodies. The compiler will warn you if you try to inline a function that violates the rules.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
45
Q

What is Function Reference?

A

A function reference converts a named function (a function defined using the fun keyword) to a value that can be passed as an argument. You can use a function reference anywhere you use a lambda expression.

To obtain a function reference, you use the :: operator with the function name you would like a reference for.

Function references are useful in a number of situations. If you have a named function that fits the needs of a parameter that requires a function argument, a function reference allows you to use it instead of defining a lambda.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
46
Q

Function Type as a Return Type?

A

Like any other type, the function type is also a valid return type, meaning you can define a function that returns a function.

You might think of function that return a function as a “function factory” – a function that sets up another function. It declares the necessary variables and assembles them in a lambda that it then returns to its caller, runSimulation.

A function that accepts or returns another function is sometimes also referred to as a higher-order function.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
47
Q

Kotlin’s Lambdas are Closures?

A

In Kotlin, an anonymous function can modify and reference variables defined outside of its scope. This means that an anonymous function has a reference to the variables defined in the scope where it is itself created.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
48
Q

File-level variable?

A

File-level variable: This file-level variable can be accessed from anywhere in the project (though a visibility modifier can be added to the declaration to change its visibility level). File-level variables remain initialized until program execution stops.

File-level variables must always be assigned when they are defined, or the code will not compile.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
49
Q

What is a Null?

A

Null is a special value that indicates that the value of a var or val does not exist.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
50
Q

Nullable and Non-nullable elements?

A

Some elements in Kotlin can be assigned a value of null, and some cannot. We say that the former are nullable and the latter are non-nullable.

val signatureDrink = “Buttered Ale”
signatureDrink = null

Even before you execute this code, IntelliJ warns you with a red underline that something is amiss. (Null can not be a value of a non-null type String)

Kotlin prevents the assignment of null to the signatureDrink variable, because it is a non-null type (String). A non-null type is one that does not support the assignment of null. The current definition of signatureDrink is guaranteed to be a string, rather than null.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
51
Q

Kotlin’s Explicit Null Type?

A

Here is an example, from the header for the function called readLine.

public fun readLine(): String?

readLine’s header looks like one that you have seen before, with one exception: the return type String?. The question mark represents a nullable version of a type. That means readLine will either return a value of type String, or it will return null.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
52
Q

Compile Time vs Runtime?

A

Kotlin is a compiled language, meaning that your program is translated into machine-language instructions prior to execution by a special program, called the compiler.

The compiler checks whether null is assigned to a nullable type. As you have seen, if you attempt to assign null to a non-nullable type, Kotlin will refuse to compile your program. Errors caught at compile time are called compile-time errors, and they are one of the advantages of working with Kotlin.

A runtime error is a mistake that happens after the program has compiled and is already running, because the compiler was unable to discover it.

Generally speaking, compile-time errors are preferable to runtime errors. Finding out about a problem while you are writing code is better than finding out later. And finding out after your program has been released? That is the worst.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
53
Q

Null Safety in Kotlin?

A

“So how do I deal with the possibility of null?

First, though, consider option zero: Use a non-nullable type, if at all possible. Often, you simply do not need null – and when you do not need it, avoiding it is the safest course.

Option one: the safe call operator (?.).

var beverage = readLine()?.capitalize()

The safe call operator ensures that a function is called if and only if the variable it acts on is not null, thus preventing a null pointer exception.

Using safe calls with let

Safe calls allow you to call a single function on a nullable type, but what if you want to perform additional work, like creating a new value or calling other functions if the variable is not null? One way to achieve this is to use the safe call operator with the function let. let can be called on any value, and its purpose is to allow you to define a variable or variables for a given scope that you provide.

Because let provides its own function scope, you can use a safe call with let to scope multiple expressions that each require the variable that they are called on to be non-null.

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

let provides a number of conveniences, two of which you take advantage of here. As you define beverage, you use the convenience value it, provided by let.

The second let convenience is behind the scenes: let returns the results of your expression implicitly, so you can (and do) assign that result to a variable once it has completed evaluating the expression you define.

Option two: the double-bang operator

The double-bang operator (!!.) can also be used to call a function on a nullable type. If you use !!., you are proclaiming to the compiler: “If I ask a nonexistent thing to do something, I DEMAND that you throw a null pointer exception!!”

There are situations where using the double-bang operator is appropriate. Perhaps you do not have control over the type of a variable, but you are sure that it will never be null.

Option three: checking whether a value is null with if

Using the safe call operator should be favored before using value != null as a means to guard against null, since it is a more flexible tool to solve generally the same problem, but in less code.

When would you use an if/else statement for null checking? This option is best for times when you have some complex logic that you would only like to be evaluated if a variable is null. An if/else statement allows you to represent that complex logic in a readable form.

The null coalescing operator

Another way to check for null values is to use Kotlin’s null coalescing operator ?: (also known as the “Elvis operator” due to its semblance to Elvis Presley’s iconic hairstyle). This operator says, “If the thing on the lefthand side of me is null, do the thing on the righthand side instead.”

The null coalescing operator can also be used in conjunction with the let function in place of an if/else statement.

54
Q

What is Kotlin Exceptions?

A

Like many other languages, Kotlin also includes exceptions to indicate that something went wrong in your program.

When an exception occurs, it must be dealt with, or execution of the program will be halted. An exception that is not dealt with is called an unhandled exception. And halting the execution of the program is known by the ugly name crash.

55
Q

How do you throw and handle an exception in Kotlin?

A

You do this with the throw operator, and it is called throwing an exception.

Kotlin allows you to specify how to handle exceptions by defining a try/catch statement around the code that might cause one.

An unhandled exception will crash your program, halting execution. Because you handled the exception using a try/catch block, code execution can continue as if a dangerous operation never caused an issue.

56
Q

What are Preconditions in Kotlin?

A

To make it easier to validate input and debug to avoid certain common issues, Kotlin provides some convenience functions as part of its standard library. They allow you to use a built-in function to throw an exception with a custom message.

These functions are called precondition functions, because they allow you to define preconditions – conditions that must be true before some piece of code is executed.

If it is null when passed to checkNotNull, then a thrown IllegalStateException makes it clear that the current state is unacceptable. checkNotNull takes two arguments: The first is a value to check for nullness, and the second is an error message to be printed to the console in the event that the first argument is null.

They can be cleaner than manually throwing your own exception, because the condition to be satisfied is included in the name of the function.

Kotlin includes five preconditions in the standard library

checkNotNull - Throws an IllegalStateException if argument is null. Otherwise returns the non-null value.

require - Throws an IllegalArgumentException if argument is false.

requireNotNull - Throws an IllegalArgumentException if argument is null. Otherwise returns the non-null value.

error - Throws an IllegalArgumentException with a provided message if argument is null. Otherwise returns the non-null value.

assert - Throws an AssertionError if argument is false and the assertion compiler flag is enabled.

57
Q

Checked vs Unchecked Exceptions?

A

In Kotlin, all exceptions are unchecked. This means that the Kotlin compiler does not force you to wrap all code that could produce an exception in a try/catch statement.

Compare this with Java, for example, which supports a mixture of both checked and unchecked exception types. With a checked exception, the compiler checks that the exception is guarded against, requiring you add a try/catch to your program.

This sounds reasonable. But in practice, the idea of checked exceptions does not hold up as well as the inventors thought it would. Often, checked exceptions are caught (because the compiler requires the checked exception to be handled) and then simply ignored, just to allow the program to compile. This is called “swallowing an exception,” and it makes your program very hard to debug because it suppresses the information that anything went wrong in the first place. In most cases, ignoring the problem at compile time leads to more errors at runtime.

Unchecked exceptions have won out in modern languages because experience has shown that checked exceptions lead to more problems than they solve: code duplication, difficult-to-understand error recovery logic, and swallowed exceptions with no record of an error even taking place.

58
Q

What is Strings?

A

In programming, textual data is represented by strings – ordered sequences of characters.

59
Q

What id destructuring?

A

destructuring – a feature that allows you to declare and assign multiple variables in a single expression.

Destructuring can often be used to simplify the assignment of variables. Any time the result is a list, a destructuring assignment is allowed. In addition to List, other types that support destructuring include Maps and Pairs, as well as data classes.

60
Q

Strings are immutable, explain?

A

In reality, the replace function does not “replace” any part of the phrase variable. Instead, replace creates a new string. It uses the old string’s value as an input and chooses characters for the new string using the expression you provide.

Whether they are defined with var or val, all strings in Kotlin are actually immutable (as they are in Java). Though the variables that hold the value for the String can be reassigned if the string is a var, the string instance itself can never be changed. Any function that appears to change the value of a string (like replace) actually creates a new string with the changes applied to it.

61
Q

Unicode?

A

As you have learned, a string consists of an ordered sequence of characters, and a character is an instance of the Char type. Specifically, a Char is a Unicode character. The Unicode character encoding system is designed to support “the interchange, processing, and display of the written texts of the diverse languages and technical disciplines of the modern world” (unicode.org).

This means the individual characters in a string can be any of a diverse palette of characters and symbols – 136,690 of them (and growing) – including characters from the alphabet of any language in the world, icons, glyphs, emoji, and more. To declare a character, you have two options. Both are wrapped in single quotes. For characters on your keyboard, the simplest option is the character itself in the single quotes:

val capitalA: Char = ‘A’

But not all 136,690 characters are included on your keyboard. The other way to represent a character is with its Unicode character code, preceded by the Unicode character escape sequence \u:

val unicodeCapitalA: Char = ‘\u0041’

62
Q

Numeric Types in Kotlin?

A

All numeric types in Kotlin, as in Java, are signed, meaning they can represent both positive and negative numbers. In addition to whether they support decimal values, the numeric types differ in the number of bits they are allocated in memory and, consequently, their minimum and maximum values.

Commonly used numeric types

Byte : 8 (bit) : 128 (max value) : -128 (min value)
Short : 16 (bit) : 32767 (max value) : -32767 (min value)
Int : 32 (bit) : 2147483647 (max value) : -2147483647 (min value)
Long : 64 (bit) : // (max value) : -// (min value)
Float : 32 (bit) : // (max value) : -// (min value)
Double : 64 (bit) : // (max value) : -// (min value)

To represent a number, Kotlin assigns a finite number of bits, depending on the numeric type chosen. The leftmost bit position represents the sign (the positive or negative nature of the number). The remaining bit positions each represent a power of 2, with the rightmost position being 20. To compute the value of a binary number, add up each of the powers of 2 whose bit is a 1.

101010 = 2^1 + 2^3 + 2^5 = 42

63
Q

Converting a String to a Numeric Type?

A

The good news is that Kotlin includes functions that convert strings to different types – including numbers. Some of the most commonly used of these conversion functions are:

.toFloat 
.toDouble 
.toDoubleOrNull 
.toIntOrNull 
.toLong
.toBigDecimal

Attempting to convert a string of the wrong format will throw an exception. For example, calling toInt on a string with the value “5.91” would throw an exception, since the decimal portion of the string value would not fit into an Int.

Because of the possibility of exceptions when converting between different numeric types, Kotlin also provides the safe conversion functions toDoubleOrNull and toIntOrNull. When the number does not convert correctly, a null value is returned instead of an exception.

64
Q

Bit manipulation of Numbers?

A

You can get the binary representation for a number at any time. For example, you could ask for the binary representation of the integer 42 with:

Integer.toBinaryString(42)
101010

Kotlin includes functions for performing operations on the binary representation of a value, called bitwise operations.

65
Q

What are Kotlin Standard Functions?

A

Standard functions are general utility functions in the Kotlin standard library that accept lambdas to specify their work.

In this chapter we will refer to an instance of a type using the term receiver. This is because Kotlin’s standard functions are extension functions under the hood, and receiver is the term for the subject of an extension function.

66
Q

Kotlin Standard Functions: apply?

A

First on our tour of the standard functions is apply. apply can be thought of as a configuration function: It allows you to call a series of functions on a receiver to configure it for use. After the lambda provided to apply executes, apply returns the configured receiver.

apply can be used to reduce the amount of repetition when configuring an object for use. Here is an example of configuring a file instance without apply:

val menuFile = File("menu-file.txt")
menuFile.setReadable(true)
menuFile.setWritable(true)
menuFile.setExecutable(false)

Using apply, the same configuration can be achieved with less repetition:
val menuFile = File(“menu-file.txt”).apply {
setReadable(true)
setWritable(true)
setExecutable(false)
}

apply allows you to drop the variable name from every function call performed to configure the receiver. This is because apply scopes each function call within the lambda to the receiver it is called on.

This behavior is sometimes referred to as relative scoping, because all the function calls within the lambda are now called relative to the receiver. Another way to say this is that they are implicitly called on the receiver.

67
Q

Kotlin Standard Functions: let?

A

let scopes a variable to the lambda provided and makes the keyword it.

let, on the other hand, allows a fluent or chainable style that only requires the variable name to be used one time.

let can be called on any kind of receiver and returns the result of evaluating the lambda you provide.

Several differences between let and apply are worth mentioning: As you saw, let passes the receiver to the lambda you provide, but apply passes nothing. Also, apply returns the current receiver once the anonymous function completes. let, on the other hand, returns the last line of the lambda (the lambda result).

Standard functions like let can also be used to reduce the risk of accidentally changing a variable, because the argument let passes to the lambda is a read-only function parameter.

68
Q

Kotlin Standard Functions: run?

A

run is similar to apply in that it provides the same relative scoping behavior. However, unlike apply, run does not return the receiver.

apply. However, unlike apply, run returns the lambda result.

run can also be used to execute a function reference on a receiver.

Chained calls using run are easier to read and follow than nested function calls.

Note that there is a second flavor of run that is not called on a receiver. This form is far less commonly seen, but we include it here for completeness:

val status = run {
if (healthPoints == 100) “perfect health” else “has injuries”
}

69
Q

Kotlin Standard Functions: with?

A

with is a variant of run. It behaves identically, but it uses a different calling convention. Unlike the standard functions you have seen so far, with requires its argument to be accepted as the first parameter rather than calling the standard function on a receiver type:

val nameTooLong = with(“Polarcubis, Supreme Master of NyetHack”) {
length >= 20
}

This is inconsistent with the way you work with the rest of the standard functions, making it a less favorable choice than run. In fact, we recommend avoiding with and using run instead.

70
Q

Kotlin Standard Functions: also?

A

The also function works very similarly to the let function. Just like let, also passes the receiver you call it on as an argument to a lambda you provide. But there is one major difference between let and also: also returns the receiver, rather than the result of the lambda.

This makes also especially useful for adding multiple side effects from a common source.

Since also returns the receiver instead of the result of the lambda, you can continue to chain additional function calls on to the original receiver.

71
Q

Kotlin Standard Functions: takeIf?

A

The last stop on our tour of the standard functions is takeIf. takeIf works a bit differently than the other standard functions: It evaluates a condition provided in a lambda, called a predicate, that returns either true or false depending on the conditions defined. If the condition evaluates as true, the receiver is returned from takeIf. If the condition is false, null is returned instead. Consider the following example, which reads a file if and only if it is readable and writable.

val fileContents = File(“myfile.txt”)
.takeIf { it.canRead() && it.canWrite() }
?.readText()

Conceptually, takeIf is similar to an if statement, but with the advantage of being directly callable on an instance, often allowing you to remove a temporary variable assignment.

72
Q

Kotlin Standard Functions: takeUnless?

A

The takeUnless function is exactly like takeIf except that it returns the original value if the condition you define is false.

73
Q

Lists?

A

Lists hold an ordered collection of values and allow duplicate values.

Collections require two steps: creating the collection (here, the list to hold the patrons) and adding contents to it (the patron names). Kotlin provides functions, like listOf, that do both at once.

Notice the diamond braces in List. is known as a parameterized type, and it tells the compiler about the type that the contents of the list will be – in this case, Strings. Changing the type parameter changes what the compiler allows the list to hold.

74
Q

Accessing a list’s elements?

A

Access any element of a list using the element’s index and the [] operator. Lists are zero-indexed, so “Eli” is at index 0, and “Sophie” is at index 2.

Now that you have seen the parameterized type that this List uses, you can return to using type inference for cleaner code.

Because accessing an element by an index can throw an exception, Kotlin provides safe index access functions that allow you to deal with the problem differently.

For example ,one of these safe index access functions, getOrElse, takes two arguments: The first is the requested index (in parentheses, not square brackets). The second is a lambda that generates a default value, instead of an exception, if the requested index does not exist.

Another safe index access function, getOrNull, returns null instead of throwing an exception.

75
Q

Checking the contents of a list?

A

Use the contains function to check whether a particular patron is present.

Note that the contains function performs a structural comparison for the elements in the list, like the structural equality operator.

You can also use the containsAll function to check whether several patrons are present at once.

76
Q

Changing a list’s contents?

A

listOf returns a read-only list that does not allow changes to its contents: You cannot add, remove, update, or replace entries. Read-only lists are a good idea, because they prevent unfortunate mistakes

The read-only nature of the list has nothing to do with the val or var keyword you used to define the list variable. Changing the variable declaration for patronList from val (as it is defined now) to var would not change the list from read-only to writable. Instead, it would allow you to reassign the patronList variable to hold a new, different list.

List mutability is defined by the type of the list and refers to whether you can modify the elements in the list.

In Kotlin, a modifiable list is known as a mutable list, and you use the mutableListOf function to create one.

Mutable lists come with a variety of functions for adding, removing, and updating items.

When you need to be able to modify the elements in a list, use a MutableList. Otherwise, it is a good idea to restrict mutability by using List.

List also provides functions for moving between read-only and mutable versions on the fly: toList and toMutableList.

Functions that change the contents of a mutable list are called mutator functions.

77
Q

Iteration?

A

For those familiar with Java, it can be surprising to find that the common Java expression for(int i = 0; i < 10; i++) { … } is not possible in Kotlin. Instead, a for loop is written for(i in 1..10) { … }. However, at the bytecode level, the compiler will optimize a Kotlin for loop to use the Java version, when possible, to improve performance.

Note the in keyword:

for (patron in patronList) { … }

in specifies the object being iterated over in a for loop.

The for loop is simple and readable, but if you prefer a more functional style to your code, then a loop using the forEach function is also an option. The forEach function traverses each element in the list – one by one, from left to right – and passes each element to the anonymous function you provide as an argument.

The for loop and the forEach function are functionally equivalent.

Kotlin’s for loop and forEach function handle indexing behind the scenes. If you also want access to the index of each element in a list as you iterate, use forEachIndexed.

The forEach and forEachIndexed functions are also available on certain other types in Kotlin. This category of types is called Iterable, and List, Set, Map, IntRange (ranges like 0..9, which you saw in Chapter3), and other collection types belong to the Iterable category.

List.shuffled() - shuffle list elements.
List.first()
List.last()

78
Q

Destructuring?

A

A list also offers the ability to destructure up to the first five elements it contains.

val (type, name, price) = menuData.split(‘,’)

By the way, you can also selectively destructure elements from a list by using the symbol _ to skip unwanted elements.

If you wanted to destructure only the first and third value in the result from splitting the patron list, you could do so with:

val (goldMedal, _, bronzeMedal) = patronList

79
Q

Sets?

A

Lists, as you have seen, allow duplicate elements (and are ordered, so duplicates – and other elements – can be identified by their position). But sometimes you want a collection that guarantees that its items are unique. For that, you use a Set.

Sets are like Lists in many ways. They use the same iteration functions, and Set also comes in read-only and mutable flavors.

But there are two major differences between lists and sets: The elements of a set are unique, and a set does not provide index-based mutators, because the items in a set are not guaranteed to be in any particular order.

80
Q

Creating a set?

A

Just as you can create a list using the listOf function, you can create a Set using the setOf function.

As with a List, you can check whether a set contains a particular element using contains and containsAll.

Set does not index its contents – meaning it provides no built-in [] operator to access elements using an index. However, you can still request an element at a particular index, using functions that use iteration to accomplish the task. Enter the following into the REPL to read the third planet in the set with the elementAt

While this works, using index-based access with a set is an order of magnitude slower than index-based access with a list, because of the way elementAt works under the hood. When you call the elementAt function on the set, the set iterates to the index you provide, one element at a time. This means that for a large set, requesting an element at a high index would be slower than accessing an element by index in a list. For this reason, if you want index-based access, you probably want a List, not a Set.

Having said that, Set does provide the very useful feature of eliminating duplicate elements. So what is a programmer who wants unique elements and high-performance, index-based access to do? Use both: Create a Set to eliminate duplicates and convert it a to a List when index-based access or mutator functions are needed.

81
Q

while Loops?

A

for loops are a useful form of control flow when you want to run some code for each element in series. But they are not as good at representing state that cannot be iterated through. That is where while loops are useful.

A while loop’s logic is, “While some condition is true, execute the code in this block.”

loop. while loops are more flexible than for loops in that they can represent state that is not purely based on iteration.

82
Q

The break Expression?

A

One way to exit a while loop is by changing the state it depends on. Another way to break out of a loop is the break expression.

Note that break does not stop execution of your program entirely. Rather, it simply breaks out of the loop from which it is called, and program execution continues. break can be used to jump out of any loop or conditional, which can be quite useful.

83
Q

Collection Conversion?

A

You can also convert a list to a set, or vice versa, using the toSet and toList functions (or their mutable cousins: toMutableSet and toMutableList). A common trick is to call toSet to drop the non-unique elements in a list.

The need to remove duplicates and resume index-based access is so common that Kotlin provides a function on List called distinct that calls toSet and toList internally.

84
Q

Array Types?

A

If you have worked with Java, you know that it supports primitive definitions of arrays – different from the reference types like List and Set that you worked with in this chapter. Kotlin also includes a number of reference types, called Arrays, that compile down to Java primitive arrays. Arrays are included primarily to support interoperability between Kotlin and Java.

Unlike a List, an IntArray is backed by a primitive type when compiled to bytecode. When the Kotlin code is compiled, the bytecode that is generated will exactly match the expected primitive int array.

It is also possible to convert a Kotlin collection to the required Java primitive array type using built-in conversion functions. For example, you could convert a list of integers to an IntArray using the toIntArray function provided by List. This would allow you to convert a collection to an int array only at the point that you need to provide a primitive array to a Java function.

IntArray - intArrayOf  
DoubleArray -  doubleArrayOf  
LongArray - longArrayOf  
ShortArray - shortArrayOf  
ByteArray - byteArrayOf  
FloatArray - floatArrayOf  
BooleanArray - booleanArrayOf  
Array - arrayOf  

Array compiles to a primitive array that holds any reference type.

As a general rule, stick with the collection types like List unless you have a compelling reason to do otherwise – like the need to interoperate with Java code. A Kotlin collection is a better choice in most cases because collections provide the concept of “read-only-ness” versus “mutability” and support a more robust set of features.

85
Q

List: Read Only vs Immutable?

A

“Immutable” means “unchangeable,” and we think it is a misleading label for Kotlin collections (and certain other types) because they can, indeed, change.

Here are declarations of two Lists. They are read-only – declared with val. The element each one happens to contain is a mutable list.

val x = listOf(mutableListOf(1,2,3))
val y = listOf(mutableListOf(1,2,3))

x == y
true

However, the lists contain mutable lists, and their contents can be modified:

val x = listOf(mutableListOf(1,2,3))
val y = listOf(mutableListOf(1,2,3))
x[0].add(4)

x == y
false

Here is another example:

var myList: List = listOf(1,2,3)
(myList as MutableList)[2] = 1000
myList
[1, 2, 1000]

In this example, myList was cast to the MutableList type – meaning that the compiler was instructed to treat myList as a mutable list, despite the fact that it was created with listOf.

A List in Kotlin does not enforce immutability – it is up to you to use it in an immutable fashion. A Kotlin List’s “immutability” is only skin deep – and whatever you wind up calling it, remember that.

86
Q

Map?

A

The third commonly used type of collection in Kotlin is Map. The Map type has a lot in common with the List and Set types: All three group a series of elements, are read-only by default, use parameterized types to tell the compiler the type of their contents, and support iteration.

Where Map is different from List and Set is that its elements consist of key-value pairs that you define, and instead of index-based access using an integer, a map provides key-based access using a type that you specify. Keys are unique and identify the values in the map; the values, on the other hand, do not need to be unique. In this way, Map shares another feature with Set: The keys of a map are guaranteed to be unique, just like the elements of a set.

87
Q

Creating a Map?

A

Like lists and sets, maps are created using functions: mapOf and mutableMapOf.

val patronGold = mapOf(“Eli” to 10.5, “Mordoc” to 8.0, “Sophie” to 5.5)

While the keys in a map must all be of the same type, and the values must be of the same type, the keys and values can be of different types. Here you have a map with string keys and double values. You are using type inference, but if you had wanted to include explicit type information, it would look like this: val patronGold: Map.

You used to to define each entry (key and value) in the map. “to” may look like a keyword, but in fact it is a special type of function that allows you to drop the dot and the parentheses around its arguments.

The “to” function converts the values on its lefthand and righthand sides into a Pair – a type for representing a group of two elements.

Maps are built using key-value Pairs. In fact, another way you could have defined the entries for the map is as follows.

val patronGold = mapOf(Pair(“Eli”, 10.75),
Pair(“Mordoc”, 8.00),
Pair(“Sophie”, 5.50))

However, building a map using the to function is cleaner than this syntax.

Adding a duplicate key

val patronGold = mutableMapOf(“Eli” to 5.0, “Sophie” to 1.0)
patronGold += “Sophie” to 6.0

You used Map’s plus assign operator (+=) to add a pair with a duplicate key into the map. Since the key “Sophie” was already in the map, the existing pair was replaced with the new one.

88
Q

Accessing Map Values?

A

You access a value in a map using its key.

As with other collections, Kotlin provides functions for accessing the values stored in a map.

  • [] (get/index operator): Gets the value for a key; returns null if the key does not exist.

patronGold[“Reginald”]
null

  • getValue: Gets the value for a key; throws an exception if the key provided is not in the map.

patronGold.getValue(“Reggie”)
NoSuchElementException

  • getOrElse: Gets the value for the key or returns a default using an anonymous function.

patronGold.getOrElse(“Reggie”) {“No such patron”}
No such patron

  • getOrDefault: Gets the value for the key or returns a default using a value you provide.

patronGold.getOrDefault(“Reginald”, 0.0)
0.0

89
Q

Adding Entries to a Map?

A
  • ”=” (assignment operator): Adds or updates the value in the map for the key specified.

val patronGold = mutableMapOf(“Mordoc” to 6.0)
patronGold[“Mordoc”] = 5.0
{Mordoc=5.0}

  • ”+=” (plus assign operator): Adds or updates an entry or entries in the map based on the entry or map specified.

val patronGold = mutableMapOf(“Mordoc” to 6.0)
patronGold += “Eli” to 5.0
{Mordoc=6.0, Eli=5.0}

val patronGold = mutableMapOf("Mordoc" to 6.0)
patronGold += mapOf("Eli" to 7.0,
                    "Mordoc" to 1.0,
                    "Jebediah" to 4.5)
{Mordoc=1.0, Eli=7.0, Jebediah=4.5}
  • put: Adds or updates the value in the map for the key specified.

val patronGold = mutableMapOf(“Mordoc” to 6.0)
patronGold.put(“Mordoc”, 5.0)
{Mordoc=5.0}

  • putAll: Adds all of the key-value pairs provided to the map.

val patronGold = mutableMapOf(“Mordoc” to 6.0)
patronGold.putAll(listOf(“Jebediah” to 5.0,
“Sahara” to 6.0))
patronGold[“Jebediah”]
5.0

patronGold[“Sahara”]
6.0

  • getOrPut: Adds an entry for the key if it does not exist already and returns the result; otherwise returns the existing entry.

val patronGold = mutableMapOf()
patronGold.getOrPut(“Randy”){5.0}
5.0

patronGold.getOrPut(“Randy”){10.0}
5.0

  • remove: Removes an entry from the map and returns the value.

val patronGold = mutableMapOf(“Mordoc” to 5.0)
val mordocBalance = patronGold.remove(“Mordoc”)
{}

print(mordocBalance)
5.0

  • ”-“ (minus operator): Returns a new map, excluding the entries specified.

val newPatrons = mutableMapOf(“Mordoc” to 6.0,
“Jebediah” to 1.0) - “Mordoc”
{Jebediah=1.0}

  • ”-=” (minus assign operator): Removes entry or map of entries from the map.

mutableMapOf(“Mordoc” to 6.0,
“Jebediah” to 1.0) -= “Mordoc”
{Jebediah=1.0}

  • clear: Removes all entries from the map.

mutableMapOf(“Mordoc” to 6.0,
“Jebediah” to 1.0).clear()
{}

90
Q

Defining a Class?

A

A class can be defined in its own file or alongside other elements, like functions or variables. Defining a class in its own file gives it room to grow as the program scales up over time.

A class is often declared in a file matching its name, but it does not have to be. You can define multiple classes in the same file – and you may want to if you have multiple classes used for a similar purpose.

91
Q

Construction Instances?

A

A class declaration is like a blueprint. Blueprints contain the details for how to construct a building, but they are not a building.

To construct a player so that it can be used in NyetHack, you must instantiate it – create an instance of it – by calling its constructor.

var player = Player()

You called Player’s primary constructor by suffixing the Player class name with parentheses.

A constructor does what its name says: It constructs. Specifically, it constructs an instance and prepares it for use.

92
Q

Class Functions?

A

Class definitions can specify two types of content: behavior and data.

Functions defined within a class are called class functions.

class Player {
    fun castFireball(numFirebals: Int = 2) = ...
} 

Here, you define a class body for Player with curly braces. The class body holds definitions for the class’s behavior and data, much like the actions of a function are defined within the function body.

93
Q

Visibility and Encapsulation?

A

Adding behavior to a class with class functions (and data with class properties,) builds a description of what that class can do and be, and that description is visible to anyone with an instance of that class.

By default, any function or property without a visibility modifier is public – meaning it is accessible from any file or function in your program.

While a public class function can be invoked anywhere in the program, a private class function cannot be invoked outside of the class on which it is defined. This idea of restricting visibility to certain class functions or properties drives a concept in object-oriented programming known as encapsulation. Encapsulation says that a class should selectively expose functions and properties to define how other objects interact with it. Anything that is not essential to expose, including implementation details of exposed functions and properties, should be kept private.

Visibility modifiers

  • public (default): The function or property will be accessible by code outside of the class. By default, functions and properties without a visibility modifier are public.
  • private: The function or property will be accessible only within the same class.
  • protected: The function or property will be accessible only within the same class or its subclass.
  • internal: The function or property will be accessible within the same module.
94
Q

Class Properties?

A

Class function definitions describe the behavior associated with a class. Data definitions, better known as class properties, are the attributes required to represent the specific state or characteristics of a class.

When an instance of a class is constructed, all of its properties must have values. This means that, unlike other variables, class properties must be assigned an initial value.

95
Q

Class Properties: Property getters and setters?

A

Properties model the characteristics of each instance of a class. They also provide a way for other entities to interface with the data that the class keeps track of, represented in a compact and concise syntax. This interaction happens through getters and setters.

For each property you define, Kotlin will generate a field, a getter, and, if needed, a setter. A field is where the data for a property is stored. You cannot directly define a field on a class. Kotlin encapsulates the fields for you, protecting the data in the field and exposing it via getters and setters. A property’s getter specifies how the property is read. Getters are generated for every property. A setter defines how a property’s value is assigned, so it is generated only when a property is writable – in other words, when the property is a var.

Although default getters and setters are provided automatically by Kotlin, you can define your own custom getters and setters when you want to specify how the data will be read or written. This is called overriding the getter or setter.

class Player {
    val name = "madrigal"
        get() = field.capitalize()
}

This was example of custom getter.

The field keyword here points to the backing field that Kotlin manages for your property automatically. The backing field is the data that the getters and setters use to read and write the data that represents the property.

When the capitalized version of name is returned, the backing field is not modified. If the value assigned to name is not capitalized, as in your code, it remains lowercase after the getter does its work.

A setter, on the other hand, does modify the backing field of the property on which it is declared.

class Player {
    val name = "madrigal"
        get() = field.capitalize()
        set(value) {
            field = value.trim()
        }
    ...
}
96
Q

Class Properties: Property visibility?

A

By default, the visibility of a property’s getter and setter match the visibility of the property itself. So if you have a public property, both its getter and setter are public.

What if you want to expose access to a property but do not want to expose its setter? You can define the visibility of the setter separately. Make the name property’s setter private:

class Player {
    var name = "madrigal"
        get() = field.capitalize()
        private set(value) {
            field = value.trim()
        }
   ...
}

Now, name can be accessed from anywhere in NyetHack, but it can only be modified from within Player.

A getter or a setter’s visibility cannot be more permissive than the property on which it is defined. You can restrict access to a property via a getter or a setter, but they are not intended for making properties more visible.

97
Q

Class Properties: Computed properties?

A

A computed property is a property that is specified with an overridden get and/or set operator in a way that makes a field unnecessary. In such cases, Kotlin will not generate a field.

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

The value is different each time the rolledValue property is accessed. This is because the value is computed each time the variable is accessed. It has no initial or default value – and no backing field to hold a value.

98
Q

Defining Classes: Using Packages?

A

A package is like a folder for similar elements that helps give a logical grouping to the files in your project.

Packages allow you to organize your project as it becomes more complex, and they also prevent naming collisions.

99
Q

A Closer Look at var and val Properties?

A

You may be wondering how a Kotlin class property works, under the hood, when targeting the JVM.

To understand how class properties are implemented, it is helpful to look at the decompiled JVM bytecode – specifically, to compare the bytecode generated for a single property depending on how it is specified.

class Student(var name: String)

Four elements of the Student class were generated in bytecode when you defined the name var on the class: a name field (where name’s data will be stored), a getter method, a setter method, and finally a constructor assignment for the field, where the name field is initialized with the Student’s name constructor argument.

Now try changing the property from a var to a val:

class Student(varval name: String)

The difference between using the var keyword and val keyword for the property is the absence of a setter.

What happens in bytecode when you define a computed property, with a custom getter and no field for storing the data?

Only one element was generated in the bytecode this time – a getter. The compiler was able to determine that no field was required, since no data from a field was read or written.

100
Q

Defining Classes: Guarding Against Race Conditions?

A

When a class property is both nullable and mutable, you must ensure that it is non-null before referencing it.

The compiler prevents the code from compiling because of the possibility of what is known as a race condition. A race condition occurs when some other part of your program simultaneously modifies the state of your code in a manner that leads to unpredictable results.

Here, the compiler sees that although weapon is checked for a null value, there is still a possibility of the Player’s weapon property being replaced with a null value between the time that check passed and the time the name of the weapon is printed.

Therefore, unlike in other cases where weapon could be smart cast within the null check, the compiler balks because it cannot safely say that weapon will never be null.

One way to fix this problem is to use a standard function like also.

Instead of referring to the class property, it, the argument to also, is a local variable that exists only within the scope of the anonymous function. Therefore, the it variable is guaranteed to not be changed by another part of your program. The smart cast issue is avoided entirely, because instead of dealing with the original nullable property, this code uses a read-only, non-nullable local variable (since also is called after the safe call operator: weapon?.also).

101
Q

Defining Classes: Package Private?

A

Kotlin class, function, or property is public by default (without a visibility modifier), which means it is usable by any other class, function, or property in the project.

If you are familiar with Java, you may have noticed that the default access level differs from that of Kotlin: By default, Java uses package private visibility, which means that methods, fields, and classes with no visibility modifier are usable from classes belonging to the same package only. Kotlin opted out of supporting package private visibility because it accomplishes little.

On the other hand, a visibility level Kotlin provides that Java does not is the internal visibility level. Internal visibility marks a function, class, or property as public to other functions, classes, and properties within the same module. A module is a discrete unit of functionality that can be run, tested, and debugged independently.

Internal visibility is useful for sharing classes within a module while disallowing access from other modules, which makes it a great choice for building libraries in Kotlin.

102
Q

Initialization?

A

When you initialize a variable, property, or class instance, you assign it an initial value to make it ready for use.

A note about terminology: Technically, an object is instantiated when memory is allocated for it, and it is initialized when it is assigned a value. However, in practice the terms are often used slightly differently. Often, initialization is used to mean “everything required to make a variable, property, or class instance ready to use,” while instantiation tends to be limited to “creating an instance of a class.”

103
Q

Constructors: Primary constructor?

A

Like a function, a constructor defines expected parameters that must be provided as arguments.

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

(Why prepend these variable names with underscores? Temporary variables, including parameters that you do not need to reference more than once, are often given a name starting with an underscore to signify that they are single-use.)

104
Q

Constructors: Defining properties in a primary constructor?

A

Notice the one-to-one relationship between the constructor parameters in Player and the class properties: You have a parameter and a class property for each property to be specified when the player is constructed.

For properties that use the default getter and setter, Kotlin allows you to specify both in one definition, rather than having to assign them using temporary variables. name uses a custom getter and setter, so it cannot take advantage of this feature, but Player’s other properties can.

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

For each constructor parameter, you specify whether it is writable or read-only.

You also implicitly assign each property to the value passed to it as an argument.

105
Q

Constructors: Secondary constructor?

A

Constructors come in two flavors: primary and secondary. When you specify a primary constructor, you say, “These parameters are required for any instance of this class.” When you specify a secondary constructor, you provide alternative ways to construct the class (while still meeting the requirements of the primary constructor).

A secondary constructor must either call the primary constructor, providing it all of the arguments it requires, or call through to another secondary constructor – which follows the same rule.

class Player(_name: String,
            var healthPoints: Int,
            val isBlessed: Boolean,
            private val isImmortal: Boolean) {
constructor(name: String) : this(name,
            healthPoints = 100,
            isBlessed = true,
            isImmortal = false)
    ...
}

The this keyword in this case refers to the instance of the class for which this constructor is defined. Specifically, this is calling into another constructor defined in the class – the primary constructor.

Although they are useful for defining alternative logic to be run on instantiation, secondary constructors cannot be used to define properties like primary constructors can. Class properties must be defined in the primary constructor or at the class level.

106
Q

Constructors: Default arguments?

A

When defining a constructor, you can also specify default values that should be assigned if an argument is not provided for a specific parameter.

class Player(_name: String,
            var healthPoints: Int = 100
            val isBlessed: Boolean
            private val isImmortal: Boolean) {
constructor(name: String) : this(name,
            isBlessed = true,
            isImmortal = false) {
...
}
....
}

Because you added a default argument value to the healthPoints parameter in the primary constructor, you removed the healthPoints argument passed from Player’s secondary constructor to its primary constructor. This gives you another way to instantiate Player: with or without an argument for healthPoints.

107
Q

Constructors: Named arguments?

A

The more default arguments you use, the more options you have for calling your constructor. More options can open the door for more ambiguity, so Kotlin provides named constructor arguments, just like the named arguments that you have used to call functions.

val player = Player(name = “Madrigal”,
healthPoints = 100,
isBlessed = true,
isImmortal = false)

val player = Player(“Madrigal”, 100, true, false)

Named argument syntax lets you include the parameter name for each argument to improve readability.

Named arguments allow you to specify the arguments to a function or constructor in any order. If parameters are unnamed, then you need to refer to the constructor to know their order.

When you have more than a few arguments to provide to a constructor or function, we recommend using named parameters.

108
Q

Initializer Blocks?

A

In addition to the primary and secondary constructors, you can also specify an initializer block for a class in Kotlin. The initializer block is a way to set up variables or values as well as perform validation – like checking to make sure that the arguments to the constructor are valid ones. The code it holds is executed when the class is constructed.

Use an initializer block, denoted by the init keyword, to enforce these requirements with preconditions.

init {
require(healthPoints > 0, { “healthPoints must be greater than zero.” })
require(name.isNotBlank(), { “Player must have a name.” })
}

The code in the initializer block will be called when the class is instantiated – no matter which constructor for the class is called, primary or secondary.

109
Q

Property Initialization?

A

So far, you have seen a property initialized in two ways – either assigned to a value passed as an argument, or defined inline in a primary constructor.

A property can (and must) be initialized with any value of its type, including function return values.

val hometown: String = selectHometown()
    ...
private fun selectHometown() = File("data/towns.txt")
            .readText()
            .split("\n")
            .shuffled()
            .first()

The rule that states that properties must be assigned on declaration does not apply to variables in a smaller scope, like a function. For example:

class JazzPlayer {
        fun acquireMusicalInstrument() {
            val instrumentName: String
            instrumentName = "Oboe"
        }
    }

Because instrumentName is assigned a value before it can be referenced, this code compiles.

Properties have more strict rules on initialization because they can be accessed from other classes if they are public. Variables local to a function, on the other hand, are scoped to the function in which they are defined and cannot be accessed from outside of it.

110
Q

Initialization Order?

A
class  Player(_name: String, val health: Int) {
    val race = "DWARF"
    var town = "Bavaris"
    val name = _name
    val alignement: String
    private var age = 0
init {
     println("initializing player")
     alignment =  "GOOD"
}

constructor(_name: String) : this(_name, health: 100) {
     town = "The shire"
} }

The resulting initialization order is as follows:

  1. the primary constructor’s inline properties (val health: Int)
  2. required class-level property assignments (val race = “DWARF”, val town = “Bavaria”, val name = _name)
  3. init block property assignments and function calls (alignment = “GOOD”, println function)
  4. secondary constructor property assignments and function calls (town = “The Shire”)

The initialization order of the init block (item 3) and the class-level property assignments (item 2) depends on the order they are specified in. If the init block were defined before the class property assignments, it would be initialized second, followed by the class property assignments.

111
Q

Delaying Initialization?

A

Wherever it is declared, a class property must be initialized when the class instance is constructed.

Despite its importance, you can bend this rule. Why would you? You do not always have control over how or when a constructor is called. One such case is in the Android framework.

112
Q

Delaying Initialization: Late initialization?

A

On Android, a class called Activity represents a screen in your application. You do not have control over when the constructor of your Activity is called. Instead, the earliest point of code execution you have is in a function called onCreate. If you cannot initialize your properties at instantiation time, when can you?

This is where late initialization becomes important – and more than just a simple bending of Kotlin’s rules on initialization.

Any var property declaration can be appended with the lateinit keyword, and the Kotlin compiler will let you put off initializing the property until you assign it.

lateinit var alignment: String

This is useful but must be regarded with care.

The lateinit keyword functions as a contract that you make with yourself: “I take responsibility for initializing this variable before it is accessed.” Kotlin does provide a way to check whether a late-initialized variable has been initialized: the isInitialized check shown in the example above. You can check isInitialized when there is any uncertainty about whether the lateinit variable is initialized to avoid an UninitializedPropertyAccessException.

However, isInitialized should be used sparingly – it should not be added to every lateinit, for example. If you are using isInitialized a lot, it is likely an indicator that you should be using a nullable type instead.

113
Q

Delaying Initialization: Lazy initialization?

A

Late initialization is not the only way to delay initialization. You can also hold off on initializing a variable until it is accessed for the first time. This concept is known as lazy initialization, and despite the name, it can actually make your code more efficient.

Most of the properties that you have initialized in this chapter have been pretty lightweight. Many classes, however, are more complex. They may require the instantiation of multiple objects, or they may involve some more computationally intensive task when being initialized, like reading from a file. If your property triggers a large number of these sorts of tasks, or if your class does not require access to a property right away, then lazy initialization could be a good choice.

Lazy initialization is implemented in Kotlin using a mechanism known as a delegate. Delegates define templates for how a property is initialized.

You use a delegate with the by keyword. The Kotlin standard library includes some delegates that are already implemented for you, and lazy is one of them. Lazy initialization takes a lambda in which you define any code that you wish to execute when your property is initialized.

val hometown = by lazy { selectHometown() }

Importantly, this code is only executed once – the first time that the delegated property (hometown, here) is accessed in name’s getter. Future access to the lazy property will use a cached result instead of performing the expensive computation again.

Lazy initialization is useful, but it can be a bit verbose, so stick to using lazy initialization for more computationally needy tasks.

114
Q

Initialization Gotchas?

A
class Player() {
        init {
            val healthBonus = health.times(3)
        }
    val health = 100
}

This code would not compile, because the health property is not initialized at the point that it is used by the init block. As we mentioned earlier, when a property is used within an init block, the property initialization must happen before it is accessed.

When health is defined before the initializer block, the code compiles.

For example, in the following code, a name property is declared, then a firstLetter function reads the first character from the property:

class Player() {
        val name: String
    private fun firstLetter() = name[0]
        init {
            println(firstLetter())
            name = "Madrigal"
        }
    }

This code will compile, because the compiler sees that the name property is initialized in the init block, a legal place to assign an initial value. But running this code would result in a null pointer exception, because the firstLetter function (which uses the name property) is called before the name property is assigned an initial value in the init block.

The compiler does not inspect the order properties are initialized in compared to the functions that use them within the init block. When defining an init block that calls functions that access properties, it is up to you to ensure that you have initialized those properties before calling the functions. When name is assigned before firstLetter is called, the code compiles and will run without error.

115
Q

Inheritance?

A

Inheritance is an object-oriented principle you can use to define hierarchical relationships between types.

116
Q

Inheritance: Creating a Subclass?

A

A subclass shares all properties with the class it inherits from, commonly known as the parent class or superclass.

For a class to be subclassed, it must be marked with the open keyword.

open class Room(val name: String)

Now that Room is marked open, create a TownSquare class in Room.kt by subclassing the Room class using the : operator, like so:

class TownSquare : Room(“Town Square”)

Another way for you to differentiate a subclass from its parent is through overriding.

Override load in TownSquare using the override keyword:

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

Protected visibility is a third option that restricts visibility to the class in which a property or function is defined or to any subclasses of that class.

117
Q

Inheritance: Creating a Subclass - Polymorphism?

A

var currentRoom: Room = TownSquare()
println(currentRoom.description())

Notice that the currentRoom variable’s type in Game.kt is still Room, despite the fact that the instance itself is a TownSquare, and its load function has been changed substantially from Room’s implementation. You explicitly declared the type of currentRoom to be Room so that it can hold any type of Room, even though you assigned currentRoom with a TownSquare constructor.

The different versions of load, based on the class they are called on, are an example of a concept in object-oriented programming called polymorphism.

Polymorphism is a strategy for simplifying the structure of your program. It allows you to reuse functions for common sets of features across groups of classes (like what happens when a player enters a room) and also to customize the behavior for the unique needs of a class (like the cheering crowd in TownSquare). When you subclassed Room to define TownSquare, you defined a new load implementation that overrides Room’s version. Now, when currentRoom’s load function is called, TownSquare’s version of load will be used – and no changes to Game.kt were required.

118
Q

Inheritance: Creating a Subclass - Prevent overriding for open class?

A

The final keyword allows you to specify that a function cannot be overridden.

Adding the final keyword to an inherited function will ensure that it cannot be overridden, even if the class in which it is defined is open.

119
Q

Type Checking?

A

The is operator is a useful tool that lets you query whether an object is of a certain type.

When branching conditionally on object type, order matters. For example.

class Room()

class TownSquare() : Room()

val townSquare = TownSquare()

println(when (townSquare) {
    is Room -> "Room"
    is TownSquare -> "Town square"
    else throw IllegalArgumentExceptinion()
})

Run this code, and this time Room is printed to the console, because the first branch evaluates to true.

println(when (townSquare) {
    is TownSquare -> "Town square"
    is Room -> "Room"
    else throw IllegalArgumentExceptinion()
})

The first branch in this when expression evaluates as true, because townSquare is of type TownSquare. The second branch is also true, because townSquare is also of type Room – but that does not matter, because the first branch was already satisfied. Run this code, and TownSquare is printed to the console.

120
Q

The Kotlin Type Hierarchy?

A

Every class in Kotlin descends from a common superclass, known as Any.

121
Q

Type casting?

A

(any as Room).name == “Fount of Blessings”

The as operator denotes a type cast. This cast says, “Treat any as if it were of type Room for the purposes of this expression.”

Casting is powerful and comes with great responsibility; you have to use it safely.

Casts allow you to attempt to cast any variable to any type, but it is up to you to make sure that you are confident in the type of what you are casting from and casting to. If you must make an unsafe cast, then surrounding it with a try/catch block is a good idea. It is best, however, to avoid type casting unless you are sure that the cast will succeed.

For example, so a cast from String to Int would cause a ClassCastException that may crash your program.

122
Q

Smart casting?

A

val isSourceOfBlessings = if (any is Player) {
any.isBlessed
}

The Kotlin compiler is smart enough to recognize that if the any is Player type check is successful for a branch, then any can be treated as a Player within that branch. Because it knows that casting any to Player will always succeed in this branch, the compiler lets you drop the cast syntax and just reference isBlessed, a Player property, on any.

Smart casting is an example of how the intelligence of the Kotlin compiler results in a more concise syntax.

123
Q

The object keyword?

A

If you need to hold on to a single instance with state that is consistent throughout the time that your program is running, consider defining a singleton. With the object keyword, you specify that a class will be limited to a single instance – a singleton. The first time you access an object, it is instantiated for you.

There are three ways to use the object keyword: object declarations, object expressions, and companion objects.

124
Q

Object declarations?

A

Object declarations are useful for organization and state management, especially when you need to maintain some state consistently throughout the lifespan of your program.

Because an object declaration is instantiated for you, you do not add a custom constructor with code to be called at initialization. Instead, you need an initializer block for any code that you want to be called when your object is initialized.

object Game {
    init {
        println("Welcome, adventurer.")
    }
}

An object declaration is referenced by one of its properties or functions.

125
Q

Object expressions?

A

Perhaps you need a class instance that is a variation of an existing class and will be used for a one-off purpose. In fact, it will be so temporary that it does not even require a name.

val abandonedTownSquare = object : TownSquare() {
override fun load() = “You anticipate applause, but no one is here…”
}

This object expression is a subclass of TownSquare where no one cheers your entrance. In the body of this declaration, the properties and functions defined in TownSquare can be overridden – and new properties and functions can be added – to define the data and behavior of the anonymous class.

This class still adheres to the rules of the object keyword in that there will only ever be one instance of it alive at a time, but it is significantly smaller in scope than a named singleton. As a side effect, an object expression takes on some of the attributes of where it is declared. If declared at the file level, an object expression is initialized immediately. If declared within another class, it is initialized when its enclosing class is initialized.

126
Q

Companion objects?

A

If you would like to tie the initialization of an object to a class instance, there is another option for you: a companion object. A companion object is declared within another class declaration using the companion modifier. A class can have no more than one companion object.

There are two cases in which a companion object will be initialized. First, a companion object is initialized when its enclosing class is initialized. This makes it a good place for singleton data that has a contextual connection to a class definition. Second, a companion object is initialized when one of its properties or functions is accessed directly.

Because a companion object is still an object declaration, you do not need an instance of a class to use any of the functions or properties defined in it.

class PremadeWorldMap {
        ...
        companion object {
            private const val MAPS_FILEPATH = "nyethack.maps"
            fun load() = File(MAPS_FILEPATH).readBytes()
        }
    }

PremadeWorldMap has a companion object with one function called load. If you were to call load from elsewhere in your codebase, you would do so without needing an instance of PremadeWorldMap, like so:

PremadeWorldMap.load()

The contents of this companion object will not be loaded until either PremadeWorldMap is initialized or load is called. And no matter how many times PremadeWorldMap is instantiated, there will only ever be one instance of its companion object.

127
Q

Nested Classes?

A

You can also use the class keyword to define a named class nested inside of another class.

128
Q

Data Classes?

A

As the name suggests, data classes are classes designed specifically for holding data, and they come with some powerful data manipulation benefits.

“Any” provides default implementations for all of these functions, but, as you have seen before, they are often not very reader friendly. Data classes provide implementations for these functions that may work better for your project.

  • toString()

The default toString() implementation for a class is not very human readable it shows reference where the object was allocated space in memory.

You can override toString in your class to provide your own implementation, just like any other open function. But data classes save you that work by providing their own default implementation.

  • equals()

By default, objects are compared by their references, because that is the default implementation of the equals function in Any.

You can provide your own equality check by overriding equals in your class to determine equality based on a comparison of properties, not memory references.

Again, data classes take care of this for you by providing an implementation of equals that bases equality on all of the properties declared in the primary constructor.

  • copy()

In addition to giving you more usable default implementations of functions on Any, data classes also provide a function that makes it easy to create a new copy of an object.

Say that you want to create a new instance of Player that has all of the same property values as another player except for isImmortal. If Player were a data class, then copying a Player instance would be as simple as calling copy and passing arguments for any properties that you would like to change.

val mortalPlayer = player.copy(isImmortal = false)

Data classes save you the work of implementing this copy function on your own.

  • Destructuring declarations

Another benefit of data classes is that they automatically enable your class’s data to be destructured.

Under the hood, destructuring declarations depend on the declaration of functions with names like component1, component2, etc., each declared for some piece of data that you would like to return. Data classes automatically add these functions for you for each property defined in their primary constructor.

There is nothing magic about a class supporting destructuring; a data class simply does the extra work required to make the class “destructurable” for you. You can make any class support destructuring by adding component operator functions to it.

However , there are also some limitations and requirements on data classes . Data classes :

  • must have a primary constructor with at least one parameter
  • require their primary constructor parameters to be marked either val or var
  • cannot be abstract , open , sealed , or inner.

If your class does not require the toString, copy, equals, or hashCode functions, a data class offers no benefits. And if you require a customized equals function – one that uses only certain properties rather than all properties for the comparison , for example – a data class is not the right tool , because it includes all properties in the equals function it automatically generates .

129
Q

Enumerated classes?

A

Enumerated classes , or “ enums , ” are a special type of class useful for defining a collection of constants , known as enumerated types .

enum class Direction { NORTH , EAST , SOUTH , WEST }

Enums are more descriptive than other types of constants , like strings . You can reference enumerated types using the name of the enum class , a dot , and the name of the type , like so :

Direction.EAST

Enums , like other classes , can also hold function declarations .

( Note that you need to add a semicolon to separate your enumerated type declarations from your function declarations . )

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

You call functions on enumerated types , not on the enum class itself , so calling updateCoordinate will look something like this :

Direction.EAST.updateCoordinate ( Coordinate ( 1 , 0 ) )

130
Q

Operator overloading?

A

When you want to use built - in operators with your custom types , you have to override the operators ’ functions to tell the compiler how to implement them for

Kotlin’s concise syntax is built on small improvements like this ( spellList [ 3 ] instead of spellList.get ( 3 ) ) .

operator fun plus ( other : Coordinate ) = Coordinate ( x + other.x , y + other.y )

    • plus Adds an object to another .
    • = plusAssign Adds an object to another and assigns the result to the first .
  • = = equals Returns true if two objects are equal , false otherwise .
  • > compareTo Returns true if the object on the lefthand side is greater than the object on the righthand side , false otherwise .
  • [ ] get Returns the element in a collection at a given index
  • . . . rangeTo Creates a range object .
  • in contains Returns true if an object exists within a collection .

These operators can be overloaded on any class , but make sure to do so only when it makes sense .

By the way , if you override equals yourself , you should also override a function called hashCode .

131
Q

Defining Structural Comparation?

A

Data classes are not permitted to be superclasses.

But you can provide your own implementation of equals and hashCode to specify how instances of your class should be compared structurally.

Note that overriding equals function we set up a structural comparison between the properties.

Whenever you define structural comparison you also should provide a hashCode. HashCode improves performance - how quickly value can be retrieved with a key when using a Map type, for example - and is tied to the uniqueness of a class instance.

override fun hashCode(): Int {
   var result = name.hashCode()
   result = 31 * result + 
   type.hashCode()
   return result
}
132
Q

Algebriac Data Types?

A

Algebriac data type (or ADTs for short) allow you to represent a closed set of possible subtypes that can be associated with a given type. Enum classes are a simple form of ADT.

One of the benefits of enums and other ADTs is that the compiler can check to ensure that you handled all possibilities, because ADT is a closed set of possible types.

For more complex ADTs, you can use Kotlin’s sealed classes to implement more sophisticated definistions. Seald classes let you specify an ADT similar to an enum but with more control over the specific subtypes that an enum provides.

Sealed class has a limited number of subclasses that must be defined within the same file where is defined - otherwise it is ineligible for subclassing.

sealed class StudentStatus {
    object NotEnrolled : StudentStatus()
    class Active(val courseId: String) : StudentStatus()
    object Graduated: StudentStatus()
} 

The object keyword is used for the statuses that require no course ID, since there will never be any variation on their instances, and the class keyword is used for the ACTIVE class because it will have different instances , since the course ID will change depending on the student.

fun studentMessage(status StudentStatus) : String {
    return when (status) {
         is StudentStatus.Active -> 
        "you are enrolled in: 
        ${status.courseId)"
        ...
        } 
}

Using the new sealed class in the when would allow you to now read the courseId from the ACTIVE class, accessible through smart casting.