Class Inheritance in Java (Hierarchy of Classes)
Class inheritance is the process of creating new classes based on already existing ones. A set of object properties (attributes) and behaviors (methods) that exist in one class can be passed on to another class through inheritance. In Java, the keyword extends is used to achieve this.
The syntax is:
public class Subclass_name extends Superclass_name
{
// Attributes
// Methods
}
The purpose of creating a subclass is to extend or specialize the existing state and behavior of the superclass. A superclass is also called a parent class, while a subclass is called a child class.
Multiple classes can form a hierarchy. If one class (for example, Class_3) inherits from another class (Class_2), and that class inherits from a previous one (for example, Class_1), then Class_3 automatically inherits all accessible properties and methods from both Class_2 and Class_1.
In other words, inheritance works transitively — each subclass receives everything that is inherited along the chain, provided that access modifiers allow it.
If we create a hierarchy of classes representing cars, it could look like this:
VIDEO 1: Nasleđivanje klasa u JAVI
If we now want to create a class that describes a student (Student), we should notice that the student will already have all the attributes defined in the Person class, because a student is actually a person. In addition to those inherited attributes, the student class can also have some new ones, such as class, average grade, success, etc.
Since Java supports class inheritance, we can create the Student class by extending the Person class, which means that Student automatically inherits its attributes and methods.
Of course, we could write the Student class completely from scratch and manually redefine all attributes and methods from Person, but that would be unnecessary duplication of code and a much worse design solution.
Here is what the Student class that inherits from Person looks like:
Notice that in the Student class we did not redefine the attributes name, surname, or birthYear. Those attributes are already defined in the Person class.
However, when a Student object is created, it still contains these attributes, because they are inherited from the Person class. In other words, the Student object consists of its own attributes (grade, average) and all accessible attributes from the Person class.
We can easily verify this in practice.
If we create a Student object and then type the dot operator (.) after the object name in the IDE, we will see all available attributes and methods — both those defined in Student and those inherited from Person.
Attributes such as grade and average are highlighted because they are specific to the Student class. On the other hand, name, surname, birthYear, and the method toString() are inherited from the Person class.
Every class in Java, even if it does not explicitly extend another class, automatically inherits from the Object class.
The Object class contains important methods such as equals(), hashCode(), notify(), wait(), and others — which is why they also appear in the list shown in the image above.
If we invoke the toString() method for a Student object, the version defined in the Person class will be executed (if it is not overridden in Student).
This is logical, because the method is inherited from the parent class.
In that case, the method will return only the data defined in Person (such as name, surname, birthYear), but not the attributes specific to Student (grade, average).
However, in class inheritance there is a possibility to override the same method in the subclass.
If we redefine toString() inside the Student class so that it also returns grade and average, then the call student.toString() will execute the overridden method from Student, instead of the inherited version from Person.
Here is an example of defining and using this method:
Access to Attributes and Methods of the Parent Class
If from a subclass (for example, Student) we want to access an attribute or a method that belongs to the parent class, this can be done using the keyword super.
The use of this keyword can be seen in the constructor of the Student class with parameters.
Class inheritance – Constructor of class Student
Figure 7: Constructor of class Student
Inside the constructor, the Student constructor invokes the constructor of the Person class using the keyword super.
The parameters required for the Person class (name, surname, birthYear) are passed to it.
This ensures that the inherited attributes are properly initialized before the specific attributes of the Student class are assigned.
Another example of using super can be seen in the toString() method.
Class Student – Overriding method toString()
Figure 8: Student class method toString() using the keyword super
It should be noted that the person's data is obtained by invoking the toString() method from the Person class.
In this context, super.toString() refers to the parent class implementation of the method.
The keyword super represents the parent part of the current object. Although we are working with a Student object, the inherited portion of that object is treated as a Person, and that is why we can explicitly call the parent method.
Famous Class Hierarchies
A well-known example of class hierarchy in Java can be found in the Swing package. Classes such as JButton, JLabel, JTextField, and many others represent graphical components used in user interfaces.
All these classes are organized in a hierarchical structure. They inherit common behavior from higher-level classes in the hierarchy.
Inheriting classes in the Swing package
At the top of the hierarchy we can see the Component class (from the AWT package). Many Swing classes ultimately inherit from this class.
This means that all Swing components share common properties such as:
- position on the screen,
- size (width and height),
- visibility,
- event handling mechanisms.
Swing components also inherit from the JComponent class.
Inheritance of the JComponent class in the Swing package
Classes such as JButton, JRadioButton, and JCheckBox extend JComponent, which provides additional functionality such as painting, borders, tool tips, and support for pluggable look-and-feel.
Thanks to inheritance, all these components share a common structure and behavior, while still having their own specific properties and methods.
This is a practical example of how class hierarchy allows large frameworks like Swing to remain organized, reusable, and scalable.
Additional Examples of Class Hierarchy
To better understand how inheritance works in practice, let us consider a few additional examples of class hierarchies.
Example 1: Vehicle Hierarchy
Suppose we want to model different types of vehicles. All vehicles share some common properties such as brand and maximum speed.
public class Vehicle {
String brand;
int maxSpeed;
public Vehicle(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String toString() {
return "Vehicle[brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
Now we can create a subclass Car that extends Vehicle.
public class Car extends Vehicle {
int numberOfDoors;
public Car(String brand, int maxSpeed, int numberOfDoors) {
super(brand, maxSpeed);
this.numberOfDoors = numberOfDoors;
}
@Override
public String toString() {
return "Car[numberOfDoors=" + numberOfDoors + ", "
+ super.toString() + "]";
}
}
The Car class inherits brand and maxSpeed from Vehicle, and adds its own specific attribute numberOfDoors.
Example 2: Animal Hierarchy
Another common example is an animal hierarchy.
public class Animal {
String name;
public Animal(String name) {
this.name = name;
}
public void makeSound() {
System.out.println("Some generic sound");
}
}
Now we define a subclass Dog:
public class Dog extends Animal {
public Dog(String name) {
super(name);
}
@Override
public void makeSound() {
System.out.println("Bark");
}
}
Here we can clearly see method overriding. The method makeSound() is inherited from Animal, but in the Dog class it is redefined to provide a specific behavior.
If we create a Dog object and call makeSound(), the overridden method will be executed.
Example 3: Multi-level Inheritance
Inheritance can also form multiple levels.
public class ElectricCar extends Car {
int batteryCapacity;
public ElectricCar(String brand, int maxSpeed,
int numberOfDoors, int batteryCapacity) {
super(brand, maxSpeed, numberOfDoors);
this.batteryCapacity = batteryCapacity;
}
}
In this case:
- ElectricCar inherits from Car
- Car inherits from Vehicle
- Therefore, ElectricCar indirectly inherits from Vehicle
This demonstrates transitive inheritance — the lowest class in the hierarchy receives all accessible members from all its ancestor classes.
From General Hierarchies to Scientific Modeling
The previous examples (Vehicle, Animal, Student) illustrate how inheritance helps us organize related classes in a logical and structured way.
However, inheritance is not used only for simple everyday examples. It becomes especially powerful in scientific and technical applications, where different entities share common physical properties and behaviors.
For example, in physics we often deal with objects that have mass, position, velocity, or are affected by forces. Instead of redefining these properties in every single class, we can define a general base class (for example, PhysicalObject) and then create specialized subclasses such as Particle, RigidBody, or Force.
In this way, inheritance allows us to:
- avoid duplication of code,
- maintain a clear hierarchical structure,
- extend existing models without modifying previously written code,
- build scalable simulations and scientific applications.
In the following section, we will see how inheritance can be applied in a physics-related example, where class hierarchy helps us model real-world physical concepts in a clear and structured manner.
Practical Application: Inheritance in Processing Simulations
On this website, inheritance is also used in physics simulations. For example, in the projectile motion simulation implemented in Processing, a base class can define common physical properties such as position, velocity, and acceleration.
A specialized class (Projectile) then extends this base class and implements specific motion equations.
You can see a complete practical example here: Projectile Motion in Processing
Java and Physics
Consider the following problem:
We want to simulate the change of position x of a body under the action of a force while it is placed on a horizontal surface. We observe position changes for small time intervals dt (for example, dt = 0.05 s).
A body of mass m will receive an acceleration in the x direction (ax) from rest if the applied force F is greater than the friction force Ftr.
The body will not move in the y direction (ay = 0) because the normal force N balances the gravitational force Fg:
- Fg – weight of the body
- N – normal reaction of the surface
To simulate motion, we need to model the forces acting on the body.
We will create one object for each force acting on the body:
- F – applied force
- Ftr – friction force
- N – normal force
- Fg – gravitational force
To create these objects, we first need classes that describe them.
Initial Separate Classes
Let us first define classes for the gravitational force (weight) and the friction force.
public class WeightOfBody {
double intensity, direction, m;
String name;
final double G = 9.81;
public WeightOfBody(double direction, double m) {
this.direction = direction;
this.m = m;
}
public void calculateIntensity() {
intensity = m * G;
}
public void setName(String name) {
this.name = name;
}
}
public class ForceFriction {
double intensity, coefOfFriction, N;
int direction;
String name;
public ForceFriction(double coefOfFriction, double N) {
this.coefOfFriction = coefOfFriction;
this.N = N;
}
public void calculateIntensity() {
intensity = coefOfFriction * N;
}
public void setName(String name) {
this.name = name;
}
}
Let us observe the following:
- Both classes contain attributes such as intensity, direction, and name.
- Both classes contain a method for calculating intensity.
- The implementation of the intensity calculation is different for each force.
- Some properties are specific only to gravity (mass), and some only to friction (coefficient of friction).
We can now ask an important design question:
Should these common properties be placed into a separate base class?
Creating a Base Class: Force
public class Force {
double intensity;
int direction;
String name;
public Force(int direction) {
this.direction = direction;
}
public void setName(String name) {
this.name = name;
}
}
Now both WeightOfBody and ForceFriction can inherit from the Force class.
public class WeightOfBody extends Force {
double m;
final double G = 9.81;
public WeightOfBody(int direction, double m) {
super(direction);
this.m = m;
}
public void calculateIntensity() {
intensity = m * G;
}
}
The attributes name and intensity, as well as the method setName(), are now defined only once in the Force class. They are automatically inherited by WeightOfBody and any other force class.
This approach eliminates code duplication and creates a clear and logical class hierarchy.
In order to test these classes, we need to create an object in a separate test class. This object will represent the gravitational force acting on the body.
An object created from the WeightOfBody class will contain all the attributes and methods defined in that class, as well as the attributes and methods inherited from the Force class.
To access a property or method of the created object, we use the dot operator (.).
The dot operator allows us to access:
- attributes (variables)
- methods (functions)
This is illustrated in the following example:
public class TestForces {
public static void main(String[] args) {
WeightOfBody gravity = new WeightOfBody(270, 10);
gravity.setName("Gravitational Force");
gravity.calculateIntensity();
System.out.println(gravity.name);
System.out.println(gravity.intensity);
}
}
In this example:
- gravity is an object of the class WeightOfBody.
- The method setName() is inherited from the Force class.
- The method calculateIntensity() is defined inside WeightOfBody.
- The dot operator is used to access both inherited and locally defined members.
This demonstrates how inheritance allows a derived class to use the functionality of its parent class while also adding its own specific behavior.
In the window that appears after typing a dot (.) following the object reference, we can see all available properties and methods.
These include:
- Methods and attributes defined directly in the WeightOfBody class,
- Members inherited from the Force class,
- Methods inherited from the Object class.
For example:
- intensity is an inherited attribute from the Force class.
- setName() is inherited from the Force class.
- toString() is inherited from the Object class.
This demonstrates that a class in Java does not inherit only from its direct parent, but also indirectly from all higher classes in the hierarchy.
Example: For a body mass of m = 3kg, create an object of gravity, give it a name, calculate the intensity of the force and display it on the screen.
public class ForceAnalysisTest {
public static void main(String[] args) {
WeightOfBody weight = new WeightOfBody(1, 3);
weight.setName("The weight of the body");
weight.calculateIntensity();
System.out.println(weight.name + " is " + weight.intensity + " N");
}
}
First, an object named weight belonging to the WeightOfBody class is created. The constructor with parameters is used, where the vector direction and the body mass are passed as arguments.
Next, the methods setName() and calculateIntensity() are called through the object reference.
The method calculateIntensity() computes the force intensity. It does not return a value because its return type is void. Instead, it assigns the calculated value to the inherited attribute intensity.
Finally, the calculated value is displayed on the standard output using System.out.println().
Method Overriding
All classes in Java implicitly inherit from the Object class. Therefore, every class inherits its methods.
One of these methods is toString(). Its purpose is to return a textual representation of the object.
The default implementation of toString() in the Object class returns a string containing the class name and a hash code. However, this is usually not meaningful for the user.
For that reason, we often override the toString() method in our own classes in order to provide a more useful textual description.
The toString () method is one of the inherited methods in the Object class, and is intended to form text to display data from the object to which it belongs.
However, there is nothing in the body of this method that is written in the Object class because there is no data in that class, so the toString () method is rewritten in the class that inherits it, in our case it is Force and WeightOfBody.
Generating the toString() Method
The Object class is offered in the dialog because every class in Java implicitly inherits from it.
From the list of available methods, we select toString() and click the OK button.
After closing the dialog, the toString() method is automatically generated.
Inside the body of this method, we now need to adjust the code so that it displays the relevant force data.
We replace the generated line:
return super.toString();
with:
return name + " equals " + intensity + " [N]";
This customized implementation will now return meaningful information about the force object.
Using the Keyword super
The keyword super refers to the parent class.
If we write:
super.toString();
we are calling the implementation of toString() from the parent class.
In the Force class, calling toString() may display basic force data. In the WeightOfBody class, we override this method in order to display additional or more specific information.
Calling toString() via Object Reference
Using a reference to an object of the WeightOfBody class, the method toString() is automatically invoked when the object is passed to System.out.println().
System.out.println(weight);
Java internally calls:
weight.toString();
and prints the returned text.
After running the program, the calculated intensity is displayed.
Example 2
A body of mass m is initially at rest on a horizontal surface. At a certain moment, a constant force F begins to act along the x-axis.
Determine the position of the center of mass after time t, relative to the initial position. The coefficient of friction is μ = 0.05.
Tasks:
- Create objects representing all forces acting on the body using class inheritance.
- Calculate the intensity of each force.
- Print the intensities of all forces.
- Consider force intensity as positive if it acts in the positive direction of the coordinate axis.
Example 2 – Complete Solution
A body of mass m is initially at rest on a horizontal surface. At a certain moment, a constant force F begins to act along the x-axis. The coefficient of friction is μ = 0.05.
We will:
- Create objects for all forces acting on the body,
- Calculate their intensities,
- Determine acceleration,
- Calculate position after time t,
- Print all results.
Step 1 – Base Class Force
// Base class representing a general force
public class Force {
double intensity; // magnitude of force
int direction; // +1 or -1 (x-axis direction)
String name; // name of the force
public Force(int direction) {
this.direction = direction;
}
public void setName(String name) {
this.name = name;
}
public String toString() {
return name + " = " + intensity * direction + " N";
}
}
Step 2 – Weight Force
// Class representing gravitational force
public class WeightOfBody extends Force {
double m; // mass of body
final double G = 9.81; // gravitational acceleration
public WeightOfBody(int direction, double m) {
super(direction);
this.m = m;
}
public void calculateIntensity() {
intensity = m * G;
}
}
Step 3 – Normal Force
// Normal reaction force
public class NormalForce extends Force {
double weightIntensity;
public NormalForce(int direction, double weightIntensity) {
super(direction);
this.weightIntensity = weightIntensity;
}
public void calculateIntensity() {
// On horizontal surface N = Fg
intensity = weightIntensity;
}
}
Step 4 – Friction Force
// Friction force
public class ForceFriction extends Force {
double coefOfFriction;
double normalForceIntensity;
public ForceFriction(int direction, double coefOfFriction, double normalForceIntensity) {
super(direction);
this.coefOfFriction = coefOfFriction;
this.normalForceIntensity = normalForceIntensity;
}
public void calculateIntensity() {
intensity = coefOfFriction * normalForceIntensity;
}
}
Step 5 – Applied Force
// Applied external force
public class AppliedForce extends Force {
public AppliedForce(int direction, double intensity) {
super(direction);
this.intensity = intensity;
}
}
Step 6 – Test Class (Complete Simulation)
public class ForceAnalysisTest {
public static void main(String[] args) {
// Given values
double m = 3; // mass in kg
double mu = 0.05; // coefficient of friction
double Fvalue = 20; // applied force in N
double t = 4; // time in seconds
// 1. Weight force (downward direction)
WeightOfBody weight = new WeightOfBody(-1, m);
weight.setName("Weight");
weight.calculateIntensity();
// 2. Normal force (upward direction)
NormalForce normal = new NormalForce(1, weight.intensity);
normal.setName("Normal force");
normal.calculateIntensity();
// 3. Friction force (opposite to motion)
ForceFriction friction = new ForceFriction(-1, mu, normal.intensity);
friction.setName("Friction force");
friction.calculateIntensity();
// 4. Applied force (positive x direction)
AppliedForce applied = new AppliedForce(1, Fvalue);
applied.setName("Applied force");
// Print all forces
System.out.println(weight);
System.out.println(normal);
System.out.println(friction);
System.out.println(applied);
// Calculate net force in x direction
double netForce = applied.intensity * applied.direction
+ friction.intensity * friction.direction;
// Newton's second law: F = m * a
double acceleration = netForce / m;
// Since body starts from rest:
// x = (1/2) * a * t^2
double position = 0.5 * acceleration * t * t;
System.out.println("Acceleration = " + acceleration + " m/s^2");
System.out.println("Position after " + t + " s = " + position + " m");
}
}
Explanation
First, we create objects representing all forces acting on the body. Each object calculates its own intensity.
The friction force acts opposite to motion, therefore its direction is negative.
The net force along the x-axis is calculated as the sum of forces considering their directions.
Using Newton’s second law:
F = m · a
we calculate acceleration. Since the body starts from rest, the position after time t is:
x = ½ · a · t²
This example demonstrates how physical laws can be modeled using class inheritance and object-oriented design.
Example 2 – Using a List of Forces
Instead of calculating the net force manually, we can store all forces in a single list.
Since all force classes inherit from the Force class, we can store different types of forces inside one collection.
Improved Test Class
import java.util.ArrayList;
public class ForceAnalysisTest {
public static void main(String[] args) {
// Given data
double m = 3; // mass (kg)
double mu = 0.05; // coefficient of friction
double Fvalue = 20; // applied force (N)
double t = 4; // time (s)
// Create a list that can store all forces
ArrayList<Force>> forces = new ArrayList<>();
// 1. Weight force
WeightOfBody weight = new WeightOfBody(-1, m);
weight.setName("Weight");
weight.calculateIntensity();
forces.add(weight);
// 2. Normal force
NormalForce normal = new NormalForce(1, weight.intensity);
normal.setName("Normal force");
normal.calculateIntensity();
forces.add(normal);
// 3. Friction force
ForceFriction friction = new ForceFriction(-1, mu, normal.intensity);
friction.setName("Friction force");
friction.calculateIntensity();
forces.add(friction);
// 4. Applied force
AppliedForce applied = new AppliedForce(1, Fvalue);
applied.setName("Applied force");
forces.add(applied);
// Print all forces using a loop
System.out.println("Forces acting on the body:");
for (Force f : forces) {
System.out.println(f);
}
// Calculate net force along x-axis
double netForce = 0;
for (Force f : forces) {
netForce += f.intensity * f.direction;
}
// Newton's second law
double acceleration = netForce / m;
// Position after time t (starting from rest)
double position = 0.5 * acceleration * t * t;
System.out.println("----------------------------");
System.out.println("Net force = " + netForce + " N");
System.out.println("Acceleration = " + acceleration + " m/s^2");
System.out.println("Position after " + t + " s = " + position + " m");
}
}
Why Is This Better?
All forces are treated as objects of type Force, even though they belong to different subclasses.
This allows:
- Cleaner code
- Easy expansion (adding new forces)
- No need to rewrite the net force calculation
- Better organization of the physical model
If we later add a new type of force, we only need to:
- Create a new class that extends Force,
- Add its object to the list.
The rest of the program will work without modification.
This demonstrates the power of class inheritance and object-oriented design.
|
Previous
|< Classes and Objects |
Next
Abstract classes and interfaces >| |