1. Kotlin Programming Flashcards
What is Kotlin?
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 IntelliJ compiles the Kotlin code?
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.”
What is a REPL?
REPL is short for “read, evaluate, print, loop.”
What does mean targeting JVM?
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.
Is Kotlin limited to JVM?
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.
Why we specify data types and what that does tell to compiler?
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.
What is static type system and static type checking?
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.
Commonly used built-in types in Kotlin?
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
What is a Type Inference?
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
What is compile time constants?
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 you can inspect Kotlin Bytecode?
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.
Java Primitive Types in Kotlin?
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.
What is structural equality operator?
You express this with the ==.
What is called string concatenation?
Using the addition operator (+) to append a value to a string is called string concatenation.
How to evaluates whether the two instances point to the same reference?
You express this with the ===.
What is if else statement?
if (condition) { // expression } else { // expression }
Kotlin logical operator?
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.
What is conditional expressions?
A conditional expression is like a conditional statement, except that you assign the if/else to a value that you can use later.
When is acceptable to removing braces from if/else expressions?
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.
Kotlin Ranges?
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.
When expression?
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" }
String templates?
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}”)
What is a function?
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 to extract code to function?
There is two (three) options:
- Right click on the code you selected and choose Refactor -> Extract -> Function …
- Use shortcut: Control + Command + M
Anatomy of a Functions & Function Scope?
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.
What is a “Calling a Function”?
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.)
Functions - Default Arguments?
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) { … }
Single - Expression Functions?
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.
Unit Functions?
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.
What kind of type is Unit?
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.
Named Function Arguments?
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.
The Nothing Type?
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 is File-level functions from Kotlin represented in Java?
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.
Function Overloading?
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.
Function Name in Backticks?
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.
Anonymous Functions?
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.
Anonymous Functions: The function type?
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.
Anonymous Functions: Implicit returns?
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.
Anonymous Functions: Function arguments?
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.
Anonymous Functions: The it keyword?
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.
Anonymous Functions: Accepting multiple arguments?
Anonymous functions can certainly accept multiple named arguments.
val greeting: (String, Int) -> String = { player, year ->
Welcome $player, year $year”
}
Anonymous Functions: Type Inference Support?
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.
Anonymous Functions: Defining a Function That Accepts a Function?
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’}
Anonymous Functions: Function Inlining
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.
What is Function Reference?
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.
Function Type as a Return Type?
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.
Kotlin’s Lambdas are Closures?
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.
File-level variable?
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.
What is a Null?
Null is a special value that indicates that the value of a var or val does not exist.
Nullable and Non-nullable elements?
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.
Kotlin’s Explicit Null Type?
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.
Compile Time vs Runtime?
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.