Kotlin for Java Developers
Kotlin is a general purpose language which supports both functional programming and object-oriented programming paradigms. It's an open-source project developed mainly by JetBrains with the help of the community. Like Java, Kotlin is a statically typed language. However, in Kotlin you can omit the types, and it often looks as concise as some other dynamically-typed languages. Kotlin is safe, it's even safer than Java in terms that, Kotlin compiler can help to prevent even more possible types of errors. One of the main characteristics of Kotlin is its good interoperability with Java. It's hard now to call Java a modern language. However, it has so huge ecosystem, that it would be really difficult to recreate it from scratch if you use a new language
Cool features
- There is no
newkeyword. - There is no
statickeyword. - The keyword
objectis a singleton in Kotlin:object K {fun foo() {}}; K.foo() - Type is inferd.
- If is an expression, you can assign it to a variable.
- There is no ternary operations.
- Instead of throwing an exception, we can just call
fail() - Smart casting
Having a class Pet that can be Dog or cat, we can do
pet.meow()orpet.woof(), kotlin is going to do the cast for us
- Formatting multiline strings
val q = """To code, or not to?""" - There are no primitives
- Creating regex expressions
val regex = """\d{2}\.d{2}\.\d{4}"""
regex.matches("15.02.2016")
- Converters like:
"a".toIntOrNull(),"1".toIn(),"1".toDouble(), - equals infix
fun getAnswer() = 42
getAnswer() eq 42 // true
getAnswer() eq 43 // false
- Infix
toto create Pairs, likeval pair: Pair<Char, Double> = 'a' to 1.0 - Mutable Extensions properties
var StringBuilder.lastChar:Char
get() = get(length -1)
set(value:Char) {
this.setCharAt(length-1, value)
}
val sb = StringBuilder("Kotlin")
sb.lastChar = '!'
println(sb) // Kotlin!
- You need to define a class as
opensometimes because the default of a class isfinal - there is no package-private, instead its:
internal - We can use
sealedmodifier to restrict class hierarchy
sealed class Expr
class Num(val value: Int): Expr()
class Sum(val left:Expr, val Right: Expr): Expr()
fun eval(e:Expr):Int = when(e) {
is Num -> e.value
is Sum -> eval(e.left) + eval(e.right)
}
// withouth the sealed modifier, it would need an else for it doesn't know where all the class inheriting from Expr are.
Data initialization, var, val and const
- val: "value" its read-only
- var: "variable" its mutable
- const: For primitive types and Strings
about const: It's value is known at compile time, the compiler replaces the constant name everywhere in the code with this value, then it's called a compile-time constant.
We can initialize two variables using a Pair
fun updateWeather(degrees: Int) {
val(description, color) = when {
degrees < 10 -> Pair("cold", BLUE)
degrees < 25 -> "mild" to ORANGE // same as pair
else -> "hot" to RED
}
}
Lists
Kotlin has mutable an imutable lists, such as:
val mutableList = mutableListOf("java")
mutableList.add("Kotlin")
val readOnlyList = listOf("Java")
readOnlyList.add("Kotlin") // <- that doesnt work
Data class
Data modifier has equals, hashCode, to-string, and copy methods.
data class person(val name)
it creates:
- Equals
- hashCode
- toString
we can copy the instance and give the new attributes
data class Contact(val name:String, val address: String)
contact.copy(address="mew address")
Functions
return void here is of type Unit
We can define 3 types of fuctions
- Top level functions
fun topLevel() = 1
- Member function:
class A {
fun member() = 2
}
- Local functions:
fun other() {
fun local() = 3
}
fun max(a:Int, b:Int):Int {
return if (a>b) a else b
}
// that becomes
fun max(a:Int, b:Int) = if (a>b) a else b
Library functions (run, let, takeIf...)
functions that look like built-in language construct
- run: Runs the block of code (lambda) and returns the last expession
val foo = run{
println("calculating")
"foo"
}
- let funtion: when you want to pass something as an argument only if it's non-null.
fun getEmail() : Email?
val email = getEmail()
if(email != null) sendEmailTo(email)
// or better
email?.let {e -> sendEmailTo(e)}
or to functions
fun analizeUserSession(session:Session) =
(session.user as? FacebookUser)?.let {
println(it.accountId)
}
- takeIf: returns the receiver if it satisfies the given predicate or returns null
issue.takeIf { it.status == FIXED }
person.patronymicName.takeIf(String::isNotEmpty)
val number = 42
number.takeIf { it > 10 } // 42
// if the predicate don't fullfill, it returns null
issues.filter { it.status == OPEN }.takeIf(List<Issue>::isNotEmpty)?.let { println("some are open")}
// filtering
people.mapNotNull { person -> person.takeIf { it.isPublicProfile }?.name }
- takeUnless: is the opposite, takeUnless which returns the receiver if the predicate is not satisfied.
- repeat It simply repeats an action for a given number of times.
repeat(10) { println("Welcome!")}
- use: The use function contains all the aforementioned logic of closing the resources in the correct way.
- withLock lock the file
- with: You can call all the members and extensions without explicit receiver specification
val sb = StringBuilder()
with(sb) {
appendln("alphabet: ")
for(c in 'a'..'z'){
append(c)
}
toString()
}
// or
with(window) {
width = 300
height = 200
isVisible = true
}
- run, but as an extension makes it possible to use it with a null-able receiver.
val windowOrNull = windowById["main"]
windowOrNull?.run {
width = 300
height = 200
isVisible = true
}
- apply: Is the same as with, but Apply is different because it returns the receiver as a result. Its helpfull in a chain.
val mainWindow = windowById["main"]?.apply {
width = 300
height = 200
isVisible = true
} ?: return
- also: Also is similar to apply, it returns the receiver as well. However, there is the difference that it takes a regular lambda not lambda with a receiver as an argument.
windowById["main"]?.apply {
width = 300
height = 200
isVisible = true
}?.also { window -> showWindow(window) }

