[OOP 3 ] Principles - Inheritance and Polymorphism

[OOP 3 ] Principles - Inheritance and Polymorphism

-in Java ☕

·

15 min read

📗Inheritance in Java

Did you know that in Java, you can Inherit attributes and methods from one class to another? 🤓

Inheritance in Java is like 👨‍👦 father-son relationship. Just like how a son inherits traits from his father, a subclass in Java can inherit attributes and behaviours from its superclass.

👴🏻👉👦🏻 Inheritance allows a subclass to access the public and protected fields and methods of its superclass as if they were it's own. This means that the subclass can reuse the code that is already present in the superclass, without having to rewrite it.

👍🏻👍🏻 This can be really helpful in programming, as it allows you to create a hierarchy of classes that share common properties and behaviours, making your code more organized, modular and reusable.

👨‍👦‍👦 In Java, you can even create a hierarchy of subclasses, where a subclass can have its own subclass, which in turn can have its own subclass, and so on. This is known as a class hierarchy.

✅ subclass (child) - the class that inherits from another class.

✅ superclass (parent) - the class being inherited from.

To inherit from a class, simply use the extends keyword.

Let's talk about Inheritance and some important terminologies that we use in Java.

🔹Firstly, a Class is a set of objects that share common behavior and properties. It (Class) is like a blueprint from which objects are created, rather than a real-world entity. 🏗️

🔹Next, we have the Super Class/Parent Class, which is the class whose features are inherited by another class. It's also known as a base class or a parent class. 🧑‍👧‍👦

🔹On the other hand, the Sub Class/Child Class is the class that inherits from the other class. It's also known as a derived class, extended class, or child class. A subclass can add its own fields and methods in addition to the superclass fields and methods. 🧒

Let's say we have a superclass called Person that has two attributes name and age and a method called speak(). We want to create a subclass called Student that inherits these attributes and methods from the Person class and also has a new attribute grade

// Person class
class Person {
    protected String name;
    protected int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void speak() {
        System.out.println("Hello, my name is " + name + " and I'm " + age + " years old.");
    }
}
// Student class (inherits from Person)
public class Student extends Person {
    protected int grade;

    public Student(String name, int age, int grade) {
        super(name, age);
        this.grade = grade;
    }

    public void displayGrade() {
        System.out.println("My grade is " + grade);
    }
}
// Main class
public class Main {
    public static void main(String[] args) {
        Person person = new Person("Mithin", 18);
        person.speak(); // Output: Hello, my name is Mithin and I'm 18 years old.

        Student student = new Student("Mithin", 18, 10);
        student.speak(); // Output: Hello, my name is Mihtin and I'm 18 years old.
        student.displayGrade(); // Output: My grade is 10.
    }
}

👋 Hey there! Did you catch the protected modifier used in the Person class? It's a special keyword in Java that restricts access to the attribute or method to only the class itself and any subclasses. (We, discussed this in the previous article😊)

🔹In the given example, we set the name and age attributes in the Person class to protected. This means that the subclass Student can inherit these attributes but other classes outside of the hierarchy cannot access it directly.

🔹This is a useful way to control access to important attributes and methods in your classes while still allowing inheritance.

💡Did you know that in Java, you can only inherit from one superclass? That's right! You can't inherit from multiple superclasses into a single subclass.

💡However, you can create a hierarchy of inheritance where a subclass becomes a superclass of another subclass. But keep in mind that no class can be a superclass in itself.

💡Another thing to keep in mind is that even though a subclass includes all of the members of its superclass, it cannot access those members of the superclass that have been declared private. So if you declare a member as private in the superclass, the subclass won't be able to access it.

🆗! 🙋‍♀️ Let's talk about using the keyword super in Java.

The super keyword in Java is like a 🎁 gift box 🎁 that a subclass receives from its superclass. It allows the subclass to access the attributes and methods of its superclass, and also call the constructor of its superclass.

