Java Inheritance Basics
[Java]
In this post, we’ll explore one of the fundamental pillars of Object-Oriented Programming: inheritance. Inheritance allows you to create new classes based on existing ones, promoting code reuse and establishing relationships between classes.
What is Inheritance?
In Java, it is possible to inherit attributes and methods from one class to another. We group the “inheritance concept” into two categories:
- subclass (child) - the class that inherits from another class
- superclass (parent) - the class being inherited from
To inherit from a class, use the extends keyword.
class Vehicle {
protected String brand = "Ford"; // Vehicle attribute
public void honk() { // Vehicle method
System.out.println("Tuut, tuut!");
}
}
class Car extends Vehicle {
private String modelName = "Mustang"; // Car attribute
public void displayInfo() {
System.out.println(brand + " " + modelName);
}
public static void main(String[] args) {
// Create a myCar object
Car myCar = new Car();
// Call the honk() method (from the Vehicle class) on the myCar object
myCar.honk();
// Display the value of the brand attribute (from the Vehicle class)
// and the value of the modelName from the Car class
myCar.displayInfo();
}
}
Access Modifiers and Inheritance
Did you notice the protected modifier in Vehicle?
We set the brand attribute in Vehicle to a protected access modifier. If it was set to private, the Car class would not be able to access it.
Access Modifier Summary
| Modifier | Same Class | Same Package | Subclass | Other Classes |
|---|---|---|---|---|
private |
✅ | ❌ | ❌ | ❌ |
default |
✅ | ✅ | ❌ | ❌ |
protected |
✅ | ✅ | ✅ | ❌ |
public |
✅ | ✅ | ✅ | ✅ |
class Animal {
private String species = "Unknown"; // Only accessible within Animal class
protected String name = "Animal"; // Accessible in subclasses
public int age = 0; // Accessible everywhere
String habitat = "Wild"; // Package-private (default)
public void displayInfo() {
System.out.println("Species: " + species); // Can access private here
System.out.println("Name: " + name);
System.out.println("Age: " + age);
System.out.println("Habitat: " + habitat);
}
}
class Dog extends Animal {
public void dogInfo() {
// System.out.println(species); // Error: Cannot access private
System.out.println("Dog name: " + name); // Can access protected
System.out.println("Dog age: " + age); // Can access public
System.out.println("Dog habitat: " + habitat); // Can access package-private
}
}
public class Main {
public static void main(String[] args) {
Dog myDog = new Dog();
myDog.name = "Buddy";
myDog.age = 3;
myDog.displayInfo();
myDog.dogInfo();
}
}
The super Keyword
In Java, the super keyword is used to refer to the parent class of a subclass.
The most common use of the super keyword is to eliminate the confusion between superclasses and subclasses that have methods with the same name.
It can be used in three main ways:
- To access attributes from the parent class
- To call methods from the parent class
- To call the parent class constructor
Access Parent Methods
If a subclass has a method with the same name as one in its parent class, you can use super to call the parent version:
class Vehicle {
public void start() {
System.out.println("Vehicle is starting...");
}
public void stop() {
System.out.println("Vehicle is stopping...");
}
}
class Car extends Vehicle {
@Override
public void start() {
super.start(); // Call parent method first
System.out.println("Car engine is running!");
}
public void fullStart() {
System.out.println("Performing full startup sequence:");
super.start(); // Call parent's start method
System.out.println("Checking systems...");
System.out.println("Car is ready to drive!");
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.start(); // Calls overridden method
System.out.println();
myCar.fullStart(); // Calls parent method explicitly
}
}
Note: Use super when you want to call a method from the parent class that has been overridden in the child class.
Access Parent Attributes
You can also use super to access an attribute from the parent class if they have an attribute with the same name:
class Vehicle {
protected String brand = "Generic Vehicle";
protected int maxSpeed = 100;
}
class Car extends Vehicle {
private String brand = "Car Brand"; // Hides parent's brand
private int maxSpeed = 200; // Hides parent's maxSpeed
public void displayBrands() {
System.out.println("Car brand: " + brand); // Child's brand
System.out.println("Vehicle brand: " + super.brand); // Parent's brand
}
public void displaySpeeds() {
System.out.println("Car max speed: " + maxSpeed);
System.out.println("Vehicle max speed: " + super.maxSpeed);
}
public static void main(String[] args) {
Car myCar = new Car();
myCar.displayBrands();
myCar.displaySpeeds();
}
}
Calling Parent Constructor
class Vehicle {
protected String brand;
protected int year;
protected String color;
public Vehicle(String brand, int year) {
this.brand = brand;
this.year = year;
this.color = "Unknown";
System.out.println("Vehicle constructor called");
}
public Vehicle(String brand, int year, String color) {
this.brand = brand;
this.year = year;
this.color = color;
System.out.println("Vehicle constructor with color called");
}
}
class Car extends Vehicle {
private String model;
private int doors;
public Car(String brand, int year, String model) {
super(brand, year); // Call parent constructor
this.model = model;
this.doors = 4; // Default to 4 doors
System.out.println("Car constructor called");
}
public Car(String brand, int year, String model, String color, int doors) {
super(brand, year, color); // Call parent constructor with color
this.model = model;
this.doors = doors;
System.out.println("Car constructor with all parameters called");
}
public void displayInfo() {
System.out.println(year + " " + color + " " + brand + " " + model +
" with " + doors + " doors");
}
public static void main(String[] args) {
System.out.println("Creating basic car:");
Car car1 = new Car("Toyota", 2023, "Camry");
car1.displayInfo();
System.out.println("\nCreating detailed car:");
Car car2 = new Car("Honda", 2024, "Civic", "Blue", 2);
car2.displayInfo();
}
}
Note: The call to super() must be the first statement in the subclass constructor.
Method Overriding
When a subclass provides a specific implementation of a method that is already provided by its parent class, it’s called method overriding:
class Animal {
public void makeSound() {
System.out.println("The animal makes a sound");
}
public void sleep() {
System.out.println("The animal sleeps");
}
public void eat() {
System.out.println("The animal eats");
}
}
class Dog extends Animal {
@Override // Good practice to use this annotation
public void makeSound() {
System.out.println("The dog barks: Woof! Woof!");
}
@Override
public void eat() {
System.out.println("The dog eats dog food");
}
// sleep() is not overridden, so it inherits the parent's implementation
public void wagTail() { // New method specific to Dog
System.out.println("The dog wags its tail");
}
}
class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("The cat meows: Meow! Meow!");
}
@Override
public void sleep() {
System.out.println("The cat sleeps 16 hours a day");
}
public void purr() { // New method specific to Cat
System.out.println("The cat purrs: Purr... Purr...");
}
}
public class Main {
public static void main(String[] args) {
Animal genericAnimal = new Animal();
Dog myDog = new Dog();
Cat myCat = new Cat();
System.out.println("=== Generic Animal ===");
genericAnimal.makeSound();
genericAnimal.eat();
genericAnimal.sleep();
System.out.println("\n=== Dog ===");
myDog.makeSound(); // Overridden method
myDog.eat(); // Overridden method
myDog.sleep(); // Inherited method
myDog.wagTail(); // Dog-specific method
System.out.println("\n=== Cat ===");
myCat.makeSound(); // Overridden method
myCat.eat(); // Inherited method
myCat.sleep(); // Overridden method
myCat.purr(); // Cat-specific method
}
}
The final Keyword
If you don’t want other classes to inherit from a class, use the final keyword:
final class MathUtils {
public static double calculateCircleArea(double radius) {
return Math.PI * radius * radius;
}
public static double calculateSquareArea(double side) {
return side * side;
}
}
// This would cause a compilation error:
// class ExtendedMathUtils extends MathUtils {
// ...
// }
public class Main {
public static void main(String[] args) {
double circleArea = MathUtils.calculateCircleArea(5.0);
double squareArea = MathUtils.calculateSquareArea(4.0);
System.out.println("Circle area: " + circleArea);
System.out.println("Square area: " + squareArea);
}
}
You can also use final with methods to prevent them from being overridden:
class Vehicle {
public final void startEngine() { // Cannot be overridden
System.out.println("Starting engine...");
}
public void honk() { // Can be overridden
System.out.println("Honk!");
}
}
class Car extends Vehicle {
// This would cause an error:
// public void startEngine() { ... }
@Override
public void honk() { // This is allowed
System.out.println("Car honks: Beep! Beep!");
}
}
Practical Example: Employee Management System
class Employee {
protected String name;
protected int employeeId;
protected double baseSalary;
public Employee(String name, int employeeId, double baseSalary) {
this.name = name;
this.employeeId = employeeId;
this.baseSalary = baseSalary;
System.out.println("Employee " + name + " created");
}
public void displayInfo() {
System.out.println("ID: " + employeeId + ", Name: " + name +
", Base Salary: $" + baseSalary);
}
public double calculateSalary() {
return baseSalary;
}
public void work() {
System.out.println(name + " is working");
}
}
class Manager extends Employee {
private double bonus;
private int teamSize;
public Manager(String name, int employeeId, double baseSalary, double bonus, int teamSize) {
super(name, employeeId, baseSalary); // Call parent constructor
this.bonus = bonus;
this.teamSize = teamSize;
System.out.println("Manager " + name + " manages " + teamSize + " employees");
}
@Override
public double calculateSalary() {
return super.calculateSalary() + bonus; // Base salary + bonus
}
@Override
public void work() {
super.work(); // Call parent work method
System.out.println(name + " is managing the team");
}
public void conductMeeting() {
System.out.println(name + " is conducting a team meeting");
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Bonus: $" + bonus + ", Team Size: " + teamSize +
", Total Salary: $" + calculateSalary());
}
}
class Developer extends Employee {
private String programmingLanguage;
private int projectsCompleted;
public Developer(String name, int employeeId, double baseSalary, String language) {
super(name, employeeId, baseSalary);
this.programmingLanguage = language;
this.projectsCompleted = 0;
System.out.println("Developer " + name + " specializes in " + language);
}
@Override
public void work() {
super.work();
System.out.println(name + " is coding in " + programmingLanguage);
}
public void completeProject() {
projectsCompleted++;
System.out.println(name + " completed project #" + projectsCompleted);
}
@Override
public void displayInfo() {
super.displayInfo();
System.out.println("Language: " + programmingLanguage +
", Projects Completed: " + projectsCompleted);
}
}
public class Main {
public static void main(String[] args) {
Employee emp = new Employee("John Doe", 1001, 50000);
Manager mgr = new Manager("Jane Smith", 2001, 80000, 15000, 5);
Developer dev = new Developer("Mike Johnson", 3001, 70000, "Java");
System.out.println("\n=== Employee Information ===");
emp.displayInfo();
mgr.displayInfo();
dev.displayInfo();
System.out.println("\n=== Work Activities ===");
emp.work();
mgr.work();
mgr.conductMeeting();
dev.work();
dev.completeProject();
dev.completeProject();
System.out.println("\n=== Updated Developer Info ===");
dev.displayInfo();
}
}
Why And When To Use Inheritance?
- Code Reusability: Reuse attributes and methods of an existing class when you create a new class
- Method Overriding: Provide specific implementations for subclasses
- Polymorphism: Treat objects of different classes uniformly (covered in the next post!)
- Hierarchical Organization: Model real-world relationships (Car is-a Vehicle)
Key Takeaways
- Use
extendsto create inheritance relationships - Access modifiers control what subclasses can access
superkeyword accesses parent class methods, attributes, and constructors- Method overriding allows subclasses to provide specific implementations
finalkeyword prevents inheritance (classes) or overriding (methods)- Call
super()first in subclass constructors
In the next post, we’ll explore abstract classes and interfaces - powerful tools for defining contracts and achieving abstraction!
Happy coding!