Default values and positional paramters
we can specify default values for parameters, such as:
fun display(character: Char = "*", size: Int = 10) {
repeat(size) {
print(character)
}
}
display('#', 5)
display('#')
display()
// or direct the parameter
display(size=5)
using the name position we can even change the order.
to call a kotlin function with default values, we need to suply an annotation to the function, like:
@JvmOverloads
fun sun(a: Int=0, b: Int=0, c:Int=0)=a+b+c
// java
sum(1);
Conditionals: If & when
if in kotlin is an expression, there is no ternary operations!
when as a switch
You no longer needs to use break;
// java
switch(color) {
case BLUE:
System.out.printLn("cold");
break;
default:
System.out.printLn("hot");
}
//kotlin
when(color) {
BLUE -> println("cold")
else -> println("hot")
}
Multiples conditions
when can or not have parameters, so if it doesn't have, i''l check its cases for a boolean expression and evaluate them, so when can be a group of ifs or a switch statement.
to check several conditions, you can specify like:
fun respondToInput(input:String) = when(input) {
"y", "yes" -> "I'm glad you agree"
"n", "no" -> "Sorry to hear that"
else -> "I don't understand you"
}
or even comparing sets, the argument is checked for equality with the banch conditions
when(setOf(c1, c2)){
setOf(RED,YELLOW) -> ORANGE
}
Checking a type
we can use is to check a class type, like if pet is a super class of Cat and Dog we can check like:
when(pet) {
is Cat -> pet.meow()
is Dog -> pet.woof()
}
doing the same as the java instanceof
Loops
The are basicly the same, but:
For loop looks a bit different. First, a different keyword is used to express iteration over something in. Second, where meets the element type.
var list = listOf("a"."b","c")
for(s in list) {
print(s)
}
Iterate over a map
val map = mapOf(1 to "one", 2 to "two", 3 to "three")
for((key, value) in map) {
println("$key = $value")
}
Iterating over an index
val list = listOf("a", "b", "c")
for((index, element) in list.withIndex()) {
println("$index: $element")
}
Iterating over Ranges
instead a forin loop we can use a range
for(i in 1..9){
print(i)
}
// -> 123456789
// or until, excluding 9
for(i in 1 until 9){
print(i)
}
// -> 12345678
or more complex cases like with a step
for(i in 0 downTo 1 step 2){
print(i)
}
// -> 97531
Using in over Ranges
there are 2 ways to use it
- Iteration:
for(i in 'a'..'z') - Check for belonging:
c in 'a'..'z'
EG, to check if a string is a letter:
fun isLetter(c:Char) = c in 'a'..'z' || c in 'A'..'Z'
isLetter('q')// true
isLetter(*) // false
and it can be used in a when case as well.
They can even be stored in a variable such as:
val intRange:IntRange = 1..9
Using in into collections
in can be used to check if an element is part of a list, in Java that would be like:
if(list.contains(element)) {}
but in Kotlin we can do as
if(element in list){...}
Comparables
We can compare anithing that has a compareTo, bein a code like the following
class MyDate:Comparable<MyDate>
if(myDate.compareTo(startDate) >= 0 && myDate.compareTo(endDate) <= 0) {...}
can be rewrite in kotlin as
class MyDate:Comparable<MyDate>
if(myDate >= startDate && myDate <= endDate) {...}
// or even
if(myDate in startDate..endDate){...}
Exceptions
They are very similar to Java with one important difference. Kotlin doesn't differentiate checked and unchecked exceptions. In Kotlin, you may or may not handle any exception, and your function does not need to specify which exception it can throw.
val percentage =
if (number in 0..100)
number
else
throw IllegalArgumentException("wron value: $value")
being then try & catch an expression, being able to be assigned to varialbes, like followingval number = try {Integer.parseInt(string)} catch(e:NumberFormatException) {null}
Extension Functions
Extension function extends the class. It is defined outside of the class but can be called as a regular member to this class.
The general usecase for them is when you have a library and wants to extends its api, or something YOU DON'T HAVE POWER OVER.
fun String.lastChar() = this.get(this.length -1) //this can be ommited
fun String.lastChar() = get(length -1)
// so we can use as
"me".lastChar() // -> e
They are not freely accessible, you need to import them.
Infix
there is a second way to extend a funtion with infix
infix fun <T> T.eq(other: T) {
if (this == other) println("OK")
else println("Error: $this != $other")
}
evaluateGuess("BCDF", "ACEB") eq result
Nullable
The introduction of null to earlier programming languages, is known to be called "billion dollar mistake". Because nulls are ubiquitous, NullPointerExceptions, attempts to dereference null pointers, are very common as an issue
to define:
val s: String? // nullable
val s: String // non nullable
Elvis for null values: If we want to assing the lenght of a nullable variable to a non nullable, we can use an elvis to return a default one.
val lenght: Int = s?.length ?: 0
to throw an esception from accessing an value of a null object, just use: person.company!!.address!!.country
but prefer explicit checks
Safe cast
a safe way to cast an expression to a type. In Java, you use a common pattern to do something with the variable only if it is of specific type. First, you use instanceof to check whether the variable is of the required type, then you cast it to this type of restoring the result in a new variable
Instead of using instanceOf or typeCast we can simply use is and as, but in the example, the as is not necessary
if(any is String) {
val s = any as String
s.toUpperCase()
}
Lambdas
Lambda is an anonymous function that can be used as an expression
button.addActionListner{ println("H1") }
we can store a lambda into a function
val sum = { x:Int, y:Int -> x+y }But we can't store a function in a variable
Sequences
filters and maps returns new values of a list, to avoid it we can use sequences, that are the same as stream
Stream or sequences: If we use operations on collections, they eagerly return the result, while the operations on sequences postponed the actual computation, and therefore avoid creating intermediate collections.
val list = listOf(1,2,-3)
val maxOddSquare = list
.asSequence() // same as .stream()
.map{ it * it}
.filter {it % 2 == 1}
.max()

