Java Inheritance: A Beginner’s Guide

In Java, inheritance is a fundamental concept of Object-Oriented Programming (OOP). It is a mechanism that allows a subclass to inherit all the attributes (fields) and behaviors (methods) of a superclass. The purpose of inheritance is to create new classes that can reuse and extend the functionality of existing classes. The new class will inherit all the fields and methods from the existing class (except those declared with the private modifier), and it may also define its own fields and methods.

In object-oriented programming, inheritance involves a parent class (superclass) that serves as the basis for a new child class (subclass). Just like in real life, a child often inherits various characteristics from their parent.

Note: Note that a class can inherit from only one parent class, but it can have an unlimited number of child classes, also known as subclasses.

Inheritance in Java is implemented using the “extends” keyword, which allows a subclass to inherit the properties and methods of a superclass. The syntax for creating a subclass that extends a superclass looks like this:

public class SubClass extends SuperClass {
    // fields
    // methods
}

Inheritance Code Example in Java

Let’s say we have classes Vehicle and Car. Since a car is a vehicle in real life, we can create a class hierarchy where the Car class extends the Vehicle class, inheriting all the properties and behaviors of the Vehicle class and adding its own specific properties and methods.

class Vehicle {
    private String brand;

    // a superclass constructor that can't be inherited
    public Vehicle(String brand) {
        this.brand = brand;
    }

    public void printBrand() {
        System.out.println("Brand is: " + brand);
    }

    // overriding the toString() method of a superclass Object
    public String toString() {
        return "This is a " + brand;
    }
}

class Car extends Vehicle {
    // car has its own attributes declared
    private int numberOfSeats;
    private String colour;

    //constructor of the subclass
    public Car(String model, int numberOfSeats, String colour) {
        super(model); // call the constructor of the superclass to set the brand field
        this.numberOfSeats = numberOfSeats;
        this.colour = colour;
    }

    // Method which returns custom string for the Car
    public String printCar() {
        return super.toString() + " with a number of seats " + numberOfSeats + " and with colour " + colour;
    }
}

public class Test {
    public static void main(String[] args) {
        Car car = new Car("Ford", 5, "blue");
        car.printBrand(); // calling the inherited method
        System.out.println(car.printCar());
    }
}
Output: 
Brand is: Ford
This is a Ford with a number of seats 5 and with colour blue

In the code example given above, the Car class inherited the printBrand() method from its superclass Vehicle. This means that even though the method was not declared in the Car class, it was still available for use because it was inherited from the superclass.

Notes:

  • The constructor of a superclass can only be called explicitly from the subclass constructor using the super keyword. This ensures that the superclass constructor is called before the subclass constructor and the superclass fields are initialized before the subclass fields.
  • All classes in Java extend the java.lang.Object class by default. This ensures that the classes we define inherit members of the Object class. The Object class is the root of the class hierarchy in Java and provides a set of methods that are common to all objects, such as equals(), toString(), and hashCode(). These methods can be overridden in your own classes to provide custom behavior.

Inheriting Constructors

Like fields and methods, a subclass can also inherit constructors from its superclass. When a subclass object is created, the constructor of its superclass is called automatically before the subclass constructor. This ensures that the superclass fields are initialized before the subclass fields.

If the superclass has a constructor that takes arguments, the subclass constructor must also take the same arguments and call the superclass constructor using the super keyword, which passes the arguments to the superclass constructor. If the superclass has multiple constructors, the subclass must explicitly call one of them using the super keyword. If the superclass has no constructor defined, a default constructor is provided automatically, which can be called implicitly by the subclass constructor.

Here’s an example of a subclass that inherits a constructor from its superclass:

class Vehicle {
    private String brand;

    public Vehicle(String brand) {
        this.brand = brand;
    }
}

class Car extends Vehicle {
    private int numberOfSeats;

    public Car(String brand, int numberOfSeats) {
        super(brand);
        this.numberOfSeats = numberOfSeats;
    }
}

In this example, the Vehicle class has a constructor that takes a String argument brand, which is used to initialize the brand field. The Car class extends Vehicle and has an additional field numberOfSeats. The Car class constructor takes two arguments, a String argument brand and an int argument numberOfSeats. The super(brand) statement in the Car constructor calls the Vehicle constructor with the brand argument, which initializes the brand field of the Car object. Then the numberOfSeats field of the Car object is initialized with the numberOfSeats argument.

Overriding Methods

When a subclass inherits a method from its superclass, it can override that method to provide its own implementation. To override a method, the subclass method must have the same name, return type, and parameter list as the superclass method. The @Override annotation can be used to indicate that a method is intended to override a superclass method.

Here’s an example of a subclass that overrides a method from its superclass:

class Animal {
    public void makeSound() {
        System.out.println("Animal is making a sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Dog is barking");
    }
}

In this example, the Animal class has a method makeSound() that prints a message to the console. The Dog class extends Animal and overrides the makeSound() method to print a different message.

Access Modifiers

Inheritance affects the access modifiers of fields and methods in a class. In general, a subclass can access public and protected members of its superclass, but not private members. If a field or method in a superclass is marked as private, it cannot be accessed by a subclass. If a field or method is marked as public or protected, it can be accessed by a subclass.

Here’s an example of a subclass that accesses a public field and a protected method of its superclass:

class Person {
    protected String name;

    public void sayHello() {
        System.out.println("Hello, my name is " + name);
    }
}

class Student extends Person {
    public Student(String name) {
        this.name = name;
    }

    public void introduce() {
        System.out.println("I am a student named " + name);
    }
}

In this example, the Person class has a protected field name and a public method sayHello() that uses the name field. The Student class extends Person and has a constructor that sets the name field. The Student class also has a method introduce() that prints a message to the console using the name field.

Abstract Classes and Interfaces

In addition to regular classes, Java also supports abstract classes and interfaces, which can be used to define common behavior for a group of related classes. An abstract class is a class that cannot be instantiated directly and can contain abstract methods, which are declared but not implemented. An interface is a collection of abstract methods and constant fields, which can be implemented by any class that wants to conform to the interface.

Here’s an example of an abstract class and an interface:

abstract class Shape {
    protected double area;

    public abstract void calculateArea();
}

interface Drawable {
    void draw();
}

class Circle extends Shape implements Drawable {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void calculateArea() {
        area = Math.PI * radius * radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }
}

In this example, the Shape class is an abstract class that has a protected field area and an abstract method calculateArea(). The Drawable interface has a single method draw(). The Circle class extends Shape and implements Drawable. It has a constructor that sets the radius field and overrides the calculateArea() method to calculate the area of a circle. It also overrides the draw() method to print a message to the console.

Conclusion

In conclusion, inheritance is a powerful feature of object-oriented programming in Java that allows us to create a hierarchy of classes with shared attributes and behaviors. By using inheritance, we can avoid code duplication and write more efficient and maintainable code. In this tutorial, we covered the basics of inheritance in Java, including how to create a subclass, how to override methods, and how to use the super keyword. We also learned how constructors work in inheritance and how to call the constructor of a superclass from a subclass.

If you want to learn more about Java programming, make sure to check out Java Tutorials for Beginners page. 

Leave a Reply

Your email address will not be published. Required fields are marked *