Chapter 2. Basic Kotlin

This chapter contains recipes that work with the fundamentals of Kotlin. They show you how to use the language without relying on specific libraries.

2.1 Using Nullable Types in Kotlin

Problem

You want to ensure that a variable is never null.

Solution

Define the type of a variable without a question mark. Nullable types also combine with the safe call operator (?.) and the Elvis operator (?:)

Discussion

The most attractive feature of Kotlin may be that it eliminates almost all possible nulls. In Kotlin, if you define a variable without including a trailing question mark, the compiler will require that value to be non-null, as in Example 2-1.

Example 2-1. Declaring a non-nullable variable
var name: String

// ... later ...
name = "Dolly" 1
// name = null 2
1

Assignment to a non-null string

2

Assignment to null does not compile

Declaring the name variable to be of type String means that it cannot be assigned the value null or the code won’t compile.

If you do want a variable to allow null, add a question mark to the type declaration, as in Example 2-2.

Example 2-2. Declaring a nullable variable
class Person(val first: String,
             val middle: String?,
             val last: String)

val jkRowling = Person("Joanne", null, "Rowling") 1
val northWest = Person("North", null, "West")     2
1

JK Rowling has no given middle name; she selected K for her initial to honor her grandmother Katherine

2

Neither does Kim and Kanye’s baby, who will no doubt have bigger issues than this

In the Person class shown, you still have to supply a value for the middle parameter, even if it’s null.

Life gets interesting when you try to use a nullable variable in an expression. Kotlin requires you to check that the value is not null, but it’s not quite as easy as that sounds. For example, consider the null check in Example 2-3.

Example 2-3. Checking nullability of a val variable
val p = Person(first = "North", middle = null, last = "West")
if (p.middle != null) {
    val middleNameLength = p.middle.length  1
}
1

Smart cast to non-null String

The if test checks whether the middle property is non-null, and if so, Kotlin performs a smart cast: it treats p.middle as though it was of type String rather than String?. This works, but only because the variable p was declared with the val keyword, so it cannot change once it is set. If, on the other hand, the variable was declared with var instead of val, the code is as shown in Example 2-4.

Example 2-4. Asserting that a var variable is not null
var p = Person(first = "North", middle = null, last = "West")
if (p.middle != null) {
    // val middleNameLength = p.middle.length  1
    val middleNameLength = p.middle!!.length   2
}
1

Smart cast to String impossible, because p.middle is a complex expression

2

Null-asserted (please don’t do this unless absolutely necessary)

Because p uses var instead of val, Kotlin assumes that it could change between the time it is defined and the time the middle property is accessed, and refuses to do the smart cast. One way around this is to use the bang-bang, or not-null, assertion operator (!!) which is a code smell if ever there was one. The !! operator forces the variable to be treated as non-null and throws an exception if that is not correct. That’s one of the few ways it is still possible to get a NullPointerException even in Kotlin code, so try to avoid it if possible.

A better way to handle this situation is to use the safe call operator (?.). Safe call short-circuits and returns a null if the value is null, as in Example 2-5.

Example 2-5. Using the safe call operator
var p = Person(first = "North", middle = null, last = "West")
val middleNameLength = p.middle?.length   1
1

Safe call; the resulting type is Int?

The problem is that the resulting inferred type is also nullable, so middleNameLength is of type Int?, which is probably not what you want. Therefore, it is helpful to combine the safe call operator with the Elvis operator (?:), as in Example 2-6.

Example 2-6. Safe call with Elvis
var p = Person(first = "North", middle = null, last = "West")
val middleNameLength = p.middle?.length ?: 0  1
1

Elvis operator, returns 0 if middle is null

The Elvis operator checks the value of the expression to the left, and if it is not null, returns it. Otherwise, the operator returns the value of the expression on the right. In this case, it checks the value of p.middle?.length, which is either an integer or null. If it is an integer, the value is returned. Otherwise, the expression returns 0.

Tip

The righthand side of an Elvis operator can be an expression, so you can use return or throw when checking function arguments.

The real challenge, perhaps, is looking at ?:, turning your head to the side, and somehow managing to see Elvis Presley. Clearly, Kotlin was designed for developers with an active imagination.1