Generating a sequence
If you need to build a sequence from scratch, for instance, you define a way to receive each new element from the network, you can use the generateSequence function. Here, it generates a sequence of random numbers
val seq = generateSequence {
Random.nextInt(5).takeIf { it > 0}
}
println(seq.toList())// [4,4,3,2,3,2]
The generateSequence function can be useful when you need to read input and stop when a specific string is typed.
val input = generateSequence {
readLine().takeIf { it != "exit" }
}
println(input.toList())
/*
>> a
>> b
>> exit
[a,b]
*/
Generating an infinite sequence
In this case, we generate an infinite sequence of integer numbers. Note that because this sequence is computed lazily, it might be infinite. Nothing happens until you explicitly ask for it.
val numbers = generateSequence(0) {it + 1}
number.take(5).toList() // [1,2,3,4,5]
Yield
Yield allows you to yield elements in a custom way. The generic sequence functions that we saw before, are white constraint
val numbers = sequence {
var x = 0
while(true) {
yield(x++)
}
}
numbers.take(5).toList() //[0,1,2,3,4,5]
lambda with receiver (DSL)
It might be considered as a union of two ideas of two other features: extension functions and lambdas.
// lambda function
val isEven: (Int) -> Boolean = { it%2 == 0 }
// lambda with receiver
val isOdd: Int.() -> Boolean = { this % 2 == 1 }
// calling
isEven(0)
1.isOdd()

Return from lambda
Why in Kotlin return returns from the outer function? Consider the following example when you have a regular for loop and inside this for loop you use return. Return simply returns from the function. If you convert the for loop to foreach, then you can't expect that return continue to behave in the same way.
fun containsZero(list: List<Int>): Boolean {
list.forEach {
if (it == 0) return true //this return the FUNCTION not the lambda!
}
return false
}
to return from the lambda we have to use the labels return syntax:
list.flatMap {
if(it === 0) return@flatMap listOf<Int>()
listOf(it, it)
}
By default, you can use the name of the function that calls this lambda as a label. but we customize it
list.flatMap l@ {
if(it === 0) return@l listOf<Int>()
listOf(it, it)
}
if this is a problem, we can use a function inside of a function like:
fun duplicateNonZeriLocalFunctcion(list: List<Int>):List<Int> {
fun duplicateNonZeroElement(e: Int) :List<Int> {
if(e==0) return listOf()
return listOf(e,e)
}
return list.flatMap(::duplicateNonZeroElement)
}
println(duplicateNonZeriLocalFunctcion(listOf(3,0,5)))
// [3,3,5,5]
Or using an anonymous funcion
fun duplicateNonZeriLocalFunctcion(list: List<Int>):List<Int> {
return list.flatMap(fun (e) :List<Int> {
if(e==0) return listOf()
return listOf(e,e)
})
}
println(duplicateNonZeriLocalFunctcion(listOf(3,0,5)))
// [3,3,5,5]
The return from the lambda doesnt stop the loop, its like a
continue
Destructing declarations
The same syntax was used to iterate over a map in a for loop, by assigning a key and a value to separate variables
EG:
map.mapValues{ entry-> "${entry.key} = ${entry.value}!" }
can be declared as:
map.mapValues{ (key, value) -> "${key} = ${value}!" }
// or
map.mapValues{ (_, value) -> "${value}!" }
Iterating over a list with index
Note that iterating over list with this index also works using destructuring declarations. With index, extension function returns a list of index to value elements.
for((index, element) in list.withIndex()) {
println("$index $element")
}
Destructing declarations & data classes
You can use any data class as the right-hand side for the destructuring declarations, since the necessary component functions are automatically generated for it
data class Contact (
val name: String,
val email: String,
val phoneNumber: String
)
val (name, _, phoneNumber) = contact
Some different lambdas
partition: its the same as filter, but returns 2 lists: One that satisfies the predicate and another that don't
val heroes = listOf(Hero("The Captain", 60, MALE),Hero("Lady Lauren", 29, FEMALE))
val (youngest, oldest) = heroes.partition { it.age < 30 }
oldest.size
associateBy: the same as groupBy but removes the duplications, its a unique key, Eg:
val heroes = listOf(Hero("The Captain", 60, MALE),Hero("Frenchy", 42, MALE),Hero("Sir Stephen", 37, MALE))
val mapByName: Map<String, Hero> = heroes.associateBy { it.name }
mapByName["Frenchy"]?.age
associate: Creates a pair over a list:listOf(1,2,3).associate{'a' + it to 10}that returns: a->10, b->20, c->30
val heroes = listOf(Hero("The Captain", 60, MALE),Hero("Sir Stephen", 37, MALE))
val mapByName = heroes.associate { it.name to it.age }
mapByName.getOrElse("unknown") { 0 } //0
zip: returns a list of pair of 2 lists:
val a = listOf(1,2,3);
val b = listOf('a', 'b', 'c')
a.zip(b)
// 1->a, 2->b and 3->c
zipWithNextis the same as the zip, but it zips one list:listOf(1,2,3,4)would be: 1->2 3->4flattencombine lists of lists into one listflatMapcan convert a string into a list of maps, and withflattenwe can merge then into 1 listfilterValues: same as filter but for Pairs, ignoring the keytaketakes only the fist x number of elements, like:incomeByDriver.take(topTwentyDrivers).sum()filterNotNullto remove null elements of a listmaxBytakes the maximum using the predicate, like:
val heroes = listOf(Hero("The Captain", 60, MALE), Hero("Frenchy", 42, MALE))
heroes.maxBy { it.age }?.name
Another interesting detail about maxBy, it can return more than one list
val heroes = listOf(Hero("The Kid", 9, null),Hero("Lady Lauren", 29, FEMALE),Hero("First Mate", 29, MALE),Hero("Sir Stephen", 37, MALE))
val mapByAge: Map<Int, List<Hero>> = heroes.groupBy { it.age }
val (age, group) = mapByAge.maxBy { (_, group) -> group.size }!!
println(age) // 29

