Like Java, Kotlin is an object-oriented programming (OOP) language. As such, it uses classes, both abstract and concrete, and interfaces in a way that is familiar to Java developers.
Some aspects of OOP in Kotlin are worth spending additional time on, and this chapter does so. It includes recipes that involve initializing objects, providing custom getters and setters, performing late and lazy initialization, creating singletons, understanding the Nothing
class, and more.
Use the modifier const
for compile-time constants. The keyword val
indicates that a variable cannot be changed once it is assigned, but that assignment can occur at runtime.
The Kotlin keyword val
indicates a variable that cannot be changed. In Java, the keyword final
is used for the same purpose. Given that, why does Kotlin also support the modifier const
?
Compile-time constants must be top-level properties or members of an object declaration or a companion object. They must be of type String
or a primitive type wrapper class (Byte
, Short
, Int
, Long
, Float
, Double
, Char
, or Boolean
), and they cannot have a custom getter function. They must be assigned outside any function, including main
, because their values must be known at compile time.
As an example, consider defining a min and max priority for a task, as in Example 3-1.
class
Task
(
val
name
:
String
,
_priority
:
Int
=
DEFAULT_PRIORITY
)
{
companion
object
{
const
val
MIN_PRIORITY
=
1
const
val
MAX_PRIORITY
=
5
const
val
DEFAULT_PRIORITY
=
3
}
var
priority
=
validPriority
(
_priority
)
set
(
value
)
{
field
=
validPriority
(
value
)
}
private
fun
validPriority
(
p
:
Int
)
=
p
.
coerceIn
(
MIN_PRIORITY
,
MAX_PRIORITY
)
}
In this example, three constants are defined using the normal Kotlin (and Java) idiom that suggests writing them in all uppercase letters. This example also takes advantage of a custom setter operation to map any provided priority into the given range.
Note that val
is a Kotlin keyword, but const
is a modifier, like private
, inline
, and so on. That’s why const
must be used along with the keyword val
rather than replacing it.
Custom setter methods like the one shown in this recipe are covered in Recipe 3.2.
Add get
and set
functions to properties in a Kotlin class.
As in other object-oriented languages, Kotlin classes combine data with functions that operate on that data, in a technique commonly known as encapsulation. Kotlin is unusual in that everything is public by default, because this seems to violate the principle of data hiding, wherein the data structure associated with information is assumed to be an implementation detail.
Kotlin resolves this dilemma in an unusual way: fields cannot be declared directly in Kotlin classes. That sounds strange when you can define properties in a class that look just like fields, as in Example 3-2.
class
Task
(
val
name
:
String
)
{
var
priority
=
3
// ...
}
The Task
class defines two properties, name
and priority
. One is declared in the primary constructor, while the other is a top-level member of the class. Both could have been defined in the constructor, of course, but this shows that you can use the alternative syntax shown. The downside to declaring priority
in this way is that you won’t be able to assign it when instantiating the class, though you could still use an apply
block:
var
myTask
=
Task
().
apply
{
priority
=
4
}
The advantage to defining a property in this way is that you can easily add a custom getter and setter. The full syntax for defining a property is shown here:
var <propertyName>[: <PropertyType>] [= <property_initializer] [<getter>] [<setter>]
The initializer, getter, and setter are optional. The type is optional if it can be inferred from the initialized value or the getter return type, though this is not true of properties declared in the constructor.
Properties declared in constructors must include a type, even when they are assigned default values.
Example 3-3 shows a custom getter being used to compute isLowPriority
.
val
isLowPriority
get
()
=
priority
<
3
As stated, the type of isLowPriority
is inferred from the return type of the get
function, which in this case is a boolean.
A custom setter is used every time a value is assigned to a property. To make sure that priority
is between 1 and 5, for example, a custom setter can be used as in Example 3-4.
priority
var
priority
=
3
set
(
value
)
{
field
=
value
.
coerceIn
(
1.
.
5
)
}
Here, at last, we see the resolution of the public property / private field dilemma listed previously. Normally, when a property needs a backing field, Kotlin provides it automatically. Here, however, in the custom setter, the field
identifier is used to reference the generated backing field. The field
identifier can be used only in a custom getter or setter.
A backing field is generated for a property if it uses the default generated getter or setter or if a custom getter or setter references it through the field
property. That means that the derived property lowPriority
will not have one.
In the literature, the terms getters and setters are formally referred to as accessors and mutators, presumably because it’s hard to charge large consulting fees for get and set.
To complete this example, imagine that you want to be able to assign a priority by using a constructor. One way to do that is to introduce a constructor parameter that isn’t a property, by leaving out the var
or val
keyword. This then leads to the implementation of Task
, shown in Example 3-1 and repeated here for reference:
class
Task
(
val
name
:
String
,
_priority
:
Int
=
DEFAULT_PRIORITY
)
{
companion
object
{
const
val
MIN_PRIORITY
=
1
const
val
MAX_PRIORITY
=
5
const
val
DEFAULT_PRIORITY
=
3
}
var
priority
=
validPriority
(
_priority
)
set
(
value
)
{
field
=
validPriority
(
value
)
}
private
fun
validPriority
(
p
:
Int
)
=
p
.
coerceIn
(
MIN_PRIORITY
,
MAX_PRIORITY
)
}
The parameter _priority
is not a property, but rather just an argument to the constructor. It is used to initialize the actual priority
property, and the custom setter is evaluated to coerce it into the desired range every time it changes. Note the term value
here is just a dummy name; you can change it to anything you like, as with any function parameter.
The constants used in the Task
example are discussed in Recipe 3.1.
Kotlin provides the keyword data
to indicate that the purpose of a particular class is to hold data. In Java, when such a class represents information from a database table, it is known as an entity, and the concept of a data class is similar.
Adding the word data
to a class definition causes the compiler to generate a whole series of functions, including consistent equals
and hashCode
functions, a toString
function that shows the class and the property values, a copy
function, and component functions used for destructuring.
For example, consider the Product
class:
data
class
Product
(
val
name
:
String
,
var
price
:
Double
,
var
onSale
:
Boolean
=
false
)
The compiler generates equals
and hashCode
functions based on the properties declared in the primary constructor. The algorithm used is the same one described by Joshua Bloch years ago in Effective Java (Addison-Wesley Professional). The tests in Example 3-5 show that they work properly.
equals
and hashCode
implementations@Test
fun
`
check
equivalence
`
(
)
{
val
p1
=
Product
(
"baseball"
,
1
0.0
)
val
p2
=
Product
(
"baseball"
,
1
0.0
,
false
)
assertEquals
(
p1
,
p2
)
assertEquals
(
p1
.
hashCode
(
)
,
p2
.
hashCode
(
)
)
}
@Test
fun
`
create
set
to
check
equals
and
hashcode
`
(
)
{
val
p1
=
Product
(
"baseball"
,
1
0.0
)
val
p2
=
Product
(
price
=
1
0.0
,
onSale
=
false
,
name
=
"baseball"
)
val
products
=
setOf
(
p1
,
p2
)
assertEquals
(
1
,
products
.
size
)
}
Since p1
and p2
are equivalent, when both are included in the setOf
function, only one is added to the actual result.
A toString
implementation converts the product into a string:
Product(name=baseball, price=10.0, onSale=false)
The copy
method is an instance method that creates a new object that starts with the property values from the original and modifies only the supplied values, as the test in Example 3-6 shows.
copy
function@Test
fun
`
change
price
using
copy
`
(
)
{
val
p1
=
Product
(
"baseball"
,
1
0.0
)
val
p2
=
p1
.
copy
(
price
=
1
2.0
)
assertAll
(
{
assertEquals
(
"baseball"
,
p2
.
name
)
}
,
{
assertThat
(
p2
.
price
,
`is`
(
closeTo
(
1
2.0
,
0.01
)
)
)
}
,
{
assertFalse
(
p2
.
onSale
)
}
)
}
The test verifies that using copy
with the price
parameter changes only that value. Note that the Hamcrest matcher closeTo
is used to compare prices, because using equality checks with floating-point values is not considered a good idea.
Note that the copy
function performs only a shallow copy, not a deep one. To demonstrate this, consider an additional data class called OrderItem
, as shown in Example 3-7.
Product
data
class
OrderItem
(
val
product
:
Product
,
val
quantity
:
Int
)
The test shown in Example 3-8 instantiates an OrderItem
and then makes a copy by using the copy
function.
@Test
fun
`
data
copy
function
is
shallow
`
(
)
{
val
item1
=
OrderItem
(
Product
(
"baseball"
,
1
0.0
)
,
5
)
val
item2
=
item1
.
copy
(
)
assertAll
(
{
assertTrue
(
item1
=
=
item2
)
}
,
{
assertFalse
(
item1
=
=
=
item2
)
}
,
{
assertTrue
(
item1
.
product
=
=
item2
.
product
)
}
,
{
assertTrue
(
item1
.
product
=
=
=
item2
.
product
)
}
)
}
The test shows that although the two OrderItem
instances are equivalent (by the equals
function invoked via ==
), they are still two separate objects because the referential equality operator ===
returns false
. They both, however, share the same internal Product
instance, because ===
on both contained references returns true
.
Invoking copy
on a data class performs a shallow copy, not a deep one.
In addition to the copy
function, data classes add functions called component1
, component2
, and so on, that return the values of the properties. These functions are used for destructuring, as shown in the test in Example 3-9.
Product
instance@Test
fun
`
destructure
using
component
functions
`
(
)
{
val
p
=
Product
(
"baseball"
,
1
0.0
)
val
(
name
,
price
,
sale
)
=
p
assertAll
(
{
assertEquals
(
p
.
name
,
name
)
}
,
{
assertThat
(
p
.
price
,
`is`
(
closeTo
(
price
,
0.01
)
)
)
}
,
{
assertFalse
(
sale
)
}
)
}
You are free to override any of these functions (equals
, hashCode
, toString
, copy
, or any of the _componentN_
functions) if you wish. You can add other functions as well.
If you don’t want a property to be included in the generated functions, add the property to the class body rather than the primary constructor.
Data classes are a convenient way of representing classes whose primary purpose is to hold data. The standard library includes two data classes, Pair
and Triple
, for holding two or three properties of any generic types. If you need more than that, create your own data class.
Say you have a class called Customer
and you want to keep a list of messages or notes you’ve saved regarding them. You don’t necessarily want to load all the messages whenever you create an instance, however, so you create the class shown in Example 3-10.
Customer
class, version 1class
Customer
(
val
name
:
String
)
{
private
var
_messages
:
List
<
String
>
?
=
null
val
messages
:
List
<
String
>
get
(
)
{
if
(
_messages
=
=
null
)
{
_messages
=
loadMessages
(
)
}
return
_messages
!!
}
private
fun
loadMessages
(
)
:
MutableList
<
String
>
=
mutableListOf
(
"Initial contact"
,
"Convinced them to use Kotlin"
,
"Sold training class. Sweet."
)
.
also
{
println
(
"Loaded messages"
)
}
}
In this class, the property messages
will hold the list of messages about that client. To avoid initializing it immediately, the additional property _messages
is added, which is of the same type but nullable. The custom getter is used to check whether the messages have been loaded yet, and if not, loads them. The test in Example 3-11 accesses the messages.
@Test
fun
`
load
messages
`
(
)
{
val
customer
=
Customer
(
"Fred"
)
.
apply
{
messages
}
assertEquals
(
3
,
customer
.
messages
.
size
)
}
You can’t load the messages by using a constructor property, because _messages
is private. If you want the messages right away, as shown here, use the apply
function. In this test, that invokes the getter method, which both loads the messages and prints the info message. The second time the property is accessed, the messages have already been loaded, and no print is seen.
While this is a useful illustration, it implements lazy loading the hard way. Much easier is the code in Example 3-12, which uses the built-in lazy
delegate function.
lazy
class
Customer
(
val
name
:
String
)
{
val
messages
:
List
<
String
>
by
lazy
{
loadMessages
(
)
}
private
fun
loadMessages
(
)
:
MutableList
<
String
>
=
mutableListOf
(
"Initial contact"
,
"Convinced them to use Kotlin"
,
"Sold training class. Sweet."
)
.
also
{
println
(
"Loaded messages"
)
}
}
Still, using a private backing field to enforce initialization of a property is a useful technique.
A variation on this is to provide a constructor argument to set a value but still enforce constraints on the property, as done in Example 3-1 and repeated here for simplicity:
class
Task
(
val
name
:
String
,
_priority
:
Int
=
DEFAULT_PRIORITY
)
{
companion
object
{
const
val
MIN_PRIORITY
=
1
const
val
MAX_PRIORITY
=
5
const
val
DEFAULT_PRIORITY
=
3
}
var
priority
=
validPriority
(
_priority
)
set
(
value
)
{
field
=
validPriority
(
value
)
}
private
fun
validPriority
(
p
:
Int
)
=
p
.
coerceIn
(
MIN_PRIORITY
,
MAX_PRIORITY
)
}
Note that the _priority
property is not marked with val
, indicating it is only a constructor argument rather than an actual property of the class. The property you care about, priority
, has a custom setter to assign its value based on the constructor argument.
The backing property technique shows up fairly often in Kotlin classes, so it’s worth understanding how it works.
The lazy
delegate is discussed further in Recipe 8.2.
Use Kotlin’s operator-overloading mechanism to implement the associated functions.
Many operators, including addition, subtraction, and multiplication, are implemented in Kotlin as functions. When you use the +
, -
, or *
symbols, you are delegating to those functions. That means by supplying those functions, you allow a client to use operators.
The classic example, given in the reference docs, is to provide a member function unaryMinus
for a Point
class, as in Example 3-13.
unaryMinus
operator on Point
(from reference docs)data
class
Point
(
val
x
:
Int
,
val
y
:
Int
)
operator
fun
Point
.
unaryMinus
()
=
Point
(-
x
,
-
y
)
val
point
=
Point
(
10
,
20
)
fun
main
()
{
println
(-
point
)
// prints "Point(x=-10, y=-20)"
}
What if you want to add the relevant functions to a class that you didn’t write? You can use extension functions to do the job.
For example, consider the Complex
class in the (Java) library Apache Commons Math, which represents a complex number (one with real and imaginary parts). If you browse the Javadocs, you’ll see that the class includes methods like add
, subtract
, and multiply
. In Kotlin, the +
, -
, and *
operators correspond to the functions plus
, minus
, and times
. If you add extension functions to Complex
to delegate to the existing functions, as in Example 3-14, you can then use the operators instead.
Complex
import
org.apache.commons.math3.complex.Complex
operator
fun
Complex
.
plus
(
c
:
Complex
)
=
this
.
add
(
c
)
operator
fun
Complex
.
plus
(
d
:
Double
)
=
this
.
add
(
d
)
operator
fun
Complex
.
minus
(
c
:
Complex
)
=
this
.
subtract
(
c
)
operator
fun
Complex
.
minus
(
d
:
Double
)
=
this
.
subtract
(
d
)
operator
fun
Complex
.
div
(
c
:
Complex
)
=
this
.
divide
(
c
)
operator
fun
Complex
.
div
(
d
:
Double
)
=
this
.
divide
(
d
)
operator
fun
Complex
.
times
(
c
:
Complex
)
=
this
.
multiply
(
c
)
operator
fun
Complex
.
times
(
d
:
Double
)
=
this
.
multiply
(
d
)
operator
fun
Complex
.
times
(
i
:
Int
)
=
this
.
multiply
(
i
)
operator
fun
Double
.
times
(
c
:
Complex
)
=
c
.
multiply
(
this
)
operator
fun
Complex
.
unaryMinus
()
=
this
.
negate
()
In each case, the extension function delegates to the existing method in the Java class. The test in Example 3-15 illustrates how to use the delegated operator functions.
Complex
instancesimport
org.apache.commons.math3.complex.Complex
import
org.apache.commons.math3.complex.Complex.*
import
org.hamcrest.MatcherAssert.assertThat
import
org.hamcrest.Matchers.`is`
import
org.hamcrest.Matchers.closeTo
import
org.junit.jupiter.api.Test
import
org.junit.jupiter.api.Assertions.*
import
java.lang.Math.*
internal
class
ComplexOverloadOperatorsKtTest
{
private
val
first
=
Complex
(
1.0
,
3.0
)
private
val
second
=
Complex
(
2.0
,
5.0
)
@Test
internal
fun
plus
(
)
{
val
sum
=
first
+
second
assertThat
(
sum
,
`is`
(
Complex
(
3.0
,
8.0
)
)
)
}
@Test
internal
fun
minus
(
)
{
val
diff
=
second
-
first
assertThat
(
diff
,
`is`
(
Complex
(
1.0
,
2.0
)
)
)
}
@Test
internal
fun
negate
(
)
{
val
minus1
=
-
ONE
assertThat
(
minus1
.
real
,
closeTo
(
-
1.0
,
0.000001
)
)
assertThat
(
minus1
.
imaginary
,
closeTo
(
0.0
,
0.000001
)
)
}
@Test
internal
fun
`
Euler
'
s
formula
`
(
)
{
val
iPI
=
I
*
PI
assertTrue
(
Complex
.
equals
(
iPI
.
exp
(
)
,
-
ONE
,
0.000001
)
)
}
}
In the last test, the exp
function from Complex
returns the value of e^{arg}
, so the test demonstrates Euler’s formula, e^{i * PI} == –1
.
The tests illustrate many of the overloaded operators. If you are writing in Kotlin and using the Complex
class, a little bit of operator overloading with extension functions lets you use the same operators you’ve used with regular numbers.
Use the lateinit
modifier on your property.
Use this technique sparingly, only when necessary. Cases such as the dependency injection described here are useful, but in general, consider alternatives like the lazy evaluation in Recipe 8.2 where possible.
Properties of a class that are declared as non-null are supposed to be initialized in a constructor. Sometimes, however, you don’t have enough information at that time to give the property a value. This occurs in dependency injection frameworks, in which the injection doesn’t happen until after all objects have been constructed, or in setup methods in unit tests. For such cases, use the lateinit
modifier on the property.
For example, the Spring framework uses the annotation @Autowired
to assign values to dependencies from the so-called application context. Again, since the value is set after the instances have already been created, mark it as lateinit
, as in the test case shown in Example 3-16.
@SpringBootTest
(
webEnvironment
=
SpringBootTest
.
WebEnvironment
.
RANDOM_PORT
)
class
OfficerControllerTests
{
@Autowired
lateinit
var
client
:
WebTestClient
@Autowired
lateinit
var
repository
:
OfficerRepository
@Before
fun
setUp
(
)
{
repository
.
addTestData
(
)
}
@Test
fun
`
GET
to
route
returns
all
officers
in
db
`
(
)
{
client
.
get
(
)
.
uri
(
"/route"
)
// ... get data and check values ...
}
// ... other tests ...
}
The lateinit
modifier can be used only on var
properties declared inside the body of a class, and only when the property does not have a custom getter or setter. Since Kotlin 1.2, you can also use lateinit
on top-level properties and even local variables. The type must be non-null, and it cannot be a primitive type.
By adding lateinit
, you are promising to initialize the variable before it is first used. Otherwise, it throws an exception, as shown in Example 3-17.
lateinit
propertiesclass
LateInitDemo
{
lateinit
var
name
:
String
}
class
LateInitDemoTest
{
@Test
fun
`
unitialized
lateinit
property
throws
exception
`
()
{
assertThrows
<
UninitializedPropertyAccessException
>
{
LateInitDemo
().
name
}
}
@Test
fun
`
set
the
lateinit
property
and
no
exception
is
thrown
`
()
{
assertDoesNotThrow
{
LateInitDemo
().
apply
{
name
=
"Dolly"
}
}
}
}
Accessing the name
property before it has been initialized throws an UninitializedPropertyAccessException
, as the test shows.
Inside the class, you can check whether one of its properties has been initialized by using isInitialized
on the property reference, as in Example 3-18.
isInitialized
on a property referenceclass
LateInitDemo
{
lateinit
var
name
:
String
fun
initializeName
()
{
println
(
"Before assignment: ${::name.isInitialized}"
)
name
=
"World"
println
(
"After assignment: ${::name.isInitialized}"
)
}
}
fun
main
()
{
LateInitDemo
().
initializeName
()
}
The output from executing the initializeName
function is as follows:
Before assignment: false After assignment: true
The lazy
delegate is discussed in Recipe 8.2.
All object-oriented languages have the concept of object equivalence versus object equality. In Java, the double equals operator (==
), is used to check whether two references are assigned to the same object. By contrast, the equals
method, as part of the Object
class, is intended to be overridden to check that two objects are equivalent.
In Kotlin, the ==
operator automatically invokes the equals
function. The open
class Any
declares the equals
function, as shown in Example 3-19, along with hashCode
and toString
.
equals
, hashCode
, and toString
in Any
open
class
Any
{
open
operator
fun
equals
(
other
:
Any
?):
Boolean
open
fun
hashCode
():
Int
open
fun
toString
():
String
}
The contract for equals
requires that the implementation be reflexive, symmetric, and transitive, as well as consistent, and handle nulls appropriately. The contract for hashCode
is that if two objects are equal by the equals
function, they should have the same hashCode
as well. The hashCode
function should be overridden whenever the equals
function is.
That said, how do you go about implementing a good equals
function? One excellent example from the library is provided by the KotlinVersion
class, whose equals
function is shown in Example 3-20.
equals
function in KotlinVersion
override
fun
equals
(
other
:
Any
?):
Boolean
{
if
(
this
===
other
)
return
true
val
otherVersion
=
(
other
as
?
KotlinVersion
)
?:
return
false
return
this
.
version
==
otherVersion
.
version
}
Note the simple elegance of this implementation, which takes advantage of several Kotlin features:
First, it checks reference equality by using ===
.
Then it uses the safe casting operator, as?
, which either casts the argument as the desired type or returns null
.
If the safe cast returns null
, the Elvis operator (?:
) then returns false
because if the instances aren’t of the same class, they can’t be equal.
Finally, the last line checks whether the version
property of the current instance (not shown) is equivalent (using the ==
operator) to the same property in the other object, and returns the result.
In three lines, this covers all the required cases. For completeness, the implementation of hashCode
is simply as follows:
override
fun
hashCode
():
Int
=
version
This is interesting, but not as directly relevant if you’re trying to understand how to write your own equals
function. Say, for example, that you have a simple class called Customer
with a string property called name
. A consistent implementation of both equals
and hashCode
is shown in Example 3-21.
equals
and hashCode
in Customer
class
Customer
(
val
name
:
String
)
{
override
fun
equals
(
other
:
Any
?):
Boolean
{
if
(
this
===
other
)
return
true
val
otherCustomer
=
(
other
as
?
Customer
)
?:
return
false
return
this
.
name
==
otherCustomer
.
name
}
override
fun
hashCode
()
=
name
.
hashCode
()
}
Incidentally, if you let IntelliJ IDEA generate an equals
and hashCode
implementation for you, the result (using the Ultimate Edition version 2019.2) is shown in Example 3-22.
equals
and hashCode
functions by IntelliJ IDEAclass
Customer
(
val
name
:
String
)
{
override
fun
equals
(
other
:
Any
?):
Boolean
{
if
(
this
===
other
)
return
true
if
(
javaClass
!=
other
?.
javaClass
)
return
false
other
as
Customer
if
(
name
!=
other
.
name
)
return
false
return
true
}
override
fun
hashCode
():
Int
{
return
name
.
hashCode
()
}
}
The difference is that the generated equals
function checks that the javaClass
properties on the KClass
are equivalent before casting, using the as
operator, and then relies on the resulting smart cast to check the name
properties. This is essentially equivalent to the procedure described previously, only a bit more verbose.
Data classes have their own autogenerated implementations of equals
and hashCode
(as well as toString
, copy
, and component methods). Here, however, you can see how easy it is to implement your own versions.
Data classes are discussed in Recipe 3.3. The KotlinVersion
class is shown in Recipe 11.1.
Use the object
keyword instead of class
.
The Singleton design pattern defines a mechanism for guaranteeing there is only one instance for a particular class. To define a singleton:
Declare all constructors of a class to be private.
Provide a static
factory method that returns a reference to the class, instantiating it if necessary.
The Singleton pattern is controversial, because it is sometimes used in cases where a small number of instances could be used rather than one. Nevertheless, it is one of the fundamental design patterns defined in Design Patterns by Erich Gamma et al. (Addison-Wesley Professional), and it can be quite helpful in certain cases.
An example of a singleton in the Java standard library is given by the Runtime
class. Say you want to know how many processors are available on a given platform. In Java, you can find out with the code in Example 3-23.
fun
main
()
{
val
processors
=
Runtime
.
getRuntime
().
availableProcessors
()
println
(
processors
)
}
The getRuntime
method is the static
method used to return the singleton instance of the class. Example 3-24 shows the relevant portion of the java.lang.Runtime
class.
Runtime
implemented to use the Singleton patternpublic
class
Runtime
{
private
static
final
Runtime
currentRuntime
=
new
Runtime
();
public
static
Runtime
getRuntime
()
{
return
currentRuntime
;
}
/** Don't let anyone else instantiate this class */
private
Runtime
()
{}
// ...
}
The Runtime
class contains a private, static, final instance of the class, which is eagerly instantiated where the currentRuntime
attribute is declared. The only constructor is declared to be private
, and the static factory method is getRuntime
, as used in Example 3-24.
To implement a singleton in Kotlin, simply use the object
keyword instead of class
, as in Example 3-25. This is known as an object declaration.
object
MySingleton
{
val
myProperty
=
3
fun
myFunction
()
=
"Hello"
}
If you decompile the generated bytecodes, you get a result similar to Example 3-26.
object
public
final
class
MySingleton
{
private
static
final
int
myProperty
=
3
;
public
static
final
MySingleton
INSTANCE
;
private
MySingleton
(
)
{
}
public
final
int
getMyProperty
(
)
{
return
myProperty
;
}
public
final
void
myFunction
(
)
{
return
"Hello"
;
}
static
{
MySingleton
var0
=
new
MySingleton
(
)
;
INSTANCE
=
var0
;
myProperty
=
3
;
}
}
When invoking code in the singleton, you can access the members from the object name, as you would for static
members in Java. The member function and property become static final
methods and attributes in the decompiled Java class, along with any required getter methods, and the properties are initialized in a static
block, along with the class itself. Kotlin code to access the members is in Example 3-27.
MySingleton
.
myFunction
()
MySingleton
.
myProperty
Accessing the singleton from Java uses the generated INSTANCE
property and is shown in Example 3-28.
MySingleton
.
INSTANCE
.
myFunction
();
MySingleton
.
INSTANCE
.
getMyProperty
();
A complication arises if you want your singleton to be instantiated with an argument. Say, for example, you’re writing a database connection pool, which would be a natural singleton. The initial size of the pool would be a reasonable input parameter when generating the singleton. Unfortunately, a Kotlin object
can’t have a constructor, so there’s no easy way to pass an argument to it.
The blog post “Kotlin Singletons with Argument” by Christophe Beyls discusses ways to handle arguments based on the implementation of the lazy
delegate in the Kotlin library.
The referenced article gets into the complexities associated with making the singleton instantiation thread-safe, based on double-checked locking and @Volatile
. See the article for details.
Use Nothing
when a function never returns.
You know Nothing, Jon Snow.
Ygritte in Game of Thrones, praising Jon Snow for reading this recipe
There is a class in Kotlin called Nothing
, whose entire implementation is given in Example 3-29.
Nothing
implementationpackage
kotlin
public
class
Nothing
private
constructor
()
The private constructor means the class cannot be instantiated outside the class, and as you can see, it isn’t instantiated inside the class either. Therefore, there are no instances of Nothing
. The documentation states that “you can use Nothing
to represent a value that never exists.”
The Nothing
class arises naturally in two circumstances. The first occurs when a function body consists entirely of throwing an exception, as in Example 3-30.
fun
doNothing
():
Nothing
=
throw
Exception
(
"Nothing at all"
)
The return type must be stated explicitly, and since the method never returns (it throws an exception instead), the return type is Nothing
.
That is virtually guaranteed to be a source of confusion for existing Java developers. In Java, if a method throws an exception of any type, the return type on the method doesn’t change. Exception handling is completely outside the normal flow of execution, but you don’t have to change the return type on a method to account for it. The type system in Kotlin, however, has different requirements.
The other context in which Nothing
arises occurs when you assign a variable to null and don’t give it an explicit type, as in Example 3-31.
val
x
=
null
The type of x
is inferred to be Nothing?
, because it’s obviously nullable (it was assigned null
, after all) and the compiler has no other information about it.
To really make matters interesting, consider this fact: in Kotlin, the Nothing
class is actually a subtype of every other type.
To see why that is necessary, consider an if
statement that can throw an exception, as in Example 3-32.
if
statement that can throw an exceptionval
x
=
if
(
Random
.
nextBoolean
())
"true"
else
throw
Exception
(
"nope"
)
The type of x
is inferred to be either String
, Comparable<String>
, CharSequence
, Serializable
, or even Any
, based on the string assigned when a true boolean is generated by the Random.nextBoolean
function. The else
clause returns a value of type Nothing
, and since Nothing
is a subtype of every type, performing a Boolean “and” with it and any other type is the other type.
Perhaps the following example will be clearer. The remainder of any number when divided by 3 must be either 0, 1, or 2. Therefore, the when
statement in Example 3-33 shouldn’t need an else
clause, but the compiler doesn’t know that.
for
(
n
in
1.
.
10
)
{
val
x
=
when
(
n
%
3
)
{
0
->
"$n % 3 == 0"
1
->
"$n % 3 == 1"
2
->
"$n % 3 == 2"
else
->
throw
Exception
(
"Houston, we have a problem..."
)
}
assertTrue
(
x
is
string
)
}
The when
construct returns a value, so the compiler requires it to be exhaustive. The else
condition should never happen, so it makes sense to throw an exception in that case. The return type on throwing an exception is Nothing
, and since String
is String
, the compiler knows that the type of x
is String
.
The TODO
function (discussed in Recipe 11.9) returns Nothing
, which makes sense because its implementation is to throw a Not
Implemented
Error
.
The Nothing
class can be confusing, but once you know the use cases for it, it makes sense in context.
18.117.189.228