Exploring Kotlin's Abstract Classes and Specialized Class Types: A Guide to Object-Oriented Programming Concepts.
Daniel Brian
Posted on April 21, 2023
In this article, we are going to look at two concepts that are crucial in Modern Android development.
By the end of this article, Our aim will be to dive into:
1. Abstract classes
2. Special kinds of classes in Kotlin
I. Abstract Classes
- Abstract classes can be thought of as a hybrid of an open class and an interface.
- An abstract class is declared using the abstract keyword in front of the class.
abstract
class
className
{}
- Abstract classes can contain both abstract and non-abstract member properties in an abstract class. These properties are marked with the abstract modifier and they do not have a body as shown below.
abstract class className(val x : String) //non-abstract property
{
abstract var y : Int //Abstract property
abstract fun method () //Abstract property
fun method () //non-abstract method property
{
println("Non abstract function")
}
}
- Abstract classes cannot be used to create objects. However, you can inherit subclasses from them but they need to be overridden.
- The key advantage of abstract classes is that they can have non-open methods and non-abstract properties.
abstract class Employee (val name : String,val experience : Int){
//abstract property
abstract var salary : Double
abstract fun dateOfBirth(date : String)
//non-abstract property
fun employeeDetails() {
println("Name of the employee : $name")
println("Experience in years : $experience")
println("Annual salary : $salary")
}
}
//derived class
class Engineer (name: String, experience: Int): Employee(name, experience) {
override var salary = 5000.00
override fun dateOfBirth(date : String){
println("Date of Birth is: $date")
}
}
fun main () {
val eng = Engineer("Praveen",2)
eng.employeeDetails()
eng.dateOfBirth("02 December 1994")
}
- Abstract classes are mainly used when you want to specify a set of generic operations for multiple classes. An abstract member of the abstract class can be overridden in all the derived classes.
- In the below class we override the cal function in three derived classes of the calculator.
abstract class Calculator {
abstract fun cal (firstNum : Int, secondNum : Int): Int
}
//Addition of two numbers
class Add : Calculator () {
override fun cal (firstNum : Int, secondNum : Int) : Int{
return firstNum + secondNum
}
}
//Division of two numbers
class Divide : Calculator () {
override fun cal (firstNum : Int, secondNum : Int) : Int{
return firstNum / secondNum
}
}
//Multiplication of two numbers
class Multiply : Calculator () {
override fun cal (firstNum : Int, secondNum : Int) : Int{
return firstNum * secondNum
}
}
//Subtraction of two numbers
class Subtract : Calculator () {
override fun cal (firstNum : Int, secondNum : Int) : Int{
return firstNum - secondNum
}
}
fun main () {
var add : Calculator = Add()
var add1 = add.cal(4,3)
println("Addition of two numbers $add1")
var subtract : Calculator = Subtract()
var add2 = subtract.cal(4,3)
println("Subtraction of two numbers $add2")
var multiply : Calculator = Multiply()
var add3 = multiply.cal(4,3)
println("Multiplication of two numbers $add3")
var divide : Calculator = Divide()
var add4 = divide.cal(24,3)
println("Division of two numbers $add4")
}
- In kotlin, we can override the non-abstract open member function of the open class using the
override keyword
followed by an abstract in the abstract class.
open class vertebrates {
open fun Backbone () {
println("All vertebrates have a backbone")
}
}
abstract class Animal : vertebrates () {
override abstract fun Backbone ()
}
class Birds : Animal () {
override fun Backbone () {
println("Birds have a backbone")
}
}
fun main () {
val animals = vertebrates ()
animals.Backbone()
val birds = Birds()
birds.Backbone()
}
II. Special Kind of Classes
- There are different classes each with a dedicated purpose. These classes include:
- Data classes
- Enum classes
- Exception classes
- Sealed classes
- Annotation classes
1. Data classes
- Data classes are a special kind of class dedicated to serving as a holder of data. They help to compare, display, read, and modify this data more easily.
- Basic values like strings can be compared using the double equality sign.
class superCars (val name : String,val year : Int)
fun main () {
var car1 = superCars("volvo",2020)
var car2 = superCars("BMW" ,2021)
var car3 = superCars("volvo",2020)
var car4 = superCars("Benz",2019)
println(car1 == car2)//false
println(car1 == car1)//true
println(car3 == car4)//false
}
- The result is true if the values are alike and false if the values vary.
- When we use the dot syntax in our main function to call the properties of the class as shown below.
class superCars (val name : String,val Modelyear : Int)
fun main () {
var car1 = superCars("volvo",2020)
var car2 = superCars("BMW" ,2021)
var car3 = superCars("volvo",2020)
var car4 = superCars("Benz",2019)
println(car1.name)
println(car1.Modelyear)
println(car4.name)
println(car4.Modelyear)
}
The output of the code will volvo
2020
and Benz
2019
- When we call the instances of our class without specifying the keyword data, the output gives the memory location of where the instance is stored in the class.
class superCars (val name : String,val Modelyear : Int)
fun main () {
var car1 = superCars("volvo",2020)
var car2 = superCars("BMW" ,2021)
var car3 = superCars("volvo",2020)
var car4 = superCars("Benz",2019)
println(car1)//superCars@37f8bb67
println(car2)//superCars@439f5b3d
}
The output of the code above is superCars@37f8bb67
for car1 and superCars@439f5b3d
for car2 respectively.
- When we specialize the
keyword data
before class the output of the code changes. This is because the behavior of equality has changed.
data class superCars (val name : String,val Modelyear : Int)
fun main () {
var car1 = superCars("volvo",2020)
var car2 = superCars("BMW" ,2021)
var car3 = superCars("volvo",2020)
var car4 = superCars("Benz",2019)
println(car1)
println(car2)
}
- Data classes have a
copy method
that creates a copy of an object. It allows you to specify what modifications you would like to introduce to an object.
data class Person(val firstName: String, val familyName:String) {
var age = 0
}
fun main() {
val person1 = Person("John", "Doe").apply { age = 25 }
val(firstName,familyName) = person1
println(person1.copy(familyName = "Maye"))
}
- You use data modifiers for classes that are used to represent a bundle of data.
Pair and Triple
- This is a data class with two constructor properties, you don't need to define it, as it is distributed with Kotlin.
- Pair is used to keep two values on properties first, and second.
- You can use
first
andsecond
properties when working with pair and triple.
fun main () {
val fruits = Pair ("orange","Banana")
//Using the first property
println(fruits.first)
//Using the second property
println(fruits.second)
}
- We can use
to function
between two values using the pair property.
fun main () {
val Pair = 10 to "A"
//Using the first property
println(Pair.first)
//Using the second property
println(Pair.second)
}
- Triple is used to keep three values on properties first, second, and third.
fun main () {
val pair = Triple("Daniel",45, 'M')
//Using the first property
println(pair.first)
//Using the second property
println(pair.second)
//Using the third property
println(pair.third)
}
- we can also get the same output using the below code
fun main () {
val pair = Triple("Daniel",45,'M')
val(name,age,gender) = pair
//Getting the first property
println(name)
//Getting the second property
println(age)
//Getting the third property
println(gender)
}
2. Enum classes
- An enum has its own specialized type, indicating that something has a number of possible values.
- Enums are defined by using the
enum keyword
in front of a class.
enum class days {
sunday,
monday,
tuesday,
wednesday,
thursday,
friday,
saturday
}
- Enum have a constructor. Their constants are instances of an enum class. The constants can be initialized by passing specific values to the primary constructor.
- we can access the colour of the ripe banana using an instance that we create in our main function.
enum class bananas (val color : String) {
ripe("yellow"),
unripe("green")
}
fun main () {
val colour = bananas.ripe.color
println(colour)
}
- Kotlin enum classes has some inbuilt properties and functions which can be used by a programmer.
- In properties we have
ordinal
andname
. Theordinal
property stores the ordinal value of the constant, which is usually a zero-based index. Thename
property stores the name of the constant. - In methods we have
values
andvalueOf
. Thevalues
property returns a list of all constants defined within the enum class. - The
valueOf
property returns the enum constant defined in enum, matching the input string. If the constant, is not present in the enum, then an illegalArgumentException is thrown.
enum class HalfYear {
January,
February,
March,
April,
May,
June
}
fun main () {
for(month in HalfYear.values()){
println("${month.ordinal} = ${month.name}")
}
}
- We can access the individual month from the class using the valueOf property in the main function.
println("${HalfYear.valueOf("April")}")
- Enum classes can be combined with when expression. Since enum classes restrict the value a type can take, so when is used with the when expression and the definitions for all the constants provided, then the need for the else clause is completely eliminated.
enum class Planets {
Mercury,
Venus,
Earth,
Mars,
Jupiter,
Saturn,
Uranus
}
fun main() {
for (planet in Planets.values()) {
when (planet) {
Planets.Mercury -> println("The Swift Planet")
Planets.Venus -> println("The Morning and evening star")
Planets.Earth -> println("The Goldilocks Planet")
Planets.Mars -> println("The Red Planet")
Planets.Jupiter -> println("The Gas giant Planet")
Planets.Saturn -> println("The Ringed Planet")
Planets.Uranus -> println("The Ice giant Planet")
}
}
}
- Enum constants also behave as anonymous classes by implementing their own functions along with overriding the abstract functions of the class.
- The most important thing is that each enum constant must be overridden.
enum class seasons (var weather : String) {
Summer ("hot") {
override fun foo () {
println("Hot days of the year")
}
},
Winter ("cold") {
override fun foo (){
println("Cold days of the year")
}
},
Rainny ("rain") {
override fun foo (){
println("Rainny days of the year")
}
};
abstract fun foo ()
}
fun main () {
seasons.Winter.foo()
}
- In enum classes functions are usuallly defined within the companion object so that they do not depend on specific instances of the class.
- However, they can be defined without companion objects also.
enum class DAYS(val isWeekend: Boolean = false){
SUNDAY(true),
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY(true);
companion object{
fun today(obj: DAYS): Boolean {
return obj.name.compareTo("SATURDAY") == 0 || obj.name.compareTo("SUNDAY") == 0
}
}
}
fun main(){
for(day in DAYS.values()) {
println("${day.ordinal} = ${day.name} and is weekend ${day.isWeekend}")
}
val today = DAYS.MONDAY;
println("Is today a weekend ${DAYS.today(today)}")
}
3. Exception classes
- Exceptions are the errors which comes at the runtime and disrupts your flow of execution of the program.
- We have two types of execution;
i. Checked Exception - these are exceptions that occur at the compile time eg IOException.
ii. Unchecked Exception - these are exceptions that occur at runtime eg OutOfBoundException. In kotlin we use unchecked exceptions.
- We have some keywords which help us to handle exceptions.
i. Try - It helps us to find the exceptions.
ii. Throw - If the exception is found it throws the exception.
iii. Catch - After throwing it will catch the exception and
execute the body.
iv. Finally - It will always execute either we get
exception or not.
- If the program exit by
exitProcess(int)
orabsort()
, then thefinally
will not be executed. - The syntax :
try {
// your code
// may throw an exception
}
catch (ex : ExceptionName)
{
// Exception handle code
}
finally
{
//This will execute everytime
//It will execute whether we find an exceptiion or not
}
- We can use the above syntax to create an exception class to divide a number by zero.
fun main () {
try {
divide (10,0)
}
catch (ex : Exception)
{
println(ex.message)
}
}
fun divide (a : Int, b : Int){
if(b == 0)
throw Exception("Divide by Zero")
println("Division is :" +a /b)
}
- We can add the finally block in our code above. In the below below finally is executed in both cases either exceptions occur or not.
fun main()
{
try
{
divide(20, 10)
}
catch (ex : Exception)
{
println(ex.message)
}
finally
{
println("I'm executed")
}
// 2nd try block
try
{
// throw an exception
divide(10, 0)
}
catch (ex : Exception)
{
println(ex.message)
}
finally
{
println("I'm executed")
}
}
fun divide(a : Int, b : Int)
{
if (b == 0)
throw Exception("Divide by zero")
println("Division is :" + a / b)
}
4. Sealed classes
- A sealed class defines a set of subclasses within it. Sealed classes ensure type safety by restricting the types to be matched at compile-time rather than at runtime.
- The syntax declaration of sealed classes is as shown below :
sealed class NameOfTheClass
- Sealed classes constructors are protected by default. Sealed class is implicitly abstract and hence it cannot be instantiated.
sealed class DemoClass
fun main () {
var InstanceDemo = Demo ()
}
- The above code would not run because of compile error as sealed classes cannot be instantiated.
- All subclasses of a sealed class must be defined within the same kotlin file.
sealed class School {
class Public:School(){
fun display(){
println("They are funded by the government")
}
}
class Private:School(){
fun display(){
println("They are funded by individuals")
}
}
}
fun main(){
val obj = School.Public()
obj.display()
val obj1= School.Private()
obj1.display()
}
- You cannot define a subclass of a sealed class inside a subclass because the sealed class woukd not be visible.
- Sealed classes mostly uses when and eliminates the else clause.
// A sealed class with a string property
sealed class Fruit(val x : String)
{
// Two subclasses of sealed class defined within
class Apple : Fruit("Apple")
class Mango : Fruit("Mango")
}
// A subclass defined outside the sealed class
class Pomegranate: Fruit("Pomegranate")
// A function to take in an object of type Fruit
// And to display an appropriate message depending on the type of Fruit
fun display(fruit: Fruit)
{
when(fruit)
{
is Fruit.Apple -> println("${fruit.x} is good for iron")
is Fruit.Mango -> println("${fruit.x} is delicious")
is Pomegranate -> println("${fruit.x} is good for vitamin d")
}
}
fun main()
{
// Objects of different subclasses created
val obj = Fruit.Apple()
val obj1 = Fruit.Mango()
val obj2 = Pomegranate()
// Function called with different objects
display(obj)
display(obj1)
display(obj2)
}
5. Annotation classes
- Annotations allows the programmer to embed supplemental information into the source file. The information does not change the actions of the program.
-
Annotations contains compile-time constants parameters which include;
- primitive types(Int,Long etc)
- strings
- enumerations
- class
- other annotations
- arrays of the above-mentioned types
We can apply annotation by putting its name prefixed with the
@ symbol
in front of a code element.
`@Positive val i : Int`
- A parameter can be passed in parenthesis to an annotation similar to function call.
`@Allowedlanguage("Kotlin")`
- When an annotation is passed as parameter in another annotation, then we should omit the @ symbol. Here we have passed Replacewith() annotation as parameter.
` @Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))`
When an annotation parameter is a class object, we should add ::class to the class name as:
@Throws(IOException::class)
- To declare an annotation, the class keyword is prefixed with the annotation keyword.
- By their nature, declarations of annotation cannot contain any code.
-
While declaring our custom annotations, we should specify to which code elements they might apply and where they should be stored.
The simplest annotation contains no parametersannotation class MyClass
-
An annotation that requires parameter is much similar to a class with a primary constructor
annotation class Suffix(val s: String)
We can also annotate the constructor of a class. It can be done by using the constructor keyword for constructor declaration and placing the annotation before it.
class MyClass@Inject constructor(dependency: MyDependency) {
//. . .
}
- We can annotate the properties of class by adding an annotation to the properties.
class Lang (
@Allowedlanguages(["Java","Kotlin"]) val name: String)
}
- Kotlin also provides certain in-built annotations, that are used to provide more attributes to user-defined annotations.
@Target
- This annotation specifies the places where the annotated annotation can be applied such as classes, functions, constructors, type parameters, etc.
- When an annotation is applied to the primary constructor for a class, the constructor keyword is specified before the constructor.
@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.LOCAL_VARIABLE)
annotation class AnnotationDemo2
class ABC @AnnotationDemo2 constructor(val count:Int){
fun display(){
println("Constructor annotated")
println("Count is $count")
}
}
fun main(){
val obj = ABC(5)
obj.display()
@AnnotationDemo2 val message: String
message = "Hello"
println("Local parameter annotated")
println(message)
}
@Retention
- This annotation specifies the availability of the annotated annotation i.e whether the annotation remains in the source file, or it is available at runtime, etc.
- Its required parameter must be an instance of the AnnotationRetention enumeration that has the following elements:
- SOURCE
- BINARY
- RUNTIME Example to demonstrate @Retention annotation:
//Specifying an annotation with runtime policy
@Retention(AnnotationRetention.RUNTIME)
annotation class AnnotationDemo3
@AnnotationDemo3 fun main(){
println("Main function annotated")
}
@Repeatable
- This annotation allows an element to be annotated with the same annotation multiple times.
- As per the current version of Kotlin 1.3, this annotation can only be used with the Retention Policy set to SOURCE.
@Repeatable
@Retention(AnnotationRetention.SOURCE)
annotation class AnnotationDemo4 (val value: Int)
@AnnotationDemo4(4)
@AnnotationDemo4(5)
fun main(){
println("Repeatable Annotation applied on main")
}
💫💫Thank you for taking the time to read my article, I appreciate your interest and support. I hope you enjoyed😇😇🥳🥳
Posted on April 21, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.