What is Inheritance: An Explanation with Examples in Java and Python
In this post, we introduce inheritance, a foundational concept in object-oriented programming, with examples in Java and Python.
What is inheritance?
In a software engineering context, inheritance describes a relationship between classes that facilitates the sharing of code. A class becomes a subclass of another class known as the superclass. The subclass can inherit functionality from the superclass.
Assume you have a class that models a bike. Any bike, whether it is a motorcycle or a bicycle, can accelerate and decelerate. Accordingly, you don’t need to repeat the code in the classes representing motorbikes and bicycles. Instead, they can inherit this functionality from Bike. But only a motorcycle has an engine. It doesn’t make sense to define the functionality related to the engine on the Bike class.
Bike is a superclass while Motorbike and Bicycle are subclasses. Their relationship is called an inheritance hierarchy.
Inheritance in Java
Java inheritance is implemented using the “extends” keyword. Given a superclass Bike, you extend the superclass in your subclass Motorcycle like this:
public class Bike { private String color; private int gears; public Bike(String color, int gears){ this.color = color; this.gears = gears; } public int getGears() { return gears; } } public class Motorbike extends Bike { public Motorbike(String color, int gears) { super(color, gears); } }
Note that the Java compiler does require us to define a custom constructor for the subclass if the superclass has a custom constructor. In the subclass constructor, we are required to call the constructor of the superclass using the “super” keyword.
The Motorbike class inherits all public and protected fields and methods from the Bike class. This means we can call the public method getGears on a Motorbike instance and it will call the method “getGears” from the Bike class.
public static void main(String[] args){ Motorbike my_bike = new Motorbike("My amazing new bike", 32); int gears = my_bike.getGears(); System.out.println(gears); //32 }
The variables color and gears are private in the Bike class. Therefore we cannot work with them directly in the Motorbike class. We can only interact with them via methods defined in the Bike class. In the Motorbike constructor assigning to the variable gears defined in Bike will not work. The IDE helpfully highlights it in red and throws an error if we run it.
But using the public setter defined in Bike in the motorbike constructor does work.
Override Java
What do you do if a method in the subclass wants to modify behavior already defined in the subclass? For example, the Bike class could define a general acceleration method inherited by the subclasses. Acceleration on a motorcycle usually involves pressing a gas pedal and the motorcycle class has its own method “pushGasPedal”. The motorcycle class can simply override the accelerate method of the superclass and thus provide its own implementation.
public class Bike { ... public void accelerate(){ speed += 1; } } public class Motorbike extends Bike { @Override public void accelerate(){ this.pushGasPedal(); } public void pushGasPedal(){ speed += 20; } }
The @override Java operator on the overriding method in the subclass is not strictly required, but it is generally good practice to use it. If you use it and the method signature on the superclass changes, the compiler will alert you that the method in the subclass no longer overrides the superclass method.
What is Type Casting in Java?
Since a subclass exhibits all the attributes and methods of its superclass, you can use an instance of the subclass and treat it as if it were an instance of the superclass. For example, you could use a motorbike object and treat it as a bike object. To make this explicit, we can upcast the motorcycle instance to a bike instance
Motorbike my_motorbike = new Motorbike("My amazing new bike", 32); Bike bike = my_motorbike; //explicitly cast motorbike to a bike object
You can also tell the compiler when instantiating the motorcycle, that he should treat it as a Bike object.
Bike my_bike = new Motorbike("My amazing new bike", 32); //
We can also downcast a bike object to a motorcycle object if the object really is a motorbike object.
Bike my_bike = new Motorbike("My amazing new bike", 32); Motorbike motorbike = (Motorbike) my_bike;
If the original object is of type Bike and not Motorbike, and you try to cast it to a motorbike it will crash at runtime. The compiler will not complain because by downcasting you are basically saying: “Hey compiler, this is definitely a motorbike. Take my word for it”.
Java Instanceof
To be on the safe side, you can use the java instanceof keyword to check whether an object is of the expected type:
Inheritance in Python
In Python inheritance works a bit differently from Java. The biggest difference is probably that in Java multiple inheritance is not supported while in Python it is. Multiple inheritance simply means a subclass inherits from more than one superclass.
We use the same example as in Java with a Bike superclass and a Motorbike subclass. As you can see, the Motorbike class calls the superclass using super() and subsequently the constructor of the superclass to initialize the variables color and gears.
class Bike: def __init__(self, color, current_gear): self.color = color self.gear = current_gear class Motorbike(Bike): def __init__(self, color, current_gear): super().__init__(color, current_gear)
Due to Python’s multiple inheritance support, we can let motorcycle inherit from a second superclass Motor as well.
class Bike: def __init__(self, color, current_gear): self.color = color self.gear = current_gear class Motor: def __init__(self, horsepower): self.horsepower = horsepower class Motorbike(Bike, Motor): def __init__(self, color, current_gear, horsepower): Bike.__init__(self, color, current_gear) Motor.__init__(self, horsepower)
Now, we explicitly call the superclasses by their names to distinguish the constructor calls in the subclass. This also requires us to pass “self” in the constructor calls of the superclasses. If we create a motorbike instance, we can call the attributes defined in both of its subclasses.
Note that contrary to Java, where the convention is to define attributes as private, we usually leave attributes as publicly accessible in Python. Therefore we can access them directly in the subclass rather than using a getter.
SImilarly, we can also call methods defined in the superclass from the subclass instance.
class Bike: ... def get_max_speed(self): return 40 class Motorbike(Bike, Motor): ... if __name__ == '__main__': motorbike = Motorbike("red", 3, 80) print(motorbike.get_max_speed()) # returns 40
Python Method Override
In Python overriding superclass methods is fairly straightforward.
class Bike: ... def get_max_speed(self): return 40 class Motorbike(Bike, Motor): ... def get_max_speed(self): return 120
In case the method in the subclass builds on a base implementation in the superclass, you can call the superclass implementation in the overriding method in the subclass
class Bike: ... def getMaxSpeed(self): return 40 class Motorbike(Bike, Motor): ... def get_max_speed(self): max_speed = Bike.get_max_speed(self) + 120 return max_speed
This will retrieve the max speed from the bike class and add 120 to it to produce the max speed of the motorcycle.
Type Casting in Python?
In Java, we explicitly cast a subclass to a superclass or vice versa. In Python, this isn’t really necessary, because there is no compiler that expects a certain type. If you pass a bike object to a function that expects a motorbike object, the function will accept it without complaint. Of course, your program will crash, if the method tries to call a method or attribute that is only defined in the motorbike, but not in the bike class.
class Bike: ... class Motorbike(Bike, Motor): ... def hello_motor(self): print("hello motor") def call_hello_motor(motorbike): motorbike.hello_motor();
This is a major cultural difference between Java and Python. Java is like a conservative parent who creates a lot of guardrails to protect children and prevent them from violating the rules. Python’s philosophy is that of a permissive liberal parent who only sets rules, but doesn’t actually enforce them. Children have to take responsibility themselves to play by the rules. If they violate them, they have to live with the consequences. This makes Python’s rule system simpler, whereas Java’s is much more bloated but also much more secure.