In our example, we used the super keyword in the Student constructor to call the constructor of the Person class and initialize the name and age attributes of the Person class. This is because the name and age attributes are defined in the Person class and not in the Student class.

// Person class
class Person {
    protected String name;
    protected int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void speak() {
        System.out.println("Hello, my name is " + name + " and I'm " + age + " years old.");
    }
}
// Student class (inherits from Person)
public class Student extends Person {
    protected int grade;

    public Student(String name, int age, int grade) {
        super(name, age);
        this.grade = grade;
    }

    public void displayGrade() {
        System.out.println("My grade is " + grade);
    }

    public void speak() {
        super.speak(); // call the `speak()` method of the superclass
        System.out.println("I'm also a student!"); // add a new message to the `speak()` method
    }
}

👨‍👦👉🎁 When we call super(name, age) in the Student constructor, we are essentially telling Java to pass the name and age parameters to the constructor of the Person class, and let the Person class handle the initialization of these attributes.

Similarly, we can also use the super keyword to call methods defined in the superclass. For example, in the Student class, we can call the speak() method of the Person class using super.speak().

🔹When it comes to constructors, super() always refers to the constructor in the closest superclass. For example, if we have a class hierarchy where class A is the superclass of class B, and class B is the superclass of class C, then super() in class C will call the constructor in class B. And super() in class B will call the constructor in class A.

Here's one more way to understand it. Like this keyword refers to the current object under consideration, the super keyword, refers to the immediate parrent class under consideration.

🔹So there you have it! super can be a powerful tool for accessing members of the superclass and calling superclass constructors.

Let's talk about using the keyword final with inheritance in Java! 🎉

So, first things first, the final keyword has three main uses. The first is to create a named constant.The final keyword in Java is like a 🔒 lock 🔒 that can be used to prevent further changes to a variable, method, or class. When a variable, method, or class is marked as final, it cannot be modified or extended.

But let's focus on the other two uses, which are related to inheritance. 🤔

When we use the final keyword with inheritance in Java, we are essentially telling Java that a particular class or method cannot be extended or overridden by any subclasses.

🚫👨‍👦‍👦🔒 For example, if we mark the Person class as final, no other class can extend it. If we mark the speak() method in the Person class as final, no subclasses can override it.

This can be useful in situations where we want to make sure that certain parts of our code remain unchanged and cannot be modified by other developers or by subclasses.

It's important to note that using the final keyword with inheritance should be used judiciously and with care, as it can limit the flexibility and extensibility of our code. It should be used only when necessary to achieve a specific design goal.

public final class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public final void speak() {
        System.out.println("Hello, my name is " + name + " and I'm " + age + " years old.");
    }
}
public class Student extends Person {
    private int grade;

    public Student(String name, int age, int grade) {
        super(name, age);
        this.grade = grade;
    }

    // This method will not compile because `speak()` in the `Person` class is marked as `final`
    /*
    public void speak() {
        System.out.println("I'm a student and my name is " + name + ".");
    }
    */

    public void displayGrade() {
        System.out.println("My grade is " + grade);
    }
}

In the Student class, we have tried to create a new speak() method, but it won't compile because speak() in the Person class is marked as final.

📘Advantages of Inheritance

Have you ever wondered why and when to use inheritance in Java? 🤔

🔑 One of the main advantages of inheritance is that it allows us to reuse code that we have already written. We can create a base class (also called a parent or superclass) with common attributes and methods, and then create subclasses (also called child classes) that inherit these attributes and methods.

👥 Another advantage of inheritance is that it allows us to create a hierarchy of classes that models real-world relationships. For example, we can create a Person class that is the parent of Student and Teacher classes, which themselves can have their own child classes.

🚀 Inheritance also makes our code more modular and easier to maintain. By creating a hierarchy of classes, we can separate different aspects of our code and modify them independently without affecting the rest of the code.

👨‍💻 In addition, inheritance allows us to override methods in subclasses to provide more specialized behaviour. For example, we can create a Dog subclass that inherits from a Pet superclass and override the speak() method to make the dog bark.

