Skip to content

Instantly share code, notes, and snippets.

@jaguililla
Last active November 26, 2023 19:09
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jaguililla/ff8e671bb770478253553f5fb7a8ff5d to your computer and use it in GitHub Desktop.
Save jaguililla/ff8e671bb770478253553f5fb7a8ff5d to your computer and use it in GitHub Desktop.
#!/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