Hi! In past lessons, we met interfaces and figured out what they're for. Today's topic will echo the previous one. Let's talk about abstract classes in Java.
Why classes are called 'abstract'
You probably remember what 'abstraction' is — we've already gone over it. :) If you forgot, fear not. Remember: it's a principle of OOP that says when designing classes and creating objects, we should identify only the entity's main properties and discard the minor. For example, if we're designing aSchoolTeacher
class, we hardly need a 'height' property. Indeed, this property is irrelevant for a teacher. But if we're creating a BasketballPlayer
class, then growth would be an important characteristic.
So listen. An abstract class is as abstract as they come — an unfinished 'blank' for a group of future classes. The blank can't be used as is. It's too 'raw'. But it describes certain state and general behavior that will be possessed by future classes that inherit the abstract class.
Examples of abstract Java classes
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, it's nothing special :)
Why would we need it?
First of all, it describes our required entity, a car, in the most abstract way possible. There's a reason why we're using the word abstract. In the real world, there are no 'abstract cars'. There are trucks, race cars, sedans, coupes, and SUVs.
Our abstract class is simply a 'blueprint' we will use later to create 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!");
}
}
This is very similar to what we talked about in the lessons on inheritance. But in those lessons, we had a Car class and its methods weren't abstract. But that solution has a number of drawbacks that are fixed in abstract classes.
First and foremost, you can't create an instance of an abstract class:
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Error! The Car class is abstract!
}
}
Java's creators specifically designed this 'feature'. Once again, as a reminder: an abstract class is just a blueprint for future 'normal' classes. You don't need copies of the blueprint, right? And you don't create instances of an abstract class :)
But if the Car
class weren't abstract, we could easily create instances of it:
public class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
// Some logic
}
public void brake() {
// Some logic
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car(); // Everything is fine. A car is created.
}
}
Now our program has some sort of incomprehensible car — it's not a truck, not a race car, not a sedan, and it's totally unclear what it is. This is the very 'abstract car' that doesn't exist in nature.
We can provide the same example using animals. Imagine if Animal
classes (abstract animals). It's unclear what kind of animal it is, what family it belongs to, and what characteristics it has. It would be strange to see that in your program. There are no 'abstract animals' in nature. Only dogs, cats, foxes, moles, etc.
Abstract classes deliver us from abstract objects. They give us basic state and behavior. For example, all cars should have a model, color, and maximum speed, and you should be able to apply the gas and brake. That's it. This is a general abstract plan. Next you design the classes you need.
Note: two methods in the abstract class are also designated as abstract, and they don't have any implementation. The reason is the same: abstract classes don't create default behavior for abstract cars. They just indicate what every car should be able to do.
However, if you do need default behavior, you can implement methods in an abstract class. Java doesn't prohibit this:
public abstract class Car {
private String model;
private String color;
private int maxSpeed;
public void gas() {
System.out.println("Gas!");
}
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:
“Gas!"
As you can see, we've implemented the first method in the abstract class, and not the second.
As a result, our Sedan
class's behavior is divided into two parts: if you call the gas()
method, the call 'rises' up to the Car
abstract parent class, but we overrode the brake()
method in the Sedan
class. This turns out to be very convenient and flexible.
But now our class isn't so abstract? After all, half of its methods are implemented.
This is actually a very important feature - a class is abstract if at least one of its methods is abstract. One of two methods, or at least one of a thousand methods — it makes no difference.
We can even implement all the methods and leave none of them abstract. Then it would be an abstract class without abstract methods. In principle, this is possible, and the compiler won't generate errors, but it's better to avoid it: The word abstract loses its meaning, and your fellow programmers will be very surprised :/
At the same time, if a method is marked with the word abstract, each child class must implement it or declare it as abstract. Otherwise, the compiler will generate an error.
Of course, each class can inherit only one abstract class, so in terms of inheritance there's no difference between abstract and ordinary classes. It doesn't matter if we inherit an abstract class or an ordinary one, there can be only one parent class.
Why Java doesn't have multiple inheritance of classes
We've already said that Java doesn't have multiple inheritance, but we haven't really explored why. Let's try to do that now. The fact is that if Java had multiple inheritance, child classes wouldn't be able to decide which specific behavior they should choose. Suppose we have two classes —Toaster
and NuclearBomb
:
public class Toaster {
public void on() {
System.out.println("The toaster is on. Toast is being prepared!");
}
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 have an on()
method. For a toaster, it starts toasting. For a nuclear bomb, it sets off an explosion.
Oops: /
Now imagine that you decided (don't ask me why!) to create something in between. And thus you have a MysteriousDevice
class!
This code, of course, doesn't work, and we only provide it as an example 'but it could be':
public class MysteriousDevice extends Toaster, NuclearBomb {
public static void main(String[] args) {
MysteriousDevice mysteriousDevice = new MysteriousDevice();
mysteriousDevice.on(); // So what should happen here? Do we get toast or a nuclear apocalypse?
}
}
Let's take a look at what we have. The mysterious device simultaneously inherits Toaster and NuclearBomb. Both have on()
methods. As a result, if we call the on()
method, it's unclear which one should be invoked on the MysteriousDevice
object. There's no way the object could ever know.
And to top it all off: The NuclearBomb doesn't have an off()
method, so if we didn't guess right, it will be impossible to disable the device.
It is precisely because of this 'confusion', where the object doesn't know what behavior to exhibit, that Java's creators abandoned multiple inheritance. However, you will recall that Java classes can implement multiple interfaces.
By the way, in your studies, you've already encountered at least one abstract class!
Though maybe you didn't even notice :)
public abstract class Calendar implements Serializable, Cloneable, Comparable<Calendar>
It's your old friend, the Calendar
class. It's abstract and has several children. One of them is GregorianCalendar
. You already used it in the lessons about dates. :)
Everything seems clear enough. There's just one question: what is the fundamental difference between abstract classes and interfaces anyway? Why did they add both to Java rather than just limiting the language to one? After all, that would have been entirely adequate.
We'll talk about this in the next lesson! Until then :)
GO TO FULL VERSION