Search icon CANCEL
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Functional Kotlin
Functional Kotlin

Functional Kotlin: Extend your OOP skills and implement Functional techniques in Kotlin and Arrow

eBook
S$41.98 S$59.99
Paperback
S$74.99
Subscription
Free Trial

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want
Table of content icon View table of contents Preview book icon Preview Book

Functional Kotlin

Kotlin – Data Types, Objects, and Classes

In this chapter, we'll cover Kotlin's type system, object-oriented programming (OOP) with Kotlin, modifiers, destructuring declarations, and more.

Kotlin is, primarily, an OOP language with some functional features. When we use OOP languages to resolve problems, we try to model the objects that are a part of our problem in an abstract way with the information that is relevant to the problem.

If we're designing an HR module for our company, we'll model employees with state or data (name, date of birth, social security number, and others) and behavior (pay salary, transfer to another division, and others). Because a person can be very complex, there is information that isn't relevant for our problem or domain. For example, the employee's favorite style of bicycle isn't relevant for our HR system, but it is very relevant for an online cycling shop.

Once we identify the objects (with data and behavior) and the relationship with other objects of our domain, we can start developing and writing the code that we'll make a part of our software solution. We'll use language constructs (construct is a fancy way to say allowed syntax) to write the objects, categories, relationships, and so on.

Kotlin has many constructs that we can use to write our programs and, in this chapter, we'll cover many of those constructs, such as:

  • Classes
  • Inheritance
  • Abstract classes
  • Interfaces
  • Objects
  • Generics
  • Type alias
  • Null types
  • Kotlin's type system
  • Other types

Classes

Classes are the foundational type in Kotlin. In Kotlin, a class is a template that provides a state, a behavior, and a type to instances (more on that later).

To define a class, only a name is required:

class VeryBasic

VeryBasic is not very useful, but is still a valid Kotlin syntax.

The VeryBasic class doesn't have any state or behavior; nonetheless, you can declare values of type VeryBasic, as shown in the following code:

fun main(args: Array<String>) {
val basic: VeryBasic = VeryBasic()
}

As you can see, the basic value has a VeryBasic type. To express it differently, basic is an instance of VeryBasic.

In Kotlin, types can be inferred; so, the previous example is equivalent to the following code:

fun main(args: Array<String>) {
val basic = VeryBasic()
}

By being a VeryBasic instance, basic has a copy of the VeryBasic type's state and behavior, namely, none. So sad.

Properties

As discussed previously, classes can have a state. In Kotlin, a class's state is represented by properties. Let's have a look at the blueberry cupcake example:

class BlueberryCupcake {
var flavour = "Blueberry"
}

The  BlueberryCupcake class has an has-a property flavour of type String.

Of course, we can have instances of the BlueberryCupcake class:

fun main(args: Array<String>) {
val myCupcake = BlueberryCupcake()
println("My cupcake has ${myCupcake.flavour}")
}

Now, because we declare the flavour property as a variable, its internal value can be changed at runtime:

fun main(args: Array<String>) {
val myCupcake = BlueberryCupcake()
myCupcake.flavour = "Almond"
println("My cupcake has ${myCupcake.flavour}")
}

That is impossible in real life. Cupcakes do not change their flavor (unless they become stale). If we change the flavour property to a value, it cannot be modified:

class BlueberryCupcake {
val flavour = "Blueberry"
}

fun main(args: Array<String>) {
val myCupcake = BlueberryCupcake()
myCupcake.flavour = "Almond" //Compilation error: Val cannot be reassigned
println("My cupcake has ${myCupcake.flavour}")
}

Let's declare a new class for almond cupcakes:

class AlmondCupcake {
val flavour = "Almond"
}

fun main(args: Array<String>) {
val mySecondCupcake = AlmondCupcake()
println("My second cupcake has ${mySecondCupcake.flavour} flavour")
}

There is something fishy here. BlueberryCupcake and AlmondCupcake are identical in structure; only an internal value is changed.

In real life, you don't have different baking tins for different cupcake flavors. The same good quality baking tin can be used for various flavors. In the same way, a well-designed Cupcake class can be used for different instances:

class Cupcake(flavour: String) { 
val flavour = flavour
}

The Cupcake class has a constructor with a parameter, flavour, that is assigned to a flavour value.

Because this is a very common idiom, Kotlin has a little syntactic sugar to define it more succinctly:

class Cupcake(val flavour: String)

Now, we can define several instances with different flavors:

fun main(args: Array<String>) {
val myBlueberryCupcake = Cupcake("Blueberry")
val myAlmondCupcake = Cupcake("Almond")
val myCheeseCupcake = Cupcake("Cheese")
val myCaramelCupcake = Cupcake("Caramel")
}

Methods

In Kotlin, a class's behavior is defined by methods. Technically, a method is a member function, so, anything that we learn about functions in the following chapters also applies to the methods:

class Cupcake(val flavour: String) {
fun eat(): String {
return "nom, nom, nom... delicious $flavour cupcake"
}
}

The eat() method returns a String value. Now, let's call the eat() method, as shown in the following code:

fun main(args: Array<String>) {
val myBlueberryCupcake = Cupcake("Blueberry")
println(myBlueberryCupcake.eat())
}

The following expression is the output of the preceding code:

Nothing mind-blowing, but this is our first method. Later on, we'll do more interesting stuff.

Inheritance

As we continue modelling our domain in Kotlin, we realize that specific objects are quite similar. If we go back to our HR example, an employee and a contractor are quite similar; both have a name, a date of birth, and so on; they also have some differences. For example, a contractor has a daily rate, while an employee has a monthly salary. It is obvious that they are similar—both of them are people; people is a superset where both contractor and employee belong. As such, both have their own specific features that make them different enough to be classified into different subsets.

This is what inheritance is all about, there are groups and subgroups and there are relationships between them. In an inheritance hierarchy, if you go up in the hierarchy, you will see more general features and behaviors, and if you go down, you will see more specific ones. A burrito and a microprocessor are both objects, but they have very different purposes and uses. 

Let's introduce a new Biscuit class:

class Biscuit(val flavour: String) { 
fun eat(): String {
return "nom, nom, nom... delicious $flavour biscuit"
}
}

Again, this class looks almost exactly same as Cupcake. We could refactor these classes to reduce code duplication:

open class BakeryGood(val flavour: String) { 
fun eat(): String {
return "nom, nom, nom... delicious $flavour bakery good"
}
}

class Cupcake(flavour: String): BakeryGood(flavour)
class Biscuit(flavour: String): BakeryGood(flavour)

We introduced a new BakeryGood class, with the shared behavior and state of both Cupcake and Biscuit classes and we made both classes extend BakeryGood. By doing so, Cupcake (and Biscuit) has an is-a relationship with BakeryGood now; on the other hand, BakeryGood is the Cupcake class's super or parent class.

Note that BakeryGood is marked as open. This means that we specifically design this class to be extended. In Kotlin, you can't extend a class that isn't open.

The process of moving common behaviors and states to a parent class is called generalisation. Let's have a look at the following code:

fun main(args: Array<String>) {
val myBlueberryCupcake: BakeryGood = Cupcake("Blueberry")
println(myBlueberryCupcake.eat())
}

Let's try out our new code:

Bummer, not what we were expecting. We need to refract it more:

open class BakeryGood(val flavour: String) { 
fun eat(): String {
return "nom, nom, nom... delicious $flavour ${name()}"
}

open fun name(): String {
return "bakery good"
}
}

class Cupcake(flavour: String): BakeryGood(flavour) {
override fun name(): String {
return "cupcake"
}
}

class Biscuit(flavour: String): BakeryGood(flavour) {
override fun name(): String {
return "biscuit"
}
}

It works! Let's have a look at the output:

We declared a new method, name(); it should be marked as open, because we designed it to be optionally altered in its subclasses.

Modifying a method's definition on a subclass is called override and that is why the name() method in both subclasses is marked as override.

The process of extending classes and overriding behavior in a hierarchy is called specialisation.

Rule of thumb

Put general states and behaviors at the top of the hierarchy (generalisation), and specific states and behaviors in subclasses (specialisation).

Now, we can have more bakery goods! Let's have a look at the following code:

open class Roll(flavour: String): BakeryGood(flavour) { 
override fun name(): String {
return "roll"
}
}

class CinnamonRoll: Roll("Cinnamon")

Subclasses can be extended too. They just need to be marked as open:

open class Donut(flavour: String, val topping: String) : BakeryGood(flavour)
{
override fun name(): String {
return "donut with $topping topping"
}
}

fun main(args: Array<String>) {
val myDonut = Donut("Custard", "Powdered sugar")
println(myDonut.eat())
}

We can also create classes with more properties and methods.

Abstract classes

So far, so good. Our bakery looks good. However, we have a problem with our current model. Let's look at the following code:

fun main(args: Array<String>) {
val anyGood = BakeryGood("Generic flavour")
}

We can instantiate the BakeryGood class directly, which is too generic. To correct this situation, we can mark BakeryGood as abstract:

abstract class BakeryGood(val flavour: String) { 
fun eat(): String {
return "nom, nom, nom... delicious $flavour ${name()}"
}

open fun name(): String {
return "bakery good"
}
}

An abstract class is a class designed solely to be extended. An abstract class can't be instantiated, which fixes our problem.

What makes abstract different from open?

Both modifiers let us extend a class, but open lets us instantiate while abstract does not.

Now that we can't instantiate, our name() method in the BakeryGood class isn't that useful anymore, and all our subclasses, except for CinnamonRoll, override it anyway (CinnamonRoll relays on the Roll implementation):

abstract class BakeryGood(val flavour: String) { 
fun eat(): String {
return "nom, nom, nom... delicious $flavour ${name()}"
}

abstract fun name(): String
}

A method marked as abstract doesn't have a body, just the signature declaration (a method signature is a way to identify a method). In Kotlin, a signature is composed of the method's name, its number, the type of parameters, and the return type.

