Object-Oriented Programming (OOP) is one of the most widely used paradigms in programming. One of its key principles is abstraction in Java, which is all about hiding complex implementation details and exposing only the necessary parts to the user. In simple terms, abstraction helps us focus on what an object does, rather than how it does it.
In this blog, we’ll focus on understanding abstraction in Java. We’ll not only explore the concept through Java code examples but also use real-life analogies and diagrams to make it more engaging and easier to grasp.
So, let’s start…
As told above, abstraction is one of the four pillars of OOP, which is used to hide complex details while displaying the ones that are easier to understand. Developers use it to hide all the functionality or the complex stuff which are irrelevant from the user’s point of view while showing only the essential features of an object to the outside world. Let’s understand this with the help of an example –
We all like watching TV while sitting on couches, sofas, etc, taking the remote and switching on the TV, changing channels to find our favorite shows, increasing or decreasing the volume or Binge watching and all these features are at our disposal, it is just a matter of clicking the buttons on the remote. But how?
Well, the answer is rather simple, the internal structure of a remote is not as simple as its exterior (as shown in the above image), a series of things happen when we press a button. Let’s understand them one by one, but only briefly.
Signal Transmission: The remote sends an infrared signal to turn the TV on.
Signal Reception: The television has a receiver that catches this signal.
Processing the Signal: The TV processes this signal and responds by powering it up.
This entire process involves complex electronic principles and programming inside both the remote and the TV, yet all we need to know is which buttons to press for which action. So, with this principle in mind, we have abstracted or are unaware of all the complex things that occur inside both the TV and the remote, as these details are not necessary for us to know.
Similarly, in programming, when we use a class or a method in Java, we don’t need to understand the details of how the process is implemented. Just like using the remote, we only need to know what functions are available and how to call them. This simplifies our role as programmers because we can use code written by others without needing to understand every detail about how it works—just what it does is enough.
There are two ways of achieving abstraction in Java: they are
Let’s understand each of them
An abstract class is a class that is declared using the abstract keyword or has one or more abstract methods. This abstract keyword is the modifier and can be used with classes and methods, but not variables. It may or may not contain all the abstract methods (methods without a body), as some of them may be concrete methods (methods with a body).
The purpose of an abstract class is to serve as the blueprint for other classes. It allows us to define a common structure and behaviour that can be shared among multiple subclasses while enforcing certain rules and guidelines. Though it has a default constructor, it can also have a parameterized constructor.
abstract class ClassName {
// Regular (concrete) method
returnType anotherMethod() {
// Method body
}
}
// Subclass that extends the abstract class
class SubClassName extends ClassName {
// Must provide implementation for all abstract methods
@Override
returnType methodName() {
// Method body
}
}
class ClassName extends SuperClassName implements InterfaceName {
// class body (fields, constructors, methods, etc.)
}
Let’s understand abstract class with the help of a code.
// Abstract class
abstract class Animal {
// Abstract method (no body)
abstract void makeSound();
// Concrete method
void sleep() {
System.out.println("Zzz");
}
}
// Subclass
class Dog extends Animal {
// Implementation of abstract method
@Override
void makeSound() {
System.out.println("Woof");
}
}
// Main class
class Main {
public static void main(String[] args) {
// Animal a = new Animal() ❌ This will not work,
Dog dog = new Dog(); // ✅ This will work
dog.makeSound(); // Outputs "Woof"
dog.sleep(); // Outputs "Zzz"
}
}
In the above code, Animal is an abstract class with one abstract method makeSound and one concrete method sleep. The Dog class is an extended class of Animal that implements the makeSound method.
Let’s break the above code into three parts and understand each of them –
abstract class Animal {
// Abstract method (no body)
abstract void makeSound();
// Concrete method
void sleep() {
System.out.println("Zzz");
}
}
The above code is just the abstract class part of the whole code, meaning that part of the code where we have initialized the abstract class using the abstract keyword, as without it, the abstract class cannot be initialized.
Let’s understand the above code step by step.
class Dog extends Animal {
// Implementation of abstract method
@Override
void makeSound() {
System.out.println("Woof");
}
}
Similar to the Abstract Class part, we will also understand this section step by step
class Main {
public static void main(String[] args) {
// Animal a = new Animal() ❌ This will not work,
Dog dog = new Dog(); // ✅ This will work
dog.makeSound(); // Outputs "Woof"
dog.sleep(); // Outputs "Zzz"
}
}
Now that we have understood each part of the code separately, it is time to understand the code as a whole –
First things first, Abstraction helps developers in many ways. It not only reduces code complexity, but it also increases code re-usability, maintainability and flexibility, and we will see how with this example.
In the above code, we created an abstract class Animal
using the abstract
keyword. This class has two methods:
makeSound()
is an abstract method, which means it has no code inside it.sleep()
is a regular method, which has some code (it prints “Zzz”).We cannot create an object of the Animal
class because it’s abstract, and abstract classes cannot be instantiated. We will get a compilation error saying that the Animal class is abstract.
Instead, we must create another class that extends it. That’s where the Dog
class comes in. It extends the Animal
class and gives its own version of the makeSound()
method. This is required because abstract methods must be completed by a subclass. The sleep()
method is already complete, so the Dog
class automatically inherits it and can use it as it is.
The main()
method is where the program starts. In this method, we create an object of the Dog
class and call its makeSound()
and sleep()
methods.
makeSound()
prints “Woof”, which shows the behaviour defined in the Dog
class.sleep()
prints “Zzz”, which comes from the Animal
class.Which gives us our output. “This clarifies that an abstract class is just a template that defines the structure, while other classes fill in the missing details. It helps make the code more organized and reusable.
This approach is rather creative; what we are doing is creating an object of the subclass and storing its reference in a variable of the abstract class type. This approach gives us the power of abstraction and polymorphism. This is a common and powerful feature used in runtime polymorphism.
So, in the above code, let’s replace
Dog a = new Dog();
with
Animal a = new Dog();
Let’s understand the above line
And when they are combined, the line Animal a = new Dog(;
means that we are creating an object of the Dog
class and storing its reference in a variable a
that is declared as type Animal
. This allows us to use the features of runtime polymorphism, where the object behaves according to its actual type (Dog
), even though the reference is of the parent type (Animal
).
In simple words, at runtime, the method that gets executed depends on the object type, not the reference type. This approach helps in writing clean, flexible, and reusable code by focusing on what the object is expected to do (as defined in the abstract class Animal
) rather than how it does it (which is written in the Dog
class).
It also makes the code easier to change in the future, as we can switch to a different subclass, like Cat
or Lion
, with minimal modifications to our existing code.
Now the new code becomes
public class Test {
public static void main(String[] args) {
Animal a = new Dog();
a.sleep();
a.makeSound();
}
}
abstract class Animal {
// Abstract method (no body)
abstract void makeSound();
// Regular method
void sleep() {
System.out.println("Sleeping...");
}
}
// Subclass (must override abstract method)
class Dog extends Animal {
@Override
void makeSound() {
System.out.println("Bark!");
}
}
Methods that have been declared with the abstract keyword are called abstract methods. These methods –
abstract class ClassName {
abstract returnType methodName(parameters);
}
See in the above example of the abstract class, we have defined and used an abstract method – makeSound(). And to print its value, it has to be overridden by a different value inside the subclass. Later, this value was printed by the object of the same subclass, as we cannot create an object of an abstract class.
In the above code, we have seen how we can use an abstract class that uses both abstract and concrete methods to extend its reach or what it can do. Like the Animal class extends itself to the Dog subclass, which makes the Bark sound when the makeSound is triggered and Zzzz when sleep is triggered.
As we’ve already discussed, an abstract class in Java can contain both abstract and concrete methods. Abstract methods are those that do not have a body, while concrete methods are fully implemented with a body. But is that the only difference between them? The answer is no. There are several other key differences between abstract and concrete methods, which we’ll explore next.
Abstract Methods | Concrete Methods |
Declared without a body | Declared with a body |
Must be overridden by a concrete subclass | Can be inherited by a subclass |
Used to enforce a contract that a subclass must follow | Provides a common behaviour that can be shared across all subclasses |
Must be implemented by any subclass of an abstract class | These can be inherited and used directly by sub classes. |
Abstract Methods defines What to do and Abstract Classes defines What to do and how to do it (optional), but the actual work is done by the subclass.
We already know that there are two ways by which we can implement abstraction in Java – Abstract classes and interfaces. We have already understood what Abstract classes are with the help of both theory and code, so let’s understand the concept of interface.
Interface is probably the most powerful tool to achieve abstraction in Java, as using an interface, we can achieve 100% abstraction in Java. Its basic use is to specify what methods a class should implement without dictating how they should be implemented. Simply said, tells us what to do, but leaves the how part to us.
An interface is a reference type, similar to a class, and all the methods declared in an interface are abstract by default and have no implementation. We will use the word interface just like we used to write the word abstract for abstract classes. Thus, it’s safe to say that the interface cannot also be instantiated.
To better understand interfaces, we’ll use the same code example as we did for abstract classes and methods. This comparison will help clarify how interfaces are both similar to and different from abstract classes.
public interface Animal {
void makeSound(); // abstract method
void sleep(); // abstract method
}
Notice something different in the above code – we have defined methods without the abstract keyword, but they aren’t concrete methods, they are abstract methods as they don’t have a body. That is because every method defined in an interface is abstract by default; thus, it doesn’t require the keyword to be declared abstract.
Now, we also know that an abstract class can extend only once, but that’s not the case with the interface. In an interface, an abstract class can have multiple interfaces, allowing it to inherit abstract methods from all the interfaces it implements, hence achieving the benefits of multiple inheritance without the associated complexities.
interface A { } === abstract interface A { }
interface A {
// int id; // Compilation error
int id = 20;
}
interface A {
int calculateArea();
}
interface B {
int calculateArea();
}
class MyClass implements A, B {
@Override
public int calculateArea() {
// Defining once is enough since both interfaces have the same method signature
int length = 5;
int breadth = 4;
return length * breadth;
}
public static void main(String[] args) {
MyClass obj = new MyClass();
System.out.println("Area: " + obj.calculateArea());
}
}
interface A {
int calulateArea();
}
interface B {
void calulateArea();
}
class MyClass implements A, B {
// Not possible to define calculateArea() for both interfaces
}
interface MyInterface {
void print();
}
class MyClass implements MyInterface {
public void print() {
System.out.println("Inside print method");
}
public void message() {
System.out.println("Inside message method");
}
public static void main(String args []) {
MyInterface obj = new MyClass(); // Assigning MyClass object into MyInterface type
// obj.message(); // Compilation error
obj.print(); // prints "Inside print method"
}
}
This behavior is intentional and is a fundamental concept of polymorphism and abstraction in object-oriented programming. By assigning an object to an interface type, we can effectively treat the object as an instance of the interface, which can only access the members (methods and properties) defined in the interface contract.
This approach promotes code modularity, flexibility, and maintainability. It allows us to write code that works with objects of different classes as long as they implement the same interface, without needing to know the specific implementation details of each class.
interface X {
int id = 10;
}
interface Y {
int id = 20;
}
class MyClass implements X, Y {
public static void main(String[] args) {
// System.out.println(id); // ❌ Compilation error: ambiguous reference
System.out.println(X.id); // ✅ Access X.id — prints 10
System.out.println(Y.id); // ✅ Access Y.id — prints 20
}
}
In the above code, we didn’t create any objects. That’s because interface variables are public static final by default, thus belong to the interface itself, not to any object or class that implements the interface.
interface A {
int id = 35; // public static final by default
void test(); // abstract method
}
class MyClass implements A {
public void test() {
id = 30; // ❌ Compilation error: cannot assign a value to final variable 'id'
}
}
The way around is to declare it as an instance variable.
class MyClass implements A {
int id = 35; // now it's a regular instance variable
public void test() {
id = 30; // ✅ OK now
}
}
Let’s understand the interface through code. In this code, we have defined
Let’s start…
interface Vehicle {
// Constants
double MAX_SPEED = 200.0; // public, static, and final by default
// Abstract methods
void start();
void accelerate(double speed);
void brake();
double getCurrentSpeed();
}
class Car implements Vehicle {
private double currentSpeed;
@Override
public void start() {
System.out.println("Car started.");
}
@Override
public void accelerate(double speed) {
currentSpeed += speed;
System.out.println("Car accelerated to " + currentSpeed + " km/h.");
}
@Override
public void brake() {
currentSpeed = 0;
System.out.println("Car stopped.");
}
@Override
public double getCurrentSpeed() {
return currentSpeed;
}
}
public class Main {
public static void main(String[] args) {
Car myCar = new Car();
myCar.start();
myCar.accelerate(50.0);
System.out.println("Current speed: " + myCar.getCurrentSpeed() + " km/h");
myCar.brake();
}
}
There are many things that are going on and can be overwhelming for beginners, thus we have decided to divide the explanation part into three subheadings – The Interface Declaration, The Implementation and The Main Method.
In the above code, we have defined an interface named Vehicle, which includes a constant MAX_SPEED. This constant is final by default and represents the maximum speed of a vehicle, set at 200 km/h. The interface also includes several abstract methods, some with a void return type, meaning they don’t return any value, and one method with a double return type that will return the current speed of the vehicle.
The implementation part begins with the Car class, which implements our Vehicle interface. Inside this class, we define an instance variable called currentSpeed, marked as private and of type double, so it can store decimal values like 30.5. This variable keeps track of the car’s current speed.
The start() method simply prints a message to indicate the car has started. The accelerate(double speed) method increases the currentSpeed by the given speed, using the formula currentSpeed += speed. The speed parameter is a double to allow for decimal values. When we call this method, the new speed is calculated and printed.
The brake() method resets the currentSpeed to 0, simulating what happens when a vehicle comes to a complete stop. Finally, the getCurrentSpeed() method is used to retrieve the car’s current speed. Inside this method, we use the return keyword to send the value of currentSpeed back to the part of the program that called it — in this case, the main() method, where it’s printed to the screen.
In the main() method, we create an object called myCar using the Car class and call its methods one by one to test the functionality of the program. First, we call the start() method to simulate starting the car. Then, we call accelerate(50.0) to increase the speed by 50.0 km/h( here, 50.0 is the argument we pass to the method, which is received as a parameter inside the method definition). This updated speed is retrieved using getCurrentSpeed() and printed to the console. Finally, the brake() method is called to stop the car by resetting the speed to 0.
A nested interface (or member interface) is an interface declared inside a class or another interface. This helps logically group interfaces that are only used in one place, promoting better encapsulation and reducing code clutter.
public
, protected
, private
, or package-private (no modifier).public
and static
.
interface first{
interface second{
// body
}
}
class abc{
interface _interface_name{
// body
}
}
Parameter | Abstract Class | Interface |
Declaration | Uses the abstract keyword | Uses the interface keyword |
Method | Both Abstract and Concrete Methods | Abstract, Default and Static Methods (after Java 8) |
Inheritance | Doesn’t support multiple inheritance | Supports multiple inheritance |
Implementation | Can provide for the interface | Can not provide for Abstract Class |
Variable types | Can have instance variables, static variables, and constants | Can only have static and final variables (constants) |
Access Modifiers | Have public, protected and private | All methods are public by default, variables are public static final by default |
Abstraction | Partial Abstraction | Complete Abstraction |
Can extend | Another Java Class | Java interface |
According to Oracle documentation, we can use Abstract Class when –
And the interface when
In conclusion, we would just say that abstraction is one of the four fundamental pillars of OOP in Java, which helps in hiding the complex part of the system from the user while showing only the essential things to the user that he needs. We can also say that Abstraction is a process that displays what something does while hiding how it’s done.
There are two ways of ways in which we can achieve abstraction – through Abstract classes and Interfaces. Neither of these methods can be instantiated on their own and needs separate keywords to do so. In the case of Abstract Classes, it’s abstract, while in the case of an interface, it’s interface. We have already discussed about them in detail in the blog, and hope you will like it.
Ans. Encapsulation is the practice of bundling data and methods within a single unit, like a class, and controlling their access, whereas abstraction is about hiding complex implementation details and exposing only the essential functionalities.
Ans. It reduces code complexity and increases code readability and security.
Ans. No, we cannot create an object of an abstract class directly. We can only create objects of subclasses that extend the abstract class and provide implementations for its abstract methods.
Ans. Yes, an abstract class can have a constructor. The constructor is called when an instance of a subclass is created. This is useful for initializing common properties that are shared among all subclasses.
Ans. No, an abstract class cannot be declared as final in Java because a final class cannot be subclassed, and an abstract class needs to be subclassed to provide implementations for its abstract methods.
Ans. The reason is that abstract classes may contain non-final variables, whereas variables in the interface are final, public, and static.
Inheritance in Java is a mechanism of creating a new class or interface from an…
In this blog, we will not only understand the concepts of OOPS in Java in…
Object-Oriented Programming System (OOPS) is a programming paradigm built around the concept of objects —…
In this blog, we will learn How to Detect a Click Outside of a React…
learn How to Use Hooks to Create Infinite Scrolling in React by making a custom…
React Movie App or Movie App in React is a fun project that every React…