path | title |
---|---|
/learnings/kotlin |
Learnings: Kotlin |
- Release schedule
- Stupid Things I always forget
- Classes
- Kotlin Standard Library
- Flow Control
- Functional Programming Patterns
- Random Notes
- Classes, Objects and Instances
- Nullability
- Duration
- Casts
- Generics
- Delegates
- DSL Stuff
- Ughhh Kotlin makes me sad
- Build Tools and Kotlin
- Basic Language Concepts
- Coroutines and kotlinx-coroutines
- and Java Interop
- Book Recommendations
1.x version once every 6 months
- var: mutable variable (can be reassigned)
- val: read only variable (not NOT be reassigned, like ES6's
const
, orfinal
in Java). Mneomnic: both final and val end with "l".
fun methodName(parameterOne: ParameterType): ReturnType {}
fun methodName(parameterOne: ParameterType) = foobar(x)
this second one will figure out the return type of foobar
and set the return type to the return type of that.
NOTES: you can use these labels at the call site
methodName(parameterOne=ParameterType.FIRST_WHATEVER)
(means you can also pass parameters out of order if they are all labelled)
rd.map { it * 1.35 } Here the function { it * 2.35 } has no name. But you could give it a name in order to be able to reuse it: val ri: Result = ... val rd: Result = ri.flatMap(inverse) val function: (Double) -> Double = { it * 2.35 } val result = rd.map(function)
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
The { x } syntax is the simplest way to write any constant function returning x, whatever argument is used.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
When the lambda is the last argument of a function, it can be put outside of the parentheses:
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
So you can assign multiple variables from attributes of a class on one line
Create operator
functions whose names are componentN
where N is as high as you want to count.
example: operator fun component1() = myField
You do NOT need to do this for data classes.
Kotlin documentation on destructuring declarations
unlike Java, the enclosing class has no access to private inner or nested classes
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Unlike Java, Kotlin has no package private visibility (the default visibility in Java).
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
An element can also be declared internal, meaning it’s only visible from inside the same module. A module is a set of files compiled together
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
TODO: primary vs secondary constructors
(auto generated) getters / setters of properties have to have the same or less than access permissions than their backing field
technically access of a property is - in background - using its auto-generated setter/getter
migrating a previously used accessor method to use a new backing field / implementation:
Two places to declare properties:
- primary constructor
- inside the class
var fullTime = maybeParameterFromPrimaryConstructorOrNot
get() {
something
return field // ONLY place you can use this keyword!
}
set(value) {
field = value
}
When you just want to store information about some state
data class MyClass(val name: String, val anotherThing: Int) {}
This gives us default values for:
- toString
- equals
- hashCopy
- copy
based on parameters in primary constructor.
primary constructor has to have at least one parameter, parameters must be val/var. Can't be abstract, sealed or inner class.
The invoke function declared with the keyword operator can be called as ClassName().
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Note that you can’t use the List() syntax to call the invoke function without an argument. You must explicitly call it; otherwise, Kotlin thinks you’re calling a constructor
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
MutableMap can’t be used in a multi-threaded environment without providing some protection mechanisms that are difficult to design correctly and to use. The Map type, on the other hand, is protected against these kind of problems
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
could use Kotlin Sequences (yes kind of like Java's Streams API, but not limited to JVM)
this is a lazy collection!
(it does add some overhead, so may be less useful in small collections, but if you're here you likely have a big collection).
two kinds of operations: intermediate, and terminal.
val l = listOf(1, 2, 3, 4)
l.asSequence().filter { ... }.map { ... }
println( l.toList() ) // terminal operation to get value of sequence
A label is a Java style annotation of any expression, but enables you to essentially reference that label - or the variable context around that label - ie to return out of a lambda without exiting its method, or to reference an above context when you are in a lower context.
Easy example: you have a for
inside a for
and want the inner for
to essentially do a next
on that outer iteration.
Medium example: you could use this to break out of collection/functional methods!!
Hard example: use a label a couple lambdas deep to get at the context object passed into the parent (or grandparent!) lambda. (Turing help you, and you probably want to refactor things to not do this, but.....)
Easy example documented:
fun foo() {
listOf(1, 2, 3, 4, 5).forEach lit@{
if (it == 3) return@lit // local return to the caller of the lambda - the forEach loop
print(it)
}
print(" done with explicit label")
}
having a plain return
in that lambda would exit the foo
function. But instead we just want to exit to the forEach
block (we have some cleanup or something to do after the functional work).
Kotlin documentation on labels
like try/catch/finally in Java
NOTES:
- can NOT return a function value in
finally
(will be ignored) catch (t: Throwable)
>catch(t: Exception)
. See SO answer pointing to tweets by head Kotlin language designer
No such thing as checked exceptions
(classifications / patterns taking from my blog entry on intermediate functional programming patterns in Javascript
Kotlin offers a simplified syntax for lambdas with a single parameter. This parameter is implicitly named it.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
The value returned by the lambda is the value of the expression on the last line.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Variables that live outside the lambda/closure can be changed inside the closure (ie: unlike Java does not have to be final
). See Java_Lambda_Outside_Variable_Restrictions for info on that restriction in Java.
val mn = listOf("January", "February","March","April","May","June","July","August","September","October","November","December")
mn.forEach { whatMonth: String ->
println(whatMonth)
}
For multiple parameters: { (whatMonth: String, monthNumber: int) ->
Q: What about specifing the return type (ie the type inferience has fubar-ed up...)? A: No, you can not. Kotlin suggests writing an anonymous function and passing it, instead of inlining / using lambda literals.
Kotlin supports Learning_Java_Lambdas_Single_Abstract_Interface and it Just Works like it does it Plain Ol' Java.
A Result type returns some success object of type T, and on failure returns a Throwable (of which you have no control over the type)
fun myThing(): Result<Boolean> {
return Result.success(true)
}
myThing().isFailure
myThing().getOrThrow()
fun myThing(): Result<Boolean> {
return Result.failure(Exception("boo"))
}
myThing().isFailure
myThing().getOrThrow()
A (probably better) result type is kotlin-result, which lets you ?? more easily model success or failure
kotlin
fun myThing(): Result<Boolean, String> {
return Ok(true)
// or...
return Err("boo!")
}
kotlin
fun callMyThing() {
val res = myThing()
// want to get the Value and Error seperately?
// (just use Kotlin's destructuring abilities!)
val (value, error) = res
val actualResult = res.getOr(default=false)
// ^^ gets the Value part, or if there was an Error return the default parameter value
val value = res.getOrElse { it == "boo!" }
// ^^ gets the Value part, or if there was an Error call that lambda.
// ESPECIALLY good for guard clauses where you want to exit the function if you have an error!!!!!!!
}
methods are final by default
try with resource construct, provided these resources implement either Closable or AutoClosable. The main difference is that to achieve this, Kotlin offers the use function:
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Overriding: need to BOTH add override
in your subclasses declaration of a method AND add open
to your declaration of the mehod in the super class
abstract: (open by default)
NOTE: that override
also by default means your methods are open
too!
By default, Java void is mapped to Unit type in Kotlin. This means that any method that returns void in Java when called from Kotlin will return Unit — for example the System.out.println() function.
Also, Unit is the default return type and declaring it is optional, therefore, the below function is also valid:
There is a Nothing
type which kinda works like null but with more type checking???!!!
Everything in Kotlin is public final
by default
So if you want an extendable class you need to use open class MySuperClass
.
abstract
classes are open by default.
Like Java BUT not the static part
Declares a class AND ALSO it's a singleton
fun Class.someMethod()
In this you can access all the public members in Class
These can also be given access controls and thus only usable inside the method it was declared in. But to force it can do final override fun foobar()
in declaration.
can NOT be opened, inner, or abstract: are final no way around this. (They can inherit).
Kotlin type checks against nullable
<<Kotlin_Let_Wrapping_An_Optional>>
let is often used for executing a code block only with non-null values. To perform actions on a non-null object, use the safe call operator ?. on it and call let with the actions in its lambda.
val str: String? = "Hello"
//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
}
YES THAT ? part of the ?.let
is IMPORTANT!!
For more information around let
, see Kotlin_Scope_Functions
ALSO NOTE: this could be an alternative to Swift's if-let statements
swift
if let l = functionThatMayReturnNull() {
print("l is something!")
}
You'll write this in Kotlin as:
kotlin
functionThatMayReturnNull().let { l ->
println("l is something")
}
(Personally I'm not sure hiding the if
statement in this way is a good idea, because it hides that potentially smelly if
behind some syntax sugar.... but whatevs
Older ways are deprecated.
The new way
1.minutes
Except, to do this new way, you have to first
import kotlin.time.Duration.Companion.minutes
NO, you can NOT just import kotlin.time.Duration.Companion.*
, that would be too easy.
List of words you can import at the Companion Object Properties parts of the Kotlin docs.
if you do an is
check before when you would do a cast, then the cast is automatic
fun demo(x: Any) {
if (x is String) {
print(x.length) // x is automatically cast to String
}
}
val x: String = y as String
val x: String? = y as String?
if (something is List<*>) {
something.forEach { println(it) } // The items are typed as `Any?`
}
Comparable is contravariant on T, meaning that the type parameter T occurs only in the in position
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
quick rundown: out
keyword for covariant means can only use type T in ie function return places
CAN get around this by using inline functions and reified
keyword
BUT it only works when type information is known at call site at compile time. (ie probably can't be used in an abstract class)
See also:
- https://kotlinlang.org/docs/reference/inline-functions.html#reified-type-parameters
- https://stackoverflow.com/a/48960832/224334
Kotlin also supplies standard delegates, among which Lazy can be used to implement laziness:
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
See Kotlin_Delegate_Equivalent
You can do this with infix notation.
Rules:
- must be member functions
- must have a single parameter
- method must not accept variable number of args and must not have a default value
<<Kotlin_Scope_Functions>>
Scope functions: let
, run
, also
, with
, apply
From Kotlin documentation:
run
,with
, andapply
refer to the context object as a lambda receiver - by keyword this.
In turn,
let
andalso
have the context object as a lambda argument. If the argument name is not specified, the object is accessed by the implicit default name it. it is shorter than this and expressions with it are usually easier for reading. However, when calling the object functions or properties you don't have the object available implicitly like this.
let
is good for turning optional objects into non-optional objects
var str: String? = "Hello"
str?.let { println("only called when str is non null") }
You could also use this as a very fancy if statement
str?.let { it } ?: "default value"
<<Kotlin_Delegate_Equivalent>>
AKA: Kotlin version of Groovy's Delegate object
See Kotlin Documentation: Scope functions
Inside the scope (lambda) of the function you'll be able to call methods of (the current scope object) without having to specify the variable name.
someVariableHere.let { it.methodThatWillActOnSomeVariableHere }
Without needing to provide the explicit it
someVariableHere.with {methodThatwillactonsomeVarariableHere} // can also be `run`
(You could also do with(someVariableHere) {lambdaStuff}
but that may be showing off a bit...)
- Kotlin_Let_Wrapping_An_Optional
Kotlin implements Tail Call Elimination (TCE). This means that when a function call to itself is the last thing the function does (meaning that the result of this call isn’t used in a further computation), Kotlin eliminates this tail call. But it won’t do it without you asking for it, which you can do by prefixing the function declaration with the tailrec keyword:
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
if you indicate that your function has a tail-recursive implementation, Kotlin can check the function and let you know if you’ve made a mistake
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
if you do use by lazy
that means the value of the instance variable might be created anytime, creating somewhat hard to read stacktraces (or paying object initialization prices at just the wrong time)
TODO: think about this more here....
Q: since you can, how do you un-init a lateinit
property?
TODO is this a correct summary? TODO: read Baeldug article
- var properties initied later in constructor or any method
- fancy
isInitialized
method - can not be a nullable type
- val properties initied when called later
- custom getter
In your build.gradle.kt file - and this can be somewhat anywhere, do
println(embeddedKotlinVersion)
gradle -version
will also tell you that number
Sure, do something like this
@SinceKotlin("1.6")
class IfIErrorThenCompilerIsNotKotlinOneSix {
}
fun main() {
IfIErrorThenCompilerIsNotKotlinOneSix()
}
Gradle Settings -> "Build and Run using Gradle" vs IntelliJ here.
- Kotlin_Gradle_BuildSrc
In IntelliJ you can use Scratch files
multi-line strings by using the triple-quoted string (""") with the trimMargin
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Identity (also called referential equality) is tested with ===. Equality (sometimes called structural equality) is tested with ==, which is a shorthand for equals
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
val result: String = payload as String If the object isn’t of the right type, a ClassCastException is thrown
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
This is called a smart cast because in the first branch of the if function, Kotlin knows that payload is of type String, so it performs the cast automatically. You can also use smart casts with the when function:
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Read How Are Coroutines Different From Java's Thread Executor either before, or after, because WOW. Then go read about what kotlinx.coroutines gives you.
Builder | Description |
---|---|
launch | When you don't care about the result of the expression |
async | returns a Deferred object which you need to call .await on later |
runBlocking | blocks thread it started on when its coroutine is suspended (unit testing, mostly) |
Let us decide what thread or thread pool or whatever a coroutine is running on
Provided ones:
- Default: CPU intensive operations, size == # of CPUs, but at least 2
- Main: main thread (see Android)
- IO: backed by a thread pool, additional threads are brought on on demand
- Unconfined: runs on thread that started it, resumes on thread that resumes it.
Can make your own with Java Executors .asCoroutineDispatcher()
is in the coroutine context, so access it like coroutineContext[Job]
or just coroutineContext.job
.
A coroutine context comes in with a job, and items spawned within the (builder) methods inherit that job as a parent.
However, joining the parent does not mean that all children are complete! coroutineContext.job.children.forEach { it.join() }
will do it...
Dispatcher parent scope seems to be determined via parent scope / coroutine context. This is well explained in this Stackoverflow answer
Use the builders, like so
launch {
something()
}
var defed Defered<MyResultType> = async {
somethingWithAResult()
}
var actualRes : MyResultType = defed.await()
If we have a function someFunction
that is
private suspend fun hereWeGo() {}
We can - in our own suspend
labelled method call it like any ordinary method.
beforeAll
and the actual unit tests must run the same dispatcher/scheduler.
Q: (Yes, but how, if you are using runTest
??!!)
TODO: ?? beats me ???
Be careful that your tests aren't accidentally using two different coroutine dispatchers, especially if you took shortcuts with one of them (a single thread or something, because testing...)
Do you really want to / need to test two dispatchers at a time, because coordinating some async events?
CoroutineTestDispatcher lets you pause and resume the dispatcher
Coroutines provides integrations for a bunch of Reactive stuff, Java stuff Play, etc etc:
Kotlin jdk8 extensions over Future, Completablefuture etc
interesting utilities here:
- Deferred / Job.asCompletableFuture()
- suspend CompletionStage.asDeferred()
- suspend CompletionStage.await()
TODO: EXAMPLE / MORE INFO HERE
See also:
- Java_Reactive_Builtins
- jdk 9 specific support
See my RxJava specific notes: Learning_Java_Rx
kotlin has a library about this
?? use runBlocking
?
<<Kotlin_Java_Interropt>>
The Java way for dealing with a default [parameter] value is through overloading. To make the function available as overloaded Java methods, use the JvmOverloads annotation
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
You can change the name by which a Kotlin function can be called from Java code. For this, you need to use the JvmName("newName") annotation on the Kotlin function
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Kotlin public fields are exposed as Java properties (meaning with getters and setters). If you want to use them as Java fields, you have to annotate them in the Kotlin code like this: JvmField
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Extension functions in Kotlin are compiled to static functions with the receiver as an additional parameter.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
Unlike Kotlin, Java doesn’t have function types; functions are handled by converting lambdas into something that’s equivalent to a Single Abstract Method (or SAM) interface implementation. (It isn’t an implementation of the interface, but it acts as if it’s one.) Kotlin, on the other hand, has true function types, so no such conversion is needed. But when calling Java methods taking SAM interfaces as their parameters, Kotlin functions are automatically converted.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
NOTE Kotlin supports the JSR-305 specification, so if you need more Java annotation support, refer to https://kotlinlang.org/docs/reference/java-interop.html.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
standard Java assertions, which are available in Kotlin. But as assertions,can be disabled at runtime
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
You must use backticks to reference the in field of a Java System class because in is a reserved word in Kotlin.
- From The Joy of Kotlin by Pierre-Yves Saumont on page 0 ()
If you have a function that throws in Kotlin you'll likely need to annotate it with @Throws(IOExceptionOrWhateverItIs::class)