Object Oriented Programming (with Java & Kotlin )


Overview

OOP is a programming concept of using objects, which contains attributes (data) and/or methods (actions).

With this we can be able to describe different things. An example of this would be using OOP to describe a car. It would have attributes (color, size etc) and actions (drive, brake etc). Another example would be describing a person, with both attributes (height, weight) and actions (sleep, study, work etc)

OOP has four main concepts:

  1. Inheritance
  2. Abstraction
  3. Polymorphism
  4. Encapsulation



Inheritance

Inheritance is where we build a new class from an existing class. Here the new class can have attributes and methods from another class, while adding on new features.

The new class is commonly termed as a child class (alternatively subclass, extended-class), while the existing class is termed as a parent class (alternatively super-class, base-class).

Let's take the example of buildings such as an appartment, a mansion, a mud-house and an office. Despite all being totally different, they will all have common features such as doors, windows, floors etc. Due to this, we can have a parent class with the common features, then each class could have its unique features.

This can be represented by having the below classes:

  • Building class: Parent
  • Appartment class: Child
  • Office class: Child

Which can therefore represented programmatically as:

Building class, which is the parent class and contains the attributes and methods that are common among all the other classes.

					
					public class Building {
					    int doors;
					    int windows;
					    int floors;

					    Building(int doors, int windows, int floors){
					        this.doors = doors;
					        this.windows = windows;
					        this.floors = floors;
					    }

					    public void window_material(){
					        System.out.println("The windows are made of glass");
					    }

					    public void structure(){
					        System.out.println("This building has " + this.doors + " doors, "
					        + this.windows + " windows, and "
					        + this.floors + " floors.");
					    }
					}
					
					

Appartment class, which is a child class and extends the Building parent class

					
					public class Apartment extends Building{
					    int tenants;
					    Apartment(int doors, int windows, int floors, int tenants) {
					        super(doors, windows, floors);
					        this.tenants = tenants;
					    }

					    public void tenants(){
					        System.out.println("There are " + this.tenants + " tenants in this apartment.");
					    }
					}
					
					

Office class, which is a child class and extends the Building parent class

					
					public class Office extends Building{
					    String company_name;

					    Office(int doors, int windows, int floors, String company_name) {
					        super(doors, windows, floors);
					        this.company_name = company_name;
					    }
					    public void companies(){
					        System.out.println("There are 20 companies here in " + this.company_name);
					    }
					}
					
					

We can now see the implementation of the objects from the classes in the main class as below:

					
					public class Main {
					    public static void main(String[] args) {
					        Office office = new Office(12, 2, 3, "The office" );
					        // inherited methods
					        office.window_material();
					        office.structure();
					        // un-inherited method from the Office class
					        office.companies();

					        System.out.println("\n");

					        Apartment dopeFlats = new Apartment(15, 20, 5, 50);
					        // inherited methods
					        dopeFlats.window_material();
					        dopeFlats.structure();
					        // un-inherited method from the Apartment class
					        dopeFlats.tenants();
					    }
					}

					/*
						Result output:

						The windows are made of glass
						This building has 12 doors, 2 windows, and 3 floors.
						There are 20 companies here in The office


						The windows are made of glass
						This building has 15 doors, 20 windows, and 5 floors.
						There are 50 in this apartment.

					*/
					
					

Super Keyword

the super keyword is used to access attributes and methods from the parent class



Abstraction

This is where we only show what is needed about the object to the user. This therefore means we do not need to know everything about the inner workings of the object. The main reason for abstraction therefore is to provide security, by hiding the workings of some features and only providing what needs to be seen.

We can use the example of a car, where we can drive from one point to another without having to know the inner workings of the engine.

In Java, we implement abstraction through interfaces or abstract classes

Abstract Classes & methods

An abstract class is declared with abstract, where the class can not be instantiated and has to be extended (inheritance).

We can have abstract classes and/or abstract methods.

Let's take the example of three classes:

  • Animal : an abstract class
  • Human : extends the Animal class
  • Cat : extends the Animal class

