This guide will help you understand kotlin's syntax.
Note that this guide does not cover any multiplatform aspects, but rather, the JVM platform. This guide also assumes that you have good knowledge of Java.
TODO:
- lambdas
A function can be declared with the fun
keyword
and an optional access modifier before it.
fun hello() {
}
To return something, we can specify the return type before the opening brace like this:
fun hello(): String {
}
Now, let's return a string:
fun hello(): String {
return "Hello!"
}
Type arguments, also referred to as generics
can be passed into a function after the fun
keyword:
fun <T> hello(value: T): String { ... }
We can specify type bounds using a colon here,
for example, we don't want any nullable types
(we will cover those later on), by adding a bound
to the Any
class:
fun <T : Any> hello(value: T): String { ... }
This will not accept any nullable types:
val nullable: Int? = null
val int: Int = 0
hello(nullable) // will not compile
hello(int) // compiles!
Inline functions are special functions that will have their body inline at compilation time. This is usually used for performance optimizations. Another benefit of inline functions are reified type parameters. They allow us to get the class of a type parameter if we mark it as reified:
inline fun <reified T> hello(value: T) {
return T::class.simpleName!!
}
As we already did above, you can declare
immutable variables with the val
keyword, and, optionally, specify the type.
For mutable variables, you use var
.
Access modifiers can be added to any non-local declaration (except virtual members).
Kotlin has four access modifiers:
public
: You usually don't need to explicitly add public to a declaration if not using the explicit API mode.private
protected
Now, these are the ones we know from Java.
Let's see the fourth one, internal
:
internal
marks a declaration as internal
to the current module, making it inaccessible from
API users, but accessible for your implementation.
Classes are declared with the class
keyword.
If we want to create a very basic, empty class, we can do it like such:
class VeryBoringClass
Notice how we don't need to use curly braces for the body here, since there is none?
Kotlin classes have two types of constructors: Primary ones and secondary ones.
The primary constructor is declared after the class name with parenthesis:
class NotSoBoringClass()
Then we can pass in arguments. There's
again, two types of arguments: Constructor
arguments and fields. Constructor arguments
are arguments passed in to the constructor
only visible for itself and local variable
initializers. They can be added to a constructor
with the name: Type
parameters. Then, we
have fields, which are specified just like
regular variable declarations:
class MyClass(
constructorParameter: Int,
val field: String
)
Fields in a constructor can also have access modifiers.
Classes are final by default and thus
non-extendable. If you want your class
to be extendable, use the open
modifier.
This also applies to functions and fields.
To implement an interface or extend a superclass, you can use a colon after your primary constructor and declare the class or interface.
If you are extending an abstract class, you will need to call its constructor:
class ClassWithSuper(name: String) : SomeSuper(name)
class InterfaceImplementation : MyInterface
To override an implementation of something,
you can use the override
keyword.
Abstract classes are classes declared
with the abstract
modifier and can
have abstract fields and functions,
also declared with the abstract
modifier.
There isn't much to cover here since
beside from this, they aren't very
different from regular classes.
Enum classes are classes declared with
the enum class
keyword. They cannot
have type parameters. Enums allow you
to specify a set of non-extendable values
which are enumerated:
enum class CoffeeMachineState {
IDLE, BREWING, FINISHED
}
Kotlin enums allow for both the
SCREAMING_SNAKE_CASE
and the
PascalCase
naming conventions.
Interfaces can be declared with the
interface
keyword and allow for virtual
member functions and fields.
You can declare a virtual function like this:
interface CoffeeMachine {
fun startBrewing()
fun stopBrewing(): BrewResult
var currentState: CoffeeMachineState
}
For default functions, you can do the same, but with a function body.
Kotlin has a special type of "classes"
called objects, which are static singleton
classes. These can do anything a regular
class can, except have type parameters
and a constructor. To create an object,
you use the object
keyword. For accessing
an object, you just use its name. All
fields and functions act like static fields/methods
in the syntax.
You can think of companion objects like static declarations in classes. They are a regular object and can even have a different name. They can also extend and implement things, just like regular objects.
Let's create a companion for our CoffeeMachine
interface:
interface CoffeeMachine {
fun startBrewing()
fun stopBrewing(): BrewResult
var currentState: CoffeeMachineState
companion object {
fun basic(): CoffeeMachine {
TODO()
}
}
}
This allows us to call CoffeeMachine.basic()
to get a basic implementation of a CoffeeMachine
.
Nullable types are types suffixed with
a question mark. These allow us to use
the ?.
null-chaining operator to
call a function on the value if it is
not null, like this:
fun hello(list: List<String>?) {
list?.map { str -> str.uppercase() }?.let(::println)
}
This will chain these calls if the
provided list
is not null:
- maps the strings to uppercase
- prints the list to a string
Kotlin has a couple of special types:
You can think of Any
as Java's Object
type.
Every class, interface and enum extends it.
Every enum class extends the Enum<E>
type.
Unit
is an object similar to Java's void
.
Nothing
is a very special type. It
marks a function that will never return
anything. This is different from Unit
in the sense that it will never even reach
any return statement. This can only apply
to functions that will always throw exceptions.
We saw this previously with the TODO
function.
This is its implementation:
fun TODO(): Nothing {
throw NotImplementedError()
}