Function types
parameter types are written inside the parentheses and then an arrow, then the return type. In this case, is the type that takes two integer parameters and returns an integer as a result.
as a lambda: val sum = { x:Int, y:Int -> x+y }
we can give it a type by:
val sum: (Int, Int) -> Int = {x,y -> x+y}
Calling a stored function
You can call a variable a function type as a regular function, providing all the unnecessary arguments.
val isEven: (Int) -> Boolean = {x:Int -> i%2 == 0}
val result:Boolean = isEven(42) // true
or as an lambda:
listOf(1,2,3).any(isEven) // true
list.filter(isEven) // [2,4]
We can run labdas by: {println("hey")}() or better: run {println("hey")}
SAM interfaces (Single abstract method invocations)
In Java, you can pass a Lambda instead of a SAM interface, an interface with only one single abstract method. In Kotlin, you can use the function types directly, but when you mix Kotlin and Java, you'd want to do the same as in Java. Whenever you call a method that takes SAM interface as a parameter, you can pass a Lambda as an argument to this method instead.
void postponeComputation(int delay, Runnable computation)
// SAM interface
public interface Runnable {
public abstract void run();
}
Whenever you call a method that takes SAM interface as a parameter, you can pass a Lambda as an argument to this method instead.postponeComputation(1000) {println(42)}
or if you want to use the constructor:
val runnable = Runnable {printl(42)}
postponeComputation(1000, runnable)
Nullable function type
The first one means that, return type is nullable. That's a possible mistake when you simply add a question mark after the function type. It means that you make the return type of this function type nullable, not the whole type itself. If you want to make the whole type nullable, you need to use the parentheses. In the example, you saw null in curly braces. It's a Lambda without arguments that always returns null.
() ->Int? // return type is null
(() -> Int)? // the variable is null
calling a null function
val f: (()-> Int)? = null
if(f != null) {
f()
}
// or safe call
f?.invoke()
Member references
Like Java, Kotlin has member references, which can replace simple Lambdas that only call a member function or return a member property.
class Person(val name: String, val age: Int)
people.maxBy {it.age}
// or
people.maxBy(Person::age)
// Person is the class
// age is the member
if the reproached function takes several arguments, you have to repeat all the parameter names as Lambda parameters, and then explicitly pass them through, that makes this syntax robust. Member references allow you to hide all the parameters, because the compiler infers the types for you.
val action = {person: Person, message: String -> sendEmail(person, message)}
// same as
val action = ::sendMail
Function references
If you try to assign a function to a variable, you'll get a compiler error. To fix this issue, use the function reference syntax. Function references allow you to store a reference to any defined function in a variable to be able to store it and qualitative it. Keep in mind that this syntax is just another way to call a function inside the Lambda, underlying implementation are the same.
fun isEven(i:Int):Boolean = i%2==0
val predicate = ::isEven
// the same as
val predicate = { i:Int -> isEven(i)}
Passing function references as an argument
whenever your Lambda tends to grow too large and to become too complicated, it makes sense to extract Lambda code into a separate function, then you use a reference to this function instead of a huge Lambda
fun isEven(i:Int): Boolean = i%2==0
val list = listOf(1,2,3,4)
list.any(::isEven) // true
list.filter(::isEven) [2,4]
Static and non static access:
class Person(val name: String, val age: Int) {
fun isOlder(ageLimit:Int) = age > ageLimit
}
val agePredicate = Person::isOlder // non-bound or static
val alice = Person("alice", 29)
agePredicate(alice, 29) // true
// bounded or non-static
val alice = Person("Alice", 29)
val agePredicate = alice::isOlder
agePredicate(21) // true
// and bound to this references
class Person(val name: String, val age: Int) {
fun isOlder(ageLimit:Int) = age > ageLimit
fun getAgePredicate() = this::isOlder //we can ommit the this (::isOlder)
}
Properties
Unlike Java where a property is not a language construct. Kotlin supports it as a separate language feature. In a most common scenario for a trivial case, property syntaxes are really concise. But you can customize it if needed
This is an customization of these field
class Retangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
val rectangle = Rectangle(2,3)
println(rectangle.isSquare) // false
Setting the field
In Kotlin, you don't work with fields directly, you work with properties. However, if you need, you can access a field inside its property accessors. It's not visible for other methods of the class.
var state = false
set(value) {
print("setting state to $field")
field = value
}
Setter visibility
You can make a setter private. Then the getter is accessible everywhere. And therefore the property is accessible everywhere. But it's allowed to modify it only inside the same class. Note that we change only the visibility of the setter, but use the default implementation.
var sample:Boolean = false
private set
Property in interfaces
You can define a property in an interface. Why not? Under the hood, it's just a getter. Then you can redefine this getter in subclasses in the way you want.
interface User {
val nickname: String
}
class FacebookUser(val accountId: Int): User {
override val nickname = getFacebookName(accountId)
}
Extension properties
In Kotlin, you can define extension properties. The syntax is very similar to the one of defining extension functions.
val String.lasIndex:Int
get() = this.length
val String.indices: IntRange
get() = 0..lasIndex
// "abc".lasIndex
Lazy property
Lazy Property is a property which values are computed only on the first success.
val lazyValue: String by lazy {
println("computed")
"Hello"
}
Lateinit: Late initialization
Sometimes, we want to initialize the property not in the constructor, but in a specially designated for that purpose method. Here, we initialized the myData property in onCreate method, but not in the constructor
class KotlinActivity: Activity {
lateinit var MyData: MyData
fun create() {
myData = intent.getParcelableExtra("MY_DATA")
}
}
OOP in Kotlin
๐คจ Overall, Kotlin doesn't introduce anything conceptually new here, the experience is very similar.
| key | value |
|---|---|
| final | used by default, cannot be overridden |
| open | can be overridden |
| abstract | must be overridden, can't have an implementation |
| override | mandatory, overrides a member in a superclass or interface |
When you put val or var before the parameter, that automatically creates a property. Without val or var, it's on the constructor parameter.
class Person(name: String) {
val name: String
init {
this.name = name
}
}
// or
class Person(val name:String)
๐ You can hame multiple constructors, but each secondary constructor must call another secondary or primary constructor
class Rectangle(val height: Int, val width: Int) {
constructor(side: Int) : this(side, side) {
}
}
Enums class
The difference with Java is that now enum is not a separate instance, but a modifier before the class keyword.
import Color.*
enum class Color {
BLUE, ORANGE, RED
}
fun getDescription(color: Color) {
when(color) {
RED->"cold"
ORANGE -> "mild"
RED -> "hot"
}
}
inner modifier
To access this reference of the outer class, you use labeled this. As the label name, you specify the name of the outer class.
class A {
class B
inner class C {
...this@A...
}
}
Class delegation
In essence, class delegation allows you to delegate the task of generating trivial matters to the compiler. Note that the controller class implements both interfaces, so we can call interface members on it
class Controller(
repository: Repository,
logger: Logger
): Repository by repository, Logger by logger
Objects
The keyword object is a singleton in Kotlin: object K {fun foo() {}}; K.foo()
Objects Expressions replace java's anonymous classes:
window.addMouseListner (
object: MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {}
override fun mouseEntered(e: MouseEvent) {}
}
)
Companion object
It's a nested object inside a class but a special one. The one which members can be accessed by the class name. In Kotlin, there are no static methods like in Java, and companion objects might be a replacement for that.
class A {
companion object {
fun foo() = 1
}
}
fun main(args: Array<String>){
A.foo()
}
Companion object can implment an interface
It would be nice if a static method could override a member of an interface. But for static, that's not possible in java. Now, it's possible for companion object.
interface Factory<T> {
fun create():T
}
class A{
private constructor()
companion object : Factory<A> {
override fun create(): A {
return A()
}
}
}
companion object can be a receiver of an extension function
Another thing which you can do with companion object, is you can define extension straight. To distinguish an extension to a class from an extension to a companion object, you use the companion suffix.
class Person(val firstName: String, val lastName: String) {
companion object{...}
}
fun Person.Companion.fromJson(json: String): Person {
...
}
val p = Person.fromJSON(json)
Generics
You define a type parameter for a function and try to use this type parameter in the function declaration, into the function body. You can call a generic function on different types.
fun <T> List<T>.filter(predicate: (T)-> Boolean): List<T>
Non-nullable upper bound
If you want to restrict the generic argument so that it was not nullable, you can specify a non-null upper bound. You add an upper bound right after the type parameter declaration using the same column which replaces the extents cube root in Kotlin
fun <T: Any> foo(list: List<T>) {
for(element in list) {}
}
foo(listOf(1, null)) // exception
Multiple constrains for a type parameter
Here, you can pass any type that extends to different interfaces, terrace sequence and aboundable. Stringbuilder implements both terrace sequence and dependable, so it's a valid argument for these function.
fun <T> ensureTrailingPeriod(seq: T)
where T: CharSequence, T: Appendable {
if(!seq.endsWith('.')) {
seq.append('.')
}
}
val helloWorld = StringBuilder("Hello, World")
ensureTrailingPeriod(helloWorld)
println(helloWorld) // "Hello, World.
Conventions
accessing map elements using square brackets, actually work via conventions, and the same syntax might be supported for your custom process.
Arithmetic operations
In Kotlin, you can use this syntax of arithmetic operations not only primitives or strings, but for custom types as well. You define a function, a member or an extension with a specific name and mark it as operator.
// a + b -> a.plus(b)
operator fun Poin.plus(other: Point): Point {
return Point(x + other.x, y + other.y)
}
It's not that you can use any name. You can see the correspondence between the syntax and the operator name that allows you to use this syntax.
| expression | function name |
|---|---|
| a + b | plus |
| a - b | minus |
| a * b | times |
| a / b | div |
| a % b | mod |
And the unary operators:
| expression | function name |
|---|---|
| +a | unaryPlus |
| -a | unaryMinus |
| !a | not |
| ++a, a++ | inc |
| --a, a-- | dec |
Unary operator is a function without arguments which we can call as an operator on this specified receiver.
Prefer val to var
assigning the value like the following will create a new list, like: list = list + 4
var list = listOf(1,2,3)
list += 4
Comparisons
Under the hood, the comparison operators are all compiled using the comparative method comparisons.
| symbol | translate to |
|---|---|
| a > b | a.compareTo(b) > 0 |
| a < b | a.compareTo(b) < 0 |
| a >= b | a.compareTo(b) >= 0 |
| a <= b | a.compareTo(b) <= 0 |
Accessing elements by index: []
Under the hood, the get and set methods are called
map[key]
mutableMap[key] = newValue
// is translate to
x[a,b] -> x.get(a, b)
x[a,b] = c -> x.set(a,b,c)
You can define get and set operator functions as members or extensions for your own custom classes.
class Board {}
board[1,2] = 'x'
board[1,2] // x
operator fun Board.get(x: Int, y: Int): Char {...}
operator fun Board.set(x: Int, y: Int) {...}
The iterator convention
For loop iteration also goes through a convention. In Kotlin, you can iterate over a string.
operator fun CharSequence.iterator() : CharIterator
for(c in "abc"){}
Exams and tests
Every week we have an exame, these are the projects solving them:
- Week 2 Exam: Mastermind
- Week 3 Exam: Nice String and Taxi Park
- Week 4 Exam: Rationals and Board
- Week 5 Exam: Games
Week 2 exame: The Secret game
data class Evaluation(val rightPosition: Int, val wrongPosition: Int)
fun evaluateGuess(secret: String, guess: String): Evaluation {
val totalSecret: MutableList<Pair<Int, Char>> = secret.subtract(guess)
val totalGuess: MutableList<Pair<Int, Char>> = guess.subtract(secret)
return Evaluation(
rightPosition = calculateRightGuess(secret, totalSecret),
wrongPosition = calculateWrongPositions(totalSecret, totalGuess)
)
}
private fun calculateRightGuess(secret: String, totalSecret: MutableList<Pair<Int, Char>>) = secret.length - totalSecret.size
private fun calculateWrongPositions(totalSecret: MutableList<Pair<Int, Char>>, totalGuess: MutableList<Pair<Int, Char>>): Int {
var wrong = 0
totalSecret.forEach {
val found = totalGuess.findChar(it.second)
if (found != null) {
wrong++
totalGuess.remove(found)
}
}
return wrong
}
private fun String.asIndexMap() = this.toCharArray().mapIndexed { index, c -> index to c }.toMutableList()
private fun String.subtract(from: String) = this.asIndexMap().minus(from.asIndexMap()).toMutableList()
private fun MutableList<Pair<Int, Char>>.findChar(needle: Char) = this.find { it.second == needle }
Kotlin Playground: Interchangeable predicates
Functions 'all', 'none' and 'any' are interchangeable, you can use one or the other to implement the same functionality. Implement the functions 'allNonZero' and 'containsZero' using all three predicates in turn. 'allNonZero' checks that all the elements in the list are non-zero; 'containsZero' checks that the list contains zero element. Add the negation before the whole call (right before 'any', 'all' or 'none') when necessary, not only inside the predicate.
fun List<Int>.allNonZero() = all { it != 0 }
fun List<Int>.allNonZero1() = none { it == 0 }
fun List<Int>.allNonZero2() = !any { it == 0 }
fun List<Int>.containsZero() = any { it == 0 }
fun List<Int>.containsZero1() = !all { it != 0 }
fun List<Int>.containsZero2() = !none { it == 0 }
fun main(args: Array<String>) {
val list1 = listOf(1, 2, 3)
list1.allNonZero() eq true
list1.allNonZero1() eq true
list1.allNonZero2() eq true
list1.containsZero() eq false
list1.containsZero1() eq false
list1.containsZero2() eq false
val list2 = listOf(0, 1, 2)
list2.allNonZero() eq false
list2.allNonZero1() eq false
list2.allNonZero2() eq false
list2.containsZero() eq true
list2.containsZero1() eq true
list2.containsZero2() eq true
}
infix fun <T> T.eq(other: T) {
if (this == other) println("OK")
else println("Error: $this != $other")
}
Assignment: Nice Strings
Nice String
A string is nice if at least two of the following conditions are satisfied:
- It doesn't contain substrings bu, ba or be;
- It contains at least three vowels (vowels are a, e, i, o and u);
- It contains a double letter (at least two similar letters following one another), like b in "abba".
Your task is to check whether a given string is nice. Strings for this task will consist of lowercase letters only. Note that for the purpose of this task, you don't need to consider 'y' as a vowel.
Note that any two conditions might be satisfied to make a string nice. For instance, "aei" satisfies only the conditions #1 and #2, and ```"nn"` satisfies the conditions #1 and #3, which means both strings are nice.
Example 1
- "bac" isn't nice. No conditions are satisfied: it contains a ba substring, contains only one vowel and no doubles.
Example 2
- "aza" isn't nice. Only the first condition is satisfied, but the string doesn't contain enough vowels or doubles.
Example 3
- "abaca" isn't nice. The second condition is satisfied: it contains three vowels a, but the other two aren't satisfied: it contains ba and no doubles.
Example 4
- "baaa" is nice. The conditions #2 and #3 are satisfied: it contains three vowels a and a double a.
Example 5
- "aaab" is nice, because all three conditions are satisfied.
val VOWELS = listOf('a', 'e', 'i', 'o', 'u')
val forbiddenWords = listOf("ba", "be", "bu")
fun String.isNice(): Boolean {
var total = 0
val hasNotForbiddenWords = { input: String -> if (forbiddenWords.none{ input.contains(it)}) 1 else 0 }
val hasMoreThenThreeVOWELS = { input: String -> if (input.count(VOWELS::contains) >= 3) 1 else 0 }
val hasDoubleLetters = { input: String -> if (input.zipWithNext().count { it.first == it.second } > 0) 1 else 0 }
total += hasNotForbiddenWords(this)
total += hasMoreThenThreeVOWELS(this)
total += hasDoubleLetters(this)
return total > 1
}
Assignment: Taxi park
/*
* Task #1. Find all the drivers who performed no trips.
*/
fun TaxiPark.findFakeDrivers(): Set<Driver> = allDrivers.minus(trips.map { it.driver }.toSet())
/*
* Task #2. Find all the clients who completed at least the given number of trips.
*/
fun TaxiPark.findFaithfulPassengers(minTrips: Int): Set<Passenger> {
val byPassenger = trips.map { it.passengers }
return allPassengers.filter {
byPassenger.count { trip -> trip.contains(it) } >= minTrips
}.toSet()
}
/*
* Task #3. Find all the passengers, who were taken by a given driver more than once.
*/
fun TaxiPark.findFrequentPassengers(driver: Driver): Set<Passenger> = trips
.filter { it.driver == driver }
.flatMap (Trip::passengers)
.groupBy { passenger -> passenger }
.filterValues {group -> group.size > 1 }
.keys
/*
* Task #4. Find the passengers who had a discount for majority of their trips.
*/
private fun Passenger.hadMoreTripsWithDiscount(trips: List<Trip>): Boolean {
val totalRides = trips.filter { it.passengers.contains(this) }.map { it.discount }
val ridesWithoutDiscount = totalRides.filter(Objects::isNull).size
val ridesWithDiscount = totalRides.filter(Objects::nonNull).size
return ridesWithDiscount > ridesWithoutDiscount
}
fun TaxiPark.findSmartPassengers(): Set<Passenger> = allPassengers.filter { it.hadMoreTripsWithDiscount(trips) }.toSet()
/*
* Task #5. Find the most frequent trip duration among minute periods 0..9, 10..19, 20..29, and so on.
* Return any period if many are the most frequent, return `null` if there're no trips.
*/
fun TaxiPark.findTheMostFrequentTripDurationPeriod(): IntRange? {
return trips
.groupBy{
val start = it.duration /10 * 10
val end = start + 9
start..end
}
.maxBy{(_,group) -> group.size}
?.key
}
/*
* Task #6.
* Check whether 20% of the drivers contribute 80% of the income.
*/
fun TaxiPark.checkParetoPrinciple(): Boolean {
if(this.trips.isEmpty()) {
return false
}
val incomeByDriver = trips
.groupBy { it.driver }
.map { (_, tripsByDriver) -> tripsByDriver.sumDouble(Trip::cost)}
.sortedByDescending { it }
val topTwentyDrivers = (allDrivers.size * 0.2).roundToInt()
var income = incomeByDriver.take(topTwentyDrivers).sum()
val total = trips.sumByDouble(Trip::cost)
val percentage = income * 100 / total
return percentage >= 80
}
Unstable val
Implement the property 'foo' so that it produced a different value on each access. Note that you can modify the code outside the 'foo' getter (e.g. add additional imports or properties).
var foo: Int = 0
get():Int {
field += 1
return field
}
fun main(args: Array<String>) {
// The values should be different:
println(foo)
println(foo)
println(foo)
}
Rational Numbers
Your task is to implement a class Rational representing rational numbers. A rational number is a number expressed as a ratio n/d , where n (numerator) and d (denominator) are integer numbers, except that d cannot be zero.
package rationals
import java.math.BigInteger.ONE
import java.math.BigInteger.ZERO
import java.math.BigInteger
class Rational(val numerator: BigInteger, val denominator: BigInteger) {
companion object {
private fun minCommonDivisor(denominatorA: BigInteger, denominatorB: BigInteger): BigInteger {
val maxCommonDivisor = denominatorA.gcd(denominatorB)
return denominatorA * (denominatorB / maxCommonDivisor)
}
fun toSameBase(first: Rational, second: Rational): Pair<Rational, Rational> {
val mmc = minCommonDivisor(first.denominator, second.denominator)
val firstEquivalentRational = Rational(first.toEquivalentNumerator(mmc), mmc)
val secondEquivalentRational = Rational(second.toEquivalentNumerator(mmc), mmc)
return firstEquivalentRational to secondEquivalentRational
}
}
fun toEquivalentNumerator(mmc: BigInteger) = this.numerator * (mmc / this.denominator)
/*
The denominator must be always positive in the normalized form.
If the negative rational is normalized, then only the numerator can be negative,
e.g. the normalized form of 1/-2 should be -1/2.wq
*/
fun normalize(): Rational {
if (numerator == ZERO) return this
val maxDivider = numerator.gcd(denominator)
val normalized = Rational(
numerator = numerator / maxDivider,
denominator = (denominator / maxDivider).abs()
)
return when {
denominator < ZERO -> -normalized
else -> normalized
}
}
override fun toString(): String {
return when (denominator) {
ONE -> numerator.toString()
else -> "$numerator/$denominator"
}
}
override fun equals(other: Any?) = when (other) {
is Rational -> this.numerator == other.numerator && this.denominator == other.denominator
else -> super.equals(other)
}
override fun hashCode(): Int {
var result = numerator.hashCode()
result = 31 * result + denominator.hashCode()
return result
}
operator fun compareTo(rational: Rational): Int {
val (sameBaseThis, sameBaseTarget) = toSameBase(this, rational)
return when {
sameBaseThis.numerator > sameBaseTarget.numerator -> 1
sameBaseThis.numerator < sameBaseTarget.numerator -> -1
else -> 0
}
}
operator fun unaryMinus(): Rational {
return Rational(-numerator, denominator)
}
operator fun rangeTo(rational: Rational): Pair<Rational, Rational> {
return Pair(this, rational)
}
operator fun plus(target: Rational): Rational {
val (sameBaseThis, sameBaseTarget) = toSameBase(this, target)
return Rational(
numerator = sameBaseThis.numerator + sameBaseTarget.numerator,
denominator = sameBaseThis.denominator
).normalize()
}
operator fun minus(target: Rational): Rational {
val (sameBaseThis, sameBaseTarget) = toSameBase(this, target)
return Rational(
numerator = sameBaseThis.numerator - sameBaseTarget.numerator,
denominator = sameBaseThis.denominator
).normalize()
}
operator fun times(target: Rational) = Rational(
numerator = this.numerator * target.numerator,
denominator = this.denominator * target.denominator
).normalize()
operator fun div(target: Rational) = Rational(
numerator = this.numerator * target.denominator,
denominator = this.denominator * target.numerator).normalize()
}
operator fun Pair<Rational, Rational>.contains(rational: Rational): Boolean {
return rational >= this.first && rational < this.second
}
fun String.toRational(): Rational {
return if (this.contains("/")) {
val (num, den) = this.split("/")
Rational(num.toBigInteger(), den.toBigInteger()).normalize()
} else {
Rational(this.toBigInteger(), ONE)
}
}
infix fun Number.divBy(first: Number) = Rational(this.toString().toBigInteger(), first.toString().toBigInteger()).normalize()
fun main() {
val half = 1 divBy 2
val third = 1 divBy 3
val sum: Rational = half + third
println(5 divBy 6 == sum)
val difference: Rational = half - third
println(1 divBy 6 == difference)
val product: Rational = half * third
println(1 divBy 6 == product)
val quotient: Rational = half / third
println(3 divBy 2 == quotient)
val negation: Rational = -half
println(-1 divBy 2 == negation)
println((2 divBy 1).toString() == "2")
println((-2 divBy 4).toString() == "-1/2")
println("117/1098".toRational().toString() == "13/122")
val twoThirds = 2 divBy 3
println(half < twoThirds)
println(half in third..twoThirds)
println(2000000000L divBy 4000000000L == 1 divBy 2)
println("912016490186296920119201192141970416029".toBigInteger() divBy
"1824032980372593840238402384283940832058".toBigInteger() == 1 divBy 2)
}
Board
Your task is to implement interfaces SquareBoard and GameBoard.
SquareBoard stores the information about the square board and all the cells on it. It allows the retrieval of a cell by its indexes, parts of columns and rows on a board, or a specified neighbor of a cell.
GameBoard allows you to store values in board cells, update them, and enquire about stored values (like any, all etc.) Note that GameBoard extends SquareBoard.
data class Cell(val i: Int, val j: Int) {
override fun toString() = "($i, $j)"
}
enum class Direction {
UP, DOWN, RIGHT, LEFT;
fun reversed() = when (this) {
UP -> DOWN
DOWN -> UP
RIGHT -> LEFT
LEFT -> RIGHT
}
}
interface SquareBoard {
val width: Int
fun getCellOrNull(i: Int, j: Int): Cell?
fun getCell(i: Int, j: Int): Cell
fun getAllCells(): Collection<Cell>
fun getRow(i: Int, jRange: IntProgression): List<Cell>
fun getColumn(iRange: IntProgression, j: Int): List<Cell>
fun Cell.getNeighbour(direction: Direction): Cell?
}
interface GameBoard<T> : SquareBoard {
operator fun get(cell: Cell): T?
operator fun set(cell: Cell, value: T?)
fun filter(predicate: (T?) -> Boolean): Collection<Cell>
fun find(predicate: (T?) -> Boolean): Cell?
fun any(predicate: (T?) -> Boolean): Boolean
fun all(predicate: (T?) -> Boolean): Boolean
}
open class SquareBoardImp(final override val width: Int) : SquareBoard {
private val cells: List<Cell> = sequence {
var i = 0
repeat(width) {
var j = 0
i++
repeat(width) {
j++
yield(Cell(i, j))
}
}
}.toList()
private fun List<Cell>.find(i: Int, j: Int) = this.find { it.i == i && it.j == j }
override fun getAllCells(): Collection<Cell> {
return cells
}
override fun getCell(i: Int, j: Int): Cell = cells.find(i, j)!!
override fun getCellOrNull(i: Int, j: Int): Cell? = cells.find(i, j)
override fun getColumn(iRange: IntProgression, j: Int): List<Cell> {
return sequence {
for (i in iRange) {
getCellOrNull(i, j)?.let { yield(it) }
}
}.toList()
}
override fun getRow(i: Int, jRange: IntProgression): List<Cell> {
return sequence {
for (j in jRange) {
getCellOrNull(i, j)?.let { yield(it) }
}
}.toList()
}
override fun Cell.getNeighbour(direction: Direction): Cell? {
val (i, j) = when (direction) {
Direction.UP -> i - 1 to j
Direction.DOWN -> i + 1 to j
Direction.RIGHT -> i to j + 1
Direction.LEFT -> i to j - 1
}
return cells.find(i, j)
}
}
class GameBoardImp<T>(size: Int) : SquareBoardImp(size), GameBoard<T> {
private var values: Set<Pair<Cell, T?>> = this.getAllCells().map { it to null }.toSet()
override fun get(cell: Cell): T? = values.find { it.first == cell }!!.second
override fun set(cell: Cell, value: T?) {
values = values.map {
val updatedValue = if (it.first == cell) value else it.second
it.first to updatedValue
}.toSet()
}
override fun filter(predicate: (T?) -> Boolean): Collection<Cell> {
return values.filter { predicate(it.second) }.map { it.first }
}
override fun find(predicate: (T?) -> Boolean): Cell? {
return values.find { predicate(it.second) }?.first
}
override fun any(predicate: (T?) -> Boolean): Boolean {
return values.any { predicate(it.second) }
}
override fun all(predicate: (T?) -> Boolean): Boolean {
return values.all { predicate(it.second) }
}
}
Fibonacci sequence
Implement the function that builds a sequence of Fibonacci numbers using 'sequence' function. Use 'yield'.
fun fibonacci(): Sequence<Int> = buildSequence {
val elements = Pair(0,1)
while(true) {
yiled(elements.first)
elements = Pair(elements.second, elements.first + elements.second)
}
}
fun main(args: Array<String>) {
fibonacci().take(4).toList().toString() eq
"[0, 1, 1, 2]"
fibonacci().take(10).toList().toString() eq
"[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]"
}
infix fun <T> T.eq(other: T) {
if (this == other) println("OK")
else println("Error: $this != $other")
}