Kotlin for Java Developers
- 25 minsThe following are my notes for the course: Kotlin for Java Developers.
This course is given by Svetlana Isakova and Andrey Breslav from JetBrains.
Basics
JVM annotations are useful for Kotlin code which will be called from Java code.
-
@JvmName
: The class name for first class functions (by default it’s the file name). -
@JvmOverloads
: Generate overloads for a function with named parameters (Not all combinations though).
Types
- Everything is an object in the sense that we can call member functions and properties on any variable.
- Types can be inferred from the context (
val i: Int = 3
can be replaced byval i = 3
).
Operators & Conditions
- In Kotlin, there is no ternary operator.
-
if
is an expression in Kotlin.
Loops
-
for (i in list)
is equivalent tofor (i:Int in list)
. - We can iterate over a map :
for ((key, value) in map)
. - There is no full form of
for
. -
for (i in 1..9)
is equivalent tofor (i in 1 until 9)
. - Reverse :
for (i in 9 downTo 1)
. - Reverse and step :
for (i in 9 downTo 1 step 2)
. - We can iterate over Strings (chars in a string).
Operator “in”
-
i in 1..9
is equivalenti <= 9 && i>=9
. - Not in :
!in
. -
"ball" in "a".."k"
is equivalent to"a" <= "ball" && "ball" <= "k"
which is equivalent to"a".compareTo("ball") <= 0 && "ball".compareTo("k") <= 0
(String are compared alphabetically).
Exceptions
- There is no checked exceptions in Kotlin.
-
throw
result can be assigned to variables. -
try
is an expression in Kotlin. -
@throws
annotation is for checked exceptions (to be catchable in Java).
Extension Functions
-
for String.lastChar() = this.get(this.length - 1)
,String
(andthis
) in this expression is called: Receiver.this
can be omitted , the expression becomesfor String.lastChar() = get(tlength - 1)
. - Extension functions needs to be imported.
- From Java, extensions are called as static methods.
- An expression can’t call the private members of the receiver.
- Kotlin Standard Library = Java Standard Library (JDK) + Extensions.
-
infix
allow us to omit the dot. Example:infix fun Int.until(to: Int): IntRange
can be called1.until(10)
or1 until 10
-
until
,to
…etc are simply extensions. - Extensions can’t override functions, but can overload them.
- Member functions have higher priority than extensions.
Nullability
- The idea is to move nullability exceptions (at runtime) to compile time errors.
- Non nullable variable :
var s1: String
, nullable variable :var s2: String?
- Dealing with nullable can done by either an explicit check
(s != null) s.length
or safe accesss?.length
. - Safe access example:
s?.length
-> in the case wheres == null
then the expression returnsnull
, in the other case wheres != null
thens.length
is returned. -
val length: Int = if (s != null) s.length else 0
is equivalent toval length: Int = s?.length ?: 0
-
?:
called Elvis operator . - If we check nullability and fail if it so, no null check later is needed:
val s: String? if (s == null) fail() //or return s.length
-
s!!
throw NPE if null. - Nullable types are implemented using
@Nullable
and@NonNull
. -
List<T?>
means the list elements are nullable, whileList<T>?
means the list itself is nullable. - Kotlin operator
is
is an analog toinstanceof
operator from Java. - The operator
as
used for casting, safe cast can be done usingas?
.val s: String? = a as? String
is equivalent toval s = if (a is String) s else null
.
Lambdas
- Lambdas always go in curly braces
{ }
. In IDEA, lambdas braces are highlighted (in bold). Lambda example{ i: Int, j: Int -> i + j}
- If the lambda is the last parameter of a function, we can move it away of the function parameters
List.any({ i: Int -> i + 1})
is equivalent toList.any() { i: Int -> i + 1}
. If a function have only one parameter as lambda parameter, we can omit the parentheses:List.any { i: Int -> i + 1}
. - Lambdas parameters types can be inferred,
List.any { i: Int -> i + 1}
can becomeList.any { i -> i + 1}
. If the lambda have only one parameter, we can replace it with it :List.any { it + 1}
. - In case of multiline lambda, the last line is the returned result.
- If a lambda parameter is not used, we can replace it with **** _(underscore),
map.mapValues{ (_, value) -> "$value" }
. - Destructing argument can be used to replace
Map.Entry
orPair
.
Extensions on Collections
-
filter
: filters out the content of a list and keeps only the elements satisfying the predicate. -
map
: transforms each element in a collection. -
any
(all
,none
): checks if the elements satisfies a predicate. -
find
(firstOrNull
): finds an element satisfying a predicate and returns it, if none is found,null
is returned. -
first
: finds the first element to satisfy a predicate, if no result found, and exception is thrown. -
count
: count’s the element satisfying the predicate. -
partition
: devices a collection to two collections: one with elements satisfying the predicate and the other the ones that do not. -
groupBy
: group elements by a provided key. -
assosiateBy
: it performs grouping, but for unique elements, duplicates are removed. -
zip
: return a list which its elements (pair) are the combination of two lists elements.(Like a zipper :D). -
flatMap
: performs two actions: first it maps”the collection (from aString
toChar
list for example), then flatten them to return a single list with all mapped elements from all collections.
Functional Programming
- Lambdas can be stored in variables :
val isEven: (int) -> Boolean = { i: Int -> i % 2 == 0 }
, we can omit the types to becomeval isEven = { i: Int -> i % 2 == 0 }
,. - It’s possible to pass stored lambdas whenever the expression of function type is expected :
list.any(isEven)
. - It’s possible to call stored lambdas :
isEven(42)
. - Lambdas can be run directly :
{ println("hi!") }()
, another more convenient way to run lambdas is usingrun
instead of()
:run { println("hi!") }
. - Autogenerated Java SAM constructors can be used to create instances :
Runnable { println(42) }
. - To call a function stored in a nullable variable, there is two possible ways :
if (f != null) f()
orf?.invoke()
. - Functions can’t be stored in variables, but their reference can :
val predicate = ::isDigit
, which is an analogueval predicate = { i: Int -> isDigit(i) }
. - Storing the reference of a class function reference is called Non-Bounded reference:
val isOlderPredicate = Person::isOlder
, isOlderPredicate is of type(Person, Int) -> Boolean
. - Storing the reference of a specific instance function reference is called Bounded reference:
val alice: Person()
andval isOlderPredicate = alice::isOlder
, isOlderPredicate is of type(Int) -> Boolean
. - Calling
return
from inside a lambda will end the function calling it. To return only from the lambda, we use labelled return:return@flatMap
. Another way is to use local functions and return from them.
Properties
- Property = field + accessor(s).
- Read-Only Property (val) = field + getter.
- Mutable Property (var) = field + getter + setter
- Accessors (get/set) are used to access variables under the hood in Kotlin.
- Backing field might be absent (defining getter/setter without any field)
- Fields can be accessed but only inside the accessors using the keyword
field
. - In some cases, the access to properties may be optimised by the compiler inside the classes of the properties.
- It’s possible to change the visibility of accessors:
var counter: Int = 0 private set
- Properties can be defined in interfaces, under the hood, a property becomes only a getter, which can be overridden by the implementations of the class:
interface User { val nickname: String } class FacebookUser(val account: Int): User { // Calculated once override val nickname = getName(account) } class Subscriber(val email: String); User { // Calculated for each access override val nickname: String get() = email.substringBefore('@') }
- Interfaces’s properties are always open (not final), and open properties can’t be smart casted.
- Extension properties are possible:
val String.lastIndex: Int get() = this.length - 1 // with `this` set(value: Char) { this.setCharAt(length - 1, value) // without `this` }
Lazy/late Initialisation
- Lazy property is a property which values are compiled only on the first access.
- Lazy properties can be defined using the
by lazy
syntax :val lazyValue: String by lazy
. - Sometimes, properties needs to be initialised later, not in the constructor, in this case
lateinit
can be used:lateinit var data: Data
. -
lateinit
can be applied tovar
only, and the property type can’t be nullable or primitive. - An exception is thrown if a
lateinit
variable is accessed without being initialised.
Oriented-Object Programming
Visibility Modifiers
- All declaration and
public
andfinal
by default. - To mark a declaration as non final, mark it with
open
. - No package visibility in Kotlin.
-
internal
is for module visibility. A module is a set of kotlin files compiled together (Maven project, Gradle source set..). -
protected
is visible inside the package in Java but not in Kotlin, only to the class and it subclasses. -
private
top-level declaration is visible in the file. -
package
name don’t have to match the directory structure.
Constructors
- Primary constructor :
class Person(val name: String)
-
init
block can be used as the constructor body, and use the constructor as parameters entry:class Person(name: String)
(noval
here). - Changing the constructor visibility:
class Person internal constructor(name: String) { // ... }
-
constructor
can be used to declare secondary constructor, however a primary constructor must be called:constructor (side: Int) : this(side, side) { ... }
. -
extends
andimplements
are replaced by semicolon in Kotlin:
. - The parentheses are used for class inheritance (constructor), but not for interfaces:
class Alice: Person()
.
Class Modifiers
-
enum
modifier is for creating Enumerations classes:enum class Color { RED, GREEN, BLUE }
. -
data
modifier generates: equals, hashCode, copy, toString and some other methods. -
==
callsequals()
, and===
checks reference equality. -
sealed
modifier restrict class hierarchy: all the subclasses must be located in the same file (protects against having a class subclassed somewhere else). - In Kotlin, nested classes are by default static classes,
inner
modifier is to declare inner classes. An inner class keeps a reference to it parent class:class A { class B // static class inner class C { ..this@A... // access using label name } }
- Class delegation:
by
modifier means by delegating to the following instance:class Controller (repository: Repository, logger: Logger) : Repository by repository, Logger by logger
.
Objects
-
object
is a singleton in Kotlin:object class KSingleton { fun foo () }
. Class members can be accessed directly:KSingleton.foo()
. In java, it corresponds to the static singleton with private constructor pattern in Java. To access the singleton from Java we use INSTANCE:KSingleton.INSTANCE.foo()
. -
object
keyword can be used to create anonymous classes, an instance is created each time though :view.addListener( object: MouseAdapter() { override fun mouseClicked(e: MouseEvent) {/*...*/} override fun mouseEntered(e: MouseEvent) {/*...*/} } )
- There is no static members in Kotlin.
companion object
is a special object inside a class which might be a replacement for static members. -
companion object
can implement interfaces:companion object: Factory<A> { /*...*/ }
. -
companion object
can be receiver of extension functions:fun Person.Companion.create(): Person { /*...*/ }
, afterward the extension can be called as functions on class name:Person.create()
. - To access
companion object
members from java:@JvmStatic
annotation can be used :// Kotlin class C { companion object { @JvmStatic fun foo() {/*...*/} fun bar() {/*...*/} } } // Java C.Companion.foo() // In case of `object` we use INSTANCE C.Companion.bar() // instead of `Companion` C.foo()
-
object
can be nested, but can’t beinner
.
Constants
-
const
-> for primitive types and strings. - Using
const
means that the value is inlined: at compile time, the constant will be substituted in the code by its actual value. -
@JvmField
-> for objects, eliminates accessors. It’s the same as defining a static final field. - Using
@JvmStatic
on a property exposes only its getter.
Operator Overload
- In Kotlin, it’s possible to overload arithmetic operators: plus (
+
), minus (-
), div (/
), mod (%
). There is no restriction on parameter type.operator fun Point.plus(other: Point): Point = Point(x + other.x, y + other.y)
- Unary operations can be overloaded too: unaryPlus (
+a
), unaryMinus (-a
), not (!
), inc (++a
,a++
), dec (--a
,a--
). - For the case of
a += b
, there is two options : ifa
is mutable thena = a.plus(b)
is called, ifa.plusAssign(b)
is available, then it’s possible to bridge off to it instead. - Using plus (
+
) for lists: if the list is immutable then a new list is created as result of the operation, if the list is mutable, then the list itself is modified.var list = listOf(1,2,3) list += 4 // new list is created: list = list + 4
Conventions
- Comparisons:
a > b
is translated toa.compareTo(b) > 0
,a >= b
is translated toa.compareTo(b) >= 0
… and so on. -
a == b
callsequals
, and correctly handles nullable valuesnull == "abc"
. -
map[a, b]
callsmap.get(a, b)
, andmap[a, b] = c
callsmap.set(a, b, c)
. Therefore, it’s possible de define extensions on theget
andset
functions. -
in
calls under the hoodcontains
:a in c
->c.constains(a)
. - Range
..
callsrangeTo
:start..end
->start.rangeTo(end)
. -
iterator
is a convention too:operator fun CharSequence.iterator(): CharIteraror
->for (c in "abc") { }
. - Destructing declaration :
val (a, b) = p
->val a = p.component1()
andval b = p.component2()
. - Any
data class
can be represented as destructing declaration:data class Contact ( val name: String, val email: String, val phone: String ) // A `contact` object can be then destructed: val (name, _, phone) = contact
- Elements that define member or extension operator function
compareTo
(with the right signature) can use the comparison operators.
Inline functions
-
run
: runs the block of code (lambda) and returns the last expression as the result:val foo = run { println("Calculating...") "foo" }
-
let
: allows to check the argument for being non-null, not only the receiver.if (email != null) sendEmail(email)
can be replaced byemail?.let { e -> sendEmail(e) }
orgetEmail()?.let { sendEmail(it) }
. Notice the safe access usage?
beforelet
. -
takeIf
: returns the receiver object if it satisfies the given predicate, otherwise returns null. -
takeUnless
: returns the receiver object if it does not satisfies the given predicate, otherwise returns null. -
repeat
repeats an action for a given number of times. -
withLock
: is an extension to call an object in asynchronized
fashion. -
use
: to use a resource withtry-with-resources
.
Inlining
-
inline
function: the compiler substitutes the body of the function instead if calling it, as result we have no performance overhead of creating an anonymous class and an object for the lambda:inline fun <R> run(block: () -> R): R = block() val name = "kotlin" run { println("hi, $name") }
// the generated bytecode : val name = "kotlin" println("hi, $name")
-
@kotlin.internal.InlineOnly
: specifies that a function should not be called directly without inlining (therefore, can’t be called from java). - The disadvantage of inlining: the size of resulting application, therefore, inlining should be used with a lot of care.
Sequences
- Sequences are similar to java streams: they perform in lazy manner, have intermediate and terminal operations.
- To convert a list to sequence:
asSequence()
-
generateSequence
: generates an infinite sequence:val numbers = generateSequence(0) { it + 1} numbers.take(5).toList() //[0, 1, 2, 3, 4]
- To prevent integer overflow while using
generateSequence
,BigInteger
can be used instead ofInt
. - A way to generate a finite sequence is to return
null
at some point:val numbers = generateSequence(0) { n -> (n + 1).takeIf (it < 3) } numbers.toList() //[0, 1, 2]
-
yield
: yields a value to the Iterator being built.
Library functions
-
people.filter { it.age < 21 }.size
->people.count { it.age < 21 }
-
people.sortedBy { it.age }.reversed()
->people.sortedByDescending { it.age }
-
mapNotNull
: callmap
in a list then filtering the only the non-null values. -
getOrPut
: get at value from a map by a key, if the entry doesn’t exist then put a default value:map.getOrPut(person.name) { mutableListOf }
.
Lambda with Receiver
- Lambda with receiver = Extension function + Lambda.
- The old name of lambda with receiver is extension lambdas.
- Regular function vs Extension function:
val isEven: (Int) -> Boolean = { it % 2 == 0 } val isOdd: Int.() -> Boolean = { this % 2 == 1 } isEven(0) // Calling as regular function. 1.isOdd() // Calling as extension function.
-
with
declaration as a use case of lambda with receiver:inline fun <T, R> with(receiver T, block: T.() -> R) : R = receiver.block()
- Lambdas with receiver are used to create Kotlin DSL’s, gradle build script…etc.
Useful library functions
- To omit repeatedly using an element name to call it inner elements, we can use:
with
.with
takes the receiver as a parameter and use it inside the lambda asthis
. -
run
is likewith
but as extension:windowById["main"]?.run { width = 200 }
. -
with
andrun
both return the last expression as a result. -
apply
returns receiver as result:windowById["main"]?.apply { width = 200 }
. -
also
is similar toapply
, it return the receiver as well, however, it take a regular lambda not a lambda with receiver as an argument :windowById["main"]?.also { showWindow(it) }
Recap
-
run
-> return result of the lambda and take lambda with receiver as parameter. -
let
-> return result of the lambda, take regular lambda as parameter. -
apply
-> return receiver, take lambda with receiver as parameter. -
also
-> return receiver, take regular lambda as parameter.
Types
- There is no primitives in the language.
- Non-nullable Kotlin primitive types (
Int
) corresponds to Java primitives (int)
- Nullable Kotlin primitives (
Int?
) corresponds the Java wrappers (java.lang.Integer
). - When non-nullable primitive is used as generic (
List<Int>
), it will be compiled to wrapper type (List<Integer>
). - Arrays with generics are converted to wrappers (
Array<Int>
->Integer[]
). To use primitive arrays, we useIntArray
,DoubleArray
,LongArray
…etc. -
kotlin.String
corresponds tojava.lang.String
with some changes in it API. -
Any
corresponds tojava.lang.Object
, however it’s a super type of all references, including primitives. -
contentEquals
: to compare primitive arrays (IntArray
,DoubleArray)
…).
Types Hiarchy
-
Unit
under the hood corresponds to Javavoid
: a type that allow only one value and thus can hold no information -> the function completes successfully. -
Nothing
is a subtype of all other types, and means: this function will never return: a type that has no value -> the function never completes (by error; or never completes: infinite loop for example). - Under the hood
Nothing
corresponds to Javavoid
because we don’t haveNothing
in the JVM. - Taking nullable in consideration,
Any?
is the super type of all types, andNothing?
is the subtype of all types. - The simplest expression of
Nothing?
type isnull
:var n: Nothing? = null
.
Nullabe Types
- Platform type
Type!
-> unknown nullability. It’s a notation, not syntax: can’t declare such type in kotlin. - To prevent NPEs when using java code: annotate java types (
@Nullable
and@NotNull
) and/or specify types explicitly in kotlin. - Specifying a default annotation in java code can be done with JSR-305:
@javax.annotation.Nonnull @TypeQualifierDefault(ElementType.Parameter, ...) annotation class MyNonnullByDefault // Use the annotation with package @MyNonnullByDefault package mypackage;
Collections Types
-
kotlin.List
(andkotlin.MutableList
) ->java.util.List
-
kotlin.MutableList
extends the interfacekotlin.List
. - Read-only ≠ Immutable: RO interface just lacks mutating methods, the actual list still can be changed by another reference.
-
java.util.List<String>
->(Mutable) List<String!>
.
Misc.
- Triple quotes (called also multiline strings) are good for regex strings.
- Expressions precedence (documentation)