We first start implementing the Animal abstract class as below. We have a constructor, two abstract methods (no method body) and one normal method (a normal method has to have a body)

					
					package Animals;

					abstract class Animal {
						Animal(){
					        System.out.println("Animal class created");
					    }
					    public abstract void breath();

					    public abstract String eats();

					    public void walk(){
					        System.out.println("Animal is walking");
					    }
					}
					
					

Next we have the Human class which extends the Animal class. We have to implement the abstract mothods from the Animal abstract class, which is implemented as below. In addition we can have our own additional methods that are not in the parent class.

					
					package Animals;

					public class Human extends Animal{
					    String name;
					    int height;

					    // class constructor
					    Human(String name, int height){
					        this.name = name;
					        this.height = height;
					    }

					    public void breath(){
					        System.out.println(name + " is breathing");
					    }

					    @Override
					    public String eats() {
					        return name + " eats meat and greens.";
					    }

					    public int height(){
					        return height;
					    }
					}
					
					

Finally we have the Cat class, also extending the Animal class. We can see that as long as it extends the abstract class, we have to include the abstract methods defined in the parent class.

					
					package Animals;

					public class Cat extends Animal{

					    public void breath(){
					        System.out.println("Cat is breathing");
					    }

					    @Override
					    public String eats() {
					        return "Cat eats meat";
					    }
					}
					
					

The above classes will now be instantiated and executed as below from the Main class.

					
					package Animals;

					public class Main {
					    public static void main(String[] args){
					        Human cena = new Human("John Cena",100);

					        System.out.println("The height of " + cena.name + " is " + cena.height);
					        cena.breath();
					        cena.walk();
					        System.out.println(cena.eats());

					        System.out.println("\n");

					        Cat gato = new Cat();
					        gato.breath();
					        gato.walk();
					        System.out.println(gato.eats());
					    }
					}

					/*
						The result of the above is:

						Animal class created
						The height of John Cena is 100
						John Cena is breathing
						Animal is walking
						John Cena eats meat and greens.


						Animal class created
						Cat is breathing
						Animal is walking
						Cat eats meat
					*/
					
					

From the above we can see that:

  • The abstract class Animal can not be instantiated, but can only be extended/inherited.
  • You can have abstract and non-abstract methods in an abstract class. You have to explicitly define a method as abstract with the abstract keyword.
  • The abstract methods can not have a body.
  • Non-abstract methods need to have a body.
  • The abstract methods have to be implemented in the child class.
  • Abstract classes can have constructors.
  • we can use final on a normal method in an abstract class to prevent it from being overriden in the child class


Abstract classes implementing Interfaces

We can actually also use abstract classes to help implement interfaces

Example, we have three classes:

  • An interface: Test_interface
  • An abstract class: Test_abstract
  • A normal class: Test_class

The interface has two methods. Note that any method in an interface is assumed to be an abstract method.

					
					public interface Test_interface {
					    public void test_1();

					    public void test_2();
					}
					
					

The abstract class then implements the interface. In addition it contains and implements the first method from the interface as a normal method.

					
					abstract class Test_abstract implements Test_interface{
					    @Override
					    public void test_1() {
					        System.out.println("implementing test 1 method from the interface class through the abstract class");
					    }
					}
					
					

We can then have the normal class (Test_class) extend the abstract class (Test_abstract), which is implementing the interface (Test_interface)

					
					public class Test_class extends Test_abstract {
					    public void test_2(){
					        System.out.println("Implementing test 2 method from interface class through the test_class");
					    }
					}
					
					

The above three classes can then be run in the main class as below:

					
					public class Main {
					    public static void main(String[] args) {
					        Test_interface int_object = new Test_class();
					        int_object.test_1();
					        int_object.test_2();
					    }
					}
					
					

The above can be illustrated as below:


Interfaces

Interfaces work in a similar way to abstract classes, but with some differences such as:

  • All methods in an interface are assumed to be abstract
  • Since all methods are abstract, you do not use the abstract keyword.
  • Other classes using an interface would use implements instead of the extends keyword.


					
					// an interface with one method
					interface TestInterface {
					    public void interfaceMethod();
					}

					// a class that implements the interface. 
					class TestClass implements TestInterface {
					    public void interfaceMethod(){
					        System.out.println("This is the implementation of a method from an interface");
					    }
					}

					public class Main {
					    public static void main(String[] args) {
					        TestClass obj = new TestClass();
					        obj.interfaceMethod();
					    }
					}
					
					

