This chapter contains recipes that work with the fundamentals of Kotlin. They show you how to use the language without relying on specific libraries.
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.
var
name
:
String
// ... later ...
name
=
"Dolly"
// name = null
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.
class
Person
(
val
first
:
String
,
val
middle
:
String
?
,
val
last
:
String
)
val
jkRowling
=
Person
(
"Joanne"
,
null
,
"Rowling"
)
val
northWest
=
Person
(
"North"
,
null
,
"West"
)
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.
val
variableval
p
=
Person
(
first
=
"North"
,
middle
=
null
,
last
=
"West"
)
if
(
p
.
middle
!
=
null
)
{
val
middleNameLength
=
p
.
middle
.
length
}
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.
var
variable is not nullvar
p
=
Person
(
first
=
"North"
,
middle
=
null
,
last
=
"West"
)
if
(
p
.
middle
!
=
null
)
{
// val middleNameLength = p.middle.length
val
middleNameLength
=
p
.
middle
!!
.
length
}
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.
var
p
=
Person
(
first
=
"North"
,
middle
=
null
,
last
=
"West"
)
val
middleNameLength
=
p
.
middle
?.
length
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.
var
p
=
Person
(
first
=
"North"
,
middle
=
null
,
last
=
"West"
)
val
middleNameLength
=
p
.
middle
?.
length
?:
0
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.
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.
The cast will either succeed, resulting in a Person
, or will fail, and p1
will receive null
as a result.
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.
var
s
:
String
=
"Hello, World!"
var
t
:
String
?
=
null
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.
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.
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.
<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.
Say you have a Kotlin function that specifies one or more default arguments, as in Example 2-12.
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.
@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.
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.
@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.
@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.
@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.
// 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.0
D
,
(
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.
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.
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.
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.
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
)
;
}
@JvmOverloads
public
Product
(
@NotNull
String
name
,
double
price
)
{
this
(
name
,
price
,
(
String
)
null
,
4
,
(
DefaultConstructorMarker
)
null
)
;
}
@JvmOverloads
public
Product
(
@NotNull
String
name
)
{
this
(
name
,
0.0
D
,
(
String
)
null
,
6
,
(
DefaultConstructorMarker
)
null
)
;
}
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.
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.
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.
int
myInt
=
3
;
long
myLong
=
myInt
;
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.
Integer
to a Long
Integer
myInteger
=
3
;
// Long myWrappedLong = myInteger;
Long
myWrappedLong
=
myInteger
.
longValue
(
)
;
myWrappedLong
=
Long
.
valueOf
(
myInteger
)
;
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.
Int
to a Long
in Kotlinval
intVar
:
Int
=
3
// val longVar: Long = intVar
val
longVar
:
Long
=
intVar
.
toLong
(
)
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.
Use the extension function toString(radix: Int)
for a valid radix.
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.
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.
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.
(
Character
.
MIN_RADIX
..
Character
.
MAX_RADIX
).
forEach
{
radix
->
println
(
"$radix: ${42.toString(radix)}"
)
}
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
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.
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
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.
Int
to a power@Test
fun
`
raise
an
Int
to
a
power
`
()
{
assertThat
(
256
,
equalTo
(
2.
toDouble
().
pow
(
8
).
toInt
()))
}
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.
infix
operation for exponentiationimport
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.
**
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.
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.
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.
@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.
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
(
0
b0010
))
// 2
assertThat
(
n1
ushr
1
,
equalTo
(
0
b0010
))
// 2
assertThat
(
n2
shr
1
,
equalTo
(-
0
b0011
))
// -3
assertThat
(
n2
ushr
1
,
equalTo
(
0
x7fff_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.
val
high
=
(
0.99
*
Int
.
MAX_VALUE
)
.
toInt
(
)
val
low
=
(
0.75
*
Int
.
MAX_VALUE
)
.
toInt
(
)
val
mid1
=
(
high
+
low
)
/
2
val
mid2
=
(
high
+
low
)
ushr
1
assertTrue
(
mid1
!
in
low
.
.
high
)
assertTrue
(
mid2
in
low
.
.
high
)
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.
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.
// 4 == 0b0000_0100 (in binary)
// Bitwise complement (flipping all the bits) is given by:
// 0b1111_1011 == 251 (in decimal)
assertEquals
(-
5
,
4.
inv
())
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.
and
, or
, and xor
@Test
fun
`
and
,
or
,
xor
`
()
{
val
n1
=
0
b0000_1100
// decimal 12
val
n2
=
0
b0001_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
(
0
b0000_1000
))
// 8
assertThat
(
n1_or_n2
,
equalTo
(
0
b0001_1101
))
// 29
assertThat
(
n1_xor_n2
,
equalTo
(
0
b0001_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.
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.
fun
intsFromColor
(
color
:
Color
)
:
List
<
Int
>
{
val
rgb
=
color
.
rgb
val
alpha
=
rgb
shr
2
4
and
0
xff
val
red
=
rgb
shr
1
6
and
0
xff
val
green
=
rgb
shr
8
and
0
xff
val
blue
=
rgb
and
0
xff
return
listOf
(
alpha
,
red
,
green
,
blue
)
}
The advantage to returning the individual values in a list is that you can use destructuring in a test, as in Example 2-37.
@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.
Int
from individual RGB and alpha valuesfun
colorFromInts
(
alpha
:
Int
,
red
:
Int
,
green
:
Int
,
blue
:
Int
)
=
(
alpha
and
0
xff
shl
24
)
or
(
red
and
0
xff
shl
16
)
or
(
green
and
0
xff
shl
8
)
or
(
blue
and
0
xff
)
This time, the values are shifted left rather than right. That’s easy enough to test as well, as shown in Example 2-39.
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
)
assertThat
(
color1
,
equalTo
(
color
)
)
}
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.
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.
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
)
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
)
val
p2
=
"a"
to
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
)
)
)
}
)
}
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.
Pair
@Test
fun
`
destructuring
a
Pair
`
()
{
val
pair
=
"a"
to
1
val
(
x
,
y
)
=
pair
assertThat
(
x
,
`is`
(
"a"
))
assertThat
(
y
,
`is`
(
1
))
}
1 Or, rather, Groovy was designed that way. The Elvis operator is borrowed from Groovy.
3.14.131.212