In Java, every class except the Object class (which has no superclass) has one direct superclass. This is known as single inheritance. 🧬 If a class doesn't specify any other explicit superclass, then it is implicitly a subclass of the Object class.

Now, a superclass can have multiple subclasses, but a subclass can only have one superclass. 😎 This is because Java does not support multiple inheritances with classes, but it does support it with interfaces.

When it comes to constructors, a subclass doesn't inherit constructors from its superclass. However, it can invoke the constructor of its superclass. 😮

Lastly, private members of the parent class are not inherited by a subclass. But don't worry, if the superclass has public or protected methods for accessing its private fields, these can also be used by the subclass. 🤝

📘Disadvantages of Inheritance

❌ One of the main disadvantages of inheritance is that it can lead to code that is more complex and harder to understand. As we create more subclasses and override methods, the relationships between the classes can become more tangled and difficult to follow.

🔒 Inheritance can also create security issues if we are not careful. If we expose too many methods or attributes in a superclass, we may inadvertently allow subclasses to access data or perform actions that they shouldn't be able to.

🧱 Inheritance can also lead to tight coupling between classes, which means that changes in one class can affect other classes. This can make our code less flexible and harder to modify.

👴 Another disadvantage of inheritance is that it can create a lot of legacy code that we are stuck with. If we create a superclass with many subclasses, it can be difficult to change the superclass without affecting all of the subclasses.

📗Polymorphism in Java

👋 Let's talk about polymorphism in Java.

Polymorphism means "many forms" 🎭, and it occurs when we have many classes related to each other by inheritance.

To understand polymorphism, let's take an example from our everyday life. Have you ever noticed how a person can have different roles and characteristics? For instance, a man can be a father, a husband, and an employee at the same time. In different situations, the same person exhibits different behaviours. This is called polymorphism. 🧑🏻‍🤝‍🧑🏻

So, polymorphism is like a magical ability that objects in Java have to transform into different forms! 🎩✨

Think of it like a shape-shifter, but for objects in Java. An object can take on different forms or be treated as if it were another object. 🦸‍♂️👀

This ability is really useful when we have a lot of different types of objects, but we want to treat them in a similar way. It's like having a bunch of different puzzle pieces, but they all fit together in the same way. 🧩👌

We can achieve this in Java through inheritance, where we have a superclass and multiple subclasses that inherit from it. We can store objects of the various subclass types in a list or array of the superclass type, and then treat each object in the list or array as if it were an object of the superclass type. 🤝🔍

We can also achieve polymorphism through method overloading and overriding. When we have methods with the same name in a class hierarchy, the Java Virtual Machine (JVM) can determine at runtime which method to call based on the actual object that is being used. This allows us to write more generic code that works with any object that has the required method, regardless of its specific type. 🧬📝

class Animal {
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }
}
class Dog extends Animal {
    public void makeSound() {
        System.out.println("The dog barks");
    }
}
class Cat extends Animal {
    public void makeSound() {
        System.out.println("The cat meows");
    }
}
class Main {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound();
        animal2.makeSound();
        animal3.makeSound();
    }
}

We have a superclass Animal and two subclasses Dog and Cat that inherit from it. Each class has its own implementation of the makeSound() method, which allows them to make different sounds.

Because of polymorphism, the makeSound() method is dynamically bound at runtime, based on the actual object that is being used. So, when we call animal1.makeSound(), the output is "The animal makes a sound". But when we call animal2.makeSound(), the output is "The dog barks", and when we call animal3.makeSound(), the output is "The cat meows".

This is an example of runtime polymorphism, where the JVM determines which method to call based on the actual object that is being used.

📘Types of polymorphism

In Java polymorphism is mainly divided into two types:

📕Run-time polymorphism

This type of polymorphism is achieved by function overriding.

Imagine you're watching a superhero movie 🎥. The movie has many different superheroes, each with their own unique powers and abilities.