Finally, Kotlin provides a safe cast operator, as?. The idea is to avoid throwing a ClassCastException if the cast isn’t going to work. For example, if you try to cast an instance of Person to that type, but the value may be null, you can write the code shown in Example 2-7.

Example 2-7. The safe cast operator
val p1 = p as? Person 1
1

Variable p1 is of type Person?

The cast will either succeed, resulting in a Person, or will fail, and p1 will receive null as a result.

2.2 Adding Nullability Indicators to Java

Problem

Your Kotlin code needs to interact with Java code, and you want it to enforce nullability annotations.

Solution

Enforce JSR-305 nullability annotations in your Kotlin code, using the compile-time parameter -Xjsr305=strict.

Discussion

One of Kotlin’s primary features is that nullability is enforced in the type system at compile time. If you declare a variable to be of type String, it can never be null, whereas if it is declared to be of type String?, it can, as in Example 2-8.

Example 2-8. Nullable versus non-nullable types
var s: String  = "Hello, World!"  1
var t: String? = null             2
1

Cannot be null, or code won’t compile

2

Question mark on type indicates a nullable type

This is fine until you want to interact with Java code, which has no such mechanism built into the language. Java does, however, have a @Nonnull annotation defined in the javax.annotation package. While this specification is now considered dormant, many libraries have what are referred to as JSR-305 compatible annotations, and Kotlin supports them.

For example, when using the Spring Framework, you can enforce compatibility by adding the code in Example 2-9 to your Gradle build file.

Example 2-9. Enforcing JSR-305 compatibility in Gradle (Groovy DSL)
sourceCompatibility = 1.8
compileKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
        freeCompilerArgs = ["-Xjsr305=strict"]
    }
}
compileTestKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
        freeCompilerArgs = ["-Xjsr305=strict"]
    }
}

To do the same using Gradle’s Kotlin DSL, see Example 2-10.

Example 2-10. Enforcing JSR-305 compatibility in Gradle (Kotlin DSL)
tasks.withType<KotlinCompile> {
    kotlinOptions {
        jvmTarget = "1.8"
        freeCompilerArgs = listOf("-Xjsr305=strict")
    }
}

For Maven, add the snippet from Example 2-11 to the POM file, as described in the Kotlin reference guide.

Example 2-11. Enforcing JSR-305 compatibility in Maven
<plugin>
    <groupId>org.jetbrains.kotlin</groupId>
    <artifactId>kotlin-maven-plugin</artifactId>
    <version>${kotlin.version}</version>
    <executions>...</executions>
    <configuration>
        <nowarn>true</nowarn>  <!-- Disable warnings -->
        <args>
            <!-- Enable strict mode for JSR-305 annotations -->
            <arg>-Xjsr305=strict</arg>
            ...
        </args>
    </configuration>
</plugin>

The @Nonnull annotation defined in JSR-305 takes a property called when. If the value of when is When.ALWAYS, the annotated type is treated as non-null. If it is When.MAYBE or When.NEVER, it is considered nullable. If it is When.UNKNOWN, the type is assumed to be a platform type whose nullability is not known.

2.3 Adding Overloaded Methods for Java

Problem

You have a Kotlin function with default parameters, and you want to invoke it from Java without having to specify explicit values for each parameter.

Solution

Add the @JvmOverloads annotation to the function.

Discussion

Say you have a Kotlin function that specifies one or more default arguments, as in Example 2-12.

Example 2-12. A Kotlin function with default parameters
fun addProduct(name: String, price: Double = 0.0, desc: String? = null) =
    "Adding product with $name, ${desc ?: "None" }, and " +
            NumberFormat.getCurrencyInstance().format(price)

For the addProduct function, a String name is required, but the description and price have default values. The description is nullable and defaults to null, while the price defaults to 0.

It is easy enough to call this function from Kotlin with one, two, or three arguments, as the test in Example 2-13 shows.

Example 2-13. Calling the overloaded variations from Kotlin
@Test
fun `check all overloads`() {
    assertAll("Overloads called from Kotlin",
        { println(addProduct("Name", 5.0, "Desc")) },
        { println(addProduct("Name", 5.0)) },
        { println(addProduct("Name")) }
    )
}

Each call to addProduct uses one fewer argument than the previous one.

Tip

Optional or nullable properties should be placed at the end of a function signature, so they can be left out when calling the function with positional arguments.