Any class that extends BakeryGood directly must override the name() method. The technical term for overriding an abstract method is implement and, from now on, we will use it. So, the Cupcake class implements the name() method (Kotlin doesn't have a keyword for method implementation; both cases, method implementation, and method overriding, use the keyword override).

Let's introduce a new class, Customer; a bakery needs customers anyway:

class Customer(val name: String) {
fun eats(food: BakeryGood) {
println("$name is eating... ${food.eat()}")
}
}

fun main(args: Array<String>) {
val myDonut = Donut("Custard", "Powdered sugar")
val mario = Customer("Mario")
mario.eats(myDonut)
}

The eats(food: BakeryGood) method takes a BakeryGood parameter, so any instance of any class that extends the BakeryGood parameter, it doesn't matter how many hierarchy levels. Just remember that we can instantiate BakeryGood directly.

What happens if we want a simple BakeryGood? For example, testing.

There is an alternative, an anonymous subclass:

fun main(args: Array<String>) {
val mario = Customer("Mario")

mario.eats(object : BakeryGood("TEST_1") {
override fun name(): String {
return "TEST_2"
}
})
}

A new keyword is introduced here, object. Later on, we'll cover object in more detail, but for now, it is enough to know that this is an object expression. An object expression defines an instance of an anonymous class that extends a type.

In our example, the object expression (technically, the anonymous class) must override the name() method and pass a value as the parameter for the BakeryGood constructor, exactly as a standard class would do.

Remember that an object expression is an instance, so it can be used to declare values:

val food: BakeryGood = object : BakeryGood("TEST_1") { 
override fun name(): String {
return "TEST_2"
}
}

mario.eats(food)

Interfaces

The open and abstract classes are great for creating hierarchies, but sometimes they aren't enough. Some subsets can span between apparently unrelated hierarchies, for example, birds and great apes are bipedal, and both are animals and vertebrates, but they not directly related. That is why we need a different construct and Kotlin gives us interfaces (other languages deal with this problem differently).

Our bakery goods are great, but we need to cook them first:

abstract class BakeryGood(val flavour: String) { 
fun eat(): String {
return "nom, nom, nom... delicious $flavour ${name()}"
}

fun bake(): String {
return "is hot here, isn't??"
}

abstract fun name(): String
}

With our new bake() method , it will cook all our amazing products, but wait, donuts aren't baked, but fried.

What if we could move the  bake() method to a second abstract class, Bakeable? Let's try it in the following code:

abstract class Bakeable { 
fun bake(): String {
return "is hot here, isn't??"
}
}

class Cupcake(flavour: String) : BakeryGood(flavour), Bakeable() { //Compilation error: Only one class may appear in a supertype list
override fun name(): String {
return "cupcake"
}
}

Wrong! In Kotlin, a class can't extend two classes at the same time. Let's have a look at the following code:

interface Bakeable { 
fun bake(): String {
return "is hot here, isn't??"
}
}

class Cupcake(flavour: String) : BakeryGood(flavour), Bakeable {
override fun name(): String {
return "cupcake"
}
}

However, it can extend many interfaces. An interface is a type that defines a behavior; in the Bakeable interface's case, that is the bake() method.

So, what are the differences between an open/abstract class and an interface?

Let's start with the following similarities:

  • Both are types. In our example, Cupcake has an is-a relationship with BakeryGood and has an is-a relationship with Bakeable.
  • Both define behaviors as methods.
  • Although open classes can be instantiated directly, neither abstract classes nor interfaces can.

Now, let's look at the following differences:

  • A class can extend just one class (open or abstract), but can extend many interfaces.
  • An open/abstract class can have constructors.
  • An open/abstract class can initialize its own values. An interface's values must be initialized in the classes that extend the interface.
  • An open class must declare the methods that can be overridden as open. An abstract class could have both open and abstract methods.

In an interface, all methods are open and a method with no implementation doesn't need an abstract modifier:

interface Fried { 
fun fry(): String
}

open class Donut(flavour: String, val topping: String) : BakeryGood(flavour), Fried {
override fun fry(): String {
return "*swimming on oil*"
}

override fun name(): String {
return "donut with $topping topping"
}
}

When should you use one or the other?:

  • Use open class when:
    • The class should be extended and instantiated
  • Use abstract class when:
    • The class can't be instantiated
    • A constructor is needed it
    • There is initialization logic (using init blocks)

Let's have a look at the following code:

abstract class BakeryGood(val flavour: String) {
init {
println("Preparing a new bakery good")
}

fun eat(): String {
return "nom, nom, nom... delicious $flavour ${name()}"
}

abstract fun name(): String
}
  • Use interface when:
    • Multiple inheritances must be applied
    • No initialized logic is needed
My recommendation is that you should always start with an interface. Interfaces are more straightforward and cleaner; they also allow a more modular design. In the case that data initialization/constructors are needed, move to abstract/open.

As with abstract classes, object expressions can be used with interfaces:

val somethingFried = object : Fried { 
override fun fry(): String {
return "TEST_3"
}
}

Objects

We already covered object expressions, but there is more on objects. Objects are natural singletons (by natural, I mean to come as language features and not as behavior pattern implementations, as in other languages). A singleton is a type that has just one and only one instance and every object in Kotlin is a singleton. That opens a lot of interesting patterns (and also some bad practices). Objects as singletons are useful for coordinating actions across the system, but can also be dangerous if they are used to keep global state.

Object expressions don't need to extend any type:

fun main(args: Array<String>) {
val expression = object {
val property = ""

fun method(): Int {
println("from an object expressions")
return 42
}
}

val i = "${expression.method()} ${expression.property}"
println(i)
}

In this case, the expression value is an object that doesn't have any specific type. We can access its properties and functions.

There is one restriction—object expressions without type can be used only locally, inside a method, or privately, inside a class:

class Outer {
val internal = object {
val property = ""
}
}

fun main(args: Array<String>) {
val outer = Outer()

println(outer.internal.property) // Compilation error: Unresolved reference: property
}

In this case, the property value can't be accessed.

Object declarations

An object can also have a name. This kind of object is called an object declaration:

object Oven {
fun process(product: Bakeable) {
println(product.bake())
}
}

fun main(args: Array<String>) {
val myAlmondCupcake = Cupcake("Almond")
Oven.process(myAlmondCupcake)
}

Objects are singletons; you don't need to instantiate Oven to use it. Objects also can extend other types:

interface Oven {
fun process(product: Bakeable)
}

object ElectricOven: Oven {
override fun process(product: Bakeable) {
println(product.bake())
}
}

fun main(args: Array<String>) {
val myAlmondCupcake = Cupcake("Almond")
ElectricOven.process(myAlmondCupcake)
}

Companion objects

Objects declared inside a class/interface can be marked as companion objects. Observe the use of companion objects in the following code:

class Cupcake(flavour: String) : BakeryGood(flavour), Bakeable {
override fun name(): String {
return "cupcake"
}

companion object {
fun almond(): Cupcake {
return Cupcake("almond")
}

fun cheese(): Cupcake {
return Cupcake("cheese")
}
}
}

Now, methods inside the companion object can be used directly, using the class name without instantiating it:

fun main(args: Array<String>) {
val myBlueberryCupcake: BakeryGood = Cupcake("Blueberry")
val myAlmondCupcake = Cupcake.almond()
val myCheeseCupcake = Cupcake.cheese()
val myCaramelCupcake = Cupcake("Caramel")
}

Companion object's methods can't be used from instances:

fun main(args: Array<String>) {
val myAlmondCupcake = Cupcake.almond()
val myCheeseCupcake = myAlmondCupcake.cheese() //Compilation error: Unresolved reference: cheese
}

Companion objects can be used outside the class as values with the name Companion:

fun main(args: Array<String>) {
val factory: Cupcake.Companion = Cupcake.Companion
}

Alternatively, a Companion object can have a name:

class Cupcake(flavour: String) : BakeryGood(flavour), Bakeable {
override fun name(): String {
return "cupcake"
}

companion object Factory {
fun almond(): Cupcake {
return Cupcake("almond")
}

fun cheese(): Cupcake {
return Cupcake("cheese")
}
}
}

fun main(args: Array<String>) {
val factory: Cupcake.Factory = Cupcake.Factory
}

They can also be used without a name, as shown in the following code:

fun main(args: Array<String>) {
val factory: Cupcake.Factory = Cupcake
}

Don't be confused by this syntax. The Cupcake value without parenthesis is the companion object; Cupcake() is an instance.

Generics

This section is just a short introduction to generics; later, we'll cover it in detail.

Generic programming is a style programming that focuses on creating algorithms (and collaterally, data structures) that work on general problems.

The Kotlin way to support generic programming is using type parameters. In a few words, we wrote our code with type parameters and, later on, we pass those types as parameters when we use them. 

Let's take, for example, our Oven interface:

interface Oven {
fun process(product: Bakeable)
}

An oven is a machine, so we could generalize it more:

interface Machine<T> {
fun process(product: T)
}

The Machine<T> interface defines a type parameter T and a method process(T).

Now, we can extend it with Oven:

interface Oven: Machine<Bakeable>

Now, Oven is extending Machine with the Bakeable type parameter, so the process method now takes Bakeable as a parameter.

Type alias

Type alias provides a way to define names of types that already exist. Type alias can help to make complex types easier to read, and can also provide other hints.

The Oven interface is, in some sense, just a name, for a Machine<Bakeable>:

typealias Oven = Machine<Bakeable>

Our new type alias, Oven, is exactly like our good old Oven interface. It can be extended and have the values of the type Oven.

Types alias also can be used to enhance information on types, providing meaningful names related to your domain:

typealias Flavour = String

abstract class BakeryGood(val flavour: Flavour) {

It can also be used on collections:

typealias OvenTray = List<Bakeable>

It can also be used with objects:

typealias CupcakeFactory = Cupcake.Companion

Nullable types

One of the main features of Kotlin is nullable types. Nullable types allow us to define if a value can contain or being null explicitly:

fun main(args: Array<String>) {
val myBlueberryCupcake: Cupcake = null //Compilation error: Null can not be a value of a non-null type Cupcake
}

This isn't valid in Kotlin; the Cupcake type doesn't allow null values. To allow null values, myBlueberryCupcake must have a different type:

fun main(args: Array<String>) {
val myBlueberryCupcake: Cupcake? = null
}

In essence, Cupcake is a non-null type and Cupcake? is a nullable type.

In the hierarchical structure, Cupcake is a subtype of Cupcake?. So, in any situation where Cupcake? is defined, Cupcake can be used, but not the other way around:

fun eat(cupcake: Cupcake?){
// something happens here
}

fun main(args: Array<String>) {
val myAlmondCupcake = Cupcake.almond()

eat(myAlmondCupcake)

eat(null)
}

Kotlin's compiler makes a distinction between instances of nullable and non-null types.

Let's take these values, for example:

fun main(args: Array<String>) {
val cupcake: Cupcake = Cupcake.almond()
val nullabeCupcake: Cupcake? = Cupcake.almond()
}

Next, we will invoke the eat() method on both nullable and non-null types:

fun main(args: Array<String>) {
val cupcake: Cupcake = Cupcake.almond()
val nullableCupcake: Cupcake? = Cupcake.almond()

cupcake.eat() // Happy days
nullableCupcake.eat() //Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type Cupcake?
}

Calling the eat() method on cupcake is easy as pie; calling eat() on nullableCupcake is a compilation error.

Why? For Kotlin, calling a method from a nullable value is dangerous, a potential NullPointerException (NPE from now on) could be thrown. So, to be safe, Kotlin marks this as a compilation error.

What happens if we really want to invoke a method or access a property from a nullable value?

Well, Kotlin provides you options to deal with nullable values, with a catch—all are explicit. In some sense, Kotlin is telling you, Show me that you know what you are doing.

Let's review some options (there are more options that we'll cover in the following chapters).

Checking for null

Check for null as a condition in the if block:

fun main(args: Array<String>) {
val nullableCupcake: Cupcake? = Cupcake.almond()

if (nullableCupcake != null) {
nullableCupcake.eat()
}
}

Kotlin will do a smart cast. Inside the if block, nullableCupcake is a Cupcake, not a Cupcake?; so, any method or property can be accessed.

Checking for non-null types

This is similar to the previous one, but it checks directly for the type:

if (nullableCupcake is Cupcake) {
nullableCupcake.eat()
}

It also works with when:

when (nullableCupcake) {
is Cupcake -> nullableCupcake.eat()
}

Both options, checking for null and non-null types, are a little bit verbose. Let's check other options.

Safe calls

Safe calls let you access methods and properties of nullable values if the value isn't null (under the hood, at the bytecode level, a safe call is transformed into if(x != null)):

nullableCupcake?.eat()

But, what if you use it in an expression?

val result: String? = nullableCupcake?.eat()

It will return null if our value is null, so result must have a String? type.

That opens up the chance to use safe calls on a chain, as follows:

val length: Int? = nullableCupcake?.eat()?.length

The Elvis (?:) operator

The Elvis operator (?:) returns an alternative value if a null value is used in an expression:

val result2: String = nullableCupcake?.eat() ?: ""

If nullabluCupcake?.eat() is null, the ?: operator will return the alternative value "".

Obviously, the Elvis operator can be used with a chain of safe calls:

val length2: Int = nullableCupcake?.eat()?.length ?: 0

The (!!) operator

Instead of a null value, the !! operator will throw an NPE:

val result: String = nullableCupcake!!.eat()

If you can deal with an NPE, the !! operator gives you a pretty convenient feature, a free smart cast:

val result: String = nullableCupcake!!.eat()

val length: Int = nullableCupcake.eat().length

If nullableCupcake!!.eat() doesn't throw an NPE, Kotlin will change its type from Cupcake? to Cupcake from the next line and onwards.

Kotlin's type system

Type systems are a set of rules that determine the type of a language construct.

A (good) type system will help you with:

  • Making sure that the constituent parts of your program are connected in a consistent way
  • Understanding your program (by reducing your cognitive load)
  • Expressing business rules
  • Automatic low-level optimizations

We have already covered enough ground to understand Kotlin's type system.

The Any type

All types in Kotlin extend from the Any type (hold on a second, actually this isn't true but for the sake of the explanation, bear with me).

Every class and interface that we create implicitly extends Any. So, if we write a method that takes Any as a parameter, it will receive any value:

fun main(args: Array<String>) {

val myAlmondCupcake = Cupcake.almond()

val anyMachine = object : Machine<Any> {
override fun process(product: Any) {
println(product.toString())
}
}

anyMachine.process(3)

anyMachine.process("")

anyMachine.process(myAlmondCupcake)
}

What about a nullable value? Let's have a look at it:

fun main(args: Array<String>) {

val anyMachine = object : Machine<Any> {
override fun process(product: Any) {
println(product.toString())
}
}

val nullableCupcake: Cupcake? = Cupcake.almond()

anyMachine.process(nullableCupcake) //Error:(32, 24) Kotlin: Type mismatch: inferred type is Cupcake? but Any was expected
}

Any is the same as any other type and also has a nullable counterpart, Any?Any extends from Any?. So, in the end, Any? is the top class of Kotlin's type system hierarchy.

Minimum common types

Due to its type inference and expression evaluation, sometimes there are expressions in Kotlin where it is not clear which type is being returned. Most languages resolve this problem by returning the minimum common type between the possible type options. Kotlin takes a different route.

Let's take a look at an example of an ambiguous expression:

fun main(args: Array<String>) {
val nullableCupcake: Cupcake? = Cupcake.almond()

val length = nullableCupcake?.eat()?.length ?: ""
}

What type does length have? Int or String? No, length value's type is Any. Pretty logical. The minimum common type between Int and String is Any. So far, so good. Let's look at the following code now:

val length = nullableCupcake?.eat()?.length ?: 0.0

Following that logic, in this case, length should have the Number type (the common type between Int and Double), shouldn't it?

Wrong, length is still Any. Kotlin doesn't search for the minimum common type in these situations. If you want a specific type, it must be explicitly declared:

val length: Number = nullableCupcake?.eat()?.length ?: 0.0

The Unit type

Kotlin doesn't have methods with void return (as Java or C do). Instead, a method (or, to be precise, an expression) could have a Unit type.

A Unit type means that the expression is called for its side effects, rather than its return. The classic example of a Unit expression is println(), a method invoked just for its side effects.

Unit, like any other Kotlin type, extends from Any and could be nullable. Unit? looks strange and unnecessary, but is needed to keep consistency with the type system. Having a consistent type system have several advantages, including better compilation times and tooling:

anyMachine.process(Unit)

The Nothing type

Nothing is the type that sits at the bottom of the entire Kotlin hierarchy. Nothing extends all Kotlin types, including Nothing?.

But, why do we need a Nothing and Nothing? types?

Nothing represents an expression that can't be executed (basically throwing exceptions):

val result: String = nullableCupcake?.eat() ?: throw RuntimeException() // equivalent to nullableCupcake!!.eat()

On one hand of the Elvis operator, we have a String. On the other hand, we have Nothing. Because the common type between String and Nothing is String (instead of Any), the value result is a String.

Nothing also has a special meaning for the compiler. Once a Nothing type is returned on an expression, the lines after that are marked as unreachable.

Nothing? is the type of a null value:

val x: Nothing? = null

val nullsList: List<Nothing?> = listOf(null)

Other types

Classes, interfaces, and objects are a good starting point for an OOP type system, but Kotlin offers more constructs, such as data classes, annotations, and enums (there is an additional type, named sealed class, that we'll cover later).

Data classes

Creating classes whose primary purpose is to hold data is a common pattern in Kotlin (is a common pattern in other languages too, think of JSON or Protobuff).

Kotlin has a particular kind of class for this purpose:

data class Item(val product: BakeryGood,
val unitPrice: Double,
val quantity: Int)

To declare data class, there are some restrictions:

  • The primary constructor should have at least one parameter
  • The primary constructor's parameters must be val or var
  • Data classes can't be abstract, open, sealed, or inner

With these restrictions, data classes give a lot of benefits.

Canonical methods

Canonical methods are the methods declared in Any. Therefore, all instances in Kotlin have them.

For data classes, Kotlin creates correct implementations of all canonical methods.

The methods are as follows:

  • equals(other: Any?): Boolean: This method compares value equivalence, rather than reference.
  • hashCode(): Int: A hash code is a numerical representation of an instance. When hashCode() is invoked several times in the same instance, it should always return the same value. Two instances that return true when they are compared with equals must have the same hashCode().
  • toString(): String: A String representation of an instance. This method will be invoked when an instance is concatenated to a String.

The copy() method

Sometimes, we want to reuse values from an existing instance. The copy() method lets us create new instances of a data class, overriding the parameters that we want:

val myItem = Item(myAlmondCupcake, 0.40, 5)

val mySecondItem = myItem.copy(product = myCaramelCupcake) //named parameter

In this case, mySecondItem copies unitPrice and quantity from myItem, and replaces the product property.

Destructuring methods

By convention, any instance of a class that has a series of methods named component1(), component2() and so on can be used in a destructuring declaration.

Kotlin will generate these methods for any data class:

val (prod: BakeryGood, price: Double, qty: Int) = mySecondItem

The prod value is initialized with the return of component1(), price with the return of component2() , and so on. Although the preceding example use explicit types, those aren't needed:

val (prod, price, qty) = mySecondItem

In some circumstances, not all values are needed. All unused values can be replaced by (_):

val (prod, _, qty) = mySecondItem

Annotations

Annotations are a way to attach meta info to your code (such as documentation, configuration, and others).

Let's look at the following example code:

annotation class Tasty

An annotation itself can be annotated to modify its behavior:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Tasty

In this case, the Tasty annotation can be set on classes, interfaces, and objects, and it can be queried at runtime.

For a complete list of options, check the Kotlin documentation.

Annotations can have parameters with one limitation, they can't be nullable:

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Tasty(val tasty:Boolean = true)

@Tasty(false)
object ElectricOven : Oven {
override fun process(product: Bakeable) {
println(product.bake())
}
}

@Tasty
class CinnamonRoll : Roll("Cinnamon")

@Tasty
interface Fried {
fun fry(): String
}

To query annotation values at runtime, we must use the reflection API (kotlin-reflect.jar must be in your classpath):

fun main(args: Array<String>) {
val annotations: List<Annotation> = ElectricOven::class.annotations

for (annotation in annotations) {
when (annotation) {
is Tasty -> println("Is it tasty? ${annotation.tasty}")
else -> println(annotation)
}
}
}

Enum

Enum in Kotlin is a way to define a set of constant values. Enums are very useful, but not limited, as configuration values:

enum class Flour {
WHEAT, CORN, CASSAVA
}

Each element is an object that extends the Flour class.

Like any object, they can extend interfaces:

interface Exotic {
fun isExotic(): Boolean
}

enum class Flour : Exotic {
WHEAT {
override fun isExotic(): Boolean {
return false
}
},

CORN {
override fun isExotic(): Boolean {
return false
}
},

CASSAVA {
override fun isExotic(): Boolean {
return true
}
}
}

Enum can also have abstract methods:

enum class Flour: Exotic {
WHEAT {
override fun isGlutenFree(): Boolean {
return false
}

override fun isExotic(): Boolean {
return false
}
},

CORN {
override fun isGlutenFree(): Boolean {
return true
}

override fun isExotic(): Boolean {
return false
}
},

CASSAVA {
override fun isGlutenFree(): Boolean {
return true
}

override fun isExotic(): Boolean {
return true
}
};

abstract fun isGlutenFree(): Boolean
}

Any method definition must be declared after the (;) separating the last element.

When enums are used with when expressions, Kotlin's compiler checks that all cases are covered (individually or with an else):

fun flourDescription(flour: Flour): String {
return when(flour) { // error
Flour.CASSAVA -> "A very exotic flavour"
}
}

In this case, we're only checking for CASSAVA and not the other elements; therefore, it fails:

fun flourDescription(flour: Flour): String {
return when(flour) {
Flour.CASSAVA -> "A very exotic flavour"
else -> "Boring"
}
}

Summary

In this chapter, we covered the basics of OOP and how Kotlin supports it. We learned how to use classes, interfaces, objects, data classes, annotations, and enums. We also explored the Kotlin type system and saw how it helps us to write better and safer code.

In the next chapter, we will start with an introduction to functional programming.

Left arrow icon Right arrow icon
Download code icon Download Code

Key benefits

  • Focus on the functional aspects of Kotlin and identify the advantages that functional programming brings to the table and the associated coding benefits.
  • Implement common functional programming design patterns and techniques.
  • Learn to combine OOP and Reactive Programming with Functional Programming and how RxKotlin and funkTionale can help you implementing Functional Programming in Kotlin

Description

Functional programming makes your application faster, improves performance, and increases your productivity. Kotlin supports many of the popular and advanced functional features of functional languages. This book will cover the A-Z of functional programming in Kotlin. This book bridges the language gap for Kotlin developers by showing you how to create and consume functional constructs in Kotlin. We also bridge the domain gap by showing how functional constructs can be applied in business scenarios. We’ll take you through lambdas, pattern matching, immutability, and help you develop a deep understanding of the concepts and practices of functional programming. If you want learn to address problems using Recursion, Koltin has support for it as well. You’ll also learn how to use the funKtionale library to perform currying and lazy programming and more. Finally, you’ll learn functional design patterns and techniques that will make you a better programmer.By the end of the book, you will be more confident in your functional programming skills and will be able to apply them while programming in Kotlin.

Who is this book for?

Kotlin developers who have no functional programming experience, will benefit from this book.

What you will learn

  • Learn the Concepts of Functional Programming with Kotlin
  • Discover the Coroutines in Kotlin
  • Uncover Using funkTionale plugin
  • Learn Monads, Functiors and Applicatives
  • Combine Functional Programming with OOP and Reactive Programming
  • Uncover Using Monads with funkTionale
  • Discover Stream Processing

Product Details

Country selected
Publication date, Length, Edition, Language, ISBN-13
Publication date : Feb 23, 2018
Length: 350 pages
Edition : 1st
Language : English
ISBN-13 : 9781788397360
Vendor :
Google
Category :
Languages :

What do you get with eBook?

Product feature icon Instant access to your Digital eBook purchase
Product feature icon Download this book in EPUB and PDF formats
Product feature icon Access this title in our online reader with advanced features
Product feature icon DRM FREE - Read whenever, wherever and however you want

Product Details

Publication date : Feb 23, 2018
Length: 350 pages
Edition : 1st
Language : English
ISBN-13 : 9781788397360
Vendor :
Google
Category :
Languages :

Packt Subscriptions

See our plans and pricing
Modal Close icon
$19.99 billed monthly
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Simple pricing, no contract
$199.99 billed annually
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just S$6 each
Feature tick icon Exclusive print discounts
$279.99 billed in 18 months
Feature tick icon Unlimited access to Packt's library of 7,000+ practical books and videos
Feature tick icon Constantly refreshed with 50+ new titles a month
Feature tick icon Exclusive Early access to books as they're written
Feature tick icon Solve problems while you work with advanced search and reference features
Feature tick icon Offline reading on the mobile app
Feature tick icon Choose a DRM-free eBook or Video every month to keep
Feature tick icon PLUS own as many other DRM-free eBooks or Videos as you like for just S$6 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total S$ 208.97
Functional Kotlin
S$74.99
Learning Concurrency in Kotlin
S$66.99
Hands-On Microservices with  Kotlin
S$66.99
Total S$ 208.97 Stars icon

Table of Contents

15 Chapters
Kotlin – Data Types, Objects, and Classes Chevron down icon Chevron up icon
Getting Started with Functional Programming Chevron down icon Chevron up icon
Immutability - It&#x27;s Important Chevron down icon Chevron up icon
Functions, Function Types, and Side Effects Chevron down icon Chevron up icon
More on Functions Chevron down icon Chevron up icon
Delegates in Kotlin Chevron down icon Chevron up icon
Asynchronous Programming with Coroutines Chevron down icon Chevron up icon
Collections and Data Operations in Kotlin Chevron down icon Chevron up icon
Functional Programming and Reactive Programming Chevron down icon Chevron up icon
Functors, Applicatives, and Monads Chevron down icon Chevron up icon
Working with Streams in Kotlin Chevron down icon Chevron up icon
Getting Started with Arrow Chevron down icon Chevron up icon
Arrow Types Chevron down icon Chevron up icon
Kotlin&#x27;s Quick Start Chevron down icon Chevron up icon
Other Books You May Enjoy Chevron down icon Chevron up icon

Customer reviews

Rating distribution
Full star icon Full star icon Full star icon Empty star icon Empty star icon 3
(1 Ratings)
5 star 0%
4 star 0%
3 star 100%
2 star 0%
1 star 0%
Yassine Jan 28, 2020
Full star icon Full star icon Full star icon Empty star icon Empty star icon 3
En general muy superficial, demasiado centrado en el lenguaje en si. Esperaba que se centrara mas en como usar Kotlin funcionalmente en vez de explicar el lenguaje en si. Solo los dos ultimos capitulos merecen la pena y son mas cortos que el resto.
Amazon Verified review Amazon
Get free access to Packt library with over 7500+ books and video courses for 7 days!
Start Free Trial

FAQs

How do I buy and download an eBook? Chevron down icon Chevron up icon

Where there is an eBook version of a title available, you can buy it from the book details for that title. Add either the standalone eBook or the eBook and print book bundle to your shopping cart. Your eBook will show in your cart as a product on its own. After completing checkout and payment in the normal way, you will receive your receipt on the screen containing a link to a personalised PDF download file. This link will remain active for 30 days. You can download backup copies of the file by logging in to your account at any time.

If you already have Adobe reader installed, then clicking on the link will download and open the PDF file directly. If you don't, then save the PDF file on your machine and download the Reader to view it.

Please Note: Packt eBooks are non-returnable and non-refundable.

Packt eBook and Licensing When you buy an eBook from Packt Publishing, completing your purchase means you accept the terms of our licence agreement. Please read the full text of the agreement. In it we have tried to balance the need for the ebook to be usable for you the reader with our needs to protect the rights of us as Publishers and of our authors. In summary, the agreement says:

  • You may make copies of your eBook for your own use onto any machine
  • You may not pass copies of the eBook on to anyone else
How can I make a purchase on your website? Chevron down icon Chevron up icon

If you want to purchase a video course, eBook or Bundle (Print+eBook) please follow below steps:

  1. Register on our website using your email address and the password.
  2. Search for the title by name or ISBN using the search option.
  3. Select the title you want to purchase.
  4. Choose the format you wish to purchase the title in; if you order the Print Book, you get a free eBook copy of the same title. 
  5. Proceed with the checkout process (payment to be made using Credit Card, Debit Cart, or PayPal)
Where can I access support around an eBook? Chevron down icon Chevron up icon
  • If you experience a problem with using or installing Adobe Reader, the contact Adobe directly.
  • To view the errata for the book, see www.packtpub.com/support and view the pages for the title you have.
  • To view your account details or to download a new copy of the book go to www.packtpub.com/account
  • To contact us directly if a problem is not resolved, use www.packtpub.com/contact-us
What eBook formats do Packt support? Chevron down icon Chevron up icon

Our eBooks are currently available in a variety of formats such as PDF and ePubs. In the future, this may well change with trends and development in technology, but please note that our PDFs are not Adobe eBook Reader format, which has greater restrictions on security.

You will need to use Adobe Reader v9 or later in order to read Packt's PDF eBooks.

What are the benefits of eBooks? Chevron down icon Chevron up icon
  • You can get the information you need immediately
  • You can easily take them with you on a laptop
  • You can download them an unlimited number of times
  • You can print them out
  • They are copy-paste enabled
  • They are searchable
  • There is no password protection
  • They are lower price than print
  • They save resources and space
What is an eBook? Chevron down icon Chevron up icon

Packt eBooks are a complete electronic version of the print edition, available in PDF and ePub formats. Every piece of content down to the page numbering is the same. Because we save the costs of printing and shipping the book to you, we are able to offer eBooks at a lower cost than print editions.

When you have purchased an eBook, simply login to your account and click on the link in Your Download Area. We recommend you saving the file to your hard drive before opening it.

For optimal viewing of our eBooks, we recommend you download and install the free Adobe Reader version 9.