Doing Nothing: Abstract Methods and Concrete Class Behavior
Let's imagine you're designing a system for managing different types of vehicles. You might have a base Vehicle
class, which defines common attributes like make
, model
, and year
. However, each specific vehicle type (car, truck, motorcycle) might have its own unique behaviors.
To handle this, you could use an abstract class for Vehicle
and define an abstract method called startEngine()
. This method wouldn't have any implementation in the abstract class, forcing concrete subclasses (like Car
, Truck
, Motorcycle
) to provide their own specific logic for starting the engine.
Here's a simplified example:
abstract class Vehicle {
String make;
String model;
int year;
Vehicle(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
abstract void startEngine();
}
class Car extends Vehicle {
Car(String make, String model, int year) {
super(make, model, year);
}
@Override
void startEngine() {
System.out.println("Car engine started!");
}
}
class Truck extends Vehicle {
Truck(String make, String model, int year) {
super(make, model, year);
}
@Override
void startEngine() {
System.out.println("Truck engine started!");
}
}
In this example, Vehicle
is an abstract class with an abstract startEngine()
method. Car
and Truck
are concrete classes that extend Vehicle
and provide their own implementations for startEngine()
.
Now, if we try to call startEngine()
on a Vehicle
instance, we get a compilation error. This is because Vehicle
is abstract and doesn't have a concrete implementation for startEngine()
.
Vehicle vehicle = new Vehicle("Toyota", "Corolla", 2023); // Error: Cannot instantiate abstract class Vehicle
vehicle.startEngine(); // Error: abstract method cannot be called from Vehicle
Key takeaway: You cannot instantiate an abstract class and directly call an abstract method. This prevents you from creating objects with undefined behaviors.
However, if we create instances of concrete subclasses (Car
or Truck
), we can successfully call startEngine()
:
Car myCar = new Car("Honda", "Civic", 2022);
myCar.startEngine(); // Output: Car engine started!
Truck myTruck = new Truck("Ford", "F-150", 2021);
myTruck.startEngine(); // Output: Truck engine started!
The key here is that abstract methods act as placeholders for functionality that must be defined in concrete subclasses. This design pattern promotes polymorphism, allowing you to treat different vehicle types uniformly through the Vehicle
interface while still having specific behavior implemented in each subclass.
This is a powerful technique for creating flexible and extensible code in object-oriented programming. By separating the common behavior from the specific implementations, you can easily add new vehicle types without modifying existing code, ensuring a robust and maintainable system.
Additional Resources: