How to Instantiate an Abstract Class in Java?


An Abstract Class is a class which is declared under the ‘Abstract’ keyword in Java. Abstract classes are a concept of one of the four principles of Object Oriented Programming (OOP) known as ‘Inheritance.’ Inheritance refers to a characteristic of Java Classes where one class known as the ‘Sub-class’ can inherit all the properties of the parent class typically known as ‘Super-class.’

In Java, Abstract Classes refer to the base super class from which other sub classes can inherit. It can contain both abstract and non-abstract methods.

Algorithm

  • Step 1 − Identify the methods in the class that have a default or no implementation.

  • Step 2 − Remove the implementation of these methods.

  • Step 3 − Add the abstract keyword to the class declaration.

  • Step 4 − Add the abstract keyword to the method declarations that were modified in step 2.

  • Step 5 − If the class has any instance variables that require initialization, add a constructor to initialize them.

  • Step 6 − Update any subclasses of the abstract class to implement the abstract methods or become abstract themselves.

Syntax

Let’s look at the syntax to instantiate an abstract class in Java −

// Abstract Class
abstract class Shape {
   public abstract void draw();
}

Approach

As Abstract classes are incomplete classes, they cannot be instantiated directly using the ‘new’ keyword.

  • Concrete Subclass − In order to properly instantiate an otherwise ambiguous or incomplete abstract class, one may opt to utilize a concrete subclass. By seamlessly extending from this parent abstraction and implementing each of its methodological requirements, the user can successfully create and implement this newly instantiated subclass without error or inconsistency in operation.

  • Lambda Expression − To create an object from an abstract class you have another option − use a lambda expression that provides implementations for all its abstractions. Afterward assign this lambda creation to a compatible functional interface variable based on those signatures.

Instantiating an Abstract Class

Let us look at a sample code snippet to understand the use of Abstract Classes. The 1st scenario provides a code with a non abstract class.

Example

class Shape {
   public void printName() {  
      System.out.println("I'm a shape"); 
   }
    
   public float area() {  
      return 0;  
   }
    
   public void printDetails() {  
      this.printName();
      System.out.println("... and my area is " + this.area());
   }
}

class Circle extends Shape {  
   private float radius;
    
   public Circle(float radius) {  
      this.radius = radius;
   }
    
   public void printName() {        
      System.out.println("I'm a circle");  
   }  
    
   public float area() {  
      return (float) (Math.PI * Math.pow(radius, 2));  
   }
}

class Rectangle extends Shape {  
   private float length;
   private float width;  
    
   public Rectangle(float length, float width) {
      this.length = length;  
      this.width = width;  
   }
    
   public void printName() {  
      System.out.println("I'm a rectangle");   
   } 
   
   public float area() {  
      return length * width;  
   }
}

public class Main {     
   public static void main(String[] args) {  
      Shape[] shapes = { new Circle(3.5f), new Rectangle(4.0f, 5.0f) };  
      for (Shape shape : shapes) {
         shape.printDetails();
      }
   }
}

Output

I'm a circle
... and my area is 38.48451
I'm a rectangle
... and my area is 20.0

The Circle and Rectangle classes both inherit the printName(), area(), and printDetails() methods from the ‘Shape’ superclass. However, neither class overrides the area() method to provide its own implementation.

By calling the printDetails() method on a Circle object, the output will be "I'm a circle... and my area is 38.48451". Likewise, calling the printDetails() method on a Rectangle object will output "I'm a rectangle... and my area is 20.0". This ensures that the output reflects the correct shape and its corresponding area based on the specific implementation provided in each class

Example 1: Concrete Subclass

// With abstract class
abstract class Shape {
   public abstract void printName();
   public abstract float area();
   public void printDetails() {
      this.printName();
      System.out.println("... and my area is " + this.area());
   }
}

// Concrete class
class Circle extends Shape {
   private float radius;
   public Circle(float radius) {
      this.radius = radius;
   }
   public void printName() {
      System.out.print("I'm a circle");
   }
   public float area() {
      return (float) (Math.PI * Math.pow(radius, 2));
   }
}

// Concrete class
class Rectangle extends Shape {
   private float length;
   private float width;
   public Rectangle(float length, float width) {
      this.length = length;
      this.width = width;
   }
   public void printName() {
      System.out.print("I'm a rectangle");
   }
   public float area() {
      return length * width;
   }
}

// Main class
public class Main {
   public static void main(String[] args) {
      Shape[] shapes = { new Circle(10), new Rectangle(5, 10) };
      for (Shape shape : shapes) {
         shape.printDetails();
      }
   }
}

Output

I'm a circle... and my area is 314.15927
I'm a rectangle... and my area is 50.0

In the above updated code, the Circle and Rectangle classes have implemented the abstract methods printName() and area() defined in the ‘Shape’ abstract class. The printDetails() method in the Shape class is able to use these methods to print out the shape name and its respective area.

By making Shape an abstract class and defining the abstract methods, we ensure that any class that extends the Shape class must provide their own implementation for the printName() and area() methods.

Example 2: Lambda Expression

interface Nameable {
   String getName();
}
 
abstract class Shape {
   private Nameable nameable;
 
   public Shape(Nameable nameable) {
      this.nameable = nameable;
   }
 
   public abstract float area();
 
   public void printDetails() {
      System.out.println("I'm a " + nameable.getName() + " ... and my area is " + this.area());
   }
}
 
class Circle extends Shape {
   private float radius;
 
   public Circle(float radius) {
      super(() -> "circle");
      this.radius = radius;
   }
 
   @Override
   public float area() {
      return (float) (Math.PI * Math.pow(radius, 2));
   }
}
 
class Rectangle extends Shape {
   private float width;
   private float height;
 
   public Rectangle(float width, float height) {
      super(() -> "rectangle");
      this.width = width;
      this.height = height;
   }
 
   @Override
   public float area() {
      return width * height;
   }
}
 
public class Main {
   public static void main(String[] args) {
      Shape[] shapes = { new Circle(10), new Rectangle(5, 10) };
      for (Shape shape : shapes) {
         shape.printDetails();
      }
   }
}

Output

I'm a circle ... and my area is 314.15927
I'm a rectangle ... and my area is 50.0

In our most recent update to this code, we've introduced a refined methodology by assigning Shape as an abstract class while also making its getName() function internalized. Further improvements involve integrating a printName method that successfully utilizes getName()'s data to display each respective shape's name. With regards to Circle and Rectangle subclasses - they now override their corresponding getNames using lambda expressions in order to accurately identify the intended form.

Conclusion

To conclude, abstract classes can be only instantiated through their base subclass but never directly. This is a concept of Inheritance.

The main reason behind this is that abstract classes are not complete implementations of its methods and objects and are used by the subclasses to inherit from them.

Updated on: 31-Jul-2023

317 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements