Kotlin is a modern programming language that compiles to Java bytecode. It is free and open source, and promises to make coding for Android even more fun.
In the previous article, you learned advanced uses of functions, such as extension functions, closures, higher-order functions, and inline functions in Kotlin.
In this post you'll get an introduction to object-oriented programming in Kotlin by learning about classes: constructors and properties, casting, and more advanced class features that Kotlin makes easy.
1. Classes
A class is a program unit that groups together functions and data to perform some related tasks. We declare a class in Kotlin using the class
keyword—similar to Java.
class Book
The preceding code is the simplest class declaration—we just created an empty class called Book
. We can still instantiate this class even if it doesn't contain a body using its default constructor.
val book = Book()
As you can observe in the code above, we didn't use the new
keyword to instantiate this class—as is usual in other programming languages. new
is not a keyword in Kotlin. This makes our source code concise when creating a class instance. But be aware that instantiation of a Kotlin class in Java will require the new
keyword.
// In a Java file Book book = new Book()
Class Constructors and Properties
Let's look into how to add a constructor and properties to our class. But first, let's see a typical class in Java:
/* Java */ public class Book { private String title; private Long isbn; public Book(String title, Long isbn) { this.title = title; this.isbn = isbn; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public Long getIsbn() { return isbn; } public void setIsbn(Long isbn) { this.isbn = isbn; } }
Looking at our Book
model class above, we have the following:
- two fields:
title
andisbn
- a single constructor
- getters and setters for the two fields (fortunately, IntelliJ IDEA can help us generate these methods)
Now let's look at how we can write the preceding code in Kotlin instead:
/* Kotlin */ class Book { var title: String var isbn: Long constructor(title: String, isbn: Long) { this.title = title this.isbn = isbn } }
A pretty tidy class! We've now reduced the number of lines of code from 20 to just 9. The constructor()
function is called a secondary constructor in Kotlin. This constructor is equivalent to the Java constructor that we called when instantiating a class.
In Kotlin, there is no concept of a field like you might be familiar with; instead, it uses the concept of "properties". For example, we have two mutable (read-write) properties declared with the var
keyword: title
and isbn
in the Book
class. (If you need a refresher on variables in Kotlin, kindly visit the first post in this series: Variables, Basic Types, and Arrays).
An amazing thing is that the getters and setters for these properties are auto-generated for us under the hood by the Kotlin compiler. Notice that we didn't specify any visibility modifiers to these properties—so by default, they're public. In other words, they can be accessed from anywhere.
Let's look at another version of the same class in Kotlin:
class Book constructor(title: String, isbn: Long) { var title: String var isbn: Long init { this.title = title this.isbn = isbn } }
In this code, we've removed the secondary constructor. Instead, we declared a constructor in the class header called a primary constructor. A primary constructor doesn't have any place to put a block of code, so we utilize the init
modifier to initialize incoming parameters from the primary constructor. Note that the init
code block is executed immediately when the class instance is created.
As you can see, our code still has a lot of boilerplate. Let's reduce it further:
class Book constructor(var title: String, var isbn: Long)
Our Book
class is now just one line of code. That's really cool! Notice that in the primary constructor parameter list, we defined our mutable properties: title
and isbn
directly inside the primary constructor with the var
keyword.
We can also add default values to any of the class properties right inside the constructor.
class Book constructor(var title: String = "default value", var isbn: Long)
In fact, we can also omit the constructor
keyword, but only if it doesn't have any visibility modifier (public
, private
, or protected
) or any annotations.
class Book (var title: String = "default value", var isbn: Long)
A very neat class, I must say!
We can now create a class instance like this:
val book = Book("A Song of Ice and Fire", 9780007477159) val book2 = Book(1234) // uses the title property's default value
Accessing and Setting Properties
In Kotlin, we can get a property by the class object book
, followed by a dot separator .
, then the property name title
. This concise style of accessing properties is called property access syntax. In other words, we don't have to call the property getter method to access or call the setter to set a property in Kotlin—as we do in Java.
println(book.title) // "A Song of Ice and Fire"
Because the isbn
property is declared with the var
keyword (read-write), we can also change the property value using the assignment operator =
.
book.isbn = 1234 println(book.isbn) // 1234
Let's see another example:
class Book ( var title: String, val isbn: Long ) val book = Book("A Song of Ice and Fire", 9780007477159) book.isbn = 1234 // error: read-only property book.title = "Things Fall Apart" // reassigned title with value
Here, we updated the isbn
parameter to be immutable (read-only) instead—by using the val
keyword. We instantiated a class instance book
and reassigned the title
property the value "Things Fall Apart". Notice that when we tried to reassign the isbn
property value to 1234
, the compiler complained. This is because the property is immutable, having been defined with the val
keyword.
Java Interoperability
Be aware that by declaring a parameter with the var
modifier inside the primary constructor, the Kotlin compiler (behind the scenes) has helped us to generate both the property accessors: getter and setter. If you use val
, it will generate only the getter.
/* Kotlin */ class Book ( var title: String, val isbn: Long )
This means that Java callers can simply get or set the property field by calling the setter or getter method of the property respectively. Remember, this depends on the modifier used to define the Kotlin property: var
or val
.
/* Java */ Book book = new Book("A Song of Ice and Fire", 9780385474542) println(book.getTitle()) // "A Song of Ice and Fire" book.setTitle("Things Fall Apart") // sets new value println(book.getTitle()) // "Things Fall Apart" book.getIsbn() // 9780385474542 book.setIsbn(4545454) // won't compile
Custom Getters and Setters
In this section, I'll show you how to create custom accessors (getters and setters) for a property in Kotlin if you want to. Creating a custom setter can be useful if you want to validate or verify a value before it's set to a class property. And a custom property getter can be useful when you want to change or modify the value that should be returned.
Creating a Custom Setter
Because we want to create our own custom getter or setter for a property, we have to define that property in the class body instead of the constructor header.
class Book (val isbn: Long) { var title = "default value" }
This is why we moved the mutable (read-write) title
property into the class body and gave it a default value (or else it wouldn't compile).
class Book (val isbn: Long) { var title = "default value" set(value) { if (!value.isNotEmpty()) { throw IllegalArgumentException("Title must not be empty") } field = value } }
You can see we defined our own setter method set(value)
for the title
right below the property definition—note that you can't modify this set()
method signature because this is what the compiler expects as a custom property setter function.
The parameter value
passed to the set
method represents the actual value that was assigned to the property by users—you can change the parameter name if you wish, but value
is much preferred. We validated the value
by checking if the value is empty. If empty, stop execution and throw an exception; otherwise, reassign the value to a special field
variable.
This special field
variable field inside the set
method is an alias for the backing field of the property—a backing field is just a field that is used by properties when you want to modify or use that field data. Unlike value
, you can't rename this special field
variable.
Creating a Custom Getter
It's very easy to create a custom getter for a property in Kotlin.
class Book (val isbn: Long) { var title = "default value" //... set method get() { return field.toUpperCase() } }
Inside the get
method, we simply return a modified field
—in our case, we returned the book title in uppercase.
val book = Book(9780007477159) book.title = "A Song of Ice and Fire" println(book.title) // "A SONG OF ICE AND FIRE" println(book.isbn) // 9780007477159
Note that each time we set a value to the title
property, its set
method block is executed—the same goes for the get
method each time we retrieve it.
If you want to learn about member functions for a Kotlin class (the kind of function that is defined inside a class, object, or interface), visit the More Fun With Functions post in this series.
More on Constructors
As I discussed earlier, we have two types of constructors in Kotlin: primary and secondary. We have the freedom to combine both of them in a single class—as you can see in the example below:
class Car(val name: String, val plateNo: String) { var new: Boolean = true constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) { this.new = new } }
Note that we can't declare properties inside a secondary constructor, as we did for the primary constructor. If we want to do this, we have to declare it inside the class body and then initialize it in the secondary constructor.
In the code above, we set the default value of the new
property for the class Car
(remember, new
is not a keyword in Kotlin)—we can then use the secondary constructor to change it if we want. In Kotlin, every secondary constructor must call the primary constructor, or call another secondary constructor that calls the primary constructor—we use the this
keyword to achieve that.
Note also that we can have multiple secondary constructors inside a class.
class Car(val name: String, val plateNo: String) { var new: Boolean? = null var colour: String = "" constructor(name: String, plateNo: String, new: Boolean) : this(name, plateNo) { this.new = new } constructor(name: String, plateNo: String, new: Boolean, colour: String ) : this(name, plateNo, new) { this.colour = colour } }
If a class extends a superclass, then we can use the super
keyword (similar to Java) to call the superclass constructor (we'll discuss inheritance in Kotlin in a future post).
// directly calls primary constructor val car1 = Car("Peugeot 504", "XYZ234") // directly calls 1st sec. constructor val car2 = Car("Peugeot 504", "XYZ234", false) // directly calls last sec. constructor val car3 = Car("Peugeot 504", "XYZ234", false, "grey")
As I said earlier, for us to explicitly include a visibility modifier to a constructor in a class, we've got to include the constructor
keyword—by default, constructors are public.
class Car private constructor(val name: String, val plateNo: String) { //...
Here, we made the constructor private—this means that users can't instantiate an object using its constructor directly. This can be useful if you want users to instead call another method (a factory method) to do the creation of objects indirectly.
2. Any and Nothing Types
In Kotlin, the topmost type in the type hierarchy is called Any
. This is equivalent to the Java Object
type. This means that all classes in Kotlin explicitly inherit from the Any
type, including String
, Int
, Double
, and so on. The Any
type contains three methods: equals
, toString
, and hashcode
.
We can also utilize the Nothing
class in Kotlin in functions that always return an exception—in other words, for functions that don't terminate normally. When a function returns Nothing
, then we know it's going to throw an exception. No equivalent type of this kind exists in Java.
fun throwException(): Nothing { throw Exception("Exception message) }
This can come in handy when testing error handling behaviour in your unit tests.
3. Visibility Modifiers
Visibility modifiers help us to restrict accessibility of our API to the public. We can provide different visibility modifiers to our classes, interfaces, objects, methods, or properties. Kotlin provides us with four visibility modifiers:
Public
This is the default, and any class, function, property, interface, or object that has this modifier can be accessed from anywhere.
Private
A top-level function, interface or class that is declared as private
can be accessed only within the same file.
Any function or property that is declared private
inside a class, object, or interface can only be visible to other members of that same class, object, or interface.
class Account { private val amount: Double = 0.0 }
Protected
The protected
modifier can only be applied to properties or functions inside a class, object, or interface—it can't be applied to top-level functions, classes, or interfaces. Properties or functions with this modifier are only accessible within the class defining it and any subclass.
Internal
In a project that has a module (Gradle or Maven module), a class, object, interface or function specified with the internal
modifier declared inside that module is only accessible from within that module.
internal class Account { val amount: Double = 0.0 }
4. Smart Casting
Casting means taking an object of another type and converting it into another object type. For example, in Java, we use the instanceof
operator to determine whether a particular object type is of another type before we then cast it.
/* Java */ if (shape instanceof Circle) { Circle circle = (Circle) shape; circle.calCircumference(3.5); }
As you can see, we checked if shape
instance is Circle
, and then we have to explicitly cast the shape
reference to a Circle
type so that we can call methods of the circle
type.
Another awesome thing about Kotlin is the smartness of its compiler when it comes to casting. Let's now see a version in Kotlin.
/* Kotlin */ if (shape is Circle) { shape.calCircumference(3.5) }
Pretty neat! The compiler is smart to know that the if
block will be executed only if the shape
object is an instance of Circle
—so the casting mechanism is done under the hood for us. We can now easily call properties or functions of the Circle
type inside the if
block.
if (shape is Circle && shape.hasRadius()) { println("Circle radius is {shape.radius}") }
Here, the last condition after the &&
in the if
header will be called only when the first condition is true
. If the shape
is not a Circle
, then the last condition won't be evaluated.
5. Explicit Casting
We can use the as
operator (or unsafe cast operator) to explicitly cast a reference of a type to another type in Kotlin.
val circle = shape as Circle circle.calCircumference(4)
If the explicit casting operation is illegal, note that a ClassCastException
will be thrown. To prevent an exception from being thrown when casting, we can use the safe cast operator (or nullable cast operator) as?
.
val circle: Circle? = shape as? Circle
The as?
operator will try to cast to the intended type, and it returns null
if the value can't be cast instead of throwing an exception. Remember that a similar mechanism was discussed in the Nullability section in Nullability, Loops, and Conditions post in this series. Read up there for a refresher.
6. Objects
Objects in Kotlin are more similar to JavaScript objects than Java objects. Note that an object in Kotlin is not an instance of a specific class!
Objects are very similar to classes. Here are some of the characteristics of objects in Kotlin:
- They can have properties, methods, and an
init
block. - These properties or methods can have visibility modifiers.
- They can't have constructors (primary or secondary).
- They can extend other classes or implement an interface.
Let's now dig into how to create an object.
object Singleton { fun myFunc(): Unit { // do something } }
We place the object
keyword before the name of the object we want to create. In fact, we are creating singletons when we create objects in Kotlin using the object
construct, because only one instance of an object exists. You'll learn more about this when we discuss object interoperability with Java.
A singleton is a software design pattern that guarantees a class has one instance only and a global point of access to it is provided by that class. Any time multiple classes or clients request the class, they get the same instance of the class. You can check out my post about the singleton pattern in Java to learn more about it.
You can access the object or singleton anywhere in your project—so long as you import its package.
Singleton.myFunc()
If you're a Java coder, this is how we typically create singletons:
public class Singleton { private static Singleton INSTANCE = null; // other instance variables can be here private Singleton() {}; public static synchronized Singleton getInstance() { if (INSTANCE == null) { INSTANCE = new Singleton(); } return(INSTANCE); } // other instance methods can follow }
As you can see, using the Kotlin object
construct makes it concise and easier to create singletons.
Objects in Kotlin can be utilized also to create constants. Typically in Java, we create constants in a class by making it a public static final field like this:
public final class APIConstants { public static final String baseUrl = "http://www.myapi.com/"; private APIConstants() {} }
This code in Java can be converted to Kotlin more succinctly like this:
package com.chike.kotlin.constants object APIConstants { val baseUrl: String = "http://www.myapi.com/" }
Here we declared the constant APIConstants
with a property baseUrl
inside a package com.chike.kotlin.constants
. Under the hood, a Java private static final member baseUrl
is created for us and initialized with the string URL.
To use this constant in another package in Kotlin, simply import the package.
import com.chike.kotlin.constants.APIConstants APIConstants.baseUrl
Java Interoperability
Kotlin converts an object to a final Java class under the hood. This class has a private static field INSTANCE
which holds a single instance (a singleton) of the class. The following code shows how simply users can call a Kotlin object from Java.
/* Java */ Singleton.INSTANCE.myFunc()
Here, a Java class called Singleton
was generated with a public static final member INSTANCE
, including a public final function myFunc()
.
To make the object function or property in Kotlin be a static member of the generated Java class, we use the @JvmStatic
annotation. Here's how to use it:
object Singleton { @JvmStatic fun myFunc(): Unit { // do something } }
By applying the @JvmStatic
annotation to myFunc()
, the compiler has turned it into a static function.
Now Java callers can call it like a normal static member call. Note that using the INSTANCE
static field to call members will still work.
/* Java */ Singleton.myFunc()
7. Companion Objects
Now we've gotten to understand what objects are in Kotlin, let's dive into another kind of objects called companion objects.
Because Kotlin doesn't support static classes, methods or properties like the ones we have in Java, the Kotlin team provided us with a more powerful alternative called companion objects. A companion object is basically an object that belongs to a class—this class is known as the companion class of the object. This also means that the characteristics I mentioned for objects also apply to companion objects.
Creating a Companion Object
Similar to static methods in Java, a companion object is not associated with a class instance but rather with the class itself—for example, a factory static method, which has the job of creating a class instance.
class Person private constructor(var firstName: String, var lastName: String) { companion object { fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) } }
Here, we made the constructor private
—this means that users outside the class can't create an instance directly. Inside our companion object block, we have a function create()
, which creates a Person
object and returns it.
Invoking a Companion Object Function
companion
object instantiation is lazy. In other words, it will be instantiated only when needed the first time. The instantiation of a companion
object happens when an instance of the companion
class is created or the companion
object members are accessed.
Let's see how to invoke a companion object function in Kotlin.
val person = Person.create("Cersei", "Lannister") println(person.firstName) // prints "Cersei"
As you can see, this is just like invoking a static method in Java as normal. In other words, we just call the class and then call the member. Note that apart from functions, we can also have properties inside our companion object.
class Person private constructor(var firstName: String, var lastName: String) { init { count++ } companion object { var count: Int = 0 fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) init { println("Person companion object created") } } }
Note also that the companion
class has unrestricted access to all the properties and functions declared in its companion object, whereas a companion object can't access the class members. We can have an init
code block inside a companion
object—this is called immediately when the companion object is created.
Person.create("Arya", "Stark") Person.create("Daenerys", "Targaryen") println(Person.count)
The result of executing the code above will be:
Person companion object created 2
Remember, only a single instance of a class companion
object can ever exist.
We're also free to provide our companion object with a name.
// ... companion object Factory { var count: Int = 0 fun create(firstName: String, lastName: String): Person = Person(firstName, lastName) } // ...
Here, we gave it a name called Factory
. We can then call it like this in Kotlin:
Person.Factory.create("Petyr", "Baelish")
This style is verbose, so sticking with the previous way is much preferred. But this might come in handy when calling a companion object function or property from Java.
As I said earlier, like objects, companion objects can also include properties or functions, implement interfaces, and even extend a class.
interface PersonFactory { fun create(firstName: String, lastName: String): Person } class Person private constructor(var firstName: String, var lastName: String) { companion object : PersonFactory { override fun create(firstName: String, lastName: String): Person { return Person(firstName, lastName) } } }
Here, we have an interface PersonFactory
with just a single create()
function. Looking at our new modified companion
object, it now implements this interface (you'll learn about interfaces and inheritance in Kotlin in a later post).
Java Interoperability
Under the hood, companion objects are compiled similarly to the way a Kotlin object is compiled. In our own case, two classes are generated for us: a final Person
class and an inner static final class Person$Companion
.
The Person
class contains a final static member called Companion
—this static field is an object of the Person$Companion
inner class. The Person$Companion
inner class also has its own members, and one of them is a public final function called create()
.
Note that we did not give our companion object a name, so the generated static inner class was Companion
. If we had given it a name, then the generated name would be the name we gave it in Kotlin.
/* Java */ Person person = Person.Companion.create("Jon", "Snow");
Here, the companion object in Kotlin has no name, so we use the name Companion
provided by the compiler for Java callers to call it.
The @JvmStatic
annotation applied on a companion object member works similarly to how it works for a regular object.
Companion Object Extensions
Similarly to how extension functions can extend the functionality of a class, we can also extend the functionality of a companion object. (If you want a refresher on extension functions in Kotlin, visit the Advanced Functions tutorial in this series).
class ClassA { companion object { } } fun ClassA.Companion.extFunc() { // ... do implementation } ClassA.extFunc()
Here, we defined an extension function extFunc()
on the companion object ClassA.Companion
. In other words, extfunc()
is an extension of the companion object. Then we can call the extension as if it's a member function (it's not!) of the companion object.
Behind the scenes, the compiler will create a static utility function extFunc()
. The receiver object as an argument to this utility function is ClassA$Companion
.
Conclusion
In this tutorial, you learned about basic classes and objects in Kotlin. We covered the following about classes:
- class creation
- constructors
- properties
- visibility modifiers
- smart casting
- explicit casting
Also, you learned about how objects and companion objects in Kotlin can easily replace your static methods, constants, and singletons you code in Java. But that's not all! There is still more to learn about classes in Kotlin. In the next post, I'll show you even more cool features that Kotlin has for object-oriented programming. See you soon!
To learn more about the Kotlin language, I recommend visiting the Kotlin documentation. Or check out some of our other Android app development posts here on Envato Tuts+!
-
Android SDKJava vs. Kotlin: Should You Be Using Kotlin for Android Development?
-
Android SDKIntroduction to Android Architecture Components
-
Android SDKHow to Use the Google Cloud Vision API in Android Apps
-
Android SDKWhat Are Android Instant Apps?
No comments:
Post a Comment