Exploring Kotlin's Abstract Classes and Specialized Class Types: A Guide to Object-Oriented Programming Concepts.
Published: 2023-04-21 21:53:35


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` `{}`

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtzgslk294vm6pwgs3tl.png)
- 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:

1. Data classes
2. Enum classes
3. Exception classes
4. Sealed classes
5. 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.

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/opgu7kj9rvqp15agc8ou.jpg)
- 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` and `second` 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. 

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t680aayqvm8msiq2g40p.jpg)
- 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` and `name`. The `ordinal` property stores the ordinal value of the constant, which is usually a zero-based index. The `name` property stores the name of the constant.
- In methods we have `values` and `valueOf`. The `values` 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;

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ekbz65dud9qor5vdrgc9.jpg)
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)` or `absort()`, then the `finally` 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.

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lr04cuxaq7ys9mt3yxt3.png)
- 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.

![Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0lpxh1rqfy8br9tuy5fy.jpg)
- Annotations contains compile-time constants parameters which include;
       1. _primitive types(Int,Long etc)_
       2. _strings_
       3. _enumerations_
       4. _class_
       5. _other annotations_
       6. _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 parameters 

      `annotation 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😇😇🥳🥳

![Image ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f35qw3t6ygbn2ruyy73w.jpeg)