Simple Example
data class Person(val name: String, val age: Int? = null)
fun main(args: Array<String>) {
val persons = listOf<Person>(Person("Alice"), Person("Bob", age = 29))
val oldest = persons.maxBy { it.age ?: 0 }
println("The oldest is: $oldest")
}
Most common uses for Kotlin:
- Server side code (web)
- Android
Kotlin can be compiled into Java and JavaScript.
Type inference => the ability of the compiler to determine types from context.
Benefits of static typing:
- Performance
- Reliability
- Maintainability
- Tool support
Key concepts of functional programming:
- First-class functions
- Immutability
- No side effects
Benefits of functional: concise code.
Lambda expressions = anonymous functions
Another benefit: multithreading.
- because immutability, pure functions, you guarantee data consistency.
Testing is easier = defined input gives defined output.
Kotlin features for functional programming:
- Function types - allows to receive function as parameters and return
- Lambda expressions - define blocks of code with minimum boilerplate.
- Data classes - concise syntax for creating immutable value objects.
- APIs and standards to work with Objects and Collections using functional style.
With Kotlin you can use both object-oriented and functional programming where necessary.
Works just like Java
- Web applications that return HTML pages to a browser
- Backends of mobile applications that expose a JSON API over HTTP
- Microservices that communicate with other microservices over an RPC protocol
- The Anko library (https://github.com/kotlin/anko)
- Application reliability. Kotlin's type system, with its precise tracking of
null
values. - Kotlin is compatible with Java 6
The code generated by Kotlin compiler is executed as efficiently as regular Java code.
Kotlin is a practical language designed to solve real-world problems.
Another aspect of Kotlin's pragmatism is its focus on tooling. The IntelliJ IDEA plug-in was developed in lockstep with the compiler, and language features were always designed with tooling in mind.
Kotlin has a rich standard library that lets you replace long, repetitive sections of code with library method calls.
Kotlin's support for lambdas makes it easy to pass small blocks of code to library functions. This helps to encapsulate all the common parts in the library and keep only the unique, task-specific portion in the user code.
Cocise code:
- less time to write
- less time to read.
- Type safety with type inference
- Error check in compile time.
- Remove
NullPointerException
- Avoid
ClassCastException
// may be null
val s: String? = null
// may NOT be null
val s2: String = ""
You can work on Kotlin with Java and vice-versa. Kotlin uses existing Java frameworks.
Kotlin source code file extension is .kt
.
The Kotlin compiler generates a .class
file.
The generated .class
files are then packaged and executed using the standard procedure for the type application you're working on.
You can use the kotlinc
command to compile your code.
Code compiled with Kotlin compiler depends on the Kotlin runtime library.
Encorages immutable data.
fun main(args: Array<String>) {
println("Hello, World!")
}
fun
=> declare function- The parameter type is after the name.
- The function can be at the top level, it doesn't need to be in a class.
- Arrays are just classes
- You don't need to declare
System.out.println
. - You can omit the semicolon at the end.
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
- Expression - has a value, which can be used as part of another expression.
- Statement - is always a top-level element in its enclosing block and doesn't have its own value.
Simplify even more:
fun max(a: Int, b:Int): Int = if (a > b) a else b
- block body - function with body between curly braces
- expression body - function returns and expression directly
Simplify even more:
fun max(a: Int, b: Int) = if (a > b) a else b
- type inference for expression body functions, the compiler can analyze the expression used as the body of the function and use its type as the function return type.
Note that for block body functions you need to explicitly define the return type.
Kotlin allows to omit the type from many variable declarations.
There are two keywords to declare variables:
- val (from value) - immutable reference.
- var (from variable) - mutable reference. but type is fixed.
Prefere to declare as val
.
val message: String
if (canPerformOperation()) {
message = "Success"
}
else {
message = "Failed"
}
fun main(args: Array<String>) {
val name = if (args.size > 0) args[0] else "Kotlin"
println("Hello, $name!")
}
More complex:
fun main(args: Array<String>) {
println("Hello, ${if (args.size > 0) args[0] else "someone"}")
}
Value Objects => Classes containing only data but no code
class Person(val name: String)
Java
- accessor methods getters and setters.
- property the combination of the field and its accessor.
In Kotlin you declare the property in a class the same way to declare a variable: with val
and var
keywords.
If the property name starts with is
, no additional prefix is added.
println(person.isMarried)
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
Like in Java, Kotlin has the concept of packages.
Every Kotlin file can have a package
statement at the beginning.
If classes are inside the same package, they don't need to be imported.
Import statements are placed at the beginning of the file using the import
keyword.
package geometry.shapes
import java.util.Random
class Rectangle(val height: Int, val width: Int) {
val isSquare: Boolean
get() {
return height == width
}
}
fun createRandomRectangle(): Rectangle {
val random = Random()
return Rectangle(random.nextInt(), random.nextInt())
}
Kotlin doesn't make any distinction between importing a class or a function.
Importing from another package
package geometry.example
import geometry.shape.createRadomRectangle
fun main(args: Array<String>) {
println(createRandomRectangle().isSquare)
}
Kotlin allows to have multiple classes in a single file and choose any name for the file. You can also use any directory structure for the file.
But it's a good idea to follow Java's package structure.
- geometry/
- example/
- Main
- shapes/
- Rectangle
- RectangleUtil
when
=> Java's switch
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
Enums with properties:
enum class Color(val r: Int, val g: Int, val b: Int) {
RED(255, 0, 0), ORANGE(255, 165, 0), YELLOW(255, 255, 0), GREEN(0, 255, 0),
BLUE(0, 0, 255), INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
The semicolon separates the enum constant list from the method definitions.
when
is an expression that returns a value, so you can write a function with an expression body, returning when
expression directly.
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
>>> println(getMnemonic(Color.BLUE))
Battle
You can also combine multiple values in the same branch if you separate them with commas.
fun getWarmth(color: Color) =
when(color) {
Color.RED, Color.ORANGE, Color.YELLOW -> "warm"
Color.GREEN -> "neutral"
Color.BLUE, Color.INDIGO, Color.VIOLET -> "cold"
}
You can import the constant values from enums:
import ch02.colors.Color
import ch02.colors.Color.*
fun getWarmth(color: Color) =
when(color) {
RED, ORANGE, YELLOW -> "warm"
GREEN -> "neutral"
BLUE, INDIGO, VIOLET -> "cold"
}
when
allows any objects.
fun mix(c1: Color, c2: Color) =
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
setOf
=> creates a Set
. A set
is a collection for which the order of items doesn't matter.
Instantiate Set
for all possibilities all the time could get innefficient. A more efficient way to implement is:
fun mixOptimized(c1: Color, c2: Color) =
when {
(c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE
(c1 == YELLOW && c2 == BLUE) || (c1 == BLUE && c2 == YELLOW) -> GREEN
(c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO
else -> throw Exception("Dirty color")
}
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
>>> println (eval(Sum(Shum(Num(1), Num(2)), Num(4))))
In Kotlin, you check type using is
check.
smart cast
=> The compiler performs the cast for you.
In Kotlin, there is no ternary operator because, unlike in Java, the if
expression returns a value.
fun eval(e: Expr): Int =
if (e is Num) {
e.value
} else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
Rewriting with when
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> throw IllegalArgumentException("Unknown expression")
}
fun evalWithLogging(e: Expr): Int =
when(e) {
is Num -> {
println("num: ${e.value}")
e.value
}
is Sum -> {
val left = evalWithLogging(e.left)
val right = evalWithLogging(e.right)
println("sum: $left + $right")
left + right
}
else -> throw IllegalArgumentException("Unknown expression")
}
The last expression in a block is the result.
A function can have either an expression body that can't be a block or a block body with explicit return
statement inside.
Just like Java.
while (condition) {
/* ... */
}
do {
/* ... */
} while (condition)
In Kotlin there's no regular Java for
loop. Kotlin uses ranges
.
val oneToTen = 1..10
Ranges in Kotlin are closed or inclusive. (the second value is part of the range.)
progression loop over all the values in a range.
fun fizzBuzz(i: Int) = when {
i % 15 == 0 -> "FizzBuzz "
i % 3 == 0 -> "Fizz "
i % 5 == 0 -> "Buzz "
else -> "$i "
}
fun main(args: Array<String>) {
for (i in 1..100) {
print(fizzBuzz(i))
}
for (i in 100 downTo 1 step 2) print(fizzBuzz(i))
}
package ch02
import java.util.*
fun main(args: Array<String>) {
val binaryReps = TreeMap<Char, String>()
for (c in 'A'..'F') {
val binary = Integer.toBinaryString(c.toInt())
binaryReps[c] = binary
}
for ((letter, binary) in binaryReps) {
println("$letter = $binary")
}
}
in
=> value is in range.!in
=> value isn't in range.
fun isLetter(c: Char) = c in 'a'..'z' || c in 'A'..'Z'
fun isNotDigit(c: Char) = c !in '0'..'9'
If any class supports comparing instances by implementing java.lang.Comparable
, you can create ranges of objects of that type.
import java.lang.IllegalArgumentException
if (percentage !in 0..100) {
throw IllegalArgumentException("A percentage value must be between 0 and 100: $percentage")
}
You don't need to use new
keyword to create an instance of the exception.
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
} catch (e: NumberFormatException) {
return null
} finally {
reader.close()
}
}
The throws
clause isn't present in the code.
The try
keyword in Kotlin introduces an expression, and you can assign its value to a variable.
Unlike with if
, you always need to enclose the statement body in curly braces.
fun readNumber(reader: BufferedReader): Int? {
val number = try {
Integer.parseInt(reader.readLine())
} catch (e: NumberFormatException) {
null
}
println(number)
}