Chapter 11. Miscellaneous

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.

11.1 Working with the Kotlin Version

Problem

You want to find out programmatically which version of Kotlin you are currently using.

Solution

Use the CURRENT property in the companion object of the KotlinVersion class.

Discussion

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.

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.

Note

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.

Example 11-2. Comparing Kotlin versions
@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.

Example 11-3. Checking that a version is above given values
@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.

11.2 Executing a Lambda Repeatedly

Problem

You want to execute a given lambda expression multiple times.

Solution

Use the built-in repeat function.

Discussion

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.

Example 11-4. Definition of the 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.

Example 11-5. Using 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.

11.3 Forcing when to Be Exhaustive

Problem

You want the compiler to force the when statement to have a clause for every possibility.

Solution

Add a simple extension property called exhaustive to a generic type that returns a value, and chain it to the when block.

Discussion

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.

Example 11-6. Remainder when a number is divided by 3
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.

Example 11-7. Using when to return a value
fun 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...") 1
}
1

Does not compile without else clause

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.

Example 11-8. Adding an exhaustive property to any object
val <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.

Example 11-9. Remainder when a number is divided by 3 (exhaustive)
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  1
}
1

Property forces the compiler to require else clause

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.

11.4 Using the replace Function with Regular Expressions

Problem

You want to replace all instances of a substring with a given value.

Solution

Use the replace function on String, which is overloaded to take either a String argument or a regular expression.

Discussion

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.

Example 11-10. Two overloads for 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.

Example 11-11. Using 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.

Example 11-12. Palindrome checker, written in Java style
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.

Example 11-13. Palindrome checker, Kotlin style
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.

See Also

The let function is discussed in Recipes 7.3 and 7.4.

11.5 Converting to Binary String and Back

Problem

You want to convert a number to a binary string (or some other base), or parse such a string to an integer.

Solution

Use the toString or toInt function overloads that take a radix as an argument.

Discussion

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.

Example 11-14. Converting an 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.

Example 11-15. Padding binary strings
@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:

  • Those who know binary,

  • Those who don’t, and

  • Those who didn’t realize this is actually a ternary joke.

11.6 Making a Class Executable

Problem

You have a class that contains a single function, and you want to make invoking that function trivial.

Solution

Override the invoke operator function on the class to call the function.

Discussion

Many operators in Kotlin can be overridden. To do so, you only need to override the function associated with that operator.

Note

The Kotlin reference docs refer to this as operator overloading, but the concept is the same.

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.