All of the calls execute properly.

Java, however, does not support default arguments for methods, so when calling this function from Java, you have to supply them all, as in Example 2-14.

Example 2-14. Calling the function from Java
@Test
void supplyAllArguments() {
    System.out.println(OverloadsKt.addProduct("Name", 5.0, "Desc"));
}

If you add the annotation @JvmOverloads to the function, the generated class will support all the function overloads, as in Example 2-15.

Example 2-15. Calling all the overloads from Java
@Test
void checkOverloads() {
    assertAll("overloads called from Java",
        () -> System.out.println(OverloadsKt.addProduct("Name", 5.0, "Desc")),
        () -> System.out.println(OverloadsKt.addProduct("Name", 5.0)),
        () -> System.out.println(OverloadsKt.addProduct("Name"))
    );
}

To see why this works, you can decompile the generated bytecodes from Kotlin. Without the @JvmOverloads annotation, the generated code resembles Example 2-16.

Example 2-16. Decompiled function from Kotlin bytecodes
@NotNull
public static final String addProduct(@NotNull String name,
    double price, @Nullable String desc) {
    Intrinsics.checkParameterIsNotNull(name, "name");
    // ...
}

When you add the @JvmOverloads annotation, the result instead resembles Example 2-17.

Example 2-17. Decompiled function with overloads
// public final class OverloadsKt {
@JvmOverloads
@NotNull
public static final String addProduct(@NotNull String name,
    double price, @Nullable String desc) {
    Intrinsics.checkParameterIsNotNull(name, "name");

   // ...
}

@JvmOverloads
@NotNull
public static final String addProduct(
    @NotNull String name, double price) {
    return addProduct$default(name, price,
        (String)null, 4, (Object)null);
}

@JvmOverloads
@NotNull
public static final String addProduct(@NotNull String name) {
    return addProduct$default(name, 0.0D,
        (String)null, 6, (Object)null);
}

The generated class includes additional methods that invoke the full method with supplied, default arguments.

You can also do this with constructors. The Product class shown in Example 2-18 generates three constructors: one with all three arguments, one with only the name and price, and one with only the name.

Example 2-18. Kotlin class with overloaded constructors
data class Product @JvmOverloads constructor(
    val name: String,
    val price: Double = 0.0,
    val desc: String? = null
)

The explicit constructor call is necessary so that you can add the @JvmOverloads annotation to it. Now, instantiating the class can be done with multiple arguments in Kotlin, as in Example 2-19.

Example 2-19. Instantiating the Product class from Kotlin
@Test
internal fun `check overloaded Product contructor`() {
    assertAll("Overloads called from Kotlin",
        { println(Product("Name", 5.0, "Desc")) },
        { println(Product("Name", 5.0)) },
        { println(Product("Name")) }
    )
}

Or you can call the constructors from Java, as in Example 2-20.

Example 2-20. Instantiating the Product class from Java
@Test
void checkOverloadedProductCtor() {
    assertAll("overloads called from Java",
        () -> System.out.println(new Product("Name", 5.0, "Desc")),
        () -> System.out.println(new Product("Name", 5.0)),
        () -> System.out.println(new Product("Name"))
    );
}

This all works, but note that a subtle trap exists. If you look at the decompiled code for the Product class, you’ll see all the necessary constructors, shown in Example 2-21.

Example 2-21. Overloaded Product constructors in decompiled code
@JvmOverloads
public Product(@NotNull String name, double price,
    @Nullable String desc) {
    Intrinsics.checkParameterIsNotNull(name, "name");
    super();
    this.name = name;
    this.price = price;
    this.desc = desc;
}

@JvmOverloads
public Product(String var1, double var2, String var4,
    int var5, DefaultConstructorMarker var6) {

    // ...

    this(var1, var2, var4);  1
}

@JvmOverloads
public Product(@NotNull String name, double price) {
    this(name, price, (String)null, 4, (DefaultConstructorMarker)null); 2
}

@JvmOverloads
public Product(@NotNull String name) {
   this(name, 0.0D, (String)null, 6, (DefaultConstructorMarker)null);   2
}
1

Calls three-argument constructor

2

Calls generated constructor, which then calls three-argument constructor

