Nullability & Functional Programming
Nullable types
- Make exceptions occur at compile time, rather than runtime.
// String only
val s1: String = "always not null"
// Allows String or null
val s2: String? = null
Dealing with Nullable Types
val s: String?
if(s != null){
s.length
}
is equivalent to
val s: String?
s?.length
Nullability operators
// if s!= null, then return s.length
// else return null
val length: Int? = s?.length
// if s!= null, then return s.length
// else return 0
val length: Int = s?.length ?: 0
- What will be printed?
val a: Int? = null
val b: Int? = 1
val c: Int = 2
val s1 = (a ?: 0) + c
val s2 = (b ?: 0) + c
print("$s1$s2")
- Answer:
23
Making Null Pointer Exception Explicit
val s: String?
s!!
- This basically means:
- If
sis not null, gives - Else if
sis null, then give a Null Pointer Exception.
- If
tip
- Don't use Null Pointer Exception excessively.
- Don't use two NPEs within the same line. Otherwise, you won't know which one thrown the exception.
- Which line(s) won't compile?
#1 fun isFoo1(n: Name) = n.value == "foo"
#2 fun isFoo2(n: Name?) = n.value == "foo"
#3 fun isFoo3(n: Name?) = n != null && n.value == "foo"
#4 fun isFoo4(n: Name?) = n?.value == "foo"
fun main(args: Array<String>) {
#5 isFoo1(null)
#6 isFoo2(null)
#7 isFoo3(null)
#8 isFoo4(null)
}
- Answer: #2 and #5
- What will be printed?
val x: Int? = 1
val y: Int = 2
val sum = x ?: 0 + y
println(sum)
- Answer:
1
Nullable types under the hood
@Nullable, @NotNullannotations- How many objects are created to store a value of a nullable String?
val s: String?- Answer: Only one object to store a String value
List of nullable elements vs nullable List
List<Int?>- Every element might be either
nullorInt
- Every element might be either
List<Int>?- The whole list might be either
nullorInt
- The whole list might be either
#1 list1.size
#2 list2.size
#3 val i: Int =
#4 list1.get(0)
#5 val j: Int =
#6 list2.get(0)
}
- Line 2:
list2?.size - Line 3:
val i: Int? - Line 5:
val j: Int? = - Line 6:
list2?.get(0)
Safe Casts
- Type cast:
as
if (any is String){
val s = any as String
s.toUpperCase()
}
- Could be shorten to:
if (any is String){
any.toUpperCase()
}
- Could use
as?as well:
(any as ?String)?.toUpperCase();
foo as? Typecan be either:foo as Typenull
Functional Programming
Lambdas
- Example:
button.addActionListener { println("Hi")}
- Allows collections to be written in a functional style
employees.filter { it.city == City.PRAGUE }
.map{ it.age }
.average()
- Lambda Syntax
{ x: Int, y: Int -> x + y}
- Passing Lambda as an argument
list.any({i: Int -> i > 0})
- When lambda is the last argument, it can be outside the parentheses.
list.any() {i: Int -> i > 0}
- If parentheses are empty, it can be omitted.
list.any {i: Int -> i > 0}
itdenotes the argument if it's the only one
list.any {it > 0}
- Multi-line lambda is allowed where the last expression is the result
list.any {
println("processing $it")
it > 0
}
- Destructuring declarations syntax instead
map.mapValues { entry -> "${entry.key} -> ${entry.value}!"}
- Another way:
map.mapValues { (key, value) -> "$key -> $value!" }
- If one parameter is not used, you can omit that parameter name:
map.mapValues { (_, value) -> "$value!" }
Common Operations on collections
Filter
.filter { it % 2 == 0}
- input:
1, 2, 3, 4 ... - output:
2, 4, ...
Map
.map { it * it }
- input:
1, 2, 3, 4 - output:
1, 4, 9, 16 - output size is the same as input size.
any
- if at least one element satisfies the condition, then return true.
all
- all elements satisfies the condition, then return true.
none
- none of the elements satisfies the condition, then return true.
find
- finds the element and returns the element if it satisfies the condition
firstOrNull
- returns the first element found if it satisfies the condition, or null if otherwise.
count
- returns a counter of the number of elements that satisfies the condition.
partition
- returns the list of elements that satisfy the condition and another list of the ones that failed the condition.
groupby
- returns collections sorted by the condition to group by
associateBy
- returns one element of the mapped value from the condition.
warning
- duplicates are removed, so make sure the key is unique in your condition to prevent data loss.
associate
- use associate to build a map based on a list.
- first part is the key and second part is the value
.associate { `a` + it to 10 * it }
- input:
1, 2, 3, 4 ... - output:
a -> 10, b -> 20, c -> 30, d -> 40
zip
- returns a combination of two collections
- input:
1, 2, 3, 4, ... - second input:
a, b, c, d, ... - output:
[1, a], [2, b], [3, c], [4, d], ...
zipWithNext
- returns a collection containing pairs of first and next element
- input:
1, 2, 3, 4, ... - output:
[1, 2], [2, 3], [3, 4], ...
flatten
- combines a list of lists into one collection
- input:
[a, b, c], [d, e], [f, g, h, i] - output:
[a, b, c, d, e, f, g, h, i]
Function Types
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
// is equivalent to
val sum = { x: Int, y: Int -> x + y }
val result: Int = sum(1, 2)
- The type of the function sum is:
(Int, Int) -> Int - The type of result is
Int
Passing a variable of function type as an argument
val isEven = { i: Int -> i % 2 == 0 }
val list = listOf(1, 2, 3, 4)
list.any(isEven)
list.filter(isEven)
Calling lambda directly
- Preferred way:
run { println("hey!") }
- Another way but ugly syntax:
{ println("hey!") }()
Function types and nullability
() -> Int?
//vs.
(() -> Int)?
- Which lines won't compile?
#1 val f1: () -> Int? = null
#2 val f2: () -> Int? = { null }
#3 val f3: (() -> Int)? = null
#4 val f4: (() -> Int)? = { null }
-
Answer: lines 1, 4
-
() -> Int?means the return type is nullable, not the whole type itself -
(() -> Int)?means the whole type is nullable (the variable is nullable) -
In line 2,
{null}means a lambda without arguments that always returns null -
In line 3,
f3is either a lambda returningIntornullreference
Working with a nullable function type
val f: (() -> Int)? = null
if( f != null){
f()
}
f?.invoke()
Member references
class Person(val name: String, val age: Int)
people.maxBy { it.age }
people.maxBy(Person::age)
You can store lambda in a variable
val isEven: (Int) -> Boolean = { i: int -> i % 2 == 0 }
- You cannot store a function in a variable such as
val predicate = isEven
Use function reference instead
fun isEven(i: Int): Boolean = i % 2 == 0
val predicate = ::isEven
// same as
val predicate = { i: int -> isEven(i) }
Member references
val action = { person: Person, message: String ->
sendEmail(person, message)
}
val action = ::sendEmail
Passing function reference as an argument
fun isEven(i: Int): Boolean = i % 2 == 0
val list = listOf(1, 2, 3, 4)
list.any(::isEven)
list.filter(::isEven)
Bound & non-bound references
class Person(val name: String, val age: Int){
fun isOlder(ageLimit: Int) = age > ageLimit
}
val agePredicate = Person::isOlder
val alice = Person("Alice", 29)
agePredicate(alice, 21)
Non-Bound Reference: the corresponding lambda
- Called on any object of a given type
class Person(val name: String, val age: Int){
fun isOlder(ageLimit: Int) = age > ageLimit
}
val agePredicate = (Person, Int) -> Boolean = { person, ageLimit -> person.isOlder(ageLimit) }
val alice = Person("Alice", 29)
agePredicate(alice, 21)
Bound Reference
- Stores the object on which the member can delay to be called
class Person(val name: String, val age: Int){
fun isOlder(ageLimit: Int) = age > ageLimit
}
val alice = Person("Alice", 29)
val agePredicate: (Int) -> Boolean = alice::isOlder
agePredicate(21)
Bound to this reference
class Person(val name: String, val age: Int){
fun isOlder(ageLimit: Int) = age > ageLimit
fun getAgePredicate() = this::isOlder
}
Question
- What is the type of
::isOlderhere?
class Person(val name: String, val age: Int) {
fun isOlder(ageLimit: Int) = age > ageLimit
fun getAgePredicate() = ::isOlder
}
- Answer:
(Int) -> Boolean - Is
::isEvena bound reference?
fun isEven(i: Int): Boolean = i % 2 == 0
val list = listOf(1, 2, 3, 4)
list.any(::isEven)
list.filter(::isEven)
- Answer: no
return from Lambda
- What will be printed?
fun duplicateNonZero(list: List<Int>): List<Int> {
return list.flatMap l@ {
if (it == 0) return@l listOf()
listOf(it, it)
}
}
println(duplicateNonZero(listOf(3, 0, 5)))
- Answer:
[]