Example 11-16. JSON data returned by the Open Notify service
{
    "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.

Example 11-17. Data classes modeling the returned JSON data
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.

Example 11-18. Accessing the RESTful service and parsing the result
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 {        1
    operator fun invoke(): AstroResult {   2
        val responseString = URL(ASTRO_URL).readText()
        return Gson().fromJson(responseString,
            AstroResult::class.java)
    }
}
1

Arbitrary name for included function

2

Operator function invoke makes class executable

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.

Example 11-19. Using the executable class
internal class AstroRequestTest {
    val request = AstroRequest()     1

    @Test
    internal fun `get people in space`() {
        val result = request()       2
        assertThat(result.message, `is`("success"))
        assertThat(result.number.toInt(),
            `is`(greaterThanOrEqualTo(0)))
        assertThat(result.people.size,
            `is`(result.number.toInt()))
    }
}
1

Instantiates the class

2

Invokes the class as a function (calls invoke)

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.

See also

Recipe 3.5 discusses operator overloading in more detail.

11.7 Measuring Elapsed Time

Problem

You want to know how long a code block takes to run.

Solution

Use either the measureTimeMillis or measureNanoTime functions in the standard library.

Discussion

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.

Example 11-20. Measuring elapsed time for a code block
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 has 8 processors
doubling 1 with on thread main
doubling 2 with on thread main
doubling 3 with on thread main
doubling 4 with on thread main
doubling 5 with on thread main
doubling 6 with on thread main
Sequential stream took 616ms
doubling 3 with on thread ForkJoinPool.commonPool-worker-11
doubling 4 with on thread main
doubling 5 with on thread ForkJoinPool.commonPool-worker-7
doubling 6 with on thread ForkJoinPool.commonPool-worker-3
doubling 2 with on thread ForkJoinPool.commonPool-worker-5
doubling 1 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.

Example 11-21. Implementation of the measureTimeMillis function
public 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.

11.8 Starting Threads

Problem

You want to run code blocks on concurrent threads.

Solution

Use the thread function in the kotlin.concurrent package.

Discussion

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.

Example 11-22. Starting multiple threads at random intervals
(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-2 for 2 after 184ms
Thread-5 for 5 after 207ms
Thread-4 for 4 after 847ms
Thread-0 for 0 after 917ms
Thread-3 for 3 after 967ms
Thread-1 for 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.

Example 11-23. Starting daemon threads
(0..5).forEach { n ->
    val sleepTime = Random.nextLong(range = 0..1000L)
    thread(isDaemon = true) {      1
        Thread.sleep(sleepTime)
        println("${Thread.currentThread().name} for $n after ${sleepTime}ms")
    }
}
1

Threads are daemon threads, so program exits before threads finish running

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.

Example 11-24. Implementation of the thread function in the standard library
public 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.

Example 11-25. Joining the threads together
(0..5).forEach { n ->
    val sleepTime = Random.nextLong(range = 0..1000L)
    thread {
        Thread.sleep(sleepTime)
        println("${Thread.currentThread().name} for $n after ${sleepTime}ms")
    }.join()  1
}
1

Causes each thread to join the previous one

The output will now resemble the following:

Thread-0 for 0 after 687ms
Thread-1 for 1 after 661ms
Thread-2 for 2 after 430ms
Thread-3 for 3 after 412ms
Thread-4 for 4 after 918ms
Thread-5 for 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.

See Also

Chapter 13 discusses concurrency in much more detail.

11.9 Forcing Completion with TODO

Problem

You want to guarantee that you complete a particular function or test.

Solution

Use the TODO function (with an optional reason) that throws an exception if you don’t complete a function.

Discussion

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.

Example 11-26. Implementation of the TODO function
public 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.

Example 11-27. Using the TODO function in regular code
fun 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.

Example 11-28. Using TODO in a test
fun `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.

11.10 Understanding the Random Behavior of Random

Problem

You want to generate a random number.

Solution

Use one of the functions in the Random class.

Discussion

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.

Example 11-29. Declarations in the abstract Random class
open 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.

Example 11-30. The overloads of the 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.

Example 11-31. Companion object inside 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.

Example 11-32. Using a seeded random number generator
@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.

11.11 Using Special Characters in Function Names

Problem

You want to write function names that are easy to read.

Solution

You can use underscores or surround your function name in backticks, but only in tests.

Discussion

Kotlin supports wrapping the names of functions inside backticks, as shown in Example 11-33.

Example 11-33. Wrapping function names inside backticks
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.

Example 11-34. Using underscores in function names
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.

Example 11-35. Test function names can use either technique
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.

11.12 Telling Java About Exceptions

Problem

Your Kotlin function throws what Java would consider a checked exception, but you need to tell Java that.

Solution

Add a @Throws annotation to the function signature.

Discussion

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.

Note

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.

Example 11-36. A Kotlin function that throws an 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.

Example 11-37. Calling the Kotlin function from Java
public static void doNothing() {
    houstonWeHaveAProblem();  1
}
1

Crashes with an IOException

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

Example 11-38. Trying to prepare for the expected exception
public static void useTryCatchBlock() {
    try {                        1
        houstonWeHaveAProblem();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

public static void useThrowsClause() throws IOException { 2
    houstonWeHaveAProblem();
}
1

Does not compile

2

Compiles, but compiler warns about “unnecessary” throws clause

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.

Example 11-39. Adding a @Throws annotation
@Throws(IOException::class)    1
fun houstonWeHaveAProblem() {
    throw IOException("File or resource not found")
}
1

Tells Java this function throws an IOException

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.219.130