Each of the overloaded constructors ultimately calls the full, three-argument version with various defaults supplied. This is probably fine, but keep in mind that when you invoke a constructor with optional arguments, you’re not calling the analogous constructor in the superclass; all calls are going through a single constructor with the most arguments.

Warning

Calls to constructors marked @JvmOverloads do not call super with the same number of arguments. Instead, they call the full constructor with supplied defaults.

In Java, each constructor calls its parent by using super, and when you overload constructors, you often invoke super with the same number of arguments. That’s not happening in this case. The superclass constructor that gets invoked is the one with all the parameters, with supplied defaults.

2.4 Converting Between Types Explicitly

Problem

Kotlin does not automatically promote primitive types to wider variables, such as an Int to a Long.

Solution

Use the explicit conversion functions toInt, toLong, and so on to convert the smaller type explicitly.

Discussion

One of the surprises Kotlin brings to Java developers is that shorter types are not automatically promoted to longer types. For example, in Java it is perfectly normal to write the code in Example 2-22.

Example 2-22. Promoting shorter primitive types to longer ones in Java
int myInt = 3;
long myLong = myInt;                        1
1

Automatic promotion of int to long

When autoboxing was introduced in Java 1.5, it became easy to convert from a primitive to a wrapper type, but converting from one wrapper type to another still requires extra code, as in Example 2-23.

Example 2-23. Converting from an Integer to a Long
Integer myInteger = 3;
// Long myWrappedLong = myInteger;          1
Long myWrappedLong = myInteger.longValue(); 2
myWrappedLong = Long.valueOf(myInteger);    3
1

Does not compile

2

Extracts a long, then wraps it

3

Unwraps int, promotes to long, then wraps it

In other words, dealing with the wrapped types directly requires you to do the unboxing yourself. You can’t simply assign an Integer instance to a Long without extracting the wrapped value first.

In Kotlin, primitives are not supported directly. The bytecodes may generate their equivalents, but when you are writing the code yourself, you need to keep in mind that you are dealing with classes rather than primitives.

Fortunately, Kotlin provides a set of conversion methods of the form toInt, toLong, and so on, as illustrated in Example 2-24.

Example 2-24. Promoting an Int to a Long in Kotlin
val intVar: Int = 3
// val longVar: Long = intVar        1
val longVar: Long = intVar.toLong()  2
1

Does not compile

2

Explicit type conversion

Since intVar and longVar are instances of classes in Kotlin, perhaps being unable to automatically assign an instance of Int to a variable of type Long is not too surprising. But it is easy to forget that, especially if you have a Java background.

The available conversion methods are as follows:

  • toByte: Byte

  • toChar: Char

  • toShort: Short

  • toInt(): Int

  • toLong(): Long

  • toFloat(): Float

  • toDouble(): Double

Fortunately, Kotlin does take advantage of operator overloading to perform type conversions transparently, so the following code does not require an explicit conversion:

val longSum = 3L + intVar

The plus operator automatically converts the intVar value to a long and adds it to the long literal.

2.5 Printing to Different Bases

Problem

You want to print a number in a base other than base 10.

Solution

Use the extension function toString(radix: Int) for a valid radix.

Discussion

Note

This recipe is for a special situation that arises infrequently. Still, it’s interesting and may be useful if you deal with alternate numerical bases.

There’s an old joke:

There are 10 kinds of people
Those who know binary, and those who don't

In Java, if you wanted to print a number in binary, you would use the static Integer.toBinaryString method or the static Integer.toString(int, int) method. The first argument is the value to convert, and the second argument is the desired base.

Kotlin, however, took the static method in Java and made it the extension function toString(radix: Int) on Byte, Short, Int, and Long. For example, to convert the number 42 to a binary string in Kotlin, you would write Example 2-25.

Example 2-25. Printing 42 in binary
42.toString(2) == "101010"

In binary, the bit positions from left to right are 1, 2, 4, 8, 16, and so on. Because 42 is 2 + 8 + 32, those bit positions have 1s and the others have 0s.

The implementation of the toString method in Int is given by the following:

public actual inline fun Int.toString(radix: Int): String =
    java.lang.Integer.toString(this, checkRadix(radix))

The extension function on Int thus delegates to the corresponding static method in java.lang.Integer, after checking the radix in the second argument.

Note

The actual keyword indicates a platform-specific implementation in multiplatform projects.

