This collection is based on my previous article, Best Practices in Java, and refers directly to that article in places. As there, some items are specific to Kotlin, some are specific to the interface between Kotlin and Java, and some are good practices that are independent of language.
Image credit: https://kotlinlang.org.
Do you have corrections or suggestions? Send me an email.
These are strongly suggested, to make sure that when multiple engineers edit the same code, the only changes are to the code, not wholesale changes to files. This makes code reviews consistent.
for (elements in list) {
. . .
}
Note: semicolons are optional in Kotlin, and therefore line breaks are significant. The language design assumes this brace style, and “you may encounter surprising behavior if you try to use a different formatting style.” (Kotlin Style Guide)
(Not a thing in Java; but similar to Categories in Objective-C.) A Kotlin class may be "extended" by writing a new method for it, even without the original source code. Here is an example:
import kotlin.math.pow
import kotlin.math.round
fun Double.round(decimals: Int): Double {
val multiplier = 10.0.pow(decimals)
return round(this * multiplier) / multiplier
}
The library function, kotlin.math.round(Double), rounds to the nearest integer and returns a Double. This extension to Double lets you round to any number of decimal places – including a negative number, to round to the nearest ten or hundred.
val x = PI.round(2) // 3.14
val y = 123.4.round(-1) // 120
Things to remember about extension functions:
We all remember PEMDAS from elementary school, right? Parentheses, Exponents, Multiplication/Division, Addition/Subtraction. (I’ve seen some remember a different mnemonic, BEDMAS, where parentheses are called “braces,” and equal-precedence operations got switched, but that’s the same thing. Sounds like British Commonwealth; “countries separated by a common language” and all that.) Well, Kotlin has its own operator precedence. It’s defined here. It’s important that you know this, or at the very least, know where to look it up.
Note: In the Java BP article, I wrote about the problem with the shift operators having a lower precedence than the equality operators. In Kotlin, the shl and shr operators have a higher precedence, just below the additive operators + and –. So that particular pitfall does not exist in Kotlin.
Step 1: Use Kotlin. https://kotlinlang.org/docs/null-safety.html
Step 2: You're done!
All right, it isn't quite that simple. There are still ways to force a NullPointerException in Kotlin, but you literally have to go out of your way. If you have a nullable type, and it happens to be null, you can apply the not-null assertion operator !! and dereference it. Or, interaction with Java classes can put a null reference into a collection of a non-nullable type.
Or, you can explicitly throw NullPointerException().
Note: the page linked above is well worth your time. Null safety is the raison d'être for Kotlin.
Also: remember the features in Kotlin that help with null-safety:
fun Any?.toString(): String {
if (this == null) return "null"
return toString()
}
A variable in Kotlin is declared either with var or val. The difference is that a var may be modified, but a val is final.
Unlike in Java, classes in Kotlin are final by default (unless they are declared abstract). To make a class inheritable, use the keyword open on the class; to make a method overridable, that method must also be declared open.
To declare an override method, the keyword override is required. Any method marked override is also open. To "close" an overridden function, it may be marked with final.
Properties may also be overridden, either in the body of the derived class, or in the declaration of the primary constructor, as in the class Square below. You may override a val property with a var, as in class Polygon; but not the other way around. This is because a val property implicitly defines a get method, and overriding it as a var also declares a set method in the derived class.
open class Shape {
open val vertexCount: Int = 0
open fun draw() { /* */ }
fun fill() { /* */ } // fill is final
}
open class Rectangle : Shape {
override val vertexCount = 4
override fun draw() { /* */ }
}
class Square(override val vertexCount: Int = 4) : Rectangle {
final override fun draw() { /* */ }
}
class Polygon : Shape {
override var vertexCount: Int = 0 // can be set later
}
There is a fairly well defined issue in C++ with multiple inheritance (the "diamond" problem), where a class inherits from two different classes which have a common ancestor. To illustrate this, consider the artistic cowboy:
class Person {
public:
virtual void draw() { /* */ }
}
class Artist : public Person {
public:
void draw() { /* */ } // draws a picture
}
class Cowboy : public Person {
public:
void draw() { /* */ } // draws a six-shooter!
}
class ArtisticCowboy : public Artist, public Cowboy {
// ...
}
There are actually two issues here: first, because both Artist and Cowboy inherit Person directly, there are actually two different Person entities inside ArtisticCowboy; but this can be solved with virtual inheritance. The more important problem is, what the heck does the ArtisticCowboy do when asked to draw? Use a pattern of bullet holes to make a picture?
Kotlin, like Java, can only extend a single class, but can implement multiple interfaces. Therefore, it's possible to encounter something like the Artistic Cowboy problem. Kotlin's solution is a rule: if a class inherits multiple implementations of the same member from its immediate superclasses, it must override this member and provide its own implementation. It can then call one (or more) parent implementations from its own implementation.
open class Artist {
open fun draw() { /* */ }
}
interface Cowboy {
fun draw() { /* */ } // interfaces and their members are open by default
}
class ArtisticCowboy : Artist, Cowboy {
// the compiler requires an override for draw()
override fun draw() {
super<Artist>.draw()
super<Cowboy>.draw()
}
}
In Java, the capitalized versions of numbers (Integer, Double, etc.) are objects, not primitives; and will automagically be converted between the boxed objects and primitives (sometimes). The important things to remember are:
In Kotlin, the integer types (Byte, Short, Int, Long) and the floating point types (Double, Float) act like primitives or like objects. For example, above we show an extension for (class) Double. The nullable versions (Int?, Float?, etc.) correspond to Java's Boxed types, and are treated as such in the JVM.
One warning from the Autobox section of the Java article is about the == and != operators. This does not apply to Kotlin, because of Kotlin's operator overloading. In particular, those operators will look for, and call if it exists, a method called .equals() to evaluate the condition.
On the other hand, Kotlin does not have any implicit type widening, so it's impossible to compare an Int? to a Long?. Why? The first thing fun Long.equals(other: Any?) does is check whether other is a Long or Long?. If other is an Int?, the function quickly returns false.
All that said, Kotlin does still provide the ability to check whether two references are pointing to the same object: the operator === provides this referential equality.
val twoK: Int? = 2000
val dosK: Int? = 2000
println(twoK == dosK) // true
println(twoK === dosK) // false
The String type in Kotlin is similar to String in Java; it is an immutable sequence of UTF-16 characters and surrogate pairs. Just as in Java, it is legal to concatenate a String with any other object with the + operator (as long as the first operand is a String). However, each + creates a new object; while Java builds an implicit StringBuilder in a multi-operand concatenation, Kotlin does no such thing.
Instead, Kotlin provides String templates. A literal string with $identifier will embed the value of the identifier in the string. A literal string with ${expression} will embed the value of the expression in the string. Example:
val three: Double = 3.0
val string = "$three cubed is ${three.pow(3)" // → 3.0 cubed is 27.0
There is also a way to create a multi-line string. See the Kotlin documentation.
Kotlin collections are quite similar to collections in Java. The same concepts of Set, List, and Map exist; there are concrete types such as HashSet and ArrayList. But Kotlin has a richer interface hierarchy of mutable collection interfaces and classes; and operator overloading allows some syntactic sugar.
The same empty collection and singleton collection methods exist (singletonList(), emptyMap(), etc.). And the same warning about immutable collections applies, but in Kotlin, one may check whether a map is mutable with the is operator:
if (myMap is MutableMap) ... // the map is mutable if it descends from MutableMap
Enumerations in Kotlin are almost identical to enumerations in Java. All the reasons for using enums in Java still apply to Kotlin