"Introduction to Kotlin Programming" Notes
kotlinc (compiler & REPL), kotlin (syntax sugar for running java with the kotlin runtime loaded).
Kotlin doesnt require a main class.
If you compile the following with kotlinc
, then you get a MainKt.class artifact.
// @Main.kt
fun main(args: Array<String>) {
println("test test")
}
For those functions defined outside a class, Kotlin automatically generates a class for them called {filename}Kt. This is important when we setup maven to generate a fat jar for example (it requires us to point to the main class that contains the entrypoint).
Add the kotlin runtime to our class path.
java -cp .:/path/to/kotlin/lib/kotlin-runtime.jar cl.sinenie.kotlindemo.MainKt
Compile to .jar
kotlinc Main.kt -d hello.jar
And include the runtime.
kotlinc Main.kt -include-runtime -d hello.jar
Which makes it easier for us to run the jar later.
java -jar hello.jar
Declaring variables
val number: Int // val is immutable
var name: String = "Robotnik" // var is mutable
val otherName = "Eggman" // inferred String type
Basic types
https://kotlinlang.org/docs/reference/basic-types.html
Numbers
Kotlin has pretty much the same number types than Java, however those are classes, and not primitives
- Double - 64 bits - 123.5 (doubles are the default floating point type)
- Float - 32 bits - 123.5f or 123.5F
- Long - 64 bits - 123L
- Int - 32 bits - 123 (default)
- Short - 16 bits - Short(123)
- Byte - 8 bits - Byte(123)
It’s possible to use hexadecimal notation (0x0F) and binary notation too (0b00001011) but NOT octal notation.
You can add underscores in number constants for readability: 1_000_000, 1234_5678_9012_3456L, 0xFF_53_00, 0b11110101_11110000
There’s no implicit conversions. Even from a small type into a bigger type. Use the toLong, toDouble, etc. number methods for explicit conversions.
Characters
Characters are that, single characters. Notable is the encoding of Unicode characters, which must be done by using Unicode escape seq. syntax: '\uFF00'
.
Booleans
true
and false
. Builtin operations are ||
, &&
and !
, the first two which are lazy (short-circuiting).
Arrays
// passing item values directly
val first = arrayOf(1, 2, 3)
// array of 5 elements, second argument is a function
// that takes the current array index and returns the element
// to be contained at that index
val asc = Array(5, { i -> (i * i).toString() })
Arrays are invariant: An Array
There’s also a few specialized array classes that store primitive values without boxing overhead:
val x: IntArray = intArrayOf(1, 2, 3)
val y: ShortArray = shortArrayOf(1, 2, 3)
val z: ByteArray = byteArrayOf(1, 2, 3)
Strings
- Strings are immutable
- Strings can be concatenated with other types, as long as the element to the left of the
+
is a String. - Normal string literals:
"This is a normal string literal, and can contain escape sequences\n"
- Raw string literals:
"""This is a raw string, can contain newlines and arbitrary text"""
Raw string literals can be used for multiline text:
val text = """
Like this,
As you see.
"""
A cool feature of Kotlin, is that you can remove leading whitespace easily with trimMargin()
. The default character for margin is |
, but can be changed by passing the desired character to trimMargin.
val text = """
|Tell me and I forget.
|Teach me and I remember.
|Involve me and I learn.
|(Benjamin Franklin)
""".trimMargin()
String Templates
val i = 10
println("i = $i") // variable in template
val s = "abc"
println("$s.length is ${s.length}") // more complex expression requires ${}
// prints "$s.length is 3"
// If you need to show a "$" inside a raw string (which doesn't support escaping)
val price = """
${'$'}9.99
"""
kotlin.Unit
This fucker represents void in Kotlin.
kotlin.Any
The equivalent of Object
in Java.
Loops and ranges
for(a: Int in 1..100) { // the Int is optional
println(a)
}
for(a in 100..1) {
println(a)
}
for(a in 100 downTo 1 step 5) {
println(a)
}
val colors = listOf("red", "blue", "orange")
for (color in colors) {
println(color)
}
// while loops, and do while loops work as usual... except for
loop@ for (i in 1..100) {
for (j in 1..50) {
if (j % i == 0) {
break@loop // break immediately off the outer loop
}
}
}
if and when
if
can be used as expressions.
val result = if (myString != "") {
20 // this is not returned
"Not empty" // if expressions return the latest value of the expression
} else {
"Empty"
}
when
is a turbo-charged switch statement. And that’s an understatement. It can also be used as a expression.
// constant expressions
when (x) {
0, 1 -> print("x == 0 or x == 1")
2 -> print("TWO!")
else -> print("otherwise")
}
// arbitrary expressions
val x = 10
val s = "10"
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}
// in or not in range
when (x) {
in 1..10 -> println("x is in the range")
in validNumbers -> println("x is valid")
!in 10..20 -> println("x is outside the range")
else -> println("none of the above")
}
// is of type
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix") // after type checking x is smart-casted.
else -> false
}
// as a replacement for if and else chains
// when argument is not provided to when, branch conditions
// are simply boolean expressions.
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
}
>Even if multiple branch conditions match, only the first one is executed. **There's no "fallthrough"**.
Packages and imports
Not much to say.
import cl.sinenie.pkg.class
import cl.sinenie.pkg.class as otherClassName
Functions in Kotlin
// Unit is a reified void type
fun hello(): Unit {
println("Hello")
}
// This function never returns a value
fun throwingExceptions(): Nothing {
throw Exception("Example")
}
// The return type can often be inferred by the compiler
// and therefore omitted.
fun sum(x: Int, y: Int): Int {
return x + y
}
// Single expression functions
fun sum(x: Int, y: Int): Int = x + y
fun sum(x: Int, y: Int) = x + y
Default and named parameters
fun sum(x: Int, y: Int, z: Int = 42, w: Int = 100) {
return x + y + z + w
}
sum(10, 20, 30, 40)
sum(1, 2, 3) // w is 100
sum(1, 2, w = 5) // z is 42
Unlimited parameters [works]
fun printNumbers(varargs numbers: Int) {
for (number in numbers) {
println(number)
}
}
// Hardcoded arguments
printNumbers(1, 3, 5, 7, 11)
printNumbers(420)
// Pass a iterable as argument
val numbers = 1..100
printNumbers(*numbers) // * is the spread operator. Works like in Python
Classes
https://kotlinlang.org/docs/reference/classes.html
// minimal class declaration
// class Customer
// Initialize properties inline
class Customer {
var id = 0
var name = ""
}
// Initialize from constructor
class Customer(initId: Int, initName: String) {
var id: Int = initId
var name: String = initName
}
// Declare and initialize in constructor
class Customer(var id: Int, var name: String) {} // brackets optional
// Customize what's done with constructor arguments
class Customer(var id: Int, var name: String) {
init {
name = name.toUpperCase()
}
}
// If the constructor has annotations or visibility modifiers,
// the constructor keyword is required, and the modifiers go before it:
class Customer public @Inject constructor(name: String) { ... }
// Constructor types
// * primary constructor, one only
class Customer(var id: Int, var name: String) {
init {
println("This was called!")
}
// * secondary constructors, zero or more
// secondary constructors EXTEND the primary constructor
// functionality, so they must (apparently) also call the primary ctor.
constructor(idNameCsv: String) : this(0, "") {
// e.g. "10,Cristian"
val values = idNameCsv.split(',')
id = parseInt(values[0])
name = values[1]
}
}
val customer = Customer(1, "cof") // no "new" required
Custom getters and setters
class UserProfile(val id: Int, var username: String) {
val alternateId: String
get() = "$id:$username"
val email: String = ""
set(value) {
if (!value.contains('@')) {
throw IllegalArgumentException("Email address doesn't contain '@' symbol.")
}
field = value
}
}
In a custom setter, the
field
keyword allows us to access the backing field.
Member functions (AKA methods)
Write a function inside a class definition and it becomes a method. You can access class properties without need of “this” or any other keyword.
Visibility modifiers
The default visibility is public
, which is “accesible anywhere”.
Besides public
, there are three more modifiers:
private
protected
internal
Whose meaning changes depending on the context.
Context: package (AKA top-level declaration)
- private: only visible inside the file where it was declared.
- internal: only visible inside the same {intellij, maven, gradle} module.
// file name: example.kt
package foo
private fun foo() { ... } // visible inside example.kt
public var bar: Int = 5 // property is visible everywhere
private set // setter is visible only in example.kt
internal val baz = 6 // visible inside the same module
Context: classes and interfaces
- private: visible inside the declaring class only
- protected: visible in class and subclasses
- internal: visible to anyone who sees the class, AND is inside the same module.
Data classes
Data classes are a special kind of class that contains only data, and no behavior. By prepending the data
keyword to a class definition, it becomes a data class.
data class UserProfile(val id: Int, val username: String, val pictureUrl: String)
By doing this, you get equals
, hashCode
, toString
and copy
methods implemented for free! copy
is particularly useful, as you can have a “base” data class and make copies where you change only part of the object.
val cof = UserProfile(1, "cof", "https://example.com/img.png")
val alt = cof.copy(id = 2, userName = "alt") // pictureUrl remains unchanged
You can also destructure data classes:
val cof = UserProfile(1, "cof", "https://example.com/img.png")
val (id, username, pictureUrl) = cof
Enum classes
Enums are actual enum classes and not just a int constant.
enum class MessageType(val color: String) {
INFO("blue") {
override fun toString(): String {
return "Informational Message"
}
},
WARNING("yellow"),
DANGER("red")
}
println(MessageType.DANGER) // prints "DANGER"
println(MessageType.INFO) // prints "Informational Message"
println(MessageType.INFO.name) // prints "INFO"
println(MessageType.WARNING) // prints "1"
println(MessageType.WARNING.color) // prints "yellow"
val msgTypes = MessageType.values() // returns all MessageType instances
In the previous example, color is a custom property. We can create custom methods for our enum class too.
enum class MessageType(val color: String) {
INFO("blue") {
override fun toString(): String {
return "Informational Message"
}
},
WARNING("yellow"),
DANGER("red"); // <--- THIS SEMICOLON IS REQUIRED.
fun getAlertClasses(): String = "alert alert-${name.toLowerCase()}"
}
We can even define abstract methods to be implemented in each of the enum instances.
data class Money(val currencyIso4217: String, val amount: BigDecimal)
data class Item(val sku: String, val price: Money)
enum class CustomerCategory(val baseDiscountPercentage: Double) {
CAT(6.66) {
override fun calculatePrice(items: List<Item>): BigDecimal {
// CAT customers get a discount based on the amount of
// things they buy, on top of their base discount.
throw NotImplementedException()
}
},
DOG(7.77) {
override fun calculatePrice(items: List<Item>): BigDecimal {
// DOG customers get a discount based on the total price
// of their order, on top of their base discount.
throw NotImplementedException()
}
};
abstract fun calculatePrice(items: List<Item>): BigDecimal
}
Objects
Mostly a good way to create singletons without having to create a class for it. However, a better way of creating singletons is not creating them at all.
Maybe objects have other uses too?
object Config {
val maxScore = 100
}
Inheritance
In Kotlin by default all types are final. But you can allow subclassing or overriding by use of the open
keyword.
open class Ball {
open fun Roll() {}
}
class TennisBall: Ball {
override fun Roll() {}
}
Abstract classes…
Create abstract classes with the abstract
keyword.
…and Interfaces
Interfaces can implement their methods, in stark contrast to any SANE language. This makes them almost abstract classes, but with two differences:
- You can implement multiple interfaces, but subclass only one parent class. This was done because since C++ there’s consensus about the perils of multiple inheritance.
- An interface cannot store any state, but you can override the getter of an abstract property to provide a “default value”.
And a very important similarity:
- You don’t need to implement (actually, override) an interface’s method if it has a default implementation.
And that’s the most important point, because allowing interfaces to have a default implementation was just a way of updating the collection interfaces to prepare for the Streams API, without breaking custom implementations of such interfaces (e.g. List
implementations). In other words, it’s an ugly hack to enable backwards compatibility.
Therefore, don’t use default methods in interfaces. I won’t even write about how you can do such stupid shit, just don’t.
Generics
// Generic class or interface
class Repository<T> {
fun getAll(): List<T> {}
}
// Non-generic class or interface, but with generic method
interface Repository {
fun <T> getAll(): List<T>
}
Null safety
In Kotlin, by default, types cannot be null.
val name: String = "Cristian" // type: non-nullable String
name = null // cannot be done
However, due to Java interop, we can still signal that a type could be null by appending a “?” to the type.
val name: String? = "Nico" // type: nullable String
name = null // OK
// Now however the compiler will demand we check for nulls
if (name != null) {
println(name.length)
} else {
println("null name")
}
Safe calls
By using the ?.
safe call syntax, methods will only be called if the caller isn’t null. If it were null, then the whole expression resolves to null. You can chain those as you need.
println(name?.length) // prints "null"
// It can also be used during assignment
// if person, or person.department is null, no assignment happens
person?.department?.head = managersPool.getManager()
If you want to perform an action only if a nullable is not null, a concise way of doing so is by using let
.
val listWithNulls: List<String?> = listOf("Kotlin", null, "Python")
for (item in listWithNulls) {
item?.let { println(it) } // prints Kotlin, ignores null, prints Python
}
let
is not specific to null safety, and can be used in other contexts too. There’s a related function called also
, which is like let
but with a slightly different function signature:
let
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/let.htmlalso
: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/also.html
Use the elvis operator to evaluate an expression to what’s on the left side of the operator if that’s not null, else to what’s in its right side.
// Ternary operation
val l: Int = if (b != null) b.length else -1
// Elvis
val l = b?.length ?: -1
Finally, you can filter nulls out of a collection by using filterNotNull
.
val nullableList: List<Int?> = listOf(1, 2, null, 4)
val intList: List<Int> = nullableList.filterNotNull()
The not-null assertion operator, AKA the #YOLO operator
With a stupid power comes a stupid responsability. – Bugs Bunny
If you absolutely know that something cannot be null (just as everyone does, of course), then you can omit the compiler checks and program regular Java with the !!
operator. The operator represents a developer’s two eyes with tears flowing as the system broke in production at 17:55 on a friday.
val l = b!!.length
Just because you can, it doesn’t mean you should.
Type casting
// === Smart casting ===
if (obj is Something) {
// can use obj as if it were of type Something
}
// === Explicit casting ===
// if obj isn't Something, throw Exception
val som = obj as Something
// === "Safe" explicit casting ===
// if obj isn't Something, evaluate as null
val som = obj as? Something
Tuples
Predefined data class types. 2 and 3 sized tuples.
Pair<T, Y>
Triple<T, Y, U>
It’s really easy to create data classes in Kotlin. And we know how FUN (/s) is to work with unnamed tuples in Python, so use them sparingly (or better yet, don’t use them at all).
Deconstructing values
https://kotlinlang.org/docs/reference/multi-declarations.html
val redColor = Pair("red", "#FF0000")
val (name, hex) = redColor
That is compiled to:
val redColor = Pair("red", "#FF0000")
val name = redColor.component1()
val hex = redColro.component2()
Any class that implements operator methods component1
, component2
, … componentN
can be deconstructed. data classes by default implement the componentN methods. Map items also have default componentN implementations.
It’s possible to easily implement component methods on a normal class as follows:
class Game(val name: String, val genre: String, val review: String) {
fun placeholder() {
println("Placeholder method, please ignore.")
}
// Component methods for deconstruction support
operator fun component1() = name
operator fun component2() = genre
operator fun component3() = review
}
fun main(args: Array<String>) {
val ro2 = Game("Red Orchestra 2", "WW2 FPS", "IT'S FUCKING AMAZING!")
// variable names don't have anything to do with how they're
// called in the class
val (gameName, gameGenre, myReview) = ro2
println(gameName)
println(gameGenre)
println(myReview)
}
Working with exceptions
Java creators: let’s add checked exceptions to force developers to think about error handling and therefore produce better software!
Java developers: gotta catch em’ all!… in empty handling blocks, with no logging whatsoever.
There are no checked exceptions in Kotlin, but exception throwing and catching still works as it does in Java.
class MyCustomException(msg: String): Throwable(msg) { }
try {
throw MyCustomException("Test message")
} catch (e: OtherException) {
// handle
} catch (e: MyCustomException) {
// handle
} finally {
// this will always be called
}
Declaring constants
Either create top level val
s, or top level objects with val
s inside. They’ll be accesible if you import the package where they’re defined, just as any other function or class.
Annotations
They work just as they work in Java.
@Test fun testPlaceholder() {
// Some test.
}
Higher-order functions
Functions that can take functions as arguments and can return functions.
fun operation(x: Int, y: Int, op: (Int, Int) -> Int) {
return op(x, y)
}
fun sum(x: Int, y: Int) -> x + y
fun main(args: Array<String>) {
operation(1, 2, ::sum) // refer to existing function by ::{functionName}
}
Lambda expressions
https://kotlinlang.org/docs/reference/lambdas.html
// We don't specify the types as the compiler
// can infer them from the function signature
operation(1, 2, { x, y -> x + y })
// Here we have to be explicit, as it cannot
// be inferred automatically
val sumLambda: (Int, Int) -> Int = { x, y -> x + y }
operation(1, 2, sumLambda)
// for lambdas that take only one parameter, we can use "it"
var double: (Int) -> Int = { x -> x * 2 }
var doubleIt: (Int) -> Int = { it * 2 }
There’s a convention in Kotlin regarding passing a lambda as last argument.
operation(1, 2) { x, y -> x + y }
We put the lambda outside and after the parentheses. While it looks somewhat silly, and even pointless, at first glance, we can use this syntax to create custom DSL very easily.
transaction(connection) {
// Do something with the connection
}
Here we have a function transaction
that takes a connection of some kind, and a () -> Unit
function as second parameter. Suppose that the transaction function calls commit if the function executes without errors, and call rollback if it doesn’t.
There are several other examples of this syntax in Kotlin, for example with let
and also
.
Closures
A lambda can access the variables declared in the outer scope, it’s closure. In Kotlin, the captured variables can be modified, allowing for stuff like this:
var sum = 0
ints.filter { it > 0 }.forEach {
sum += it
}
print(sum)
The second lambda is defined when sum
is 0, but for further executions it acceses the current value of sum, not what it was when it was captured.
Extension functions
Extend a class without inheriting from it.
fun Int.theAnswer() {
return 42
}
fun String.appendCapitalized(ext: String): String {
return this + ext.toUpperCase()
}
println(Int.theAnswer())
println("This text is a... ".appendCapitalized("test"))
You can use extension functions as long as you have imported the package where they’re defined.
NOTE: If for some reason you had a method and an extension function with the same signature, the method will take precedence.
Extension functions are statically resolved
This is a big potential gotcha that deserves its own section.
fun main(args: Array<String>) {
val derived: DerivedClass = DerivedClass()
val base: BaseClass = derived
println("Same reference: ${derived == base}") // true
derived.extension() // prints: Derived extension
base.extension() // prints: Base extension
}
open class BaseClass
class DerivedClass : BaseClass()
fun BaseClass.extension() = println("Base extension")
fun DerivedClass.extension() = println("Derived extension")
Removing the derived class extension, would make both calls to print “Base extension”, though.
Interop with Java
https://kotlinlang.org/docs/reference/java-interop.html
Call Java code from Kotlin
There are a few regular use things to remember, and some more specific “gotchas” listed in the java interop page.
- getters and setters in Java are accesible by using property accesor syntax in Kotlin.
- void return types get mapped to Unit in Kotlin.
- Java methods with kotlin keywords as names can be used by enclosing them in backticks. e.g.
foo.'is'(bar)
(replace the'
for actual backticks, though.) - Classes and interfaces defined in Java can be implemented / derived in Kotlin. Most likely you’ll have to declare return types as nullable, as Java can store nulls in any reference.
Also, for interfaces that have only one method, as for example with the java.lang.Runnable
interface, we can do as follows:
val runnable = Runnable { println("Runnable is running!") }
Doing so saves us from having to explicitly implement the Runnable interface in a class.
About Java nulls and other barbarities
When interoping with Java code there are a few things to take into account. Let’s assume we have a UserProfileRepository
with an getById
method, and a getAll
method.
val repo = UserProfileRepository()
Platform types
The lazy option.
// Omitting types when assigning from a interop
// interaction leaves us with UserProfile!, where
// the "!" denotes it as a "platform type".
val userProfile = repo.getById(2)
// Platform types cannot be used directly
val userProfile: UserProfile! = repo.getById(1) // <-- doesn't compile
// And are ambiguous regarding if a value can or cannot be null
println(userProfile.id) // <-- this compiles, implies that userProfile cannot be null
println(userProfile?.id) // <-- this compiles too, says the opposite
// In the first case, if it happens to be null, then an exception is thrown
// Exception in thread "main" java.lang.IllegalStateException: userProfile must not be null
Explicitly declaring something as nullable or not
The responsible option.
// Explicitly nullable
val userProfile: UserProfile? = repo.getById(1)
println(userProfile?.id) // safe call operator is required
// Explicitly not-null
// If repo.getById(2) returns null, the program crashed right there
// Exception in thread "main" java.lang.IllegalStateException: repo.getById(2) must not be null
val userProfile: UserProfile = repo.getById(2)
println(userProfile.id)
Interop with Kotlin (from Java)
https://kotlinlang.org/docs/reference/java-to-kotlin-interop.html
Most important points:
- Non-nullable Kotlin types are represented as types annotated with
@NotNull
in Java. - Kotlin properties are accesible with getters and setters unless we annotate the property with
@JvmField
, then it’s shown as a Java field. - Java has no concept of default parameters, so Kotlin functions that have default parameters appear as a function with a normal parameter to Java. If we annotate the Kotlin function with
@JvmOverloads
, then overloads that don’t require passing a value for the default parameters will be generated for access in Java. - Exceptions launched from Kotlin can’t be caught in Java, unless we annotate the Kotlin method with
@Throws(MyException::class)
. Then we can catch exceptions of typeMyException
.
Actually, you could live pretty happily knowing only the last point, which is quite important.
Top-level functions
This may be relevant even if we don’t touch Java, as for example when declaring entrypoints in Maven.
In Java there’s no concept of a top-level function (function outside a class), but in Kotlin there is.
The Kotlin compiler automatically creates a class for all the top level stuff, which by default is called ${filename}Kt. A file called Asdf.kt
will get compiled into AsdfKt.class
.
To change the generated class name, add this annotation at the top of the file: @file:JvmName("MyCustomClass")
, where “MyCustomClass” is obviously your custom class name.
Top-level properties will get exposed with getters and setters as usual. If you have a constant (val) that you want to expose as a… constant, instead of through a getter, then prefix it with const
.
const val MyConstant = 42
Extension functions
Clunky, but possible.
GeneratedClassNameKt.extensionName(instanceOfClassExtended)
Kotlin Standard Library
https://kotlinlang.org/api/latest/jvm/stdlib/index.html
Collections
Mutable interfaces, and immutable interfaces that interact with the Java collections.
Listjava.utils.ArrayList
or kotlin.collections.EmptyList
.
MutableListjava.utils.ArrayList
.
Other collection interfaces, and functions to create them, are:
- Collection: base interface for
List
andSet
.- MutableCollection: base interface for
MutableList
andMutableSet
.
- MutableCollection: base interface for
- Map
- Set
Filtering, mapping, flatmapping
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/index.html#functions
Kotlin adds several chainable methods reminiscent of LINQ. Some of them are:
- filter: returns a list containing only elements matching the given predicate.
- forEach: execute an action on each element.
- sortBy: Sort an array in place according to some value returned by the selector function.
- sortedBy: same as sortBy, but the result is returned, instead of changing the sorted array.
- zip: merges two lists together, creating pairs for elements with the same index from the lists. This process continues only as the shortest list allows.
- first: return the first element matching a given predicate, or throw
NoSuchElementException
if no element is found. - firstOrNull:
first
, but it returns null if no element is found. - single: same as
first
, but also throwIllegalArgumentException
if the list has more of one matching element. - singleOrNull: same as
single
, but it returns null if no element, or more than one element, is found. There’s no distinction on whether there’s more than one match or no match at all, careful. - take: Returns a list with the first n elements.
- drop: This is the equivalent to
Skip
in LINQ. Returns a list without the first n elements. - map: execute a transform function on each element, return another element. A list contaning the transformed elements will be returned.
- flatMap: execute a transform function on each element, return a list of other elements. A list containing the concatenation of all those returned lists will be returned.
- reduce: applies a function to each element in a list, where the function takes the current “accumulated” value as first argument, and the current iterated element as second argument. The function is expected to return an updated “accumulated” value which will then be used to for calling the function with the next element in the list.
reduce
deserves some additional explanation and an example as what it does may not be obvious:
fun main(args: Array<String>) {
val numbers = listOf(1, 2, 3, 4, 5)
val add: (Int, Int) -> Int = { x, y ->
println("accumulator: $x, element: $y")
x + y
}
val reduced = numbers.reduce(add)
println(reduced)
}
The console output for that is:
accumulator: 1, element: 2
accumulator: 3, element: 3
accumulator: 6, element: 4
accumulator: 10, element: 5
15
Note that the first element is used as the starting value for the accumulator.
Lazy evaluation with sequences
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.sequences/index.html
A sequence that returns values through its iterator. The values are evaluated lazily, and the sequence is potentially infinite. – Kotlin documentation for Sequence
interface.
That definition makes sense when we look at the implementation of asSequence:
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
return Sequence { this.iterator() }
}
Completely custom sequences can be created using generateSequence.
// This would return increasingly doubling numbers each iteration...
// ... until it overflows and starts returning 0.
val duplicatingSeq = generateSequence(1) { it * 2 }
// repoNameGenerator would return pseudo-random combinations of
// adjectives and nouns, like it's sometimes suggested for repo
// or project names in some sites.
val adjectives = listOf("some", "adjectives", "...")
val nouns = listOf("some", "nouns", "...")
val random = Random()
val repoNameGenerator = generateSequence {
val adjective = adjectives.shuffled(random).first()
val noun = nouns.shuffled(random).first()
"$adjective-$noun"
}
repoNameGenerator.forEach { println(it) }
Another reminder:
val elements = (1..100000000000).asSequence()
val filtered = elements.filter { it < 10 }.map{ "Yes" to it }
filtered.forEach { println(it) }
String extensions
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/