The checkRadix method verifies that the specified base is between Character.MIN_RADIX and Character.MAX_RADIX (again, this is for the Java implementation), and throws an IllegalArgumentException otherwise. The valid min and max values are to 2 and 36, respectively. Example 2-26 shows the output of printing the number 42 in all the valid radices.

The output (with some formatting) is as follows:

Radix  Value
 2:   101010
 3:     1120
 4:      222
 5:      132
 6:      110
 7:       60
 8:       52
 9:       46
10:       42
...
32:       1a
33:       19
34:       18
35:       17
36:       16
Tip

The number 42 is the Answer to the Ultimate Question of Life, the Universe, and Everything (at least according to Douglas Adams in his Hitchhiker’s Guide to the Galaxy series).

Combining this capability with multiline strings gives a Kotlin version of a nice variation of the original joke, as in Example 2-27.

Example 2-27. Improving the binary joke
val joke = """
    Actually, there are ${3.toString(3)} kinds of people
    Those who know binary, those who don't,
    And those who didn't realize this is actually a ternary joke
""".trimIndent()
println(joke)

That code prints the following:

Actually, there are 10 kinds of people
Those who know binary, those who don't,
And those who didn't realize this is actually a ternary joke

2.6 Raising a Number to a Power

Problem

You want to raise a number to a power but notice that Kotlin doesn’t have a predefined exponentiation operator.

Solution

Define an infix function that delegates to the Kotlin extension function pow already added to Int and Long.

Discussion

Kotlin, like Java, does not have a built-in exponentiation operator. Java at least includes the static pow function in the java.lang.Math class, whose signature is as follows:

public static double Math.pow(double a, double b)

Since Java automatically promotes shorter primitive types to longer ones (for example, int to double), this is the only function required to do the job. In Kotlin, however, there are no primitives, and instances of classes like Int are not automatically promoted to Long or Double. This becomes annoying when you notice that the Kotlin standard library does define an extension function called pow on Float and Double, but that there is no corresponding pow function in Int or Long.

The signatures for the existing functions are as follows:

fun Double.pow(x: Double): Double
fun Float.pow(x: Float): Float

That means to raise an integer to a power, you need to go through a conversion to Float or Double first, then invoke pow, and finally convert the result back to the original type, as in Example 2-28.

Example 2-28. Raising an Int to a power
@Test
fun `raise an Int to a power`() {
    assertThat(256, equalTo(2.toDouble().pow(8).toInt()))
}
Tip

If all you want to do is raise to a power of 2, the shl and shr functions are ideal, as shown in Recipe 2.7.

That works, but the process can be automated by defining extension functions on Int and Long with the following signatures:

fun Int.pow(x: Int) = toDouble().pow(x).toInt()
fun Long.pow(x: Int) = toDouble().pow(x).toLong()

This might be better done as an infix operator. Although only the predefined operator symbols can be overloaded, you can fake one by enclosing it in backticks, as in Example 2-29.

Example 2-29. Defining an infix operation for exponentiation
import kotlin.math.pow

infix fun Int.`**`(x: Int) = toDouble().pow(x).toInt()
infix fun Long.`**`(x: Int) = toDouble().pow(x).toLong()
infix fun Float.`**`(x: Int) = pow(x)
infix fun Double.`**`(x: Int) = pow(x)

// Pattern similar to existing functions on Float and Double
fun Int.pow(x: Int) = `**`(x)
fun Long.pow(x: Int) = `**`(x)

The infix keyword was used in the definition of the ** function, but not in extending pow to Int and Long to keep with the pattern in Float and Double.

The result is that you can use the ** symbol as a synthesized exponentiation operator, as in Example 2-30.