Now, let's relate this to Java. Think of each superhero as a class, with its own unique methods and properties. One of the superheroes could be the parent class, and the other superheroes could be subclasses that inherit from the parent class.

At runtime, when the movie is playing, the superheroes are dynamically performing their unique powers and abilities based on the situation they're in. This is similar to how Java works with runtime polymorphism.

In Java, runtime polymorphism is achieved through method overriding. When a subclass overrides a method in the parent class, it provides its own implementation of that method. At runtime, when the method is called on an object of the subclass, the overridden method in the subclass is executed instead of the method in the parent class.

👨‍💻 Did you know about Dynamic Method Dispatch in programming? 🤔 It's when a function call is resolved at runtime through Method Overriding. 🤯 Method Overriding occurs when a derived class defines a member function that already exists in the base class. 🚀

class Animal {
    public void makeSound() {
        System.out.println("The animal makes a sound");
    }
}

class Dog extends Animal {
    public void makeSound() {
        System.out.println("The dog barks");
    }
}

class Cat extends Animal {
    public void makeSound() {
        System.out.println("The cat meows");
    }
}

class Main {
    public static void main(String[] args) {
        Animal animal1 = new Animal();
        Animal animal2 = new Dog();
        Animal animal3 = new Cat();

        animal1.makeSound();
        animal2.makeSound();
        animal3.makeSound();
    }
}

In the Main class, we create three objects: animal1 of type Animal, animal2 of type Dog, and animal3 of type Cat. When we call the makeSound() method on each object, the JVM determines which implementation of the method to use at runtime.

When we call animal1.makeSound(), the output is "The animal makes a sound", because animal1 is an object of the Animal class. But when we call animal2.makeSound(), the output is "The dog barks", because animal2 is an object of the Dog class and has overridden the makeSound() method. Similarly, when we call animal3.makeSound(), the output is "The cat meows", because animal3 is an object of the Cat class and has overridden the makeSound() method.

📕Compile-time Polymorphism

This type of polymorphism is achieved by function overloading.

👨‍💻 Did you know about method overloading in programming? 🤔 It's when multiple functions share the same name but differ in their parameters. 🤯 This can help make code more flexible and readable! 🚀

🫡Here's an example,

class Calculator {
    static int add (int a, int b) {
        return a + b;
    }

    static double add (double a, double b) {
        return a + b;
    }
}

class Main {
    public static void main(String[] args) {
        System.out.print(Calculator.add(2, 4) + ", ");
        System.out.println(Calculator.add(5.5, 5.5));
    }
}

Output:

6, 11.0

📘Advantages of Polymorphism

Let's talk about the advantages of using Polymorphism in Java 🤓

🔹Firstly, it increases code reusability by allowing objects of different classes to be treated as objects of a common class. This means that we can write a piece of code that works with different objects, as long as they share a common interface or superclass.

🔹Moreover, it also improves the readability and maintainability of our code by reducing the amount of code that needs to be written and maintained. This is because we can define a common interface or superclass and implement it across different classes, rather than duplicating code.

🔹Another advantage is that Polymorphism supports dynamic binding, enabling the correct method to be called at runtime based on the actual class of the object. This means that our code can adapt and work with different objects, even if they have different implementations.

🔹Last but not least, it enables objects to be treated as a single type, making it easier to write generic code that can handle objects of different types. This is incredibly useful when we want to write code that works with a range of objects, without having to write separate code for each one.

📘Disadvantages of Polymorphism

🔹Can make it more difficult to understand the behaviour of an object, especially if the code is complex.

🔹This may lead to performance issues, as polymorphic behaviour may require additional computations at runtime.

Thank You Soo Much for your valuable time.😊🥳👋


👋 Hi there! Let's connect and collaborate!

Here are some ways to reach me:

🔹 GitHub: github.com/mithindev

🔹 Twitter: twitter.com/MithinDev

🔹 LinkedIn: linkedin.com/in/mithin-dev-a-397983247

Looking forward to connecting with you!