Before learning about the significance of abstract classes and how they help in achieving abstraction, let’s first see what an abstract class is and how to create it.
An abstract class is a class whose object can’t be made (it is serving just as a theoretical definition). However, the objects of its subclasses can be made (if the subclasses are not abstract). If we try to create an object of an abstract class, we will get an error.
A class is declared as an abstract
class by using the abstract keyword.
For example, an abstract class named Animal can be defined as shown below.
abstract class Animal {
// attributes and methods
}
An abstract class can contain abstract methods as well as regular methods. Let’s see what an abstract method is.
Java Abstract Methods
An abstract method is a method that doesn’t have a body. A method is declared as an abstract method by using the abstract
keyword.
For example, an abstract method named printSound
can be defined as follows.
abstract void printSound();
Abstract methods are implemented in subclasses. It means the parent class will only have a signature of the method and subclasses will use that signature and define the body according to their needs.
So, an abstract class may or may not contain an abstract method. However, if a class contains an abstract method, then it must be declared as an abstract class.
Now let’s see an example of an abstract class.
// abstract class
abstract class Animal {
// abstract method
public abstract void printSound();
// regular method
public void displayInfo() {
System.out.println("I am an Animal");
}
}
// subclass
class Dog extends Animal {
//implementing abstract method
public void printSound() {
System.out.println("Dogs Bark");
}
}
class Test {
public static void main(String[] args) {
Dog d = new Dog();
d.printSound();
d.displayInfo();
}
}
Here, we defined an abstract class named Animal
having two methods - an abstract method printSound()
and a regular method displayInfo()
. Notice that the body of the abstract method printSound()
is not defined.
We created a subclass Dog
of the class Animal
. Since Dog
is a subclass of an abstract class, it must override the abstract method present in the abstract class. Therefore, the abstract method printSound()
is implemented in the class Dog by providing it a body.
After defining the two classes, we created an object d of the class Dog which called the methods printSound()
and displayInfo()
.
So this was a simple example of an abstract class. Now let’s create more than one subclass of the abstract class in the above example.
// abstract class
abstract class Animal {
// abstract method
public abstract void printSound();
}
// subclass 1
class Dog extends Animal {
//implementing abstract method
public void printSound() {
System.out.println("Dogs Bark");
}
}
// subclass 2
class Cat extends Animal {
//implementing abstract method
public void printSound() {
System.out.println("Cats Meow");
}
}
// subclass 3
class Monkey extends Animal {
//implementing abstract method
public void printSound() {
System.out.println("Monkeys whoop");
}
}
class Test {
public static void main(String[] args) {
Dog d = new Dog();
Cat c = new Cat();
Monkey m = new Monkey();
d.printSound();
c.printSound();
m.printSound();
}
}
In this example, Animal
is an abstract class having an abstract method printSound()
. We created its three subclasses Dog
, Cat
and Monkey
and implemented the abstract method printSound()
in all the subclasses.
Thus, the abstract method(s) in an abstract class are implemented in all its subclasses. So you now understood how to create abstract classes and abstract methods.
Abstract Class Helps Achieve Partial Abstraction
Abstract classes can have abstract methods as well as regular methods. The flexibility to have non-abstract (regular) methods is the reason abstract classes can’t provide full abstraction.
Look at the following abstract class.
abstract class Animal {
// abstract method
public abstract void printSound();
// regular method
public void displayInfo() {
System.out.println("I am an Animal");
}
}
Animal
is an abstract class which contains a method displayInfo()
which is defined with a body. As a result, we can see its implementation (implementation is not hidden). Therefore, the Animal
class provides abstraction by hiding the implementation of the printSound()
method but doesn’t provide full abstraction by showing the implementation of the displayInfo()
method.
Full abstraction can be achieved using interfaces which will be covered in the next chapter.
Real Life Example of AbstractionWe know that abstraction helps us reduce complexity as well as scale our program structure. Let’s see how both these are achieved by taking a real-life example.
Suppose you want to create 3 classes of bikes, 2 classes of bicycles and 4 classes of cars, some of the classes having the same methods like printPrice(). Let’s make this structure more organised using abstract classes and inheritance.
We know that there are mainly two types of vehicles - two wheeler and four wheeler. Further classifying, there are two types of two wheeler vehicles - bike and bicycle.
Now, there can be different brands of bikes like Yamaha, Bajaj, etc.
So, let’s use abstract classes for organising this.
We can create an abstract class named Vehicle with an abstract method printPrice() (because each vehicle has some price). Then we can create two more abstract classes named TwoWheeler and FourWheeler, both extending the Vehicle abstract class (because a two wheeler is a vehicle and a four wheeler is also a vehicle). After that, we can create two more abstract classes named Bike and Bicycle, both extending the TwoWheeler abstract class (because both bike and bicycle are two wheelers). An abstract method printMileage() can be defined inside the Bike abstract class (because a bike gives mileage).
Finally, we can create classes of bikes like Yamaha or Bajaj inheriting the Bike abstract class and classes of bicycles inheriting the Bicycle abstract class. This was just to give you an idea of how a real-life example would look like. You can add or remove abstract classes and normal classes based on your use case. This example is demonstrated below.
abstract class Vehicle {
abstract void printPrice();
}
abstract class TwoWheeler extends Vehicle {
abstract void printType();
}
abstract class FourWheeler extends Vehicle {}
abstract class Bike extends TwoWheeler {
abstract void printMileage();
}
abstract class Bicycle extends TwoWheeler {
abstract void hasBasket(boolean basket);
}
// class Yamaha inheriting abstract class Bike
class Yamaha extends Bike {
public void printPrice() {
System.out.println("Price: 145300");
}
public void printType() {
System.out.println("This is a Bike");
}
public void printMileage() {
System.out.println("Mileage: 49");
}
public void printBrand() {
System.out.println("Brand: Yamaha");
}
}
// class Bajaj inheriting abstract class Bike
class Bajaj extends Bike {
public void printPrice() {
System.out.println("Price: 85536");
}
public void printType() {
System.out.println("This is a Bike");
}
public void printMileage() {
System.out.println("Mileage: 50");
}
public void printBrand() {
System.out.println("Brand: Bajaj");
}
}
class Test {
public static void main(String[] args) {
Yamaha y = new Yamaha();
Bajaj b = new Bajaj();
y.printType();
y.printBrand();
y.printMileage();
y.printPrice();
System.out.println("########");
b.printType();
b.printBrand();
b.printMileage();
b.printPrice();
}
}
As you can see, we used abstraction here. We reduced the number of methods (functionality) in each class by creating various abstract classes. This can be useful in many ways.
Suppose apart from cars, in future you need to add trucks as well. We know that trucks are also four wheelers. In that case, you can simply create a class named Truck inheriting the FourWheeler abstract class. The added Truck class will define the abstract methods available in the FourWheeler and Vehicle abstract classes. This shows that abstraction (using abstract classes) made our structure scalable because we can easily add a new class in the structure without making any change in the existing flow or classes or breaking anything.
You can also see that all the classes inheriting TwoWheeler need to implement the abstract methods defined in TwoWheeler. Thus, we are forcing each two wheeler class like Bike or Bicycle to implement the methods that a two wheeler class must have. This reduces complexity and improves the uniformity of the structure of our classes.
In this chapter, we learned about abstraction and that it can be achieved using abstract classes and interfaces (we will learn about interfaces in the next chapter). We also learned about abstract classes and how they can help in achieving abstraction in Java. In case you have any doubt in the concepts covered in this topic, feel free to ask us in the discussion section.