Example 2-30. Using the ** extension function
@Test
fun `raise to power`() {
    assertAll(
        { assertThat(1, equalTo(2 `**` 0)) },
        { assertThat(2, equalTo(2 `**` 1)) },
        { assertThat(4, equalTo(2 `**` 2)) },
        { assertThat(8, equalTo(2 `**` 3)) },

        { assertThat(1L, equalTo(2L `**` 0)) },
        { assertThat(2L, equalTo(2L `**` 1)) },
        { assertThat(4L, equalTo(2L `**` 2)) },
        { assertThat(8L, equalTo(2L `**` 3)) },

        { assertThat(1F, equalTo(2F `**` 0)) },
        { assertThat(2F, equalTo(2F `**` 1)) },
        { assertThat(4F, equalTo(2F `**` 2)) },
        { assertThat(8F, equalTo(2F `**` 3)) },

        { assertThat(1.0, closeTo(2.0 `**` 0, 1e-6)) },
        { assertThat(2.0, closeTo(2.0 `**` 1, 1e-6)) },
        { assertThat(4.0, closeTo(2.0 `**` 2, 1e-6)) },
        { assertThat(8.0, closeTo(2.0 `**` 3, 1e-6)) },

        { assertThat(1, equalTo(2.pow(0))) },
        { assertThat(2, equalTo(2.pow(1))) },
        { assertThat(4, equalTo(2.pow(2))) },
        { assertThat(8, equalTo(2.pow(3))) },

        { assertThat(1L, equalTo(2L.pow(0))) },
        { assertThat(2L, equalTo(2L.pow(1))) },
        { assertThat(4L, equalTo(2L.pow(2))) },
        { assertThat(8L, equalTo(2L.pow(3))) }
    )
}

The tests on Double.** use the Hamcrest matcher closeTo to avoid comparing for equality on doubles. The set of tests with Float probably should do the same, but the tests currently pass as they are.

Note

The idea of defining an infix function for this purpose was suggested by an answer by Olivia Zoe to a question on Stack Overflow.

If you find wrapping the star-star operator in backticks annoying, it’s easy enough to define an actual function name, like exp, instead.

2.7 Using Bitwise Shift Operators

Problem

You want to perform bitwise shift operations.

Solution

Kotlin includes bitwise infix functions like shr, shl, and ushr for this purpose.

Discussion

Bitwise operations come up in a variety of applications, including access control lists, communication protocols, compression and encryption algorithms, and computer graphics. Unlike many other languages, Kotlin does not use specific operator symbols for shifting operations, but instead defines functions for them.

Kotlin defines the following shift operators as extension functions on Int and Long:

shl

Signed left shift

shr

Signed right shift

ushr

Unsigned right shift

Because of two’s complement arithmetic, shifting bits left or right is like multiplying or dividing by 2, as shown in Example 2-31.

Example 2-31. Multiplying and dividing by 2
@Test
fun `doubling and halving`() {
    assertAll("left shifts doubling from 1",    // 0000_0001
        { assertThat(  2, equalTo(1 shl 1)) },  // 0000_0010
        { assertThat(  4, equalTo(1 shl 2)) },  // 0000_0100
        { assertThat(  8, equalTo(1 shl 3)) },  // 0000_1000
        { assertThat( 16, equalTo(1 shl 4)) },  // 0001_0000
        { assertThat( 32, equalTo(1 shl 5)) },  // 0010_0000
        { assertThat( 64, equalTo(1 shl 6)) },  // 0100_0000
        { assertThat(128, equalTo(1 shl 7)) }   // 1000_0000
    )

    assertAll("right shifts halving from 235",   // 1110_1011
        { assertThat(117, equalTo(235 shr 1)) }, // 0111_0101
        { assertThat( 58, equalTo(235 shr 2)) }, // 0011_1010
        { assertThat( 29, equalTo(235 shr 3)) }, // 0001_1101
        { assertThat( 14, equalTo(235 shr 4)) }, // 0000_1110
        { assertThat(  7, equalTo(235 shr 5)) }, // 0000_0111
        { assertThat(  3, equalTo(235 shr 6)) }, // 0000_0011
        { assertThat(  1, equalTo(235 shr 7)) }  // 0000_0001
    )
}

The ushr function is needed when you want to shift a value and not preserve its sign. Both shr and ushr behave the same for positive values. But for negative values, shr fills in from the left with 1s so that the resulting value is still negative, as shown in Example 2-32.

Example 2-32. Using the ushr function versus shr
val n1 = 5
val n2 = -5
println(n1.toString(2))  //  0b0101
println(n2.toString(2))  // -0b0101

assertThat(n1  shr 1, equalTo(0b0010))      // 2
assertThat(n1 ushr 1, equalTo(0b0010))      // 2

assertThat(n2  shr 1, equalTo(-0b0011))     // -3
assertThat(n2 ushr 1, equalTo(0x7fff_fffd)) // 2_147_483_645

