This chapter consists of recipes that don’t fit any of the other headings. Here you’ll find how to make the when
function exhaustive, how to measure the elapsed time of a function, and how to use the TODO
function from the standard library, among many others.
Use the CURRENT
property in the companion object of the KotlinVersion
class.
Since version 1.1, the kotlin package includes a class called KotlinVersion
that wraps the major, minor, and patch values for the version number. Its toString
method returns the combination as major.minor.patch given an instance of this class. The current instance of the class is contained in a public field called CURRENT
in the companion object.
It’s therefore trivially easy to return the current Kotlin version. Just access the field KotlinVersion.CURRENT
, as in Example 11-1.
fun
main
(
args
:
Array
<
String
>)
{
println
(
"The current Kotlin version is ${KotlinVersion.CURRENT}"
)
}
The result is the major/minor/patch version of the Kotlin compiler, such as 1.3.41
. All three parts of this quantity are integers between 0 and MAX_COMPONENT_VALUE
, which has the value 255.
The CURRENT
property is annotated as a public @JvmField
in the source code, so it is available from Java as well.
The KotlinVersion
class implements the Comparable
interface. That means you can use operators like <
or >
with it. The class also implements both equals
and hashCode
. Finally, the constructors for KotlinVersion
allow you to supply either a major
and a minor
value, or a major
, a minor
, and a patch
value.
As a result, you can do any of the following shown in Example 11-2.
@Test
fun
`
comparison
of
KotlinVersion
instances
work
`
()
{
val
v12
=
KotlinVersion
(
major
=
1
,
minor
=
2
)
val
v1341
=
KotlinVersion
(
1
,
3
,
41
)
assertAll
(
{
assertTrue
(
v12
<
KotlinVersion
.
CURRENT
)
},
{
assertTrue
(
v1341
<=
KotlinVersion
.
CURRENT
)
},
{
assertEquals
(
KotlinVersion
(
1
,
3
,
41
),
KotlinVersion
(
major
=
1
,
minor
=
3
,
patch
=
41
))
}
)
}
The isAtLeast
function is also available, to check whether a particular version of Kotlin is not less than major, minor, and patch values, as shown in Example 11-3.
@Test
fun
`
current
version
is
at
least
1
_3
`
()
{
assertTrue
(
KotlinVersion
.
CURRENT
.
isAtLeast
(
major
=
1
,
minor
=
3
))
assertTrue
(
KotlinVersion
.
CURRENT
.
isAtLeast
(
major
=
1
,
minor
=
3
,
patch
=
40
))
}
It is therefore easy enough to check the Kotlin version and work with it directly.
Use the built-in repeat
function.
The repeat
function is in the standard library. It is an inline function that takes two arguments: an Int
representing the number of times to iterate, and a function of the form (Int) -> Unit
to execute.
The current implementation looks like Example 11-4.
repeat
function@kotlin
.
internal
.
InlineOnly
public
inline
fun
repeat
(
times
:
Int
,
action
:
(
Int
)
->
Unit
)
{
contract
{
callsInPlace
(
action
)
}
for
(
index
in
0
until
times
)
{
action
(
index
)
}
}
The function executes a provided lambda a specified number of times, providing a zero-based index of the current iteration as a parameter.
A trivial example is shown in Example 11-5.
repeat
fun
main
(
args
:
Array
<
String
>)
{
repeat
(
5
)
{
println
(
"Counting: $it"
)
}
}
The output is simply this:
Counting: 0 Counting: 1 Counting: 2 Counting: 3 Counting: 4
Using repeat
rather than a loop is an illustration of an internal iterator, where the actual looping process is handled by the library.
Like the if
statement, a notable feature of the when
clause is that it returns a value. It behaves similarly to Java’s switch
statement, but unlike Java, you don’t need to break out of each section, nor do you need to declare a variable outside it if you want to return a value.
For example, say you want to print out the remainder when a number is divided by 3, as in Example 11-6.
fun
printMod3
(
n
:
Int
)
{
when
(
n
%
3
)
{
0
->
println
(
"$n % 3 == 0"
)
1
->
println
(
"$n % 3 == 1"
)
2
->
println
(
"$n % 3 == 2"
)
}
}
If a when
expression does not return a value, Kotlin does not require it to be exhaustive, and this is an example where that is useful. Mathematically, we know that the remainder can be only 0, 1, or 2, so this is in fact an exhaustive check, but the compiler can’t make that leap. If this simple function is converted into an expression, as in Example 11-7, that becomes clear.
when
to return a valuefun
printMod3SingleStatement
(
n
:
Int
)
=
when
(
n
%
3
)
{
0
-
>
println
(
"$n % 3 == 0"
)
1
-
>
println
(
"$n % 3 == 1"
)
2
-
>
println
(
"$n % 3 == 2"
)
else
-
>
println
(
"Houston, we have a problem..."
)
}
The compiler requires the else
clause in this expression, even though the println
function does not return anything. The presence of the equals sign means there’s an assignment, which means Kotlin requires an exhaustive conditional expression.
Since Kotlin interprets any return as forcing an else
block, you can take advantage of that to force all when
blocks to be exhaustive by making them automatically return a value. To do so, create an extension property called exhaustive
, as shown in Example 11-8.
exhaustive
property to any objectval
<
T
>
T
.
exhaustive
:
T
get
()
=
this
This block adds the exhaustive
property to any generic type T
, with a custom getter method that returns the current object.
Now this property can be added to anything, including a when
block, to force an artificial return. Example 11-9 shows how this is done.
fun
printMod3Exhaustive
(
n
:
Int
)
{
when
(
n
%
3
)
{
0
-
>
println
(
"$n % 3 == 0"
)
1
-
>
println
(
"$n % 3 == 1"
)
2
-
>
println
(
"$n % 3 == 2"
)
else
-
>
println
(
"Houston, we have a problem..."
)
}
.
exhaustive
}
The exhaustive
property at the end of the when
block returns the current object, so the Kotlin compiler requires it to be exhaustive.
While the purpose of this example was to force when
to be exhaustive, it is also a nice example of how adding a simple extension property to a generic type can be useful as well.
Use the replace
function on String
, which is overloaded to take either a String
argument or a regular expression.
The String
class implements the CharSequence
interface, which means there are actually two versions of the replace
function defined for it, as shown in Example 11-10.
replace
fun
String
.
replace
(
oldValue
:
String
,
newValue
:
String
,
ignoreCase
:
Boolean
=
false
):
String
fun
CharSequence
.
replace
(
regex
:
Regex
,
replacement
:
String
):
String
Each of these replaces all the occurrences of the matching string or regular expression with the supplied value. The replace
function defined on String
takes an optional argument about case sensitivity, which defaults to not ignoring case.
These two overloads can be confusing, because a user might assume that the first one (which takes a String
argument) will treat the string as though it were a regular expression, but that turns out not to be the case. The test in Example 11-11 shows the differences between the two functions.
replace
with the two overloads@Test
fun
`
demonstrate
replace
with
a
string
vs
regex
`
()
{
assertAll
(
{
assertEquals
(
"one*two*"
,
"one.two."
.
replace
(
"."
,
"*"
))
},
{
assertEquals
(
"********"
,
"one.two."
.
replace
(
"."
.
toRegex
(),
"*"
))
}
)
}
The first example replaces the (literal) dots with an asterisk, while the second example treats the dots as they would be handled by regular expressions, meaning any single character. The first option therefore replaces only the two dots with asterisks, while the second one replaces all the individual characters with asterisks.
In fact, two potential traps exist for Java developers here:
The replace
function replaces all occurrences, not just the first one. In Java, the equivalent method is called replaceAll
.
The overload with a string as the first argument does not interpret that string as a regular expression. Again, this is unlike the Java method behavior. If you meant for your string to be interpreted as a regular expression, first convert it by using the toRegex
function.
To give a more interesting example, consider checking whether a string is a palindrome. Palindromes are defined as strings that are the same forward and backward, ignoring both case and punctuation. Note that you can implement the function in a Java style (i.e., “speaking Kotlin with a Java accent”), as in Example 11-12.
fun
isPal
(
string
:
String
):
Boolean
{
val
testString
=
string
.
toLowerCase
().
replace
(
"""[W+]"""
.
toRegex
(),
""
)
return
testString
==
testString
.
reversed
()
}
There is nothing wrong with this approach, and it works just fine. It changes the string to lowercase and then uses the regular expression version of replace
to replace all “nonword characters” with empty strings. In a regular expression, w
would represent any word character, meaning lowercase a–z
, uppercase A–Z
, the numbers 0–9
, and underscores. The capitalized version of w
is W
, which is the opposite of w
.
An arguably more idiomatic version of this same function is shown in Example 11-13.
fun
String
.
isPalindrome
()
=
this
.
toLowerCase
().
replace
(
"""[W+]"""
.
toRegex
(),
""
)
.
let
{
it
==
it
.
reversed
()
}
The differences are as follows:
In this case, isPalindrome
is added as an extension function to String
, so no argument is needed. Inside the implementation, the current string is referenced as this
.
The let
function allows you to write the entire test as a single expression on the generated test string. The local variable testString
is no longer needed.
Because now the body is a single expression, the braces have been replaced with an equals sign, as often happens in Kotlin functions.
The two approaches work the same way, but you’re more likely to encounter the second approach from more experienced Kotlin developers. It’s no doubt best to be familiar with both techniques. Either way, the implementation uses the overload of replace
that takes a regular expression as its first argument.
Use the toString
or toInt
function overloads that take a radix
as an argument.
The StringsKt
class contains an inline extension function on Int
called toString
that takes a radix. Likewise, the same class contains an extension function on Int
to go the other way. That means you can convert from an Int
into a binary string (i.e., a string composed of 1s and 0s) by invoking that method, as in Example 11-14.
Int
to a binary string@Test
internal
fun
toBinaryStringAndBack
()
{
val
str
=
42.
toString
(
radix
=
2
)
assertThat
(
str
,
`is`
(
"101010"
))
val
num
=
"101010"
.
toInt
(
radix
=
2
)
assertThat
(
num
,
`is`
(
42
))
}
The string produced by toString(Int)
will truncate any leading zeros. If you don’t want to do that, you can postprocess the string by using the padStart
function.
Say that you need to encode data by a single binary property. For example, playing cards come in red and black colors, and you want all permutations of four consecutive cards. That’s a simple matter of counting from 0 to 15 in binary (with 0 representing red and 1 for black, or the other way around), but you don’t want to lose the leading zeros in that case. You can solve that problem as shown in Example 11-15.
@Test
internal
fun
paddedBinaryString
()
{
val
strings
=
(
0.
.
15
).
map
{
it
.
toString
(
2
).
padStart
(
4
,
'0'
)
}
assertThat
(
strings
,
contains
(
"0000"
,
"0001"
,
"0010"
,
"0011"
,
"0100"
,
"0101"
,
"0110"
,
"0111"
,
"1000"
,
"1001"
,
"1010"
,
"1011"
,
"1100"
,
"1101"
,
"1110"
,
"1111"
))
val
nums
=
strings
.
map
{
it
.
toInt
(
2
)
}
assertThat
(
nums
,
contains
(
0
,
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
,
10
,
11
,
12
,
13
,
14
,
15
))
}
Because the toString
and toInt
functions work for all integer bases, you don’t have to restrict yourself to binary, though that’s probably the most common use case. That means a nice variation on the classic joke (mentioned in Example 2-27) is as follows:
val
joke
=
"""
There
are
$
{
3.
toString
(
3
)}
kinds
of
developers
:
-
Those
who
know
binary
,
-
Those
who
don
'
t
,
and
-
Those
who
didn
'
t
realize
this
is
actually
a
ternary
joke
"""
println
(
joke
)
That prints the following:
There are 10 kinds of developers:
Many operators in Kotlin can be overridden. To do so, you only need to override the function associated with that operator.
To implement an operator, you provide a member function or an extension function with the proper name and arguments. Any function that overloads operators needs to include the operator
modifier.
One particular function is special: invoke
. The invoke
operator function allows instances of a class to be called as functions.
As an example, consider the free RESTful web service provided by Open Notify that returns JSON data representing the number of astronauts in space at any given moment. An example of the returned data is shown in Example 11-16.
{
"people"
:
[
{
"name"
:
"Oleg Kononenko"
,
"craft"
:
"ISS"
},
{
"name"
:
"David Saint-Jacques"
,
"craft"
:
"ISS"
},
{
"name"
:
"Anne McClain"
,
"craft"
:
"ISS"
}
],
"number"
:
3
,
"message"
:
"success"
}
The response shows that three astronauts are currently aboard the International Space Station.
The nested JSON objects imply that to parse this structure, two Kotlin classes are required, as shown in Example 11-17.
data
class
AstroResult
(
val
message
:
String
,
val
number
:
Number
,
val
people
:
List
<
Assignment
>
)
data
class
Assignment
(
val
craft
:
String
,
val
name
:
String
)
The Assignment
class is the combination of astronaut name and craft. The AstroResult
class is used for the overall response, which includes (hopefully) the “success” message, the number of astronauts, and their assignments.
If all you need is a simple HTTP GET request, Kotlin added an extension function called readText
to the java.net.URL
class. Invoking the sample service is therefore as simple as calling
var
response
=
URL
(
"http://..."
).
readText
()
and processing the resulting JSON string. Since the desired URL is a constant and you can use any library, such as Google’s Gson, to parse the JSON data, a reasonable class for accessing the service is given in Example 11-18.
import
com.google.gson.Gson
import
java.net.URL
class
AstroRequest
{
companion
object
{
private
const
val
ASTRO_URL
=
"http://api.open-notify.org/astros.json"
}
// fun execute(): AstroResult {
operator
fun
invoke
(
)
:
AstroResult
{
val
responseString
=
URL
(
ASTRO_URL
)
.
readText
(
)
return
Gson
(
)
.
fromJson
(
responseString
,
AstroResult
::
class
.
java
)
}
}
In this class, the URL for the service is added to the companion object and specified to be a constant. The single function is used to access the service and provide the resulting string to Gson for parsing into an instance of AstroResult
.
The function could be called anything. If it had been called execute
, for instance, then invoking it would be done as follows:
val
request
=
AstroRequest
()
val
result
=
request
.
execute
()
println
(
result
.
message
)
And so on. There’s nothing wrong with that approach, but observe that the class exists only to contain the single function. Kotlin is fine with using top-level functions, but it seems appropriate to include the URL as a constant as well. In other words, it is natural to create a class like AstroRequest
as shown.
Since there is only one purpose for the class, changing the name of the function to invoke
and adding the keyword operator
to it makes the class itself executable, as Example 11-19 shows.
internal
class
AstroRequestTest
{
val
request
=
AstroRequest
(
)
@Test
internal
fun
`
get
people
in
space
`
(
)
{
val
result
=
request
(
)
assertThat
(
result
.
message
,
`is`
(
"success"
)
)
assertThat
(
result
.
number
.
toInt
(
)
,
`is`
(
greaterThanOrEqualTo
(
0
)
)
)
assertThat
(
result
.
people
.
size
,
`is`
(
result
.
number
.
toInt
(
)
)
)
}
}
Because AstroResult
and Assignment
are data classes, you can always just print the result, which looks like this:
AstroResult(message=success, number=3, people=[Assignment(craft=ISS, name=Oleg Kononenko), Assignment(craft=ISS, name=David Saint-Jacques), Assignment(craft=ISS, name=Anne McClain)])
The tests verify the individual properties.
By supplying the invoke
operator function, the instance can be executed directly by adding parentheses to a reference. If desired, you can also add overloads of the invoke
function with any needed arguments.
Recipe 3.5 discusses operator overloading in more detail.
The kotlin.system package includes the measureTimeMillis
and measureNanoTime
functions. Using them to determine how long a block takes to run is quite simple, as shown in Example 11-20.
fun
doubleIt
(
x
:
Int
):
Int
{
Thread
.
sleep
(
100L
)
println
(
"doubling $x with on thread ${Thread.currentThread().name}"
)
return
x
*
2
}
fun
main
()
{
println
(
"${Runtime.getRuntime().availableProcessors()} processors"
)
var
time
=
measureTimeMillis
{
IntStream
.
rangeClosed
(
1
,
6
)
.
map
{
doubleIt
(
it
)
}
.
sum
()
}
println
(
"Sequential stream took ${time}ms"
)
time
=
measureTimeMillis
{
IntStream
.
rangeClosed
(
1
,
6
)
.
parallel
()
.
map
{
doubleIt
(
it
)
}
.
sum
()
}
println
(
"Parallel stream took ${time}ms"
)
}
The output of this snippet resembles the following:
This machine has8
processors doubling1
with on thread main doubling2
with on thread main doubling3
with on thread main doubling4
with on thread main doubling5
with on thread main doubling6
with on thread main Sequential stream took 616ms doubling3
with on thread ForkJoinPool.commonPool-worker-11 doubling4
with on thread main doubling5
with on thread ForkJoinPool.commonPool-worker-7 doubling6
with on thread ForkJoinPool.commonPool-worker-3 doubling2
with on thread ForkJoinPool.commonPool-worker-5 doubling1
with on thread ForkJoinPool.commonPool-worker-9 Parallel stream took 110ms
Since the JVM reports eight processors, the parallel
function on a stream splits the work among them and each processor gets a single element to double. Thus the result is that running the operation in parallel takes only about 100 milliseconds, while running it sequentially takes about 600 milliseconds.
The implementation of the measureTimeMillis
function in the standard library is shown in Example 11-21.
measureTimeMillis
functionpublic
inline
fun
measureTimeMillis
(
block
:
()
->
Unit
):
Long
{
val
start
=
System
.
currentTimeMillis
()
block
()
return
System
.
currentTimeMillis
()
-
start
}
Because it takes a lambda as an argument, this is a higher-order function, so as is typical, it is inlined for efficiency. The implementation just delegates to Java’s System.currentTimeMillis
method before and after executing the block argument. The implementation of measureNanoTime
does the same thing, but delegates to System.nanoTime
.
These two functions make it easy to do a simple profile of code performance. For a better estimate, consider the Java Microbenchmark Harness (JMH) project at OpenJDK.
Use the thread
function in the kotlin.concurrent package.
Kotlin provides a trivial extension function called thread
that can be used to create and start threads easily. The signature of thread
is shown here:
fun
thread
(
start
:
Boolean
=
true
,
isDaemon
:
Boolean
=
false
,
contextClassLoader
:
ClassLoader
?
=
null
,
name
:
String
?
=
null
,
priority
:
Int
=
-
1
,
block
:
()
->
Unit
):
Thread
Because start
defaults to true
, this makes it easy to create and start multiple threads, as in Example 11-22.
(
0.
.
5
).
forEach
{
n
->
val
sleepTime
=
Random
.
nextLong
(
range
=
0.
.
1000L
)
thread
{
Thread
.
sleep
(
sleepTime
)
println
(
"${Thread.currentThread().name} for $n after ${sleepTime}ms"
)
}
}
This code starts six threads, each of which sleeps for a random number of milliseconds between 0 and 1,000, and then prints the name of the thread. The output resembles this:
Thread-2for
2
after 184ms Thread-5for
5
after 207ms Thread-4for
4
after 847ms Thread-0for
0
after 917ms Thread-3for
3
after 967ms Thread-1for
1
after 980ms
Note you don’t need to call start
to start each thread, since the start
parameter in the thread
function is true by default.
The isDaemon
parameter lets you create daemon threads. If all the remaining threads in an application are daemon threads, the application can shut down. In other words, if the code in the previous example is replaced by that in Example 11-23, there will be no output at all, because the main
function will exit before any threads complete.
(
0.
.
5
)
.
forEach
{
n
-
>
val
sleepTime
=
Random
.
nextLong
(
range
=
0.
.
1
0
0
0L
)
thread
(
isDaemon
=
true
)
{
Thread
.
sleep
(
sleepTime
)
println
(
"${Thread.currentThread().name} for $n after ${sleepTime}ms"
)
}
}
The required code block is a lambda that takes no arguments and returns Unit
. This is consistent with the Runnable
interface, or simply the signature of the run
method in Thread
. The implementation of the thread
function is shown in Example 11-24.
thread
function in the standard librarypublic
fun
thread
(
start
:
Boolean
=
true
,
isDaemon
:
Boolean
=
false
,
contextClassLoader
:
ClassLoader
?
=
null
,
name
:
String
?
=
null
,
priority
:
Int
=
-
1
,
block
:
()
->
Unit
):
Thread
{
val
thread
=
object
:
Thread
()
{
public
override
fun
run
()
{
block
()
}
}
if
(
isDaemon
)
thread
.
isDaemon
=
true
if
(
priority
>
0
)
thread
.
priority
=
priority
if
(
name
!=
null
)
thread
.
name
=
name
if
(
contextClassLoader
!=
null
)
thread
.
contextClassLoader
=
contextClassLoader
if
(
start
)
thread
.
start
()
return
thread
}
The implementation creates an object of type Thread
and overrides its run
method to invoke the supplied block
. It then sets the various supplied properties and calls start
.
Because the function returns the created thread, you can make all the threads run sequentially by invoking the join
method on them, as in Example 11-25.
(
0.
.
5
)
.
forEach
{
n
-
>
val
sleepTime
=
Random
.
nextLong
(
range
=
0.
.
1
0
0
0L
)
thread
{
Thread
.
sleep
(
sleepTime
)
println
(
"${Thread.currentThread().name} for $n after ${sleepTime}ms"
)
}
.
join
(
)
}
The output will now resemble the following:
Thread-0for
0
after 687ms Thread-1for
1
after 661ms Thread-2for
2
after 430ms Thread-3for
3
after 412ms Thread-4for
4
after 918ms Thread-5for
5
after 755ms
Of course, that obviates the need to run inside threads in the first place, but it demonstrates that you can invoke methods on the returned threads.
Chapter 13 discusses concurrency in much more detail.
Use the TODO
function (with an optional reason) that throws an exception if you don’t complete a function.
Developers often leave notes to themselves to complete a function that they’re not ready to finish at the moment. In most languages, you add a “TODO” statement in a comment, as in this example:
fun
myCleverFunction
()
{
// TODO: look up cool implementation
}
The Kotlin standard library includes a function called TODO
, the implementation of which is shown in Example 11-26.
TODO
functionpublic
inline
fun
TODO
(
reason
:
String
):
Nothing
=
throw
NotImplementedError
(
"An operation is not implemented: $reason"
)
The source is inlined for efficiency and throws a NotImplementedError
when invoked. In regular source code, it’s easy enough to use, as in Example 11-27.
TODO
function in regular codefun
main
()
{
TODO
(
reason
=
"none, really"
)
}
fun
completeThis
()
{
TODO
()
}
The result of executing this script is as follows:
Exception in thread"main"
kotlin.NotImplementedError: An operation is not implemented: none, really at misc.TodosKt.main(
todos.kt:4)
at misc.TodosKt.main(
todos.kt)
The optional reason
argument can explain what the developer intends.
The TODO
function can also be used in a test, with an expected exception until the test is completed, as in Example 11-28.
TODO
in a testfun
`
todo
test
`
()
{
val
exception
=
assertThrows
<
NotImplementedError
>
{
TODO
(
"seriously, finish this"
)
}
assertEquals
(
"An operation is not implemented: seriously, finish this"
,
exception
.
message
)
}
The TODO
function is one of those convenient additions to the library that are easy to overlook until someone points it out to you. Hopefully, you can find ways to take advantage of it.
Use one of the functions in the Random
class.
The basics of kotlin.random.Random
are straightforward, but the implementation is quite subtle. First, the easy part. If you want a random Int
, use one of the overloads of nextInt
. The documentation for kotlin.random.Random
states that it is an abstract class, but includes the methods in Example 11-29.
Random
classopen
fun
nextInt
():
Int
open
fun
nextInt
(
until
:
Int
):
Int
open
fun
nextInt
(
from
:
Int
,
until
:
Int
):
Int
All three of these functions are given a default implementation. Using them is easy enough, as shown in Example 11-30.
nextInt
function@Test
fun
`
nextInt
with
no
args
gives
any
Int
`
()
{
val
value
=
Random
.
nextInt
()
assertTrue
(
value
in
Int
.
MIN_VALUE
..
Int
.
MAX_VALUE
)
}
@Test
fun
`
nextInt
with
a
range
gives
value
between
0
and
limit
`
()
{
val
value
=
Random
.
nextInt
(
10
)
assertTrue
(
value
in
0.
.
10
)
}
@Test
fun
`
nextInt
with
min
and
max
gives
value
between
them
`
()
{
val
value
=
Random
.
nextInt
(
5
,
10
)
assertTrue
(
value
in
5.
.
10
)
}
@Test
fun
`
nextInt
with
range
returns
value
in
range
`
()
{
val
value
=
Random
.
nextInt
(
7.
.
12
)
assertTrue
(
value
in
7.
.
12
)
}
That last example, however, is not listed as one of the functions in the Random
class. Instead, it’s an extension function, whose signature is shown here:
fun
Random
.
nextInt
(
range
:
IntRange
):
Int
The result is that if you look at the import statements in the previous test cases, you’ll see that they import both kotlin.random.Random
and kotlin.random.nextInt
, the latter being the extension function.
The implementation of the Random
class is quite interesting. The methods listed in Example 11-29 are provided, followed by a companion object of type Random
. A snippet from the implementation is shown in Example 11-31.
Random
companion
object
Default
:
Random
()
{
private
val
defaultRandom
:
Random
=
defaultPlatformRandom
()
override
fun
nextInt
():
Int
=
defaultRandom
.
nextInt
()
override
fun
nextInt
(
until
:
Int
):
Int
=
defaultRandom
.
nextInt
(
until
)
override
fun
nextInt
(
from
:
Int
,
until
:
Int
):
Int
=
defaultRandom
.
nextInt
(
from
,
until
)
// ...
}
The companion object gets the default implementation, and overrides all the declared methods to delegate to the default. The implementation of the defaultPlatformRandom
function is internal.
The same pattern is included for other types, like Boolean
, Byte
, Float
, Long
, and Double
, as well as the unsigned types UBytes
, UInt
, and ULong
.
To make things even more fun, there is also a function called Random
that takes an Int
or Long
seed, which returns a repeatable random number generator seeded with the argument. See Example 11-32.
@Test
fun
`
Random
function
produces
a
seeded
generator
`
()
{
val
r1
=
Random
(
12345
)
val
nums1
=
(
1.
.
10
).
map
{
r1
.
nextInt
()
}
val
r2
=
Random
(
12345
)
val
nums2
=
(
1.
.
10
).
map
{
r2
.
nextInt
()
}
assertEquals
(
nums1
,
nums2
)
}
Given the same seed, the calls to nextint
provide the same sequence of random numbers.
Using the random number generators in Kotlin is straightforward, but examining the implementation of the methods (using an abstract class whose methods are overridden inside its own companion object) may give you ideas about how to design your own classes in the future.
You can use underscores or surround your function name in backticks, but only in tests.
Kotlin supports wrapping the names of functions inside backticks, as shown in Example 11-33.
fun
`
only
use
backticks
on
test
functions
`
()
{
println
(
"This works but is not a good idea"
)
}
fun
main
()
{
`
only
use
backticks
on
test
functions
`
()
}
Wrapping the function name inside backticks allows you to put spaces in the name for readability. If you do this in a regular function, IntelliJ IDEA will flag it, saying, “Function name may only contain letters and digits.” So the function will compile and run, as shown, but isn’t a good practice.
Another option is to use underscores, as in Example 11-34.
fun
underscores_are_also_okay_only_on_tests
()
{
println
(
"Again, please don't do this outside of tests"
)
}
fun
main
()
{
underscores_are_also_okay_only_on_tests
()
}
Again, this will compile and run, but the compiler will issue a warning like, “Function name should not contain underscores.”
On the other hand, you can use either mechanism inside tests, and it counts as idiomatic Kotlin (as shown in the Coding Conventions guide). So the example shown in Example 11-35 is fine.
class
FunctionNamesTest
{
@Test
fun
`
backticks
make
for
readable
test
names
`
()
{
// ...
}
@Test
fun
underscores_are_fine_here_too
()
{
// ...
}
}
Even better, the provided readable names will show up on the test report, and anything that makes testing clearer and easier is a good thing.
All exceptions in Kotlin are considered unchecked, meaning the compiler does not require you to handle them. It’s easy enough to add try/catch/finally blocks to a Kotlin function if you wish to catch an exception, but you are not forced to do so.
Kotlin doesn’t have the throws
keyword that Java uses to declare that a method may throw an exception.
That’s fine until you try to invoke that function from Java. If the Kotlin function potentially throws an exception that Java would consider checked, you need to let Java know about it if you want to catch it.
For example, say you have a Kotlin function that throws an IOException
, which is a checked exception in Java, shown in Example 11-36.
IOException
fun
houstonWeHaveAProblem
()
{
throw
IOException
(
"File or resource not found"
)
}
In Kotlin, this function does not need a try/catch block or a throws
clause in order to compile. The function throws an IOException
, as shown.
This function can be called from Java, and an exception will result, as in Example 11-37.
public
static
void
doNothing
(
)
{
houstonWeHaveAProblem
(
)
;
}
(The source code for this example includes a static import for the invoked function.)
The problem comes if you decide you want to prepare for the IOException
by either wrapping the call inside a try/catch block or adding a throws
clause to the Java call, as in Example 11-38.
public
static
void
useTryCatchBlock
(
)
{
try
{
houstonWeHaveAProblem
(
)
;
}
catch
(
IOException
e
)
{
e
.
printStackTrace
(
)
;
}
}
public
static
void
useThrowsClause
(
)
throws
IOException
{
houstonWeHaveAProblem
(
)
;
}
Neither of these work the way you want. If you try to add an explicit try/catch block, the code won’t compile because Java thinks the specified IOException
in the catch
block is never thrown in the corresponding try
block. In the second case, the code will compile, but your IDE (and the compiler) will warn you that you have “unnecessary” code.
The way to make either approach work is to add a @Throws
annotation to the Kotlin code, as in Example 11-39.
@Throws
annotation@Throws
(
IOException
::
class
)
fun
houstonWeHaveAProblem
(
)
{
throw
IOException
(
"File or resource not found"
)
}
Now the Java compiler knows you need to prepare for the IOException
. Of course, the doNothing
function no longer compiles, because IOException
is checked so you need to prepare for it.
The @Throws
annotation exists simply for Java/Kotlin integration. It solves a specific problem, but it works exactly as advertised.
3.15.219.130