Chapter 4
IN THIS CHAPTER
Making decisions with Kotlin statements
Repeating actions with Kotlin statements
Adding exception handling
Human thought centers on nouns and verbs. Nouns are the “stuff,” and verbs are the stuff's actions. Nouns are the pieces, and verbs are the glue. Nouns are, and verbs do. When you use nouns, you say, “book,” “room,” or “stuff.” When you use verbs, you say “Do this,” “Do that,” “Hoist that barge,” or “Lift that bale.”
Kotlin also has nouns and verbs. Kotlin’s nouns include String
, List
, and Array
, along with Android-specific things such as AppCompatActivity
, ActionBar
, and Bundle
. Kotlin’s verbs involve assigning values, choosing among alternatives, repeating actions, and other courses of action.
This chapter covers some of Kotlin’s verbs. In Chapter 5 of this minibook, you bring in the nouns.
When you’re writing computer programs, you’re constantly hitting forks in roads. Did the user correctly type his or her password? If yes, let the user work; if no, kick the bum out. So the Kotlin programming language needs a way of making a program branch in one of two directions. Fortunately, the language has a way: It’s called an if
statement. The use of an if
statement is illustrated in Listing 4-1. (If you want to play with the code that follows and in other areas of this chapter without having to start Android Studio, you can use an online interpreter, such as the one at https://try.kotlinlang.org/
.)
LISTING 4-1: Using an if
Statement
fun main(args: Array<String>) {
val a = 2
val b = 4
if (a > b) {
print("a is greater")
} else {
print("b is equal or greater")
}
}
An if
statement checks a condition, such as whether a
is greater than b
. In this case, b
is greater, so the output is "b is equal or greater"
.
An if
statement has the following form:
if (condition) {
statements to be executed when the condition is true
} else {
statements to be executed when the condition is false
}
In Listing 4-1, the condition being tested is
(a > b)
The condition in an if
statement must be enclosed within parentheses. The condition must be a Boolean
expression — an expression whose value is either true
or false
. (See Chapter 3 of this minibook for information about Java's primitive types, including the Boolean
type.) So, for example, the following condition is okay:
if (numberOfTries < 17) {
But the strange kind of condition that you can use in languages such as C++ is not okay:
if (17) { //This is incorrect.
if (a > b)
print("a is greater")
else
print("b is equal or greater")
An if
statement can also enjoy a full and happy life without an else
part. So the following code forms a complete if
statement:
if (a > b)
print("a is greater")
Kotlin also has an if
expression. An if
expression differs from an if
statement in the way that you create and use it. Instead of performing an action within the if
statement, you place the result in the variable output of the if
expression and then use it as needed. Listing 4-2 shows an example of an if
expression.
LISTING 4-2: Using an if
Expression
fun main(args: Array<String>) {
val a = 2
val b = 4
val max = if (a > b) {
print("a is greater")
a
} else {
print("b is equal or greater")
b
}
print("
The value of max is ${max}.")
}
In this case, max
receives the output of the if
expression. When a
is greater, it receives the value of a
. Likewise, when b
is greater, it receives the value of b
. However, the output expression need not be a number. For example, you could change the code to make max
a Boolean
, a Double
, or a String
, as shown here:
val max: String = if (a > b) {
print("a is greater")
a.toString()
} else {
print("b is equal or greater")
b.toString()
}
Kotlin has several ways to test for equality (“Is this value the same as that value?”). None of these ways is the first thing you'd think of doing. In particular, to find out whether variable a
is equal to variable b
, you don't write if (a = b)
. Instead, you use a double equal sign (==
). You write if (a == b)
. In Kotlin, the single equal sign (=
) is reserved for assignment. So val n = 5
means “Let n
stand for the value 5
.”
Comparing two strings is yet another story. When you compare two strings with one another, you don't want to use the double equal sign. Using the double equal sign would ask, “Is this string stored in exactly the same place in memory as that other string?” That’s usually not what you want to ask. Instead, you usually want to ask, “Does this string have the same characters in the same order in it as that other string?” To ask the second question (the more appropriate question), Kotlin’s String
type has a method named equals
:
if (response.equals("yes")) {
The equals
method compares two strings to see whether they have the same characters in them. In this paragraph's tiny example, the variable response
refers to a string, and the text "yes"
refers to a string. The condition response.equals("yes")
is true if response
refers to a string whose letters are 'y'
, and then 'e'
, and then 's'
.
Like most programming languages, Kotlin has the usual complement of comparison operators (such as <
for “less than”) and logical operators (such as &&
for “and”). For a list of such operators, visit https://kotlinlang.org/docs/reference/keyword-reference.html
.
fun main(args: Array<String>) {
val a: String = "A String Value"
val b = a
println(a === b)
}
Because Kotlin spends so much time ensuring that null
values appear only in the right place, you also use a special !!
operator to verify that a nullable object isn't equal to null
. This particular kind of check is called an assertion. Kotlin asserts whether a
is equal to null
. You use this operator before performing a task that would fail with a null
value, such as obtaining the length of a string, as shown here:
fun main(args: Array<String>) {
val a: String? = "A String Value"
println(a!!.length)
}
This particular form will raise an exception when a
equals null
. If you don't want to raise an exception, you use the Elvis operator (?:
) form, shown here:
fun main(args: Array<String>) {
val a: String? = null
println(a?.length ?: 0)
}
In this case, Kotlin first checks whether a
equals null
. If so, it outputs a value of 0
, rather than trying to obtain the length of a
.
We'll bet you were expecting something about the switch
statement here. Most languages use some type of switch
statement to choose from many alternatives, but a switch
statement can be extremely limited. What a switch
statement does is replace a series of if…else
statements with a single statement. You compare a single value with one or more constants, and there is only one word for the situation: boring. A when
structure is so much more flexible, for these reasons:
break
clause).Here’s a basic when
statement:
fun main(args: Array<String>) {
val a = 5
when (a) {
0, 1, 2, 3, 4 -> println("Less than 5")
5 -> println(5)
else -> print("Greater than 5")
}
}
The code begins by providing an argument, a
, which equals 5
. It then checks a
against the values 0
through 4
and the precise value 5
, and it includes a clause to use when all else fails: else
. If you have worked with other languages and relied on a switch
statement, you immediately notice that
when
statement is more concise.The when
statement isn't limited to constants. This version of the previous example uses an expression (a range) in place of individual constants. It also checks to determine whether the argument is an Int:
when (a) {
in 0..4 -> println("Less than 5")
is Int -> println("It's an Int")
5 -> println(5)
else -> print("Greater than 5")
}
The expression form of when
works much like the expression form of if
, except you're looking at multiple conditions:
fun main(args: Array<String>) {
val a = 5
val result: Any = when (a) {
in 0..4 -> "Less than 5"
5 -> 5
else -> false
}
println(result)
}
Depending on the value of a
, result could contain a String
, Int
, or Boolean
. The use of the Any
type enables you to receive the most appropriate value type. Type casting would let you convert the value to what you need in given situations.
fun main(args: Array<String>) {
val a = false
val result = when (a) {
true -> "The value is true."
false -> "The value is false."
}
println(result)
}
The expression form of when
can also appear as part of a function. Consider this example in which the expression form of when
forms the body of a function used to check data type:
fun main(args: Array<String>) {
println(checkType(3))
println(checkType(true))
println(checkType("Hello"))
println(checkType(arrayOf(1, 2, 3)))
}
fun checkType(x: Any) = when (x) {
is String -> "Value is a String."
is Int -> "Value is an Int."
is Boolean -> "Value is a Boolean."
else -> "Value isn't a usable type."
}
Another use of when
is to check various conditions without an argument, which is quite frankly mind boggling. You use it to choose an action based on likely conditions, with the most likely or most desirable conditions first in the list. Here is a basic example:
fun main(args: Array<String>) {
val a = 5
val b = "Goodbye"
val c = true
when {
a - 1 > 5 -> println("a is greater than 5")
b == "Hello" -> println("Hello There!")
c is Boolean -> println("c is ${c}")
else -> println("None of the conditions is true.")
}
}
In this case, no argument is supplied because none is needed. The code checks each condition in turn and provides the desired output. Note that you can include math or other kinds of expressions as part of the conditions. Kotlin will perform the required manipulation before it determines the truth value of a particular condition. The only real requirement is that the condition eventually valuate to a Boolean
.
In 1966, the company that brings you Head & Shoulders shampoo made history. On the back of the bottle, the directions for using the shampoo read, “Lather, rinse, repeat.” Never before had a complete set of directions (for doing anything, let alone shampooing your hair) been summarized so succinctly. People in the direction-writing business hailed this as a monumental achievement. Directions like these stood in stark contrast to others of the time. (For instance, the first sentence on a can of bug spray read, “Turn this can so that it points away from your face.” Duh!)
Aside from their brevity, the thing that made the Head & Shoulders directions so cool was that, with three simple words, they managed to capture a notion that's at the heart of all instruction-giving — the notion of repetition. That last word, repeat, took an otherwise bland instructional drone and turned it into a sophisticated recipe for action.
The fundamental idea is that when you’re following directions, you don’t just follow one instruction after another. Instead, you take turns in the road. You make decisions (“If HAIR IS DRY, then USE CONDITIONER”), and you go into loops (“LATHER-RINSE and then LATHER-RINSE again”). In application development, you use decision-making and looping all the time.
A while
statement has the following form:
while (condition) {
statements to be executed
}
You can omit the curly braces when the loop has only one statement to be executed.
A traditional while
statement performs a task while a condition remains true
. The following loop counts down from 5 through 1:
fun main(args: Array<String>) {
var a = 5
while (a > 0) {
println(a)
--a
}
}
Each time the loop executes, the --a
statement reduces the value of a
by 1
. Note that this is a prefix form; you could also use a-=1
to reduce the value by 1. You don't see a 0
in the output because the condition is a > 0
, and the condition is checked before the println(a)
statement is executed. The while
statement has no expression form, so you won't ever see a variable made equal to a while
statement as you can with the if
or when
expressions.
In an Android app, a content provider feeds a cursor to your code. You can think of the cursor as a pointer to a row in a table. In Listing 4-3, each table row has three entries: an _id
, a name
, and an amount
. Supposedly, the _id
uniquely identifies a row, the name
is a person's name, and the amount
is a huge number of dollars owed to you by that person.
LISTING 4-3: A while
Loop
cursor.moveToFirst()
while (!cursor.isAfterLast()) {
val _id: String = cursor.getString(0)
val name: String = cursor.getString(1)
val amount: String = cursor.getString(2)
textViewDisplay.append(_id + " " +
name + " " + amount + "
")
cursor.moveToNext()
}
A cursor's moveToFirst
method makes the cursor point to the first row of the table. Regardless of the row a cursor points to, the cursor's moveToNext
method makes the cursor point to the next row of the table. The cursor's isAfterLast
method returns true
when, having tried to move to the next row, it finds that there is no next row.
In Kotlin, an exclamation point (!
) means “not,” so while (!cursor.isAfterLast())
means “while it's not true that the cursor has reached past the table's last row …” So the loop in Listing 4-3 repeatedly does the following:
As long as the cursor has not reached past the last row,
get the string in the row's initial column and
make _id refer to that string,
get the string in the row's middle column and
make name refer to that string,
get the string in the row's last column and
make amount refer to that string, and
append these strings to the textViewDisplay, and then
move the cursor to the next row in preparation
for returning to the top of the while statement.
Imagine that a particular cursor's table has 100 rows. Then a processor executes the statements inside Listing 4-3's while
loop 100 times. Using the official developer lingo, the processor performs 100 loop iterations.
Kotlin provides a second form of the while
statement called the do
statement. Unlike the while
statement, which may execute only once, a do
statement will always execute at least one time because the condition is checked at the end of the loop, rather than at the beginning of the loop. A do
statement has the following form:
do {
statements to be executed
} while (condition)
The following example shows how the do
statement works by setting a variable
to a value that won't meet conditions.
fun main(args: Array<String>) {
var a = 0
do {
println(a)
--a
} while (a > 0)
}
The example outputs a 0
but then stops. Even though the value of a
at the outset doesn't meet the condition while (a > 0)
, the loop executes once anyway.
To find a particular row of a cursor's table, you normally do a query. (For straight talk about queries, see Book 4.) You almost never perform a do-it-yourself search through a table's data. But just this once, look at a loop that iterates through row after row. The loop is in Listing 4-4.
LISTING 4-4: Leap Before You Look
cursor.moveToFirst()
name: String = ""
do {
val _id: String = cursor.getString(0)
name = cursor.getString(1)
val amount: String = cursor.getString(2)
textViewDisplay.append(_id + " " +
name + " " + amount + "
")
cursor.moveToNext()
} while (!name.equals("Burd") && !cursor.isAfterLast())
In Listing 4-4, you're looking for a row with the name Burd. (After all, the bum owes you lots of money.) When you enter the loop, the cursor points to the table's first row. Before checking a row for the name Burd, you fetch that first row's data and add the data to the
where the user can see what's going on.textViewDisplay
Before you march on to the next row (the next loop iteration), you check a condition to make sure that another row is worth visiting. (Check to make sure that you haven't yet found that Burd guy, and that you haven't moved past the last row of the table.)
Also note the use of the &&
(AND) operator. The while (!name.equals("Burd") && !cursor.isAfterLast())
expression has two conditions: !name.equals("Burd")
and !cursor.isAfterLast()
. Both conditions must be true
for the do
loop to continue processing. If you had used the ||
(OR) operator instead, the loop would continue to run as long as one of the two conditions was true
.
An array is a bunch of values placed in a single object. When working with a Kotlin array, what you're really doing is working with a class that stores information, not a primitive construct as found in other languages. Kotlin arrays are extremely flexible depending on how you declare them. For example, here’s a basic Kotlin array containing mixed types:
fun main(args: Array<String>) {
var myArray = arrayOf(1, 2, true, false, "Hello")
println(myArray[1])
}
The object myArray
now contains five members: two Int
values, two Boolean
values, and a String
. To access a particular element in the array, you provide the array name, followed by an index in square brackets. The output of this example is 2 because Kotlin arrays are zero-based. So, myArray[1]
is actually the second array element.
An array can also accept input from expressions. Here is an example that fills the variable byThree
with the values 1 through 3 and then repeats the process.
fun main(args: Array<String>) {
var byThree = Array(6) {i -> (i % 3) + 1}
println(byThree[3])
}
The output is 1
in this case. The example uses a lambda expression, which you find explained in detail in Chapter 6 of this minibook. The value i
is the current index, starting with 0 and going through 5. Therefore, (i % 3) + 1
is the remainder of the index divided by 3 and with 1 added.
Now that you have a basic idea of what arrays are, you can look at a few details. The following sections give some specifics that you'll find handy while creating your Android apps.
Being able to place anything in an array might seem nice at first, but you might want to limit an array to a specific type. To make this happen, you must define the type as part of the array by placing the type in angle brackets, as shown in the following example:
fun main(args: Array<String>) {
var words: Array<String> = Array<String>(6) {String()}
println("Content of words[0] ${words[0]}")
words[0] = "Hello"
println("Content of words[0] ${words[0]}")
}
Notice that you must define a size for the array or that the initialization arguments must imply a size. Kotlin arrays can’t change size after you create them. To make a larger array, you must first create a new array and then move the current content to the new one. The String
array, words
, begins with empty strings, which you verify by printing the array content at index 0.
The words
array is invariant, which means that it accepts only one kind of input, a String
. If you tried to add a line of code like words[1] = 2
, the compiler would complain that the entry is of the wrong type. Creating an invariant array means that you can depend on the array to contain a specific type of information, which makes working with the array easier and less error prone.
fun main(args: Array<String>) {
var words: Array<String?> = Array<String?>(6) {null}
words[0] = "Hello"
words[1] = "Goodbye"
println("Content of words[0] ${words[0]}")
println("Content of words[1] ${words[1]}")
println("Content of words[2] ${words[2] == null}")
}
Because this is a nullable array, as signified by Array<String?>
, you can set the initializer to {null}
. Until you specifically assign a value to an element, its entry equals null
. In this case, words[2] == null
returns true
because you haven't specifically set words[2]
to a String
value.
Kotlin arrays have many marvelous features that you can access through functions or properties. The complete list of these functions and properties appears at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-array/index.html
. However, here is an example showing some of the more interesting functions that are at your disposal when working with numeric arrays:
fun main(args: Array<String>) {
var numbers: Array<Int> = arrayOf(1, 2, 4, 8, 3, 7, 4, 5)
println(numbers.all({i -> i > 0}))
println(numbers.any({i -> i == 8}))
println(numbers.contains(3))
println(numbers.find({i -> i > 4}))
println(numbers.findLast({i -> i > 4}))
println(numbers.distinct())
numbers.sort()
println(numbers.distinct())
println(numbers.average())
println(numbers.distinct().count())
println(numbers.max())
println(numbers.min())
println(numbers.sum())
}
The functions are grouped into three categories: search, list manipulation, and math. When you need to look for things, you may not always need to look for a specific value, but simply know that the value exists, which is where numbers.all({i -> i > 0})
and numbers.any({i -> i == 8})
come in. These functions help you determine whether all the values meet a minimum criteria or at least one of the values does. The lambda expression {i -> i > 0}
says that for each element in the array, compare the element to 0
and if the element is greater than 0
, return true
. The numbers.contains(3)
function looks for a specific value. If you need to find the element where a value appears, you can use functions like numbers.find({i -> i > 4})
and numbers.findLast({i -> i > 4})
. Here are the outputs from this section.
true
true
true
8
5
The array functions include a lot of list-manipulation features — possibly more than you can use in a lifetime. However, the ability to find distinct values and to sort the array so that the values are in order are high on the list. Fortunately, you don't have to come up with any odd lambda expressions to use these functions. Here are the outputs:
[1, 2, 4, 8, 3, 7, 5]
[1, 2, 3, 4, 5, 7, 8]
The math functions let you perform statistics on the array. Statistics are useful for all sorts of things, like determining whether item one or item two is a better value in your shopping app. You also use statistics for games and to determine whether fraud is occurring. In some cases, you can combine functions to get unique results, such as numbers.distinct().count()
, which counts only the unique entries in a list. Here is the output from this part of the code:
4.25
7
8
1
34
Because you need to interoperate with Java at times, it would be handy to have an array type that works with primitive variables without the boxing overhead (the time needed to place a primitive variable into an object). In this case, you rely on a specific kind of array that has nothing to do with the standard Kotlin Array
object. These primitive arrays take the following form:
var numbers: IntArray = intArrayOf(1, 2, 3, 4)
You can also initialize these arrays to a specific size and specific content using any of these methods:
val numbers1 = IntArray(5)
val numbers2 = IntArray(5) {-1}
val numbers3 = IntArray(5) {i -> i + 1}
In the first case, you get an array of five elements containing zeros. The second case gives you five elements filled with –1. The third case uses a lambda expression to fill the elements with values from 1 through 5.
ByteArray
ShortArray
IntArray
LongArray
FloatArray
DoubleArray
BooleanArray
CharArray
Kotlin doesn’t have a for
statement of the type used with other programming languages in which you define a variable to keep track of a specific number of loop iterations. When the variable meets a certain criterion, the loop stops, so basically you can say, “Execute this loop five times and then stop.” Instead, Kotlin relies on a for
statement that uses an iterator, as described in the introduction to this section. Essentially, an iterator makes it easy to access members of an object one item at a time, but you don't have to know how many times at the outset. As with a standard for
loop, however, the loop executes a fixed number of times and then stops. The following sections describe how the Kotlin for
statements work.
A Kotlin for
statement has this form:
for (item in collection) print(item)
A collection is any object that provides an iterator. To qualify, the object must possess these features:
iterator()
. The return type of the iterator()
function must provide access to these elements: next()
member or extension function that obtains the next item.hasNext()
member or extension function that returns Boolean indicating whether there is a next item to obtain.iterator()
, next()
, and hasNext()
— need to be marked as operator
.A collection is a generic sort of grouping of items into a single object using the Iterable
interface. However, you can get more specific with certain kinds of collections because they follow a particular pattern. Here are three kinds of Kotlin collections normally associated with numeric data that follow a particular pattern:
Comparable
interface. Not all ranges implement the Iterable
interface, which means that you can't use them in a for
statement without modification. For example, the following code will raise an error if you try to use versionRange
in a for
loop:
fun main(args: Array<String>) {
val versionRange = KotlinVersion(1, 11)..KotlinVersion(1, 30)
println(KotlinVersion(0, 9) in versionRange) // Displays false
println(KotlinVersion(1, 14) in versionRange) // Displays true
}
Int
, Long
, and Char
, that provides a starting point, ending point, and non-zero step between values.Ranges take all sorts of forms, and you’ll use a lot of them in Android development. However, the simplest ranges are found when working with numbers. The following example shows a range of Int
values from 1 through 10 and two methods of printing that range:
val numbers = 1..10
for (number in numbers) println(number)
numbers.forEach({println(it)})
Progressions specify a step between values in a range. You can define them using a number of methods. Here are two methods of creating a progression as a variable:
val numbers = 2..10 step 2
val numbers = IntProgression.fromClosedRange(2, 10, 2)
In both cases, you specify a starting value, ending value, and a step value. You can also create a progression as part of the for
loop:
val numbers = 2..10
for (number in numbers step 2) println(number)
numbers.forEach({i -> if (i % 2 == 0) println(i)})
In the first case, you specify a step
value as part of the for
loop condition. When working with the forEach()
function, you need to specify the step value as part of a lambda expression, as shown here, or use some other method of filtering values. The lambda expression used here selects even values by obtaining the result of the current value mod 2.
You can turn any range into a sequence by using the asSequence()
function. Using a sequence can reduce processing steps when working with a multiple-step process. However, after you turn a range into a sequence, you can't work with it as a progression, which means that you can’t specify a step
value as part of a for
loop. Instead, you need to resort to techniques such as filtering, as shown here:
val numbers = (2..10).asSequence()
for (number in numbers.filter({it % 2 == 0})) {
println(number)
}
numbers.forEach({i -> if (i % 2 == 0) println(i)})
fun main(args: Array<String>) {
val versionRange = KotlinVersion(1, 11)..KotlinVersion(1, 30)
val start = versionRange.start
val end = versionRange.endInclusive
for (major in start.major..end.major) {
for (minor in start.minor..end.minor) {
println("$major.$minor")
}
}
}
When working with a standard for
loop, you have an index value you use to access each member of the collection. However, using the iterator approach means that you either need to create a counter or simply do without an index value in Kotlin using its approach. Fortunately, you have two methods to work with indexes in Kotlin:
withIndex()
forEachIndex
These two functions produce about the same results and should have the same performance characteristics. The only difference is in how you implement them. Use the method that works best for you. Here's an example of withIndex()
:
val numbers = 2..10
for ((index, number) in numbers.withIndex()) {
println("The value $number is at index $index.")
}
You don’t have to do anything special with the range. The only changes are your addition of withIndex()
to the range and output values to two variables: index
and number
. The index is always the first variable, and the value is always the second variable in the output.
The forEachIndex
approach does rely on a lambda expression. It looks like this:
val numbers = 2..10
numbers.forEachIndexed{index, number ->
println("The value $number is at index $index.")}
The results are the same as before, but the technique used to obtain them differs. In this case, index
and number
act as inputs to the lambda expression, but otherwise the code looks the same.
When working with standard loops, each iteration of the loop depends on the previous iteration. Consequently, it's difficult to perform looping in a multiprocessing environment because of the element of state, the recording of loop conditions, in each iteration. One of the goals of functional programming is to enable you to split up everything in an app so that each part can execute on a different processor. Although this might not seem a very laudable goal with the kinds of examples you see in this book, it’s an essential element of higher-end apps that perform machine learning or deep learning tasks (among other things). In these environments, developers often use GPUs instead of CPUs to perform processing, and some of the GPU cards used contain hordes of processors. (The NVidia Titan V contains 5,120 CUDA core; see https://www.nvidia.com/en-us/titan/titan-v/
.) Consequently, you need another way to perform looping, which is where recursion comes into play. However, even in an Android app, the ability to break up a problem into very small pieces for execution on separate processors can be important, especially when it comes to games.
Chapter 6 of this minibook delves more deeply into recursion, but for now, take a look at a simple example:
fun main(args: Array<String>) {
val numbers = (2..10)
LoopIt(numbers, numbers.count() - 1)
}
fun LoopIt(range: Iterable<Any>, posit: Int) {
if (posit >= 0) {
LoopIt(range, posit - 1)
println(range.elementAt(posit))
} else {
println("All elements visited!")
}
}
The actual display of values occurs in the LoopIt()
function. The idea is that you can't display multiple range values — you can display only one range value using println(range.elementAt(posit))
. So, you must work through the range until you get to the first element and then display each single element in turn until you reach the end. The LoopIt(range, posit - 1)
call keeps calling LoopIt()
one element position at a time until it reaches the beginning of the range: element 0.
The amazing thing is that each of these calls is separate and the variables are val
, which means they can't change. Until the code reaches element 0, the LoopIt(range, posit - 1)
call doesn’t return; instead, it keeps moving forward in a circle until it reaches its destination. When the code does reach element 0, it prints the message "All elements visited!"
. What you see as output is
All elements visited!
2
3
4
5
6
7
8
9
10
Sometimes a loop condition that you didn't expect or want to avoid will occur. The condition isn’t necessarily an error, but it’s just not what you wanted. In this case, you need to perform a structural jump. Kotlin provides three such jumps:
continue
: The loop stops at its current point of processing and continues with the next loop iteration. This type of jump allows processing to continue, even if you want to avoid a particular condition.break
: The loop exits at its current point of processing and continues with the next statement in the current function. You use this kind of jump when a condition will make further loop processing pointless, but you want to continue with the current processing instructions.return
: The loop exits at its current point of processing and returns to the caller. You use this kind of jump when a condition will prevent further processing in a called function, but the condition isn't necessarily an error — it’s just unexpected or unwanted.You use continue
or break
in any loop, whether the code exists in the current function or in a called function. Here's an example of using continue
and break
in a for
loop:
fun main(args: Array<String>) {
val numbers = (2..10)
for (number in numbers) {
if (number == 3) continue
if (number == 8) break
println(number)
}
println("The loop has ended.")
}
The output from this example is
2
4
5
6
7
The loop has ended.
The return
jump works a bit differently from continue
or break
. Here's an example of return
:
fun main(args: Array<String>) {
val numbers = (2..10)
processData(numbers)
println("The call has returned.")
}
fun processData(numbers: IntRange) {
for (number in numbers) {
if (number == 8) return
println(number)
}
println("The loop has ended.")
}
In this case, the jump occurs in a called function, processData()
. You see the values 2 through 7 output, but when the value reaches 8, the code calls return
. The example never displays "The loop has ended."
, as it would when break
is called. Instead, the entire processData()
function ends and the call returns to main()
.
The Kotlin programming language has a mechanism called exception handling. With exception handling, a program can detect that things are about to go wrong and respond by creating a brand-new object. In the official terminology, the program is said to be throwing an exception. That new object, an instance of the Exception
class, is passed like a hot potato from one piece of code to another until some piece of code decides to catch the exception. When the exception is caught, the program executes some recovery code, buries the exception, and moves on to the next normal statement as if nothing had ever happened (at least in theory).
The whole thing is done with the aid of several Kotlin keywords. These keywords are as follows:
throw
: Creates a new exception object.try
: Encloses code that has the potential to create a new exception object. In the usual scenario, the code inside a try
clause contains calls to methods whose code can create one or more exceptions.catch
: Deals with the exception, buries it, and then moves on.finally
: Sometimes you need to execute some code regardless of whether an exception occurs. For example, you might need to perform some cleanup so that when the program recovers because of your exquisite error handling, it doesn't immediately throw another exception.Nothing
: This is a special type used to tell the Kotlin compiler what a particular function never returns. It’s an end point when something too terrible for words occurs. When the compiler sees Nothing
, it assumes that the app won't continue. Here’s an example:
fun appFailure(message: String): Nothing {
throw IllegalArgumentException(message)
}
Kotlin spends a good deal of time looking over your shoulder, so you'll encounter fewer exceptions as you write apps. However, it’s a good idea to make preparations to handle them anyway. In the following example, the code attempts to convert a String
value to an Int
value:
val myIntStr: String = "Hello"
try {
val myInt = myIntStr.toInt()
println("Success: $myInt!")
} catch (e: NumberFormatException) {
println("myIntStr is in the wrong format.")
} finally {
println("Try another int conversion?")
}
Obviously, a value of "Hello"
will never convert to an Int
value, so the toInt()
function throws a NumberFormatException
, which the code catches and then displays "myIntStr is in the wrong format."
. However, if you change "Hello"
to "2"
, the conversion does take place and you see "Success: 2!"
instead. No matter what happens, however, you always see the final output of "Try another int conversion?"
.
Unlike other languages, try
isn't a statement; it’s an expression. So you can use it as part of any expression you create. Here’s an example of performing an Int
conversion that relies on an expression:
val myIntStr: String = "Hello"
val myInt: Int = try { myIntStr.toInt() }
catch (e: NumberFormatException) {
println("myIntStr is in the wrong format.")
0
}
println(myInt)
A collection is a grouping of items in a specific kind of container. Some containers, such as List
, are simple; others, such as Map
, are more complicated. The container you choose depends on the complexity of the app environment — that is, how the data in the container will get used. The following sections take a brief look at Kotlin containers.
Kotlin doesn’t actually support many native collection types. You have three choices, as described here:
List
: An ordered collection of items in which item values can appear more than once and each value is accessed by a unique index. You would use a List
to hold the individual words in a sentence, for example, when order is important.Set
: A collection of unique items in which each value is accessed by an index, but the order of the items is unimportant. You would use a Set
to hold the letters of the alphabet or the numbers 0 through 9, for example.Map
: A collection of key and value pairs in which the keys are ordered and unique, while the values need not be unique. Each key points to a specific value, even when that value is the same as some other value in the Map
. Another term for a Map
is a dictionary. You often see a Map
used to store logical connections between items, such as locations on a map or the hierarchy in an organization.However, these three choices aren't the extent of your options. Kotlin provides Java interoperability, so this is one place where you may need to go the Kotlin version of the Java route to make things work. With this idea in mind, you also have these Java-specific options:
ArrayList |
LinkedList |
Vector |
Stack |
Queue |
PriorityQueue |
Deque |
ArrayDeque |
HashSet |
LinkedHashSet |
SortedSet |
TreeSet |
val myMap = mapOf("Yellow" to 1, "Green" to 2, "Blue" to 3)
for ((Key, Value) in myMap) {
println("The value $Value is associated with $Key.")
}
Notice that when you create a Map
, you must associate a key, such as "Yellow"
, with a value, such as 1
. Likewise, when iterating through a Map, you obtain both a key and a value for each loop.
As previously mentioned, sometimes you need to use a Java collection when Kotlin doesn't provide what you need. One of the most common Java collections you use is the Stack
. A Stack
pushes new values into the collection and pops them back off the top, which makes this is a last-in/first-out (LIFO) collection. Here is an example of a Java Stack
implemented in Kotlin:
import java.util.Stack
fun main(args: Array<String>) {
val myStack = Stack<String>()
myStack.push("Hello")
myStack.push("Goodbye")
myStack.push("Yesterday")
println(myStack.count())
println(myStack.pop())
}
The example begins by importing the proper collection from java.util
, which is a Stack
in this case. You use the standard Kotlin approach to creating a new Stack
, but you must make sure to specify the data type that will appear in the Stack
or the compiler will complain. Theoretically, you can use the Any
type with a Java collection.
To add a new item to myStack
, you call the push()
function with whatever value you want to add. You determine the number of items on the stack by calling count()
. To remove an item, you call pop()
, and the value is provided as a return.
By default, Kotlin collections are immutable, meaning read-only. After you create the collection, you can't add, subtract, or change values. For example, the documentation for List
at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-list/index.html
contains all sorts of methods for searching, selecting, and otherwise stretching the List
in every conceivable direction, but the data remains the same. You can obtain part of the data and put it into a new List
, but the current List
is immutable.
val myList = listOf(1, 2, 3, 4, 5)
for (item in myList) println(item)
A MutableList
offers the flexibility of being able to grow or reduce the size of the collection and to change collection items. When you look at the documentation for MutableList
at https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/-mutable-list/index.html
, you see all sorts of functions to modify the list content. Here is an example of a MutableList
:
val myList = mutableListOf(1, 2, 3, 4, 5)
myList.add(6)
myList.set(2, 20)
myList.remove(0)
for (item in myList) println(item)
Here is the output from this example:
1
2
20
4
5
6
All the changes that the code made are shown in the output, as expected. Even with this sort of list, you have limits on what you can do unless you create your code correctly at the outset. For example, myList.add("Hello")
would fail with myList
in this case. To make it possible to add a string, you'd need to declare myList
like this:
val myList: MutableList<Any> = mutableListOf(1, 2, 3, 4, 5)
Notice that the myList
is now defined as being of type MutableList<Any>
. As you work through your Android app, you have to choose the correct methods of interacting with app data to make your app secure, efficient, and fast. The collection choices that Kotlin provides let you make good choices more easily.
34.231.180.210