ted.neward@newardassociates.com | Blog: http://blogs.newardassociates.com | Github: tedneward | LinkedIn: tedneward
pick up some Kotlin syntax
understand Kotlin semantics
see some Kotlin idioms
walk away with an overview understanding of the language
Code: https://github.com/tedneward/Demo-Kotlin
Slides: http://www.newardassociates.com/presentations/BusyDevsGuide/Kotlin.html
What is Kotlin?
From the website:
"Statically typed programming language for the JVM, Android and the browser"
From Wikipedia:
"Kotlin is a statically-typed programming language that runs on the Java Virtual Machine and also can be compiled to JavaScript source code"
Kotlin is...
statically typed
object-oriented
compiled
cross-targeting
JVM
Android (Dalvik, ART)
JavaScript
IntelliJ has a plugin
Command-line installs
Eclipse plugin
but if you're still using Eclipse... I'm just so, so sorry
(nothing to do!)
Manual install:
Download the package, unzip it
Make sure Kotlin bin/ directory is on the PATH
SDKMAN!
Use SDKMAN! to handle installs (any *nix system)
$ curl -s https://get.sdkman.io | bash
$ sdk install kotlin
OSX Homebrew
$ brew install kotlin
Hello, Kotlin
fun main(args: Array<String>) { println("Hello, World!") }
Compiling the standalone executable (command-line)
$ kotlinc hello.kt -include-runtime -d hello.jar $ java -jar hello.jar Hello World! $
(This produces a "fat JAR"--includes all the Kotlin dependencies)
Compiling the standalone executable (command-line)
$ kotlinc hello.kt
(This produces HelloKt.class in the current directory)
$ kotlin HelloKt Hello World! $
(This uses the JVM to run HelloKt.)
Running kotlinc as a scripting tool
Code expected to start execution on line 1 (hello.kts):
println("Hello, World!")
$ kotlinc -script hello.kts
use "kotlinc-js" to do the transformation
compile either "executables" or "libraries"
libraries can be distributed and consumed as JAR files
curly-brackets for scope
alpha/alphanumeric identifiers
/* */ multiline comments
// end-of-line comments
optional semicolon line terminators
indicates lexical namespace for all types found within
the unnamed/unspecified package is called "default"
no relationship between package name and filesystem state is assumed
allows for flexible packaging conventions
use "import" to bring in top-level lexical elements
import foo.Bar
makes "Bar" accessible without qualification
import foo.*
makes all foo elements accessible without qualification
import foo.Bar as bBar
makes foo.Bar accessible as bBar
Primitive types
"everything is an object"
val
denotes immutable value declaration
var
denotes mutable variable declaration
"destructuring" declarations permitted
if the instance being destructured supports this
suffixed ("Pascal-style") type descriptors
Kotlin will infer type if type descriptor is absent
Nullable type declared with "?" suffix
Boolean
true
, false
: literals
logical operations: ||
, &&
, !
Numbers
Integral Numbers: Long
(64), Int
(32), Short
(16), Byte
(8)
integral literals are Int by default
Long literals require an "L" suffix
Floating-point Numbers: Double (64), Float(32)
floating-point literals are Double by default
Float literals require an "F" or "f" suffix
Hexadecimal constants: 0x prefix; Binary constants: 0b prefix
Numbers
No implicit conversion (except for literals)
Explicit conversion required for widening or narrowing conversions
All numeric types support conversion methods:
toByte
, toShort
, toInt
, toLong
toFloat
, toDouble
, toChar
Kotlin conversions
val a: Int = 10000 print(a === a) // Prints 'true' val boxedA: Int? = a val anotherBoxedA: Int? = a print(boxedA === anotherBoxedA) // !!!Prints 'false'!!! val b: Byte = 1 // OK, literals are checked statically // val i: Int = b // ERROR val i: Int = b.toInt() // OK: explicitly widened
from https://github.com/tedneward/Demo-Kotlin
Char: single character
single-quote literals
backslash-escaped: \t \b \n \r ' " \ $
Unicode escape sequence syntax: '\uFF00'
Strings: multiple-character
double-quite literals
triple-quote "heredoc" strings
individual elements accessed using ""
elements can be iterated using "for" loop
supports "${expression}" interpolation
Kotlin strings
val s = "Hello, world!\n" val s2 = "s = ${s}!" val text = """ for (c in "foo") print(c) """ val quote = """ |Tell me and I forget. |Teach me and I remember. |Involve me and I learn. | (Benjamin Franklin) """.trimMargin()
from https://github.com/tedneward/Demo-Kotlin
ordered collection
often a great replacement for an Array
supports mutable and immutable
construct using mutableListOf()
or listOf()
Kotlin lists
val items = listOf(1, 2, 3, 4) items.first() == 1 items.last() == 4 val evens = items.filter { it % 2 == 0 } println(evens == listOf(2, 4)) // true val rwList = mutableListOf(1, 2, 3, 4) if (rwList.none { it > 6}) println("No item in rwList is greater than 6")
from https://github.com/tedneward/Demo-Kotlin
unordered collection, guarantees uniqueness
supports mutable and immutable
construct using mutableSetOf()
or setOf()
Kotlin sets
val numbers = hashSetOf(0, 1, 2, 2, 3, 3, 3, 4, 5, 4) println(numbers) // [0, 1, 2, 3, 4, 5]
from https://github.com/tedneward/Demo-Kotlin
unordered collection of key/value pairs
also called "hashes" or "dictionaries" in other languages
supports mutable and immutable
construct using hashMapOf(key to value, ...)
Kotlin maps
val readWriteMap = hashMapOf("foo" to 1, "bar" to 2) println(readWriteMap["foo"]) // prints "1" val snapshot: Map<String, Int> = HashMap(readWriteMap) println(snapshot) //snapshot["foo"] = 12 // ERROR!
from https://github.com/tedneward/Demo-Kotlin
represented by Array
class
fixed-size, provides size
, []
access and iteration
construct using arrayOf
or arrayOfNulls
, or factory function
specialized intArrayOf
for each primitive type available
generally only useful for Java interop; prefer Lists to Arrays
Kotlin arrays
fun main(args: Array<String>) { // {{## BEGIN arrays ##}} val nums = arrayOf(1, 2, 3) // [1, 2, 3] println(nums[0]) //nums[1] = 12 // ERROR! println(nums[2]) val nulls : Array<Any?> = arrayOfNulls(3) // [null, null, null] val asc = Array(5, { i -> (i * i).toString() }) // [0,1,4,9,16] println(asc) // {{## END arrays ##}} }
from https://github.com/tedneward/Demo-Kotlin
finite sequence
formed using rangeTo
or ..
operator
membership tested using in
or !in
operators
supports iteration
Kotlin ranges
val nums = 0..10 // 0,1,2,3,...,10 if (1 in nums) println("true!") val desc = 10 downTo 0 // 10,9,8,...,0 val evens = 0..10 step 2 // 0,2,4,...,10 if (5 in evens) println("Errr... broken?") val odds = 1..10 step 2 // 1,3,5,...,9 if (5 in odds) println("true!")
from https://github.com/tedneward/Demo-Kotlin
if
(decision-branching)
true
/false
expression evaluation
itself is an expression (yields a value)
each branch can be a single expression or a block
if a block, last value is assumed value for the block
Kotlin if
var a = 5 var b = 12 var max: Int = 0 if (a > b) max = a else max = b // As expression val max2 = if (a > b) a else b
when
(decision-branching)
replaces 'switch' from C-family languages
matches expression sequentially against branches until a match is found
else
branch is used if no match is found
can be either statement or expression
Kotlin when
val x = 2 when (x) { 1 -> println("x == 1") 2 -> println("x == 2") else -> { // Note the block println("x is neither 1 nor 2") } }
when branch conditions
can be combined with a comma
can be arbitrary expressions
can be in a range or collection
can check for type using "is"
Kotlin when
val s = "Fred"; val validNumbers = 1..10 when (x) { 0, 1 -> println("x == 0 or x == 1") parseInt(s) -> println("s encodes 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("otherwise") } val hasPrefix = when(y) { is String -> y.startsWith("prefix") else -> false } print(hasPrefix)
for
(iteration/loops)
iterates through anything that provides an iterator
body can be either single expression or block
note that different collections permit different iteration options
Kotlin for
val array = arrayOf(1, 2, 3, 4, 5, 6) for (i in array) println(i) for (idx : Int in array) { println(idx) } for (index in array.indices) { println(array[index]) } for ((index,value) in array.withIndex()) { println("the array element at $index holds $value") }
while
, do
/while
(loops)
each takes statement body for repeated execution
while evaluates expression before evaluation of body
do/while evaluates expression after evaluation of body
break
terminates nearest enclosing loop
continue
proceeds to next step of nearest enclosing loop
return
returns from nearest enclosing function
Kotlin manages errors as Exceptions
(very much in line with the JVM)
exception objects are thrown "up the stack"
exception objects can be caught and handled or re-thrown
Throwing an exception is simple
"throw (instance)"
type must derive (ultimately) from Throwable
types usually imply some level of detail
Kotlin does not follow Java standard of "checked/unchecked" exceptions; all exceptions are "unchecked"
Guarded blocks frame exception-handling behavior
try
indicates guarded block
body of try indicates area of code guarded
guarded block must have 1+ catch
clauses and/or finally
clause
try
block is itself an expression (yields a value)
Kotlin Exceptions
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
fun
keyword
optional identifier name (none = anonymous function literal)
parenthesized parameter list
parameter declarations can have default values
vararg-declared parameters are an Array-of-T parameter
return type descriptor
no return means return type is Unit or can be omitted
expression or block
single-expression functions can omit the curly brackets and return type
parenthesized invocation syntax
type/number of parameters must match
parameter names can be given at point of call to replace default-valued parameters
note that if last parameter to a function is a function, a lambda can be passed outside of the parentheses
Function declarations
fun add(left: Int, right: Int): Int { return left + right } val five = add(2, 3) val adder = fun(left: Int, right: Int): Int { return left + right } val six = adder(3, 3) val adderUp = fun(left: Int, right: Int): Int = left + right
Function parameter variations
fun doComplicatedThings(a: Int = 0, b: Int = -1, c: Boolean = false): Int { return if (c) a else b } doComplicatedThings() // = -1 doComplicatedThings(c = true) // = 0 fun log(msg: String, vararg msgs: String): Unit { print(msg) for (m in msgs) { print(" " + m) } println() } log("Howdy!") log("Howdy","y'all!")
non-parenthesized reference syntax
function parameter list (type/number) and return type must match
function type is marked as (T1, T2, ...) -> RT
where T1
, T2
, ... are parameter types and RT
is return type
parenthesized parameter names (no types)
singular parameters can be defined without parens
singular parameters can be omitted entirely using default name "it"
->
body
Lambdas
val ints = arrayOf(4, 8, 16) val doubled = ints.map { it -> it * 2 } val halved = ints.map { it / 2 } fun doSomethingNTimes(times: Int, body: () -> Unit) { for (i in 1..times) { body() } } doSomethingNTimes(5) { println("Whee!") }
standardized parts of Kotlin library
set up a "context object" for block of code
useful in a variety of situations
let
run
with
apply
also
takeIf
, takeUnless
context object is an argument (it
)
return value is the lambda result
let use
/* Instead of: val numbers = mutableListOf("one", "two", "three", "four", "five") val resultList = numbers.map { it.length }.filter { it > 3 } println(resultList) we can write: */ val numbersStr = mutableListOf("one", "two", "three", "four", "five") numbersStr.map { it.length }.filter { it > 3 }.let { println(it) } // ... or even... numbersStr.map { it.length }.filter { it > 3 }.let(::println) // Safe null-value handling: val str: String? = "Hello" // change to null to demonstrate //processNonNullString(str) // compilation error: str can be null val length = str?.let { println("let() called on $it") processNonNullString(it) // OK: 'it' is not null inside '?.let { }' it.length }
context object is implicit (this
)
return value is the lambda result
with use
// Assume numbersStr is mutableListOf("one", "two", "three", "four", "five") val firstAndLast = with(numbersStr) { "The first element is ${first()}," + " the last element is ${last()}" } println(firstAndLast) // "one", "five"
context object is implicit (this
)
return value is the lambda result
run use
data class Person(var firstName: String = "", var lastName: String = "", var age: Int = 0, var city: String = "") // Attached (via extension) to an object val person = Person().run { firstName = "Ted" lastName = "Neward" age = 51 } // Generic block returning a result val hexNumberRegex = run { val digits = "0-9" val hexDigits = "A-Fa-f" val sign = "+-" Regex("[$sign]?[$digits$hexDigits]+") } for (match in hexNumberRegex.findAll("+123 -FFFF !%*& 88 XYZ")) { println(match.value)
context object is implicit (this
)
return value is the object itself
apply use
// Assume: data class Person(var firstName: String = "", var lastName: String = "", // var age: Int = 0, var city: String = "") val adam = Person("Adam").apply { age = 32 lastName = "London" } println(adam)
context object is the argument (it
)
return value is the object itself
also use
numbersStr .also { println("The list elements before adding new one: $it") } .add("six")
context object is implicit (this
)
return value is the lambda result
takeIf/takeUnless use
val rngNumber = Random.nextInt(100) val evenOrNull = rngNumber.takeIf { it % 2 == 0 } val oddOrNull = rngNumber.takeUnless { it % 2 == 0 } println("even: $evenOrNull, odd: $oddOrNull") val hellostr = "Hello" val caps = hellostr.takeIf { it.isNotEmpty() }?.uppercase() //val caps = str.takeIf { it.isNotEmpty() }.uppercase() //compilation error println(caps)
followed by identifier
primary constructor appears after identifier
includes parameter list
named parameters can be referenced inside class body
val
or var
declared parameters declare immutable or mutable properties
parameters can have default values and/or varargs, just as with functions
initializer blocks (init
) in class body are executed on construction
use class type as "constructor function" (no "new")
constructor parameter list (type/number) must be satisfied
Simple class and use
class Person(var firstName: String, val lastName: String, var age: Int) { init { println("Person $firstName $lastName constructed") } } val ted = Person("Ted", "Neward", 45) println("${ted.firstName} is ${ted.age} years old")
Classes can declare secondary constructors
must delegate to primary constructor using ": this(params)" after declaration
body is then able to provide whatever logic/behavior desired
Secondary constructors
class Person(var firstName: String, val lastName: String, var age: Int) { constructor(soleName: String, age: Int) : this(soleName, "", age) { } }
Use
val ted = Person("Ted", "Neward", 45) println("${ted.firstName} is ${ted.age} years old") val cher = Person("Cher", 39) println("${cher.firstName} is ${cher.age} years old")
Classes can contain
secondary constructors
properties (field-like things)
functions (methods)
object declarations (statics)
nested/inner classes
nested classes declared with "inner" have access to private members of enclosing class
properties can be read-only (val
) or mutable (var
)
properties can be initialized with values
type declaration is optional if initializer is present
Simple property declarations
public class Address { public var name: String = "" public var street: String = "" public var city: String = "" public var state: String? = "" public var zip: String = "" }
Simple property usage
val addr = Address() addr.name = "Fred Flintstone" addr.street = "1 Bedrock Way"
get()
returns a value of the correct type
set(value)
provides for validation concerns
these both "attach" to the immediate-previously-declared property
both are functions (with all the syntactic shortcuts)
backing fields referenced via field
keyword
Getter/setter usage
class SecretAsset { var codeName: String = "" var realName: String = "" get() = "<REDACTED>" set(value) { field = value } }
define a "backing property"
operates/behaves basically like languages without properties at this point
Backing properties
class Container { private var _table: Map<String, Int>? = null public val table: Map<String, Int> get() { if (_table == null) _table = HashMap() return _table ?: throw AssertionError("Set to null by another thread") } }
for compile-time constants only
must be top-level in a package or as part of an object
must be initialized with String or primitive type
no custom getter
only available on var
properties
cannot have custom getter/setter
not exactly the same as "initialized on first use"; just implies "initialization will happen later", after constructor is finished (but before first use!)
enforced by exception if used before initialized
expressed either as methods (named) or operators (symbolic or inferred)
both are essentially functions in character
Methods
class FootballPlayer { fun run() { println("Running!") } fun pass() { println("Passing!") } }
val genoSmith = FootballPlayer() genoSmith.run() genoSmith.pass()
operator overloads are methods by fixed names
unary operations
"+a" == "unaryPlus()"
"-a" == "unaryMinus()"
"!a" == "not()"
"a++" == "inc()" (shouldn't modify object)
"a--" == "dec()" (shouldn't modify object)
binary operations
"a + b" == plus()
"a - b" == minus()
"a * b" == times()
"a / b" == div()
"a % b" == mod()
"a..b" == rangeTo()
"a in b" == b.contains()
subscript operations
"ai" == "a.get(i)"
"ai, j" == "a.get(i, j)"
"ai_1, ..., i_n" == "a.get(i_1, ..., i_n)"
"ai = b" == "a.set(i, b)"
"ai, j = b" == "a.set(i, j, b)"
"ai_1, ..., i_n = b" == "a.set(i_1, ..., i_n, b)"
invocation operations
"a()" == "a.invoke()"
"a(i)" == "a.invoke(i)"
"a(i, j)" == "a.invoke(i, j)"
"a(i_1, ..., i_n)" == "a.invoke(i_1, ..., i_n)"
Operators
data class Money(val amount: Int, val currency: String = "USD") { operator fun plus(other: Money): Money { if (this.currency != other.currency) throw Exception("Conversion required!") else return Money(this.amount + other.amount, this.currency) } } operator fun Money.unaryMinus() = Money(-amount, currency)
val salary = Money(100000, "USD") val bonus = Money(50000, "USD") println(salary + bonus)
Member functions can be used with infix notation
mark method with infix
name need not be an operator
typically must return a value
Infix operations
class Matrix(var number : Int) { infix fun rotate(amt: Int): Matrix { return this } }
var m = Matrix(5) m = m rotate 5
Object declarations
pseudo-replacement for static members/fields/methods
use "object" instead of "class" to create Singleton instance
objects can have supertypes if desired
use object identifier name to reference instance
Objects
object DataProviderManager { fun registerDataProvider(provider: DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ... } DataProviderManager.registerDataProvider(...)
Companion objects can be declared using "companion" keyword
companion object declared/defined inside its partner
methods on companion object are "as if" they were declared on the enclosing class
name can be omitted (in which case the companion is called "Companion")
Companion objects
class MyClass { companion object Factory { fun create(): MyClass = MyClass() } } val instance = MyClass.create() class MyOtherClass { companion object { } } val companionInstance = MyOtherClass.Companion
Access consists of four modifiers:
public
(default if nothing specified)
private
internal
protected
Package-level declarations
public
: accessible everywhere
private
: only visible inside the file
internal
: visible to entire module
protected
: N/A
Class declarations
public
: visible everywhere
private
: visible in this class alone
internal
: visible in this class and the rest of the module
protected
: visible in this class and subclasses
meaning one base implementation class
same rules as most O-O post-C++ languages
base class must be declared "open"
meaning, it must be declared ready for inheritance
base class appears after class name and colon
after primary constructor parameter list
base class primary constructor parameters passed here
if the base has no primary, secondary ctors must use super()
Simple inheritance
open class Base(p: Int) class Derived(p: Int) : Base(p) // Must always pass parameters required by base class constructor
this is not java.lang.Object
it provides a much smaller interface
toString()
equals()
hashCode()
Equivalent declarations
open class Base : Any open class AlsoBase // implicitly extends "Any"
B is A
: is
operator tests B to see if it is type-compatible with A
yields true/false value
"!is" yields inverse value
"smart check"; Kotlin can infer that if "A is B", "A" should be cast to B
"smart casts" apply to if, when and while expressions (and a few other places)
"unsafe" cast operator "as"
throws exception if the cast is not safe
null will not cast as per Java rules; nullable type ("?") comes into play
"safe" cast operator "as?"
yields correctly-cast object on success
yields null on failure
Casting
open class Mammal class Human(val name : String = "Fred") : Mammal() class Bear() : Mammal() fun offerBeer(body : Mammal) { if (body is Human) { println("Here, have a beer, ${body.name}") // Notice the "smart cast"--inside this block, // "body" is a reference to Human, not Mammal // so no explicit cast required } } val human = Human("Ted") val bear = Bear() offerBeer(human) // "Here, have a beer, Ted" offerBeer(bear) // No exception, nothing prints
"Unsafe" Casting
fun offerWhiskey(body : Mammal) { body as Human // or we could write as val whiskeyDrinker = body as? Human } offerWhiskey(human) // cast works without issue //offerWhiskey(bear) /* Exception! java.lang.ClassCastException: class Inheritance$Bear cannot be cast to class Inheritance$Human */
like base classes, base properties must be declared "open"
"override" can also be used in the primary constructor of the derived
Overriding properties
open class Person(val firstName: String, val lastName: String, val age: Int) { open var salary: Int = 50000 } class Actor(val name: String, var oscars: Int) : Person(name, "", 39) { override var salary: Int get() { return 1000000 } set(value) { /* ignored */ } } val willSmith = Actor("Will", 0) println(willSmith.salary)
like base classes, base methods must be declared "open"
derived method must be declared "override"
override methods are implicitly "open"
to prevent further overrides, use "final"
as with any virtual dispatch, most-overridden method is called
call up to base class using super
Overriding methods
open class Base { open fun v() { } fun nv() { } } class Derived() : Base() { override fun v() { } }
cannot be instantiated
must be inherited
abstract
implies open
if a member is abstract, so must the class
a class can be abstract w/o any abstract members
Abstract classes
abstract class Shape(val x : Int, val y : Int) { abstract val area : Double abstract val perimeter : Double } class Circle(x : Int, y : Int, val radius : Int) : Shape(x,y) { override val area : Double get() { return 3.14 * radius * radius } override val perimeter : Double get() { return 2 * 3.14 * radius} } val shapes = listOf(Circle(0,0,3)) for (shape in shapes) println(shape.area)
"object expressions"
builds an anonymous dervied class
use object
keyword, then inherit from desired base type and/or interfaces
constructor obligations must be fulfilled as normal
can access variables from enclosing outer scope
useful for one-off derivatives
Object expressions
open class A(x: Int) { public open val y: Int = x } interface B { fun doSomething() } val ab: A = object : A(1), B { override val y = 15 override fun doSomething() { println("Yay $y") } } ab.doSomething()
simply declare object
and describe contents
unknown type, one-off declaration
only useful within very short scope
Ad-hoc object expressions
val adHoc = object { var x: Int = 0 var y: Int = 0 } print(adHoc.x + adHoc.y)
interfaces cannot have state/properties/fields
interfaces "promise" behavior on the part of implementors
Kotlin interfaces can also carry default behavior
A simple interface
interface MyInterface { fun bar() fun foo() { // optional body } }
any number of interfaces allowed
interface methods are all implicitly "open"
if member names clash, use "super" to resolve
only necessary when two interfaces implemented on a class have same method and both have default implementations
these can either be abstract or have default getter implementation
interfaces cannot have backing properties/fields
Interface with properties
interface MyInterface { val property: Int // abstract val propertyWithImplementation: String get() = "foo" fun foo() { print(property) } } class MyClass : MyInterface { override val property: Int = 29 }
Some classes just want to be named bundles of data
Kotlin calls these "data classes"
use the "data class" keyword
must follow the following requirements
primary constructor must have 1+ parameters
all primary constructor parameters must be val/var-declared
cannot be abstract, sealed, open or inner
may not extend other classes (but may implement interfaces)
Kotlin can generate numerous support methods/properties automatically
Data classes out-of-the-box support:
equals()
/hashCode()
toString()
destructuring declaration support
copy()
(cloning with named parameter replacements)
Data classes
data class Currency(var amount: Int, val type: String) // Implicitly has toString(), hashCode(), equals(), and copy()
Using a data class
val yourSalary = Currency(100000, "USD") println("Your salary is ${yourSalary.type}${yourSalary.amount}") val mySalary = Currency(100000, "USD") if yourSalary == mySalary { println("We are peers") } val euroSalary = salary.copy(type = "EUR") println("In Europe, you would make ${euroSalary}")
Basic usage
type-safe bounded list of possible values
"enum class" followed by list of values (typically all-capped)
Basic enums
enum class Direction { NORTH, SOUTH, EAST, WEST } val heading = Direction.NORTH println(heading)
Enums can also be initialized with values
defined in enumeration typename
parameter name available as property on enum instance
Valued enums
enum class Color(val rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF) } val r = Color.RED println(r.rgb)
Enums can also declare anonymous (sub) classes
provides limited override capabilities
great for state machines
Anonymous subclass enums
enum class ProtocolState { WAITING { override fun signal() = TALKING }, TALKING { override fun signal() = WAITING }; abstract fun signal(): ProtocolState } var state = ProtocolState.WAITING println(state) // WAITING state = state.signal() println(state) // TALKING
many similarities to enumerated types, but full-blown class/subclasses
also known as "discriminated unions" in some languages
all derived classes appear within sealed class body
subclasses of derived don't necessarily have to (but probably should)
A sealed expression library
<</home/runner/work/Slides/Slides/Content/Kotlin/code/classes.kts NOT FOUND>>
Kotlin supports parameterized types
that is, types that take parameters
think of them as "placeholders" for a type used internally
adds greater type-safety
type parameter specified in angle brackets
type parameters are part of signature
must be supplied (or inferred) at construction
Simple generics example
class Box<T>(t: T) { var value = t } val box1: Box<Int> = Box<Int>(1) val box2 = Box(1) // 1 is-a Int, so box2 must be a Box<Int>
Functions can also have type parameters
declare the type parameter before the function
multiple generic parameter types require multiple type parameters
Simple generic functions
fun <T> singletonList(item: T): List<T> { // ... } fun <T,U> swap(params: Pair<T,U> ): Pair<U,T> { return Pair(params.second, params.first) }
Annotations are metadata for code
usage is @
-prefixed (a la Java)
can be customized to particular class elements if necessary
definition uses annotation
keyword before a class
some restrictions, but mostly minimal
Usage
declare before target element
for primary constructor, use "@Annotation constructor" syntax in class declaration line
for property setters, declare annotation before "get/set" keyword
function literals can also have annotations
declare right before opening bracket
when using annotations, sometimes more precise declaration usage is required
these are called "Use-site targets"
colon-prefixed "keyword" before the annotation type name
Use-site target list: file, property, field, get, set, receiver, param, setparam, delegate
Annotation definition
<<https://raw.githubusercontent.com/tedneward/Demo-Kotlin/main/annotations.kts NOT FOUND>>
Annotation definition
<<https://raw.githubusercontent.com/tedneward/Demo-Kotlin/main/annotations.kts NOT FOUND>>
Definition
use annotation
instead of class
additional annotations can specify annotation attributes
@Target
: which elements can this annotation apply to
@Retention
: where is the annotation value stored
@Repeatable
: allows annotation to be used multiple times
@MustBeDocumented
: include annotation in generated documentation
annotations may have constructors
parameter types restricted to primitives, strings, classes, enums, other annotations and arrays of any of the previous list
these are turned into annotation properties
Annotation definition
<<https://raw.githubusercontent.com/tedneward/Demo-Kotlin/main/annotations.kts NOT FOUND>>
Kotlin Reflection is access to code elements by name without invocation
uses ::{name}
syntax to reference code elements
class
, get()/set()
, code element by name, and so on
particularly useful where function objects are expected
functional-style programming idioms
Function composition in Kotlin
<</home/runner/work/Slides/Slides/Content/Kotlin/code/functions.kts NOT FOUND>>
Dynamic is a type that represents a dynamic type
in essence, it turns off the static type checker
unavailable when targeting the JVM
useful for interoperation with dynamic runtime target environments
for now, that means "ECMAScript"
Dynamic is a type
dynamic values can be assigned to any variable
dynamic values can be passed for any parameter
any value can be assigned to a dynamic instance
any value can be passed for a dynamic parameter
null checks are disabled for dynamics
dynamic calls always return dynamic
Kotlin is...
an object-oriented language
with some functional features/primitives
and a dash of some syntactic flexibility
but overall, a relatively easy language to approach, understand, and use
Architect, Engineering Manager/Leader, "force multiplier"
http://www.newardassociates.com
http://blogs.newardassociates.com
Sr Distinguished Engineer, Capital One
Educative (http://educative.io) Author
Performance Management for Engineering Managers
Books
Developer Relations Activity Patterns (w/Woodruff, et al; APress, forthcoming)
Professional F# 2.0 (w/Erickson, et al; Wrox, 2010)
Effective Enterprise Java (Addison-Wesley, 2004)
SSCLI Essentials (w/Stutz, et al; OReilly, 2003)
Server-Based Java Programming (Manning, 2000)