These notes are adapted from slides by Talib Hussain. There may be some mistakes — let me know if there is something here that doesn’t make sense.
- “Fields” (from Java) are generally called properties
- “Methods” (from Java) are called class functions
- visibility is public by default
We’re about to see one of the main reasons to use Kotlin instead of Java, that is, the ability to “avoid boilerplate code”. This is everywhere in Kotlin, but class definitions are an especially clear example.
The best way to see this is just to look for yourself:
/* The following is a class called SomeClass. It has a constructor, a private field, and a public setter/getter*/public class SomeClass { private int someField;
public SomeClass(int someField) { this.someField = someField; }
public int getSomeField() { return someField; }
public void setSomeField(int someField) { this.someField = someField; }}
/* The following is a class called SomeClass. It has a constructor, a private field, and a public setter/getter*/public class SomeClass { private int someField;
public SomeClass(int someField) { this.someField = someField; }
public int getSomeField() { return someField; }
public void setSomeField(int someField) { this.someField = someField; }}
/* The following is a class called SomeClass. It has a constructor, a property, and a public setter/getter for that property*/class SomeClass(var someField : Int)
/* The following is a class called SomeClass. It has a constructor, a property, and a public setter/getter for that property*/class SomeClass(var someField : Int)
In the following sections, we’ll deconstruct how this is achieved in Kotlin.
For a simple class, we can just define a constructor. Unlike Java, we don’t have to explicitly define all the fields, etc.
class Car(var brand: String, var model: String, var year: Int)
class Car(var brand: String, var model: String, var year: Int)
Constructor arguments can have default values:
class Car(var brand: String, var model: String = "Unknown", var year:Int = 2022)
class Car(var brand: String, var model: String = "Unknown", var year:Int = 2022)
When calling a constructor in Kotlin, we don’t use the ‘new’ keyword:
val c1 = Car("Ford", "Mustang", 1969)
val c1 = Car("Ford", "Mustang", 1969)
Multiple constructors are possible (self-study).
class Car(var brand: String, var model: String, var year: Int) { // Class function fun drive() { println("Wrooom!") } // Class function with parameters fun speed(maxSpeed: Int) { println("Max speed is: " + maxSpeed) }}
class Car(var brand: String, var model: String, var year: Int) { // Class function fun drive() { println("Wrooom!") } // Class function with parameters fun speed(maxSpeed: Int) { println("Max speed is: " + maxSpeed) }}
Unlike java the getter and setter for a property of a class simply uses the name of the property; i.e. not getX()
setX()
etc..
val car = Car("Honda", "Camry", 2023)println(car.brand) // Access with .<property name>car.model = "Civic" // Set with .<property name>println(civis.name)
val car = Car("Honda", "Camry", 2023)println(car.brand) // Access with .<property name>car.model = "Civic" // Set with .<property name>println(civis.name)
Kotlin does not have the static
keyword.
If you want to create the equivalent to a static method in Kotlin, you can use companion objects.
Companion objects are the singleton objects whose properties and functions are tied to a class but not to the instance of that class. Hence, we can access them just like a static method of the class.
Note that only one companion class is allowed per class. More than one companion object per class will lead to a runtime error in Kotlin.
class MyClass { companion object{ fun myStaticMethod(): String{ return "This method can be called without object" } }}
fun main(args: Array<String>) { println(MyClass.myStaticMethod())}
class MyClass { companion object{ fun myStaticMethod(): String{ return "This method can be called without object" } }}
fun main(args: Array<String>) { println(MyClass.myStaticMethod())}
More information: Kotlin: static member fields and singletons
To allow a class to be subclassed, it must be defined with the keyword open
. A class extends another using the colon syntax below:
// Superclassopen class MyParentClass { val x = 5}// Subclassclass MyChildClass: MyParentClass() { fun myFunction() { println(x) // x is now inherited from the superclass }}
// Superclassopen class MyParentClass { val x = 5}// Subclassclass MyChildClass: MyParentClass() { fun myFunction() { println(x) // x is now inherited from the superclass }}
If the parent has constructor values, must also pass the appropriate constructor values when subclassing.
// Superclassopen class MyClass(var value:Int)
// Subclassclass SubClass(var value:Int, var otherValue:String): MyClass(value)
// Superclassopen class MyClass(var value:Int)
// Subclassclass SubClass(var value:Int, var otherValue:String): MyClass(value)
A function of a parent class cannot be overridden in a subclass unless the function in the parent is declared open. The override keyword must also be used when defining the overridden function.
open class MyClass(var value:String) { open fun myFunction() { println(value) }}
class SubClass(var value:String, var otherValue:String): MyClass(value) { override fun myFunction() { println(otherValue); }}
open class MyClass(var value:String) { open fun myFunction() { println(value) }}
class SubClass(var value:String, var otherValue:String): MyClass(value) { override fun myFunction() { println(otherValue); }}
Elsewhere:
val x = MyClass("Hi")val y = SubClass("Hi", "Bye")x.myFunction()y.myFunction()Should print out "Hi" then "Bye"
val x = MyClass("Hi")val y = SubClass("Hi", "Bye")x.myFunction()y.myFunction()Should print out "Hi" then "Bye"
Kotlin offers different types of classes, e.g.,
- data classes
- enum classes
- sealed classes Data classes are a very useful concise way to define a class whose purpose is to store data.
- E.g., Similar to a “POJO” – plain old Java object or JavaBean https://antonioleiva.com/data-classes-kotlin/
Kotlin is designed as a null safe language. By default, variables in Kotlin cannot be set to null
.
Instead, there is a special nullable type which allows you to use null
subject to certain rules.
var str: String = "xyz"str = null // Compile-time error
var str: String = "xyz"str = null // Compile-time error
vs.
var str: String? = "xyz"str = null // OK
var str: String? = "xyz"str = null // OK
fun getLength(str: String?): Int? { return str.length // Compile-time error}
fun getLength(str: String?): Int? { return str.length // Compile-time error}
Since str could be null
, checking its length opens you up to the possibility of a null pointer error. Instead:
fun getLength(str: String): Int? { return str.length // OK}
fun getLength(str: String): Int? { return str.length // OK}
We can also explicitly check for null (“old-school” way)
fun getLength(str: String?): Int? { if (str != null) { return str.length // OK } return 0}
fun getLength(str: String?): Int? { if (str != null) { return str.length // OK } return 0}
Or can use the safe call operator ?
.
fun getLength(str: String?): Int? { return str?.length // OK}
fun getLength(str: String?): Int? { return str?.length // OK}
This returns null if the given str string is null
public ZipCode getZipCode(User user) { if (user != null) { if (user.getAddress() != null) return user.getAddress().getZipCode(); } return null; }}
public ZipCode getZipCode(User user) { if (user != null) { if (user.getAddress() != null) return user.getAddress().getZipCode(); } return null; }}
fun getZipCode(user: User?): ZipCode? { return user?.address?.zipCode;}
// This is an equivalent one-linerfun getZipCode(user: User?): ZipCode? = user?.address?.zipCode
fun getZipCode(user: User?): ZipCode? { return user?.address?.zipCode;}
// This is an equivalent one-linerfun getZipCode(user: User?): ZipCode? = user?.address?.zipCode
- Recommended: The Google Developer Kotlin Bootcamp is highly recommended for following along with the course so far. At this point, you should know enough to complete all the way to the end of Lesson 4.