Last active
November 26, 2023 19:09
-
-
Save jaguililla/ff8e671bb770478253553f5fb7a8ff5d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env kotlin | |
@file:Suppress( | |
"USELESS_IS_CHECK", | |
"FunctionName", | |
"PackageDirectoryMismatch", | |
"UnusedImport", | |
"unused", | |
"RedundantNullableReturnType", | |
"ReplaceManualRangeWithIndicesCalls", | |
"MoveLambdaOutsideParentheses", | |
) | |
/* | |
KOTLIN LANGUAGE | |
============================================================================= | |
Origin and Goals of Kotlin | |
-------------------------- | |
- JetBrains developed Kotlin to meet its own needs: | |
- Maintain a large amount of Java code (for Web and Desktop) | |
- Designed to ease the development of tools (REPL and IDE) | |
- Bidirectional Java integration to adopt it incrementally | |
- It should be easy to learn for Java programmers | |
- Released under Apache 2 license on GitHub in 2012 | |
- Version 1.0 released in February 2016 | |
- Led by JetBrains and Google by the Kotlin Foundation: kotlinfoundation.org | |
Language DNA | |
------------ | |
- Compiled to: | |
- Java bytecode: Android, Servers and Desktop | |
- JavaScript: Browser | |
- Native code: Linux, Mac, Windows (Beta) | |
- WASM: Browser, Servers and Desktop (Experimental) | |
- Uses static typing (and supports type inference) | |
- Allows dynamic typing (for backend JavaScript only) | |
- Multi paradigm: OO and FP [^1] | |
- Can be run from the shell as scripts in `.kts` files | |
[^1]: FP immutability, first level functions and "expressiveness" | |
Type System | |
----------- | |
- `null` is part of the language, not the library (unlike Optional) | |
- `Any` is the root of the type hierarchy (as Object in Java) | |
- All instances are objects (`Int`, `Boolean`, `Float`). No primitive types | |
- `Unit` is the equivalent of `void` in Java | |
- `Nothing` is used by expressions that do not return any value | |
Install Kotlin | |
-------------- | |
- Most of the times you won't need to (Maven or Gradle will do it) | |
- To install a Kotlin version manually, your best option would be sdkman.io | |
*/ | |
/* | |
CODE STRUCTURE | |
============================================================================= | |
*/ | |
// `KotlinLanguage.kt` compiles to class `com.example.KotlinLanguageKt` | |
// Common base packages' directories can be skipped | |
package com.example // Directory structure is not enforced | |
// Imports can be renamed (but don't overuse it) | |
import java.io.File as JavaFile | |
import java.io.FileReader | |
// Kotlin files can hold variables, constants and functions in addition to | |
// classes You can define many public classes in a file (good for prototyping) | |
// Definitions are `public` by default. They can also be `internal` or `private` | |
val packageVal = 0 // Java: import com.example.KotlinLanguageKt.getPackageVal | |
fun packageFun() = true // Java: com.example.KotlinLanguageKt.packageFun() | |
// Executed as `java com.example.KotlinLanguageKt`. | |
fun main() { // args are optional: `vararg args: String` or `args: Array<String>` | |
println("Hello Kotlin!") | |
} | |
/* | |
VARIABLES, CONSTANTS AND LITERALS | |
============================================================================= | |
- Avoid type inference outside local scope | |
*/ | |
val booleanConstant: Boolean = true | |
// booleanConstant = false // Compile error | |
object Constants { | |
const val JAVA_CONSTANT = true // Basic types only (Int, String...) | |
const val STRING_CONSTANT = "string" // Declared only in objects or top level | |
} | |
var multilineText: String = """multiline strings with | |
substitution of variables $booleanConstant ${2 + 2}""" | |
// Allows creation of not null vars without initial values | |
lateinit var lateInitVariable: String | |
val hexLiteral: Int = 0xCAFE | |
val bitLiteral: Int = 0b0101_0101_0101 | |
// Initialized on first access | |
val lazyInit: Long by lazy { System.currentTimeMillis() } | |
/* | |
EQUALITY | |
============================================================================= | |
==, !=, ===, !== | |
TODO | |
*/ | |
/* | |
ARRAYS AND RANGES | |
============================================================================= | |
- They are classes, not language constructions | |
- Ranges: 1..2 (both included) 1 until 3 (3 not included) | |
*/ | |
// Can be initialized with a lambda | |
val array: Array<Int> = Array(5) { ii -> ii * 2 } // [ 0, 2, 4, 6, 8 ] | |
val arrayLiteral = arrayOf(1, 2, 3) | |
// Transform arrays into lists (and vice versa) | |
array.toList() | |
listOf('A', 'B', 'C').toTypedArray() | |
val firstPosition = array[0] | |
val subArray: List<Int> = array.slice(1..2) // '1..2' is a range of integers | |
/* | |
`NULL` MANAGEMENT | |
============================================================================= | |
- If `null` is checked, the reference makes a cast of T? to T | |
*/ | |
val integer: Int? = null // '?' Indicates optional value | |
//val error: Int = integer + 1 | |
val notNull: String? = "not null" | |
// There are null checking utilities in the standard library | |
checkNotNull(notNull) { "Exception's message if value is null. It is optional" } | |
requireNotNull(notNull) { | |
"This method does the same, but throwing an IllegalArgumentException" | |
} | |
// With `?:` You can control `null` in a concise way | |
val ok: Int = (integer ?: 0) + 1 | |
data class Address(val street: String, val locality: String?) | |
val address: Address? = Address("Fifth Avenue, 1", "New York") | |
// Operator safe call `?.` | |
val locality: String = address?.locality ?: "Without locality" | |
/* | |
FUNCTIONS | |
============================================================================= | |
- Methods can be overloaded | |
*/ | |
// Parameters with default values | |
fun function(integer: Int = 0, parameter: String = "defect"): String = | |
"$parameter $integer" | |
function() // Arguments by default | |
function(1) | |
function(parameter = "value") // Passing arguments by name | |
fun procedure() { println("Hello") } // Unit is optional | |
fun oneFunction() { | |
val local = 1 | |
fun nestedFunction() = local | |
} | |
fun multipleArgs(vararg params: String) = params.joinToString("; ") | |
multipleArgs("One", "Two", "Three") | |
/* | |
FUNCTION TYPES | |
============================================================================= | |
*/ | |
// Blocks of code stored as variables | |
val lambda: (Int, Int) -> String = { a: Int, b: Int -> "$a $b" } | |
val inferredLambda: (Int, Int) -> String = { a, b -> "$a $b" } | |
// You can use typealias to give more meaningful names to Function types | |
typealias IntInt2String = (Int, Int) -> String | |
val lambda2: IntInt2String = { a: Int, b: Int -> "$a $b" } | |
// They are invoked as a function | |
lambda(10, 20) | |
// Can be passed as a parameter | |
fun higherOrderFun(p: Int, lambda: (Int) -> Int) = lambda(p) | |
// If the lambda is the last parameter accepts alternative syntax | |
higherOrderFun(10, { it * 2 }) | |
higherOrderFun(10) { it * 2 } | |
/* | |
EXTENSION METHODS | |
============================================================================= | |
*/ | |
// Add methods to existing classes | |
// Useful to extend libraries without source code | |
fun String.addTimestamp() = this + " " + System.currentTimeMillis() | |
// The object on which they are applied is called 'receiver' | |
// You can specify a nullable receiver | |
fun String?.addNullTimestamp() = | |
"${this ?: "This is null"} ${System.currentTimeMillis()}" | |
"hello".addTimestamp() | |
null.addNullTimestamp() | |
/* | |
INFIX FUNCTIONS | |
============================================================================= | |
*/ | |
infix fun String.concat(d: Int) = "$this $d" | |
"A" concat 1_000_000 | |
// Bit operators are infix functions | |
0b1 shl 1 and 0b0 | |
/* | |
CLASSES | |
============================================================================= | |
*/ | |
interface Interface { | |
val value: Int // Interfaces may contain variables | |
fun function(): String | |
} | |
abstract class AbstractClass { | |
abstract fun function(): String | |
} | |
// Classes are 'final' by default (problem for Mocks and Spring) | |
// The parameters of the main constructor are defined in the class | |
open class BaseClass(val field: String = "foo") | |
// param is only visible in the initialization | |
class AClass(param: String, val property: Int = 0): BaseClass(param), Interface { | |
var property2 = param | |
override val value = 42 | |
init { | |
println(param) | |
} | |
constructor(): this("parameter", 99) | |
fun method() = "result" | |
override fun function() = "implemented" | |
} | |
val aClass = AClass("parameter") // 'new' is not used | |
/* | |
ENUMS | |
============================================================================= | |
- Can have members and methods | |
- May implement interfaces | |
*/ | |
interface Balance { | |
fun balanced(): Boolean | |
} | |
enum class Vehicle(private val wheels: Int) : Balance { | |
MOTORBIKE(2), | |
CAR(4), | |
TRUCK(6); | |
override fun balanced(): Boolean = | |
wheels > 2 | |
} | |
/* | |
PROPERTIES | |
============================================================================= | |
*/ | |
class PersonV1 { | |
var age: Int = 0 | |
} | |
// `field` is the variable to assign the new field value | |
class PersonV2 { | |
var age: Int = 0 | |
set(value) { | |
require(value < 18) | |
field = value | |
} | |
} | |
// Compiles with `PersonV1` and `PersonV2` | |
val person = PersonV1() | |
// With `PersonV2` throws an exception | |
person.age = 80 | |
/* | |
DATA CLASSES | |
============================================================================= | |
- They are named tuples (can be used in "destructuring") | |
*/ | |
// immutable DTO (overwrites `equals`,` hashcode` and `toString` | |
// Can implement interfaces, can not inherit classes | |
data class Person(val name: String, val age: Int = 0) | |
val linux = Person("Linux Torvalds", 50) | |
// Copy instance by changing fields | |
val linus = linux.copy( | |
name = "Linus Torvalds", | |
age = 51, | |
) | |
/* | |
STATICS AND OBJECTS (SINGLETONS) | |
============================================================================= | |
*/ | |
class Statics { | |
// It is better to use package level methods | |
companion object { | |
fun staticMethod() {} | |
} | |
} | |
object Singleton { | |
fun singletonMethod() {} | |
} | |
/* | |
WHEN | |
============================================================================= | |
*/ | |
val state: Any = 0 | |
// Automatic Cast | |
when (state) { | |
is String -> println(state.substring (1..2)) | |
in 1..9 -> println("Int") | |
!in 1..9 -> println("Out of range") | |
10, 20, 50 -> println ("Tens") | |
0 -> println ("Zero") | |
else -> println ("None of the above") | |
} | |
// Can be used as a cleaner if-elseif | |
when { | |
state is Int && state > 10 -> println("+ 10") | |
"" is String -> println("Empty string") | |
else -> println("Never run") | |
} | |
/* | |
LISTS | |
============================================================================= | |
*/ | |
// They are not modifiable | |
val list = listOf(0, 1, 2) | |
assert(list[0] == 0) | |
assert(1 in list) | |
assert(3 !in list) | |
val mutableList = mutableListOf(0, 1, 2) | |
/* | |
MAPS (AND PAIRS) | |
============================================================================= | |
*/ | |
val pair: Pair<String, String> = "key" to "value" // pair.first & pair.second | |
val map = mapOf( | |
"Spain" to "ES", | |
"United Kingdom" to "UK", | |
pair, | |
) | |
assert("Spain" in map) | |
assert(map["Spain"] == "ES") | |
val map2 = map + ("Italy" to "IT") | |
val mutableMap: MutableMap<String, Any> = mutableMapOf("a" to 0, "b" to true) | |
mutableMap["a"] = 'C' | |
/* | |
DESTRUCTURING | |
============================================================================= | |
*/ | |
val (name, age) = Person("Name", 18) | |
assert(name == "Name") | |
assert(age == 18) | |
map.forEach { (key, value) -> | |
println("$key - $value") | |
} | |
/* | |
GENERICS | |
============================================================================= | |
TODO | |
*/ | |
/* | |
STANDARD LIBRARY | |
============================================================================= | |
*/ | |
// Take receiver and map it to another value | |
"Str".let { it.substring(it.indices) } | |
// Executes the passed block and returns receiver unchanged | |
"Str".apply { substring(0 until length) } | |
// Same as apply but with a parameter (use `it` instead `this`) | |
"Str".also { | |
println(it.substring(0 until it.length - 1)) | |
} | |
// Same as apply passing the instance as a parameter instead using a receiver | |
with("Str") { substring(0 until length) } | |
// For 'Closeable' objects | |
JavaFile("local_file.txt").writeText("text") | |
FileReader("local_file.txt").use { | |
println(it.readText()) | |
} | |
// Shortcut of if-param-throw | |
require(true) { "Invalid parameter" } | |
// Shortcut of if-var-throw | |
check(true) { "Invalid status" } | |
// Shortcut of `throw IllegalState ... | |
fun fail() { error("Invalid state") } | |
/* | |
OPERATOR OVERLOADING | |
============================================================================= | |
- Operators can be overloaded / overwritten, can't define new ones (as Scala) | |
- To define a new operator, use an infix function (closest syntax) | |
- Can be defined as extension functions or inside classes | |
*/ | |
operator fun String.get(range: IntRange) = this.subSequence(range) | |
"abcde"[1..3] | |
"abcde"[1 until 3] | |
/* | |
SEALED CLASSES | |
============================================================================= | |
*/ | |
sealed class Value { | |
class IntValue(val integer: Int) : Value() | |
class StringValue(val string: String) : Value() | |
data object SpaceValue : Value() | |
} | |
val input: Value = Value.IntValue(9) | |
when (input) { | |
is Value.IntValue -> println("Sealed Int" + input.integer) | |
is Value.StringValue -> println("Sealed String" + input.string) | |
is Value.SpaceValue -> println("Sealed Space") | |
else -> println("None of the above") | |
} | |
/* | |
TESTING | |
============================================================================= | |
*/ | |
// Functions can have names with spaces (useful for tests) | |
fun `If this then that`() { | |
assert("" is String) | |
} | |
/* | |
CODE DOCUMENTATION | |
============================================================================= | |
- Dokka is used to generate API documentation | |
- Output can be Markdown or HTML | |
*/ | |
/** | |
* You can document code using *Markdown*. And link other elements with brackets | |
* [BaseClass]. | |
* | |
* @param intParam Tags for documenting parameters are similar to Javadoc ones. | |
* @param stringParam Another parameter. | |
* @return Return value. | |
*/ | |
fun dokka(intParam: Int, stringParam: String): String = | |
"Code documentation $intParam $stringParam" | |
/* | |
OTHER FEATURES | |
============================================================================= | |
- Multi-platform projects: sharing common code across platforms | |
- Asynchronous programming helpers (coroutines) | |
Tools | |
----- | |
- Gradle: https://kotlinlang.org/docs/reference/using-gradle.html | |
- Maven: https://kotlinlang.org/docs/reference/using-maven.html | |
- IntelliJ IDEA: https://www.jetbrains.com/idea | |
- Eclipse: https://github.com/JetBrains/kotlin-eclipse | |
- SonarQube: https://docs.sonarqube.org/display/PLUG/SonarKotlin | |
- Detekt: static code analysis | |
- And of course... Vim: https://github.com/udalov/kotlin-vim | |
Third-party libraries | |
--------------------- | |
- Official Android language (Google support) | |
- Web (Kotlin): Ktor, HTTP4K, Hexagon | |
- Web (Java): Vert.x, Spring Boot, Micronaut, Jooby and Javalin | |
- Testing: MockK, Kotest and Spek | |
- Dependency injection: Kodein and Injekt | |
- It's very easy to use Java libraries, and they work perfectly | |
Resources | |
--------- | |
- Kotlin documentation: very complete and precise | |
- Kotlin Coding Standard | |
- Kotlin koans: when you finish them you will know the language quite well | |
- Kotlin playground: http://play.kotlinlang.org | |
- Blog: latest language news | |
- Slack: very active (has a channel in Spanish) | |
- Java/Kotlin comparison: https://www.kotlinvsjava.com | |
That's all folks! | |
----------------- | |
Thanks for coming | |
If you have questions, go ahead | |
*/ | |
"That's all folks!" // Use 'Unit' if you do not want to return output |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment