Picked up a book cause that worked for C++ and Ruby
It wants me to skip the first section if I'm experienced but I kinda don't want to?
No references to other programming languages is interesting for a language that is inherently based on java
Atomic concepts seems good, but they present it as if it's a novel idea which is funny
4 spaces is standard indentation?
Excercises require IntilliJ IDEA, which I don't really intend to use
Early languages focused on hardware limitations. As computers become more powerful, newer languages shift toward more sophisticated programming with an emphasis on reliability.
An interesting theory. I wonder if the power of computers is really what did it though, or just the power of having built up a bedrock of abstractions
Kotlin is compiled, JVM is one option (I think js is another?)
Statically-typed languages like Kotlin discover as many errors as possible at compile time, while dynamic languages perform their safety checks at runtime (some dynamic languages don’t perform as many safety checks as they might).
snarky
I'm intrigued that they list FORTRAN as an inspiration for kotlin, I'm not sure what the influence was other than having been one of the first "high level" programming languages
For example, the GNU Emacs editor is written entirely in LISP, and can be extended using LISP.
parts of emacs are definitely written in C, but w.e
Similar intrigue at listing ALGOL, COBOL, and BASIC
Never really knew Simula was the first "OO" language
I'm not sure what the lesson learned from Pascal was for Kotlin, it notes that constraints at the time made the language awkward but that the people who worked on it went on to write a bunch of other languages
C having such a small blurb is funny to me
Smalltalk is the kind of language I would love to inspect one day. The OO programmers of the world wax lyrical about its ideas but the language itself is long dead.
Bjarne Stroustrup created C++ because he wanted a better C
This has made a lot of people very angry and been widely regarded as a bad move
Not surprise that Java gets a whole page
Although Java was remarkably successful, an important Kotlin design goal is to fix Java’s flaws so programmers can be more productive
interesting
Java being a demonstration of the practicality of virtual machines are GC is funnily enough still up for debate almost 30 years later
At this writing, the Kotlin team is working to add WASM as a target.
I think they done did it https://blog.jetbrains.com/kotlin/2021/11/k2-compiler-kotlin-wasm-and-tooling-announcements-at-the-2021-kotlin-event/
Kotlin notes that it took a lot of ideas from C# in terms of "fixing" Java
Scala was probably one of the first in the family of JVM languages
Everyone says their language prioritizes readability, but nobody seems to define what that truly means
Multi paradigm at it's core is something I haven't fucked with since C++
The two features they want to highlight the most are that it runs on the JVM and thus works with Java and how is handles empty values. An interesting choice for sure, definitely can see what drove the language design (and I wonder how I would feel about it if I were a kotlin -> js user).
Kotlin prevents dereferencing null at compile time (or so it says?)
We can have "just" a top level main
like in Go, which is interesting.
We distinguish between statements, which produce an effect, and expressions, which yield a value
An empty main function is a valid program, no need to return or anything
C++ style comments
We don't need to import anything to println
This is an interesting way to deal with what C++ would call const
. Essentially two entirely different ways to declare.
println only takes a single value (do we have variadic functions?)
Kotlin style says first letter of a local is lower, then camelcase, e.g. coffeeTime
Kotlin uses the upper case String
, Boolean
, etc for basic types (like the Java impls?)
Single quote is for Character
, not strings
Kotlin infers types when you declare, but you can be explicit
Kotlin uses name -> type in function declarations (Java does not right?)
Unit
is what is returned when there is no return value (kind like void
?). You don't have to specify it.
There is an abbreviated syntax for a function that consists of only a single expression (is that gonna be common?)
Kotlin can infer return types for single expression functions, but not block style ones (why?)
We use if { expr } else if { expr } else { expr }
style
if
is an expr itself, we can use it as a value
We can interpolate using $
pretty much like in bash. I have no idea why new languages (2011) continue to choose this implementation.
We can also just +
strings together (as god intended)
We can evaluate expressions in ${}
(ruby style?)
backslash to escape as usual
triple "
works like triple ` in go
Integers (and Longs) do integer things. Floats (and Doubles) do floating point things
We can do Int.MAX_VALUE
, which hints that these are really classes
Kotlin infers number types. It will figure out that they need to be Longs or Doubles if they are too big. Inference follows Order of Operations.
Booleans doing boolean things
&&
gets evaled first, then ||
(use parens if it's ever not obvious tho)
We have both while (cond) { expr }
and do { expr } while (cond)
. I feel like I never use the latter form, but language developers keep putting it in there.
This section also introduces short form arithmetic assignment (e.g. +=
)
Ranged for
can do for (v in values)
There are range literals for integers using ..
(inclusive) and until
([)
)
You can modify a range with step
(e.g. 0..10 step 2
). I would like to know how to define these for user defined classes.
There is also downTo
to go the other way
All of these yield an IntProgression
type
Strings function like arrays of characters where the characters are unicode code points. They can be iterated over with in
.
We have a stdlib function repeat(n: Int)
that looks like the equivalent of taking a ruby block. We will learn about it later.
in
if used outside of a for
statement tests whether a value is contained within a range.
The in keyword is used for both iteration and membership. An in inside the control expression of a for loop means iteration, otherwise in checks membership
We can do !in
to invert it
in
can be used for substrings
A statement has an effect but produces no result, an expression has a result.
Function calls are expressions
for
is an example of a statement
The Unit type contains a single value called Unit
This is used for empty returns
Expressions are evaluated where they are, not evaled later
We have both prefix and postfix ++
Defining a var
or val
inside of a block scopes it to that block
Kotlin is a hybrid object-functional language.
I am skeptical...
Objects have vals and vars
Classes, Members, instances, so on and so forth
built-in types like Double
and Boolan
are objects
If you try calling one that needs arguments, the IDE will ask for those arguments.
lol
Earlier object-oriented languages used the phrase “sending a message” to describe calling a member function for an object. Sometimes you’ll still see that terminology.
some still do
Conversions are just member functions on builtins (e.g. .toDouble()
)
Every object in your program has its own unique address.
I wonder what these addresses mean in a JVM world
We call methods "member functions". We didn't want to confuse functions and methods. The other kind are called "top level" functions.
We have this
in classes but it's implicit, and considered bad form to include it if not needed
They can be val
or var
An object that is declared as a val
can still have mutable var
properties
However an object that is declared as a var
cannot be assigned to if it has val
properties?
Instances are references to a given object.
Constructors define arguments that are scoped to the class and not available outside of it. If you want to make them accessible (as properties?) use val
or var
To override functions we must explicitly say override
You only get one by default
toString()
does what you think it does
We have public
, private
, protected
, and internal
public
does what you think it does. It is the default if you don't specify anything.
private
is only accessible from members of the same class. At the top level this means it is accessible for anything within this file.
Maintaining multiple references to a single object is called aliasing
internal
means it is visible anywhere in the same module but otherwise not accessible. This book doesn't use internal
. Modules are a higher level concept determined by your build system (and they are separate from packages)
import <package name>.<identifier> (as <name>)
We can use fully qualified names instead of import
The package statement must be the first non-comment statement in the file. package is followed by the name of your package, which by convention is all lowercase:
Kotlin allows you to choose any name for your package, but it’s considered good style for the package name to be identical to the directory name where the package files are located
Tests are important y'all
Kotlin supports infix
notation with a special keyword modifying a function to allow a func b
instead of a.func(b)
(more on this to come?)
There's a lil testing library in this book
TDD is a thing
This atom covers the basics of exceptions as an error-reporting mechanism. In Section VI: Preventing Failure, we look at other ways to deal with problems.
intrigued
We can throw an exception, if uncaught you get a strace
Some stuff will return null
instead, but code that could produce null
must be checked before using the result (more to come)
You can throw a specific instance of an exception
Lists are part of the standard Kotlin package so they don’t require an import
make with listOf
(no dedicated literal syntax)
index in with [i]
, you get an exception if it's not there
list is actually a List<T>
and we can declare its type if it's unclear
List<T>
is immutable, you can use mutableListOf
to make a MutableList<T>
, which has add(elem: T)
(and presumably other shit)
You can slice a MutableList<T>
to a List<T>
+=
works on a List
if it's var
because it's the same as l = l + e
(which overwrites)
figured we'd have to have variadic functions eventually
uses a keyword vararg
, you can only have one and it's simplest if it's last
vararg
yields an Array
(Array<T>
?), which is a low level class required by Java (prefer List<T>
)
You can make an Array
with arrayOf
You can use the spread operator (splat) on an Array
// Varargs/SpreadOperator.kt
import varargs.sum
import atomictest.eq
fun main() {
val array = intArrayOf(4, 5)
sum(1, 2, 3, *array, 6) eq 21 // [1]
// Doesn't compile:
// sum(1, 2, 3, array, 6)
val list = listOf(9, 10, 11)
sum(*list.toIntArray()) eq 30 // [2]
}
If you pass an Array of primitive types (like Int, Double or Boolean) as in the example above, the Array creation function must be specifically typed. If you use arrayOf(4, 5) instead of intArrayOf(4, 5), line [1] will produce an error complaining that inferred type is Array but IntArray was expected.
That seems fucky, might have to ask about that shit
fun main(args: Array<String>)
is how you get your args
A Set
is like a C++ std::set<T>
has the same mutable vs immutable split as List<T>
a List<T>
supports toSet()
which returns a Set<T>
(and also distinct()
which returns a List<T>
with no dupes)
Map<K, V>
preeetty much is what you think it is
can index with [k]
, get keys with keys(): Set<K>
, values with values(): Collection<V>
and loop over it to get entries() Entry<K, V>
// Unpack during iteration:
for ((key, value) in constants)
I want to know more ^
You get null
if you index with a key that DNE
getValue(key: K): V
will throw if key
DNE, getValueOrDefault(key: K, def: V): V
returns def
if key
DNE
It’s possible to use class instances as keys in a Map, but that’s trickier so we discuss it later in the book.
intriguing, given the existing keys are class instances :think:
When you define a property (i.e. a var prop
(or val
?) you automatically get a get(): V
and set(value: V)
that are called when you .prop
or .prop=
. You can override these as you like.
You can do arbitrary shit in your get()
so you can make functions look like fields if you so desire (this is discouraged if the details are actually complex)
If the property is private
then so are its accessors. You can also just declare one of the accessors private
if you like (for example, RO field)
Property accessors provide a kind of protection for properties. Many object-oriented languages rely on making a physical field private to control access to that property. With property accessors you can add code to control or modify that access, while allowing anyone to use a property
So "fields" and "properties" are different. We can use the field
keyword to access the field that corresponds to a given getter and setters property
You can monkeypatch on functions fun ReceiverType.extensionFunction() { ... }
They're package scoped and can be imported from other packages.
Extensions can only access public
stuff from the thing they are extending.
They have access to this
and the same implicit nature as a regular member function.
You can use the names of parameters instead of their positions. Good style not to mix named and positional (but you can)
You can also give arguments defaults (which is particularly nice with named arguments)
Use trailing comma in parameter lists
This works with constructors too
works pretty much the way you would expect it to
If a class already has a member function with the same signature as an extension function, Kotlin prefers the member function. However, you can overload the member function with an extension function:
when you have overloaded functions and default arguments, if you omit an argument, the compiler will select the function that doesn't specify that argument over one that does specify it with a default.
i.e.
fun foo(arg: Int = 0) {
println("doesn't get called")
}
fun foo() {
println("does get called")
}
fun main() {
foo()
}
These are basically case
or switch
or w.e
the cases are expressions so you can do kinda arbitrary shit in there
when
is an expression, and if you use it's yielded value then it must be exhaustive (i.e. have an else
)
when expressions can overlap the functionality of if expressions. when is more flexible, so prefer it over if when there’s a choice.
This is a strange recommendation, I think that every if
statement can be written as a when
instead
when has a special form that takes no argument. Omitting the argument means the branches can check different Boolean conditions.
makes the recommendation above even stranger
enum class T {
T1, T2, T3; // semicolon required
// rest of class body
}
toString()
is automagically defined, but we can add other methods as we like (and presumably override toString
?)
Essentially just structs
automatically overrides .toString()
to be simpler and .equals()
to just compare fields
automatically defines .copy()
which does what it sounds like it does
automatically defines .hashCode()
so that it can be used as a HashMap<K,V>
key
We can have Pair<T1, T2>
and return that
we can do val(a, b) = myfun()
if fun myfun(): Pair><T1, T2>
There's also Triple<T1, T2, T3>
(which supports this)
Data Classes also support this (can't be named, only ordered)
implemented in terms of operators, component1()
, component2()
, etc.
Map entries (what you get when you loop) can also be destructured
One possible solution to this problem is for a language to never allow nulls in the first place, and instead introduce a special “no value” indicator. Kotlin might have done this, except that it must interact with Java, and Java uses nulls.
null is bad mkay
You add a ?
to the end of a type to allow for null
of that type
If you reference members of a nullable type kotlin will not compile
You don't have to declare a nullable version of your type, it's just always there
You can avoid the compilation issue by checking if (thing != null) { thing.method /* safe */ }
?.
will either call if the receiver is non null or no-op if it is
lhs ?: rhs
yields lhs
if lhs
is not null, otherwise rhs
When you chain access to several members using safe calls, the result is null if any intermediate expressions are null
myval!!
asserts that myval
is not null and converts it from (potentially) nullable to non nullable. If the assertion is wrong, it throws NPE.
Avoid non-null assertions and prefer safe calls or explicit checks
You can write an extension that adds a member function to a nullable type
Take care when using extensions for nullable types
no shit, lol
class GenericHolder<T>(private val value T) { /* body */ }
fun <T> identity(arg: T): T = arg
fun <T> List<T>.first(): T {
if (isEmpty())
throw NoSuchElementException("Empty List")
return this[0]
}
fun ReceiverType.extensionFunction() { ... }
val ReceiverType.extensionProperty: PropType
get() { ... }
You have to define your own get()
You can make them generic
val <T> List<T>.firstOrNull: T?
get() = if (isEmpty()) null else this[0]
If you aren't using the generic argument(s) you can use <*>
, this means you lose the type info of the underlying type
No goto
break
goes to end of a loop, continue
goes to the beginning
They're only available within loops (for
, while
)
you can label loops with @label
to allow break
and continue
to jump to an outer loop
Consider alternative approaches, and choose the simpler and more readable solution. This typically won’t include break and continue.
Lambdas are what you think they are
list.map({ n: Int -> "[$n]" })
Kotlin can infer the type of the argument a lot of the time. You can also use it
for the argument if there is only one.
If the lambda is the only or last argument then you can put it outside the parens, e.g. list.map { "[it]" }
(looking very ruby like)
run
will exec a lambda (or another function?) (ok run
is complex, maybe more to come)
lambdas are first class functions that can be passed around which makes them useful for FP
lambdas are closures, they work the way you think.
regular functions are also closures
List
has a constructor that takes a count and a lambda and inits by running the lambda with the index
val list1 = List(10) { it }
list1 eq "[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]"
List() and MutableList() are not constructors, but functions. Their names intentionally begin with an upper-case letter to make them look like constructors.
ok, so it's not a constructor I guess
there's like a bunch of Enumerable
style methods
You can use a member function or a member property as a lambda by passing Class::member
. This works a bit like ruby's .map(&:method)
it seems.
Top level functions are just ::function
and constructors are ::Class
.
Arguments are passed down in paramter order
Extension functions work like regular member functions
val isPlus: (Int) -> Boolean = { it > 0 }
to hold a lambda as a value
val sum: (Int, Int) -> Int = { x, y -> x + y }
with named args
recall that Unit
is what we return if we don't do anything
a function can return a nullable type
lhs.zip(rhs)
creates a new list of pairs [(lhs[0], rhs[0]), (lhs[1], rhs[1]), ... ]
zip
stops when one list runs out
takes an optional (Pair<L, R>) -> T
and will return a List<T>
instead
flatten()
does what you think it does
flatMap()
-> .map().flatten()
List<T>.groupBy((T) -> K): Map<K, List<T>>
groups items by the key produced by the passed function
people().groupBy(Person::age)
for example
List<K>.associateWith((K) -> V): Map<K,V>
List<V>.associateBy((V) -> K): Map<K,V>
getOrElse
runs a lambda for its default value
getOrPut
inserts (and returns) via lambda if not found
Map<K, V>.map(MapEntry<K, V> -> T)): List<T>
.mapKeys()
and .mapValues()
do what you think
Converting Lists to Sequences using asSequence() enables lazy evaluation.
They support "stream" operations rather than eagerly doing the whole .map
or w.e
Can't index into one
Essentially stores the operations to be done and delays doing them until someone asks for a value back out
There are two categories of Sequence operations: intermediate and terminal. Inter- mediate operations return another Sequence as a result. filter() and map() are intermediate operations. Terminal operations return a non-Sequence. To do this, a terminal operation executes all stored computations.
Sequences don't necessarily have a size, you can make an "infinite" one with generateSequence
which has an optional "seed" and a lambda
fun fibonacci(): Sequence<Int> {
// fibonacci terms
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, ...
return generateSequence(Pair(0, 1), { Pair(it.second, it.first + it.second) }).map { it.first }
}
println(fibonacci().take(10).toList()) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
You can only iterate once through a Sequence. Further attempts produce an exception.
You can define functions in any scope, it doesn't have to be top level.
When you do they are scoped to where you declare them.
You can do all the normal function shit with em.
They can be anonymous (which makes it so their return
does not fuck shit up)
a return
inside of a lambda will return out of the surrounding function?
we can return
to a label from a lambda, either identifying that label when we declare the lambda or referring to the function that calls the lambda
fun main() {
sessions.any { session ->
if (session.title.contains("Kotlin") &&
session.speaker in favoriteSpeakers) {
return@any true
}
// ... more checks
false
} eq true
}
... this seems pretty evil?
to pass around references to local functions create a ::
reference to them
Collection<T>.fold(R, (R, T) -> R): R
accumulates an R
from a collection
Looks like ruby's collect
?
foldRight()
does it in reverse order
reduce()
is the same but instead of an initial value it just uses the first value of the list
runningFold
and runningReduce
produce a list of each step
recursion is recursion
performance risks associated with Java are still there
we have TCO but it's opt-in, use the tailrec
keyword
To use tailrec successfully, recursion must be the final operation, which means there can be no extra calculations on the result of the recursive call before it is returned.
compiler warns if you flag tailrec
but this condition is not actually satisfied
fun fibonacci(n: Int): Long {
tailrec fun fibonacci(
n: Int,
current: Long,
next: Long
): Long {
// base case
if (n == 0) return current
// recursive case
return fibonacci(n - 1, next, current + next)
}
// essentially a "private" helper
return fibonacci(n, 0L, 1L)
}
Looks like Java interfaces.
When implementing a member of an interface, you must use the override modifier
class MyClass : MyInterface { /* body */ }
when you do listOf
a bunch of things with the same interface, kotlin infers the type?
interfaces can declare properties too
enums can impl them either at the top level or at the level of individual instances of the enum
SAM is a Java thing, we have special syntax with fun interface Class { fun f(): }
You can fulfill a SAM with a regular class or with a lambda
You can just pass a lambda directly where a SAM is expected, no need to declare it with the type
magic init
block is the constructor
Constructor parameters are accessible inside the init section even if they aren’t marked as properties using var or val.
If you define a val
that isn't specced in the automatic constructor then it must be initialized exactly once within init
Kotlin allows multiple init sections, which are executed in definition order
this seems like a bad idea most of the time ^
constructor
kw allows us to define secondary constructors. They must have a unique signature.
They must call another secondary or primary constructor via this(*args)
Calling another constructor from a secondary constructor (using this) must happen before additional constructor logic, because the constructor body may depend on those other initializations. Thus it precedes the constructor body.
We use "base" class and "derived" class
package inheritance
open class Base
class Derived : Base()
You must be open
to allow inheritance. Default is final
, which cannot be inherited from (you can explicitly spec if you like)
extensions are inherited
derived classes don't get base classes private
parts. protected
is accessible from derived classes but not available to the wider world.
you must specifically declare a function to be override
if it matches the base class (otherwise compiler error)
you must declare a function as open
to allow derived classes to override it
use super.function()
to call function()
on the base class
package baseclassinit
open class SuperClass1(val i: Int)
class SubClass1(i: Int) : SuperClass1(i)
open class SuperClass2
class SubClass2 : SuperClass2()
You have to actually call the base class constructor (it can be primary or secondary)
package baseclassinit
open class Base(val i: Int)
class Derived : Base {
constructor(i: Int) : super(i)
constructor() : this(9)
}
You can use super
instead of this
to call a base class constructor from a constructor
block
After Kotlin creates memory for your object, it calls the base-class constructor first, then the constructor for the next-derived class, and so on until it reaches the most- derived constructor.
oh yeah, it's getting messy now
abstract class MyClass { /* body */ }
flag functions and properties with abstract
too if you don't wanna provide a defn
abstract
is the default for interface's properties and functions
no multiple inheritance from classes
abstract classes can contain state where interfaces cannot
you can inherit from multiple interfaces if you like
if you are inheriting multiple definitions of the same function (or property) (through multiple interfaces) then you must override them. you can spec with super
you want by doing super<Class>
in the eyes of the kotlin designers, the upcast is the raison d'etre for inheritance at all
Indeed, in virtually every case where there’s inheritance without upcasting, in- heritance is being misused—it’s unnecessary, and it makes the code needlessly complicated.
otherwise prefer composition
when we upcast we slice down to the base and the compiler will enforce that
Connecting a function call to a function body is called binding. Ordinarily, you don’t think much about binding because it happens statically, at compile time. With polymorphism, the same operation must behave differently for different types—but the compiler cannot know in advance which function body to use. The function body must be determined dynamically, at runtime, using dynamic binding. Dynamic binding is also called late binding or dynamic dispatch.
like C++ virtual
bring out the vtables!
there is a runtime cost to this
I'm surprised they even mentioned the runtime cost, a friggin vtable lookup has got to be completely negligible for the uses cases where kotlin is a good choice of language
prefer composition to inheritance, it is simple and flexible
consider extensions as an alternative to inheritance + adding methods
I do not undertstand the value of this "interface by convention" segment. You are allowed to define the same extension on two different types... and then get nothing interesting out of it?
It's common to create a class as an adapter to by used by a library or w.e (using inheritance or composition)
A member function reflects the essence of a type; you can’t imagine the type without that function. Extension functions indicate “auxiliary” or “convenience” operations that support or utilize the type, but are not necessarily essential to that type’s existence. Including auxiliary functions inside a type makes it harder to reason about, while defining some functions as extensions keeps the type clean and simple.
you cannot override extensions (no dynamic dispatch)
Sometimes we want a middle ground between composition and inheritance
Class delegation essentially allows you to implement an interface in terms of composition
interface AI
class A : AI
class B(val a: A) : AI by a
you can use this as a janky multiple inheritance
proceed with caution only after exhausting regular composition or inheritance
also a janky way to get around lack of open
ness without copying a lot of code
we can "smart cast" with is
. Any code within the scope of if(instance is Klass)
will treat instance
as type Klass
fun main() {
val b1: Base = Derived1() // Upcast
if(b1 is Derived1)
b1.g() // Within scope of "is" check
val b2: Base = Derived2() // Upcast
if(b2 is Derived2)
b2.h() // Within scope of "is" check
}
We can use it with when
for good effect
fun what(c: Creature): String =
when (c) {
is Human -> c.greeting()
is Dog -> c.bark()
is Alien -> c.mobility()
else -> "Something else"
}
You cannot smart cast on a var
because it's underlying type could change between reading and accessing
Concurrency is an advanced topic that we do not cover in this book
:c
instance as Klass
yields instance
typed as Klass
if it actualy is a Klass
, otherwise it throws
We can also use as?
which just returns null
instead (recall all the null
checking rules)
We can also use is
as a predicate to find shit in lists
Sometimes we don't want anybody to add to a class heirarchy
sealed class MyClass
allows inheriting from MyClass
only within the same file as MyClass
This allows us to do when (instance) { is TypeA -> expr; is typeB -> expr }
exhaustively (no else
case)
there is no sealed
interface
you can inherit from something that inherits from sealed
MyClass::class
returns the class object, which supports reflection including .sealedSubclasses()
this::class.simpleName
gets the "basename" of the class
type checking all over the place becomes a problem eventually
sealed allows us to use exhaustive when
this example seems really contrived. I feel like I would have to see a "real" use case cause otherwise this just seems massively obtuse?
Yo dawg, we heard you like classes, so we put classes in yo classes so you can... express a class as being constituent of or owned by a larger class without necessarily being an instance of that class
Nested classes have access to the nesting classes private
parts, otherwise they are like any old class
Nested classes can be declared as private, but then you have to return them as something else if you have a method that returns one
You can have a local class inside of a function body or other scope. They are not accessible outside that scope (so you must return as something else). You can't have a local interface. Use these sparingly
enums can be nested (and enum items have .ordinal()
which returns their "rank")
object
creates a singleton class
there's one instance globally shared, be careful and use private
if you don't want people touching it
objects can’t be placed inside functions, but they can be nested inside other objects or classes (as long as those classes are not themselves nested within other classes)
the parenthetical seems like a weird restriction? but w.e
inner
is a keyword for nested classes that gives them unqualified access to the containing class
regular nested classes cannot inherit from inner
You cannot have an inner data
class (why?)
In an inner
class you can use this@Klass
to refer to the right this
I am unreasonably upset that it's not this<Klass>
the way super
is
An inner class can inherit another inner class from a different outer class
this seems bonkers
I once again need to see a non-contrived example to even understand why you would ever want this, lol
Classes defined inside member functions are called local inner classes
this is getting worse than JOIN
, lol
these imply inner
, otherwise they are the same as normal local classes
ah shit, here come the factories
and then of course at the end "try to avoid using this feature much", aces
this is basically class << self
You could just use a nested object
but then you have to qualify access to it all the time
You can name it if you like or just leave it at top level
ok this entire section is very poorly written. all the ZI
examples are like the ultimate misuse of f()
g()
u()
etc type shit. If you're just writing a small example it's fine to use placeholder names but when you expand this across multiple pages my tired eyes are totally lost. Might revisit when I have energy. tl;dr seems to be that companion objects obey the normal object rules?
Exceptions combine reporting, recovery, and cleanup
Exceptions bubble up the stack
You can throw any Throwable
Good to inherit from Exception
class Exception1(
val value: Int
): Exception("wrong value: $value")
try {
expr
} catch(e: ExceptionKlass1) {
expr
} catch(e: ExceptionKlass2) {
expr
}
finally
does what you expect it to do
kotlin recommends not catching, throw exceptions generally for major issues
for type related stuff just write the check rather than catching
IO throws exceptions, retry
runtime checking of constraints for clarity, they throw when not met
require(predicate: Boolean, lazymessage: () -> Any)
throws IllegalArgumentException
if predicate
is false, uses the lambda for a custom message
<T: Any> requireNotNull(value: T?, lazymessage: () -> Any): T
returns a non-nullable value
if value
is in fact not null, otherwise throws
You don't usually need the return value because value
will be smart-cast in scope of the call
check(predicate: Boolean, lazymessage: () -> Any)
throws IllegalStateException
if predicate
is false, used for postconditions (this seems dumb, better to test)
assert
exists from java but it needs a flag and also you shouldn't use it (lol)
Nothing
is not the same as Unit
, it is for a function that never returns (usually because it throws)
builtin TODO(msg: String)
throws NotImplementedException
with msg
(cute)
Kotlin treats a throw as type Nothing, and Nothing can be assigned to any type.
seems weird written out but the examples explain, essentially a function that returns a real type or Nothing
can be considered to return the real type (if it is in the Nothing
branch it isn't gonna return)
When given a plain null with no additional type information, the compiler infers a nullable Nothing
fun <T : AutoCloseable?, R> T.use(block: (T) -> R): R
AutoCloseable
is a java interface, it requires only void close()
use()
wraps block
and guarantees a close()
, it reraises any exceptions it encounters
useLines()
reads a file and yields a Sequence<String>
to the passed block (and guarantees closing)
forEachLine()
reads a file and yields each line to the block (and guarantees closing)
patterns like these are encouraged to avoid having to manually do a bunch of finally
shit
Logging is good
You must create a logger before using it. You’ll almost always want to create it at file scope so it’s available to all components in that file:
interested to see if that holds up in bigger projects ^
https://github.com/MicroUtils/kotlin-logging FOSS logger for kotlin
Of the numerous unit test frameworks, JUnit is the most popular for Java. There are also frameworks created specifically for Kotlin. The Kotlin standard library includes kotlin.test, which provides a facade for different test libraries. This way you’re not limited to using a particular library. kotlin.test also contains wrappers for basic assertion functions
gotta pull in org.jetbrains.kotlin:kotlin-test-common
to use it?
Kotlin supports annotations for definitions and expressions. An annotation is the @ sign followed by the annotation name, and indicates special treatment for the annotated element.
You can use spaces in function names if you wrap w/ `
JUnit requires some setup (gotta learn how packages work)
IDEA will do this for you
val vb: String.(Int) -> String = {
this.repeat(it) + repeat(it)
}
The Kotlin documentation usually refers to extension lambdas as function literals with receiver. The term function literal encompasses both lambdas and anonymous functions. The term lambda with receiver is often used synonymously for extension lambda, to emphasize that it’s a lambda with the receiver as an additional implicit parameter.
You can pass an extension lambda to a function that expects an ordinary lambda, as long as the parameter lists conform to each other:
Polymorphism does what you want it to
These are often used for builders cause they "yield" the thing being built as this
, which is super convenient
we call these "builders"
let
yields the thing you called it on as it
and returns the last expr
in the block (this is functionally Kernel#then
)
run
yields the thing you called it on as this
(using extension lambdas I suppose?) and returns the last expr
in the block
with
is run but it's with(T)
instead of T.run
also
yields the thing you called it on as it
and returns the (modified) object (this is functionally Kernel#tap
)
apply
yields the thing you called it on as this
and returns the (motified) object
you can use ?.
to ensure that the scope function only gets called if the receiver exists, and this functions to null check the receiver w/in that scope (doesn't work with with
)
you can do expr ?: return
to short circuit
if you nest them then the innermost becomes this
or it
nesting these is kinda questionable
When the compiler sees an inline function call, it substitutes the function body for the function call, replacing all parameters with actual arguments.
this must be a Kotlin feature? can inline
be ignored by the compiler?
Generics are complex, some of it is beyond this book
They work well for dealing with potential different behaviour that can't be identified by an interface or a class hierarchy
Any
is the root of all classes
Any
is mostly used to downcast
fun <T> f(arg: T): T = arg
class GClass<T>(val x: T)
containers are a common use case for generics
use <T: Base>
to require that T
'is a' Base
the upside over polymorphism is that we can return the exact type, not the upcast
you cannot test the type of a generic type at runtime because of type erasure (a Java constraint)
reified
for generic functions allows you to preserve this information (equivalent to just passing the class as an argument)
To use reified, the function must also be inline
<out T>
is covariant. you can upcast a covariant Container<T1>
to Container<T2>
if T1
is a T2
. it can return T
but not accept it
<in T>
is contravariant. you can upcast a contravariant Container<T2>
to Container<T1>
if T1
is a T2
. it can accept T
but not return it.
<T>
is invariant. you cannot cast to any other <T>
Functions can have covariant return types. This means that an overriding function can return a type that’s more specific than the function it overrides:
Kotlin allows you to override operators, but only certain pre-defined ones
+
is operator fun T.plus(rval: T): T
operator
implies infix
, you don't have to spec it
You can declare as an extension or as a member, members access private parts
==
-> equals
which takes Any?
and must be a member
When overriding equals() you should also override hashCode(). This is a complex topic, but the basic rule is that if two objects are equal, they must produce the same hashCode() value. Standard data structures like Map and Set will fail without this rule. Things get even more complicated with an open class because you must compare an instance with all possible subclasses
lol, IDEA will generate them for you
===
checks whether two objects refer to the same memory (and cannot be overriden?)
I'm not gonna note every overrideable operator, there's a list in the book and they're most straightforward
operator fun T.compareTo(other: T): Int
defines all the other comparison operators (but not ==
)
operator fun T.rangeTo(other: T): R<T>
allow defining ranges
contains
overrides in
invoke
allows you to make a thing "call-able". you can have multiple
Kotlin allows spaces, certain nonstandard characters, and reserved words in a function name by placing that function name inside backticks:
You don't have to overload operators much but a lot of the stdlib uses them
compareTo() was introduced as a standalone extension function in Operator Over- loading. However, you get greater benefits if your class implements the Comparable interface and overrides its compareTo()
Prefer implementing Comparable
you can overload componentN
for custom destructuring
val/var property by delegate
for a val
the delegated thing must have getValue()
for var
it needs getValue()
and setValue()
class ReadWriteable(var i: Int) {
var msg = ""
var value: String by BasicReadWrite()
}
class BasicReadWrite {
operator fun getValue(
rw: ReadWriteable,
property: KProperty<*>
) = "getValue: ${rw.i}"
operator fun setValue(
rw: ReadWriteable,
property: KProperty<*>,
s: String
) {
rw.i = s.toIntOrNull() ?: 0
rw.msg = "setValue to ${rw.i}"
}
}
the delegated-to class is "bound" to the delegating class, so it has access to all it's internal bits
you can impl an interface to communicat this
the ReadOnlyPropert<T, V>
can be used for a SAM conversion
you don't need to use an exact T
, you can specify a hierarchy including Any?
KProperties<*>
can be used to figure out some details about the property that has been delegated to you (for example, it's name)
MutableMap<K, V>
can be used as a delegate for properties right out of the box
kotlin.properties.Delegates
has some cute things that can check/watch/prevent updates
val lazyProperty by lazy { initializer }
means lazyProperty
is only initialized when first called
lateinit
allows you to specify that a property of the class will be initialized later
there are a bunch of restrictions, notably it must be var
and it must not have custom get()
or set()
::prop.isInitialized
will tell you whether it has been initialized
this seems hard to justify, but w.e
You just import classes and then init them
JavaBean-style getters and setters in a Java class become properties in Kotlin
When working with Java, the package name must be identical (including case) to the directory name. Java package names typically contain only lowercase letters. To conform to this convention, this appendix uses only lowercase letters in the interoperability example subdirectory name.
extensions are a good choice to fixup java stuff
properties become private fields with getters and setters (if var
)
member functions become Java methods
If a Kotlin class is in the same package as Java code, you don’t need to import it
data classes generated methods work in Java
You need to include kotlin-runtime.jar
if you're going to call Kotlin from java
top level functions become static methods on a class named for the filename, TopLevel.kt
-> TopLevelKt
(why include the kt
?)
you can import static
to use the name unqualified
There is an annotation to change the name of the generated class if you like
You can use extensions to make Java utilities feel more like their Kotlin counterparts, esp operator overloading
checked exceptions are considered a mistake by the Kotlin team
there is a @Throws
annotation if you must specify what you throw for interoperability reasons
If a type comes from Java, the Kotlin compiler cannot necessarily check it at compile time for nullability
This means that you can assign the result of a Java function that returns Type
to Type
in Kotlin rather than Type?
even when it might be null
You might throw NPE at assignment to a non nullable type because Kotlin will check there (at runtime ofc)
The proper strategy when calling un-annotated Java code is to avoid type inference, and instead understand whether or not the code you are calling can produce nulls.
There are annotations for the Java side using @Nullable
and @NotNull
to make type inference work magically on the Kotlin side
There's different annotations for different platforms, check the docs
Kotlin largely implements its collections in terms of Java ones for improved interoperability
Biggest departure is that Kotlin defaults its collections to immutable through an interface
You can cast to and/or use the Java collections directly but it produces a warning
recall: just because you don't have the ability to modify the underlying data doesn't mean it cannot be modified
In the JVM anything with new
goes on the heap, a small set of primitives go on the stack
Kotlin does not distinguish between on the stack primitives and on the heap primitive wrappers (e.g. Int
vs int
in Java)
The Kotlin compiler will sometimes put (non-nullable) primitive types on the stack, but you don't really need to worry about it