The seemingly strange behavior of the last example comes from two’s complement arithmetic. Because ushr fills in from the left with 0s, it does not preserve the negative sign of –3. The result is the two’s complement of –3 for a 32-bit integer, giving the value shown.

The ushr function comes up in many places. One interesting example occurs when trying to find the midpoint of two large integers, as in Example 2-33.

Example 2-33. Finding the midpoint of two large integers
val high = (0.99 * Int.MAX_VALUE).toInt()
val low  = (0.75 * Int.MAX_VALUE).toInt()

val mid1 = (high + low) / 2     1
val mid2 = (high + low) ushr 1  2

assertTrue(mid1 !in low..high)
assertTrue(mid2  in low..high)
1

Sum is greater than max Int, so result is negative

2

Unsigned shift ensures result inside desired range

If both values are large, adding them together will produce a result larger than Int.MAX_VALUE, so the sum will be negative. By doing an unsigned right shift to divide by 2, the result is between the low and high values.

Many algorithms, like binary searches or sorts, require computing the mean of two integers, each of which could potentially be very large. Using ushr in this way ensures that the result is bounded in the way you want.

2.8 Using Bitwise Boolean Operators

Problem

You want to apply masks to bit values.

Solution

Use the bitwise and, or, xor, and inv operators supplied by Kotlin for that purpose.

Discussion

In addition to the shift operators defined on Int and Long, Kotlin defines masking operations and, or, xor, and inv (rather than “not”).

Taking the last one first, the inv function flips all the bits on a number. As a simple example, the number 4 in binary is 0b00000100. Flipping all the bits gives 0b11111011, which is 251 in decimal. When you invoke the inv function on 4, however, you get –5, as shown in Example 2-34.

Note

Precede a numeric literal with 0b to express it in binary.

Example 2-34. Inverse of 4
// 4 == 0b0000_0100 (in binary)
// Bitwise complement (flipping all the bits) is given by:
//      0b1111_1011 == 251 (in decimal)
assertEquals(-5, 4.inv())
Tip

You can add underscores ( _ ) to numeric literals to make them easier to read. They are ignored by the compiler.

Why do you get –5 instead of 251? The system is doing two’s complement arithmetic. For any integer n, the two’s complement of n is given by –(~n + 1), where ~n is the one’s complement (i.e., flip all the bits) of n. Therefore:

0b1111_1011 -> -(0b0000_0100 + 1) -> -0b0000_0101 -> -5

Hence the two’s complement of 251 is –5.

The bitwise operations and, or, and xor are familiar to most developers. The only difference between them and their logical counterparts is that they do not short-circuit. As a trivial example, see Example 2-35.

Example 2-35. Simple example of and, or, and xor
@Test
fun `and, or, xor`() {
    val n1 = 0b0000_1100       // decimal 12
    val n2 = 0b0001_1001       // decimal 25

    val n1_and_n2 = n1 and n2
    val n1_or_n2  = n1 or n2
    val n1_xor_n2 = n1 xor n2

    assertThat(n1_and_n2, equalTo(0b0000_1000)) //  8
    assertThat(n1_or_n2,  equalTo(0b0001_1101)) // 29
    assertThat(n1_xor_n2, equalTo(0b0001_0101)) // 21
}

For a more interesting example, consider the RGBA model for representing colors, as exemplified by the java.awt.Color class in Java. A color can be represented as a 4-byte integer, where 1 byte contains the values for red, green, blue, and alpha (a measure of transparency). See Figure 2-1 for details.

Integer as four color bytes
Figure 2-1. 32-bit integer with 1 byte for each color

Given an instance of the Color class, the getRGB method documentation says that the method returns an int for “the RGB value representing the color in the default sRGB ColorModel (bits 24–31 are alpha, 16–23 are red, 8–15 are green, 0–7 are blue).”

That means that given the returned integer, Kotlin can extract the actual RGB and alpha values by using a function like that in Example 2-36.

Example 2-36. Converting an integer to its individual RGB values
fun intsFromColor(color: Color): List<Int> {
    val rgb   = color.rgb             1
    val alpha = rgb shr 24 and 0xff   2
    val red   = rgb shr 16 and 0xff   2
    val green = rgb shr  8 and 0xff   2
    val blue  = rgb and        0xff   2
    return listOf(alpha, red, green, blue)
}
1

