Inheritance and Polymorphism
Dive deeper into inheritance, its benefits, and different types (single, multiple, multilevel). Understand polymorphism (method overriding and overloading) and its applications.
Abstract Classes and Methods in Java
What are Abstract Classes and Methods?
In Java, an abstract class is a class that cannot be instantiated directly. It serves as a blueprint for other classes. Abstract classes may contain abstract methods, which are methods declared without an implementation. A subclass must provide the implementation for all abstract methods of its abstract superclass unless the subclass is also declared abstract.
Abstract Classes
- An abstract class is declared using the
abstract
keyword. - It can have both abstract and non-abstract (concrete) methods.
- You cannot create objects (instances) of an abstract class directly.
- It acts as a base class that provides a common interface for its subclasses.
abstract class Shape {
// Abstract method (no implementation)
abstract double getArea();
// Concrete method (has implementation)
void display() {
System.out.println("This is a shape.");
}
}
Abstract Methods
- An abstract method is declared using the
abstract
keyword and does not have a method body (no curly braces{}
). - It must be implemented (overridden) by any concrete (non-abstract) subclass of the abstract class.
- Abstract methods define a contract that subclasses must adhere to.
abstract class Animal {
abstract void makeSound(); // Abstract method - no body
}
Abstraction and Polymorphism
Abstract classes and methods play a crucial role in achieving abstraction and polymorphism in Java.
- Abstraction: Abstraction allows you to hide the complex implementation details of a class and expose only the essential features to the user. Abstract classes enforce a certain structure and behavior in derived classes, focusing on *what* the subclasses should do rather than *how* they should do it. The
Shape
example above abstracts the general concept of a shape. We know shapes have an area, but the specific calculation is left to the concrete implementations like Circle or Rectangle. - Polymorphism: Polymorphism (many forms) enables you to treat objects of different classes in a uniform way. Because subclasses of an abstract class are guaranteed to implement the abstract methods, you can call these methods on any object of these subclasses and get behavior specific to that subclass.
Practical Examples and Use Cases
Example 1: Shapes
abstract class Shape {
abstract double getArea();
abstract double getPerimeter();
void display() {
System.out.println("This is a shape.");
}
}
class Circle extends Shape {
double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
double getArea() {
return Math.PI * radius * radius;
}
@Override
double getPerimeter() {
return 2 * Math.PI * radius;
}
}
class Rectangle extends Shape {
double width;
double height;
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
@Override
double getArea() {
return width * height;
}
@Override
double getPerimeter() {
return 2 * (width + height);
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle(5);
Shape rectangle = new Rectangle(4, 6);
System.out.println("Circle Area: " + circle.getArea());
System.out.println("Rectangle Area: " + rectangle.getArea());
circle.display(); // Output: This is a shape.
rectangle.display(); // Output: This is a shape.
}
}
In this example, Shape
is an abstract class with abstract methods getArea()
and getPerimeter()
. Circle
and Rectangle
are concrete subclasses that implement these methods, providing specific calculations for their respective shapes. The `display()` method is a concrete method inherited by both subclasses.
Example 2: Database Connections
abstract class DatabaseConnection {
protected String connectionString;
public DatabaseConnection(String connectionString) {
this.connectionString = connectionString;
}
abstract void connect();
abstract void executeQuery(String query);
abstract void disconnect();
public void performTransaction(String query) {
connect();
executeQuery(query);
disconnect();
}
}
class MySQLConnection extends DatabaseConnection {
public MySQLConnection(String connectionString) {
super(connectionString);
}
@Override
void connect() {
System.out.println("Connecting to MySQL database using: " + connectionString);
// Actual MySQL connection logic here
}
@Override
void executeQuery(String query) {
System.out.println("Executing MySQL query: " + query);
// Actual MySQL query execution logic here
}
@Override
void disconnect() {
System.out.println("Disconnecting from MySQL database.");
// Actual MySQL disconnection logic here
}
}
class PostgreSQLConnection extends DatabaseConnection {
public PostgreSQLConnection(String connectionString) {
super(connectionString);
}
@Override
void connect() {
System.out.println("Connecting to PostgreSQL database using: " + connectionString);
// Actual PostgreSQL connection logic here
}
@Override
void executeQuery(String query) {
System.out.println("Executing PostgreSQL query: " + query);
// Actual PostgreSQL query execution logic here
}
@Override
void disconnect() {
System.out.println("Disconnecting from PostgreSQL database.");
// Actual PostgreSQL disconnection logic here
}
}
public class Main {
public static void main(String[] args) {
DatabaseConnection mysql = new MySQLConnection("jdbc:mysql://localhost:3306/mydb");
mysql.performTransaction("SELECT * FROM users");
DatabaseConnection postgres = new PostgreSQLConnection("jdbc:postgresql://localhost:5432/mydatabase");
postgres.performTransaction("SELECT * FROM products");
}
}
Here, DatabaseConnection
is an abstract class that defines the basic operations for connecting to and interacting with a database. MySQLConnection
and PostgreSQLConnection
provide concrete implementations for connecting, executing queries, and disconnecting from specific database systems. The `performTransaction` method is concrete and defines a workflow, relying on the abstract methods to be implemented by subclasses.
Use Cases:
- Framework Design: Abstract classes are often used in framework design to provide a common structure and enforce certain behaviors for components within the framework.
- Template Method Pattern: An abstract class can define a template method that outlines the steps of an algorithm, while allowing subclasses to implement specific steps.
- Defining Interfaces: Abstract classes can be used to define interfaces or contracts that subclasses must adhere to, ensuring consistency and predictability.
- Code Reusability and Extensibility: Abstract classes promote code reusability by providing a common base class for related classes, while also allowing for extensibility through subclassing.