Hi! Let's talk about abstract classes in Java.

Why are classes called "abstract"?

You probably remember what abstraction is — we discussed it earlier :) If you forgot, no worries. Remember, it is an OOP principle that says, when designing classes and creating objects, you should represent only the entity's main properties and discard secondary ones.

For example, if we're designing a SchoolTeacher class, height is probably not going to be a necessary property of a teacher. Indeed, this characteristic is not important for a teacher. But if we're creating a BasketballPlayer class, then height will be one of the most important characteristics.

Well, an abstract class is the most abstract, "rough workpiece" for a group of future classes. The workpiece cannot be used directly — it's too "rough". But it defines certain characteristic state and behavior that future classes — the descendants of the abstract class — will have.

Examples of abstract classes in Java

Consider a simple example with cars:


public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;
  
   public abstract void gas();

   public abstract void brake();

   public String getModel() {
       return model;
   }

   public void setModel(String model) {
       this.model = model;
   }

   public String getColor() {
       return color;
   }

   public void setColor(String color) {
       this.color = color;
   }

   public int getMaxSpeed() {
       return maxSpeed;
   }

   public void setMaxSpeed(int maxSpeed) {
       this.maxSpeed = maxSpeed;
   }
}

This is what the simplest abstract class looks like. As you can see, nothing special :)

Why might we need this?

First, it provides the most abstract description of the entity we need — a car. The abstract keyword means something here. In the real world, there's no such thing as "just a car". There are trucks, race cars, sedans, coupes, and SUVs.

Our abstract class is simply a "blueprint" that we will later use to create specific car classes.


public class Sedan extends Car {
  
   @Override
   public void gas() {
       System.out.println("The sedan is accelerating!");
   }

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }
  
}

In many ways, this is similar to what we talked about in the lessons on inheritance. Only in that case we had a Car class whose methods were not abstract. But such a solution has several disadvantages that are fixed in abstract classes.

First and foremost, an instance of an abstract class cannot be created:


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // Error! The Car class is abstract!
   }
}

Java's creator's made this "feature" on purpose. Once again, remember: an abstract class is just a blueprint for future "regular" classes. You don't need copies of a blueprint, right? Similarly, there is no need to create instances of an abstract class :)

And if the Car class were not abstract, then we could easily create instances of it:


public class Car {

   private String model;
   private String color;
   private int maxSpeed;
  
   public void go() {
       // ...some logic
   }

   public  void brake() {
       // ...some logic
   }
}


public class Main {

   public static void main(String[] args) {

       Car car = new Car(); // This is okay. The car is created.
   }
}

At present, our program has some kind of incomprehensible car. It's not a truck, not a race car, and not a sedan, but it's not really clear what it is. It's the "just a car" that doesn't exist in reality.

The same example can be given with animals. Imagine if your program had Animal objects ("just animals"). It's not clear what kind it is, what family it belongs to, or what characteristics it has. It would be strange to see one in a program. There are no "just animals" in nature. Only dogs, cats, foxes, moles, etc.

Abstract classes save us from "just objects". They give us baseline state and behavior. For example, all cars must have a model, color and top speed, and they must also be able to apply the gas and brake. That's it. It's a general abstract blueprint that you will use later to design the classes you need.

Note: the two methods in the abstract class are also abstract, which means they have no implementation at all. The reason is the same: abstract classes don't create "default behaviors" for "just cars". They just indicate what every car must be able to do.

That said, if you do need default behavior, you can implement methods in an abstract class. Java does not forbid this:


public abstract class Car {

   private String model;
   private String color;
   private int maxSpeed;

   public void gas() {
       System.out.println("Accelerating!");
   }

   public abstract void brake();
  
   // Getters and setters
}


public class Sedan extends Car {

   @Override
   public void brake() {
       System.out.println("The sedan is slowing down!");
   }

}

public class Main {

   public static void main(String[] args) {

       Sedan sedan = new Sedan();
       sedan.gas();
   }
}

Console output:

Accelerating!

As you can see, we implemented one method in the abstract class, but not the other.

As a result, the behavior of our Sedan class is divided into two parts: if we call its gas() method, the behavior is "pulled up" from the abstract Car parent class, and we implement the brake() method in the Sedan class. That's super convenient and flexible.

But our class isn't so abstract now, is it? After all, it actually implemented half of the methods.

The fact is — and this is a very important feature — a class is abstract if even one of its methods is abstract. One method of two, or one of a thousand — it doesn't matter.

We could even implement all the methods, leaving none abstract. The result would be an abstract class without any abstract methods. This is possible in principle — the compiler won't generate any errors — but it's better not to do this, since it deprives the word abstract of its meaning. Your fellow programmers will also be very surprised to see this :/

That said, if a method is marked as abstract, each descendant class must implement it or be declared as abstract. Otherwise, the compiler will throw an error.

Of course, each class can only inherit one abstract class, so there is no difference between abstract and regular classes in terms of inheritance. It doesn't matter if we inherit an abstract class or a regular one — there can be only one parent class.

Why Java doesn't have multiple class inheritance

We have already said that there is no multiple inheritance in Java, but we did not really delve into why. Let's try to do that now.

The fact is that if Java did have multiple inheritance, then child classes would not be able to decide which behavior to choose.

Let's say we have two classes: Toaster and NuclearBomb:


public class Toaster {
  
  
 public void on() {

       System.out.println("The toaster is on. We're toasting!");
   }
  
   public void off() {

       System.out.println("The toaster is off!");
   }
}


public class NuclearBomb {

   public void on() {

       System.out.println("Boom!");
   }
}

As you can see, both classes have an on() method. For the toaster, the method starts making toast, but in the case of the nuclear bomb, it sets off an explosion.

Uh-oh :/

Now imagine that you've decided (don't ask me why!) to create something in between. Here's your class: MysteriousDevice!

This code won't work, of course. We present it simply as an example of "what might have been":


public class MysteriousDevice extends Toster, NuclearBomb {

   public static void main(String[] args) {
      
       MysteriousDevice mysteriousDevice = new MysteriousDevice();
       mysteriousDevice.on(); // And what should happen here? Will we get toast or a nuclear apocalypse?
   }
}

Let's see what we've got. The mysterious device derives from both Toaster and NuclearBomb at the same time. Both have an on() method. As a result, it is not clear which implementation should be executed if we call on() on a MysteriousDevice object. The object won't understand.

And to top it all off, NuclearBomb does not have an off() method, so if we don't guess correctly, then it will be impossible to turn off the device.

This "misunderstanding", when it is unclear which behavior should be executed, is precisely the reason why Java's creators rejected multiple inheritance. That said, you will learn that Java classes can implement many interfaces.