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
$29.99 $43.99
Paperback
$54.99
Subscription
Free Trial
Renews at $19.99p/m

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
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
Estimated delivery fee Deliver to Egypt

Standard delivery 10 - 13 business days

$12.95

Premium delivery 3 - 6 business days

$34.95
(Includes tracking information)

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 : 9781788476485
Vendor :
Google
Category :
Languages :
Tools :

What do you get with Print?

Product feature icon Instant access to your digital eBook copy whilst your Print order is Shipped
Product feature icon Paperback book shipped to your preferred address
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
Estimated delivery fee Deliver to Egypt

Standard delivery 10 - 13 business days

$12.95

Premium delivery 3 - 6 business days

$34.95
(Includes tracking information)

Product Details

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

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 $5 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 $5 each
Feature tick icon Exclusive print discounts

Frequently bought together


Stars icon
Total $ 152.97
Functional Kotlin
$54.99
Learning Concurrency in Kotlin
$48.99
Hands-On Microservices with  Kotlin
$48.99
Total $ 152.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

What is the delivery time and cost of print book? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela
What is custom duty/charge? Chevron down icon Chevron up icon

Customs duty are charges levied on goods when they cross international borders. It is a tax that is imposed on imported goods. These duties are charged by special authorities and bodies created by local governments and are meant to protect local industries, economies, and businesses.

Do I have to pay customs charges for the print book order? Chevron down icon Chevron up icon

The orders shipped to the countries that are listed under EU27 will not bear custom charges. They are paid by Packt as part of the order.

List of EU27 countries: www.gov.uk/eu-eea:

A custom duty or localized taxes may be applicable on the shipment and would be charged by the recipient country outside of the EU27 which should be paid by the customer and these duties are not included in the shipping charges been charged on the order.

How do I know my custom duty charges? Chevron down icon Chevron up icon

The amount of duty payable varies greatly depending on the imported goods, the country of origin and several other factors like the total invoice amount or dimensions like weight, and other such criteria applicable in your country.

For example:

  • If you live in Mexico, and the declared value of your ordered items is over $ 50, for you to receive a package, you will have to pay additional import tax of 19% which will be $ 9.50 to the courier service.
  • Whereas if you live in Turkey, and the declared value of your ordered items is over € 22, for you to receive a package, you will have to pay additional import tax of 18% which will be € 3.96 to the courier service.
How can I cancel my order? Chevron down icon Chevron up icon

Cancellation Policy for Published Printed Books:

You can cancel any order within 1 hour of placing the order. Simply contact customercare@packt.com with your order details or payment transaction id. If your order has already started the shipment process, we will do our best to stop it. However, if it is already on the way to you then when you receive it, you can contact us at customercare@packt.com using the returns and refund process.

Please understand that Packt Publishing cannot provide refunds or cancel any order except for the cases described in our Return Policy (i.e. Packt Publishing agrees to replace your printed book because it arrives damaged or material defect in book), Packt Publishing will not accept returns.

What is your returns and refunds policy? Chevron down icon Chevron up icon

Return Policy:

We want you to be happy with your purchase from Packtpub.com. We will not hassle you with returning print books to us. If the print book you receive from us is incorrect, damaged, doesn't work or is unacceptably late, please contact Customer Relations Team on customercare@packt.com with the order number and issue details as explained below:

  1. If you ordered (eBook, Video or Print Book) incorrectly or accidentally, please contact Customer Relations Team on customercare@packt.com within one hour of placing the order and we will replace/refund you the item cost.
  2. Sadly, if your eBook or Video file is faulty or a fault occurs during the eBook or Video being made available to you, i.e. during download then you should contact Customer Relations Team within 14 days of purchase on customercare@packt.com who will be able to resolve this issue for you.
  3. You will have a choice of replacement or refund of the problem items.(damaged, defective or incorrect)
  4. Once Customer Care Team confirms that you will be refunded, you should receive the refund within 10 to 12 working days.
  5. If you are only requesting a refund of one book from a multiple order, then we will refund you the appropriate single item.
  6. Where the items were shipped under a free shipping offer, there will be no shipping costs to refund.

On the off chance your printed book arrives damaged, with book material defect, contact our Customer Relation Team on customercare@packt.com within 14 days of receipt of the book with appropriate evidence of damage and we will work with you to secure a replacement copy, if necessary. Please note that each printed book you order from us is individually made by Packt's professional book-printing partner which is on a print-on-demand basis.

What tax is charged? Chevron down icon Chevron up icon

Currently, no tax is charged on the purchase of any print book (subject to change based on the laws and regulations). A localized VAT fee is charged only to our European and UK customers on eBooks, Video and subscriptions that they buy. GST is charged to Indian customers for eBooks and video purchases.

What payment methods can I use? Chevron down icon Chevron up icon

You can pay with the following card types:

  1. Visa Debit
  2. Visa Credit
  3. MasterCard
  4. PayPal
What is the delivery time and cost of print books? Chevron down icon Chevron up icon

Shipping Details

USA:

'

Economy: Delivery to most addresses in the US within 10-15 business days

Premium: Trackable Delivery to most addresses in the US within 3-8 business days

UK:

Economy: Delivery to most addresses in the U.K. within 7-9 business days.
Shipments are not trackable

Premium: Trackable delivery to most addresses in the U.K. within 3-4 business days!
Add one extra business day for deliveries to Northern Ireland and Scottish Highlands and islands

EU:

Premium: Trackable delivery to most EU destinations within 4-9 business days.

Australia:

Economy: Can deliver to P. O. Boxes and private residences.
Trackable service with delivery to addresses in Australia only.
Delivery time ranges from 7-9 business days for VIC and 8-10 business days for Interstate metro
Delivery time is up to 15 business days for remote areas of WA, NT & QLD.

Premium: Delivery to addresses in Australia only
Trackable delivery to most P. O. Boxes and private residences in Australia within 4-5 days based on the distance to a destination following dispatch.

India:

Premium: Delivery to most Indian addresses within 5-6 business days

Rest of the World:

Premium: Countries in the American continent: Trackable delivery to most countries within 4-7 business days

Asia:

Premium: Delivery to most Asian addresses within 5-9 business days

Disclaimer:
All orders received before 5 PM U.K time would start printing from the next business day. So the estimated delivery times start from the next day as well. Orders received after 5 PM U.K time (in our internal systems) on a business day or anytime on the weekend will begin printing the second to next business day. For example, an order placed at 11 AM today will begin printing tomorrow, whereas an order placed at 9 PM tonight will begin printing the day after tomorrow.


Unfortunately, due to several restrictions, we are unable to ship to the following countries:

  1. Afghanistan
  2. American Samoa
  3. Belarus
  4. Brunei Darussalam
  5. Central African Republic
  6. The Democratic Republic of Congo
  7. Eritrea
  8. Guinea-bissau
  9. Iran
  10. Lebanon
  11. Libiya Arab Jamahriya
  12. Somalia
  13. Sudan
  14. Russian Federation
  15. Syrian Arab Republic
  16. Ukraine
  17. Venezuela