Lambda Expressions

Let's consider the interface TestInterface defined below with only one method.

					
					interface TestInterface {
					    public void interfaceMethod();
					}
					
					

Naturally we would need a normal class that implements the interface, because we can not instantiate the interface.


Programmatically, this would be:

					
					// interface
					@FunctionalInterface
					interface TestInterface {
					    public void interfaceMethod();
					}

					// normal class
					class TestClass implements TestInterface {
					    public void interfaceMethod(){
					        System.out.println("This is the implementation of a method from an interface");
					    }
					}

					// object creation and implementation of interface method
					public class Test {
					    public static void main(String[] args) {
					        TestClass obj = new TestClass();
					        obj.interfaceMethod();
					    }
					}
					
					

One can bypass the use of another class to implement the interface in two ways:

  • Using an anonymous inner class
  • Usin a lambda expression

An Anonymous inner class is a class within a class, and it has no name. With this concept, we can then have the above code implementation re-written as:

					
					@FunctionalInterface
					interface TestInterface {
					    public void interfaceMethod();
					}

					//class TestClass implements TestInterface {
					//    public void interfaceMethod(){
					//        System.out.println("This is the implementation of a method from an interface");
					//    }
					//}

					public class Test {
					    public static void main(String[] args) {
					        TestInterface obj = new TestInterface(){
					            public void interfaceMethod(){
					                System.out.println("This is the implementation of a method from an interface");
					            }
					        };
					        obj.interfaceMethod();
					    }
					}
					
					

We have successfully skipped the process of creating the class that implements the interface, and put the class content as we instantiate the object through the interface. This also goes to show that we can also instantiate an object with an interface through inner classes.

We can go a step further and use lambda expressions, which are implementations of functional interfaces (interface with one abstract method). This therefore also allows the implementation of interfaces without the use of another class.

Lambda expressions follow the format of

					
					// a simple lambda expression with one statement
					() -> statement;

					// multiple statements in a code block
					() -> {
					    statement;
					    statement;
					};

					// passing parameters 
					(param1, param2) -> {
					    statement;
					    statement;
					};
					
					

The above code examples can therefore be rewritten with a lambda expression as:

					
					@FunctionalInterface
					interface TestInterface {
					    public void interfaceMethod();
					}

					//class TestClass implements TestInterface {
					//    public void interfaceMethod(){
					//        System.out.println("This is the implementation of a method from an interface");
					//    }
					//}

					public class Test {
					    public static void main(String[] args) {
					        TestInterface obj = () -> System.out.println("This is the implementation of a method from an interface");
					        obj.interfaceMethod();
					    }
					}
					
					

We pass parameters to the lambda expression and have multiple statements as below:

					
					@FunctionalInterface
					interface TestInterface {
					    public void interfaceMethod(String name, int age);
					}

					public class Test {
					    public static void main(String[] args) {
					        TestInterface obj = (name, age) -> {
					            System.out.println("The name of the superhero is " + name);
					            System.out.println(name + " is " + age + " years");
					        };
					        obj.interfaceMethod("Batman", 40);
					    }
					}
					
					

It is clear that use of Lambda expressions are cleaner and readable as compared to Inner classes



Polymorphism

Polymorphism is the concept of having methods take have behaviours.

Polymorphism is categirised into two main areas:

  • Compile time polymorphism : Done through method overloading
  • Run-time polymorphism: Done through method overrunning


Compile time polymorphism

This takes place when the program is being compiled, and in this case, it is the compiler that decides which method is going to be called. This is done through method overloading, where a method is

Run-time polymorphism

This happens when the program is being executed, and it is the JVM (Java Virtual Machine) that decides which method to be used. This happens during run-time, when the user is interacting with the program



Encapsulation

Encapsulation is where we restrict access to the attributes and/or methods of a class. The main purpose of encapsulation is to secure attributes and methods from external interference.




© Copyright Kifaru Codes. All Rights Reserved.

Designed by KifaruCodes