Invokes Java’s getRGB method

2

Shifts right and applies mask to return the proper Int

The advantage to returning the individual values in a list is that you can use destructuring in a test, as in Example 2-37.

Example 2-37. Destructuring and testing
@Test
fun `colors as ints`() {
    val color = Color.MAGENTA
    val (a, r, g, b) = intsFromColor(color)

    assertThat(color.alpha, equalTo(a))
    assertThat(color.red,   equalTo(r))
    assertThat(color.green, equalTo(g))
    assertThat(color.blue,  equalTo(b))
}

Going in the other direction, from the RGB values as integers, you can build up the overall Int value as shown in Example 2-38.

Example 2-38. Creating an Int from individual RGB and alpha values
fun colorFromInts(alpha: Int, red: Int, green: Int, blue: Int) =
    (alpha and 0xff shl 24) or
    (red   and 0xff shl 16) or
    (green and 0xff shl 8) or
    (blue  and 0xff)

This time, the values are shifted left rather than right. That’s easy enough to test as well, as shown in Example 2-39.

Example 2-39. Converting from RGB and alpha to an Int
@Test
fun `ints as colors`() {
    val color = Color.MAGENTA
    val intColor = colorFromInts(color.alpha,
        color.red, color.green, color.blue)
    val color1 = Color(intColor, true)  1
    assertThat(color1, equalTo(color))
}
1

Second constructor arg indicates alpha value is present

To end this recipe, consider the following xor joke: “An xor-cist eliminates one daemon or the other, but not both.” Sorry about that, but feel free to inflict that joke on your friends.

2.9 Creating Pair Instances with to

Problem

You want to create instances of the Pair class (often as entries for a map).

Solution

Rather than instantiate the Pair class directly, use the infix to function.

Discussion

Maps are made up of entries, which are combinations of keys and values. To create a map, Kotlin provides a handful of top-level functions, like mapOf, that allow you to create a map from a list of Pair instances. The signature of the mapOf function in the standard library is as follows:

fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V>

Pair is a data class that holds two elements, called first and second. The signature of the Pair class is shown here:

data class Pair<out A, out B> : Serializable

The Pair class properties first and second correspond to the generic values of A and B.

Although you can create a Pair class by using the two-argument constructor, it is more common to use the to function. The to function is defined as follows:

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

The implementation of the to function is to instantiate the Pair class.

Putting all of these features together, Example 2-40 shows how to create a map with pairs supplied by the to function.

Example 2-40. Using the to function to create pairs for mapOf
@Test
fun `create map using infix to function`() {
    val map = mapOf("a" to 1, "b" to 2, "c" to 2) 1
    assertAll(
        { assertThat(map, hasKey("a")) },
        { assertThat(map, hasKey("b")) },
        { assertThat(map, hasKey("c")) },
        { assertThat(map, hasValue(1)) },
        { assertThat(map, hasValue(2)) })
}

@Test
fun `create a Pair from constructor vs to function`() {
    val p1 = Pair("a", 1)                         2
    val p2 = "a" to 1                             1

    assertAll(
        { assertThat(p1.first, `is`("a")) },
        { assertThat(p1.second, `is`(1)) },
        { assertThat(p2.first, `is`("a")) },
        { assertThat(p2.second, `is`(1)) },
        { assertThat(p1, `is`(equalTo(p2)))}
    )
}
1

Creates Pair using to

2

Creates Pair from constructor

The to function is an extension function added to any generic type A, with generic argument B, that returns an instance of Pair combining the values supplied for A and B. It is simply a way to create map literals with less noise.

Incidentally, because Pair is a data class, the individual elements can be accessed by using destructuring, as in Example 2-41.

Example 2-41. Destructuring Pair
@Test
fun `destructuring a Pair`() {
    val pair = "a" to 1
    val (x,y) = pair

    assertThat(x, `is`("a"))
    assertThat(y, `is`(1))
}
Note

There is also a class in the standard library called Triple that represents a triad of values. There are no convenient extension functions for creating Triple instances, however; you use the three-argument constructor directly.

1 Or, rather, Groovy was designed that way. The Elvis operator is borrowed from Groovy.

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

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