Table of Contents
- Introduction to Abstraction
- Types of Abstraction in Java
- Abstract Classes
- Interfaces
- Abstract Classes: Deep Dive
- Syntax and Usage
- Abstract vs Concrete Methods
- Constructors and Abstract Classes
- Interfaces: Deep Dive
- Syntax and Usage
- Default Methods and Static Methods in Interfaces
- Functional Interfaces and Lambda Expressions
- Differences Between Abstract Classes and Interfaces
- When to Use Abstract Classes vs Interfaces
- Abstraction in Action: Code Examples
- Summary
1. Introduction to Abstraction
Abstraction is one of the key principles of Object-Oriented Programming (OOP), along with encapsulation, inheritance, and polymorphism. Abstraction focuses on hiding the implementation details of a system and exposing only the essential features. In Java, abstraction is implemented through abstract classes and interfaces.
Abstraction allows you to define the “what” (the behavior) while leaving the “how” (the implementation) to be handled by subclasses or implementing classes. This reduces complexity and increases the reusability and maintainability of the code.
2. Types of Abstraction in Java
Java provides two ways to achieve abstraction:
- Abstract Classes
- Interfaces
Each method has its own set of advantages, and both are used for specific design purposes in Java applications.
3. Abstract Classes: Deep Dive
a. Syntax and Usage
An abstract class is a class that cannot be instantiated on its own. It may contain both abstract methods (methods without a body) and concrete methods (methods with a body). Abstract classes are used when you want to define a common structure for subclasses, while still allowing them to define their specific implementations of certain behaviors.
Syntax of an abstract class:
abstract class Animal {
abstract void sound(); // Abstract method (no implementation)
void eat() { // Concrete method
System.out.println("This animal is eating.");
}
}
b. Abstract vs Concrete Methods
- Abstract Methods: These methods do not have a body. Subclasses are required to provide a concrete implementation for these methods.
- Concrete Methods: These methods have an implementation. Subclasses can either use the inherited implementation or override it if needed.
Example of subclass implementing an abstract class:
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
In the example above, Dog
must implement the abstract method sound()
because it extends the abstract class Animal
.
c. Constructors and Abstract Classes
An abstract class can have constructors, but you cannot create an instance of an abstract class directly. Constructors in abstract classes are invoked when a subclass is instantiated.
Example:
abstract class Animal {
Animal() {
System.out.println("Animal created");
}
abstract void sound();
}
class Dog extends Animal {
Dog() {
super(); // Calls the constructor of Animal
System.out.println("Dog created");
}
@Override
void sound() {
System.out.println("Dog barks");
}
}
When you create an instance of Dog
, the constructor of Animal
is called first, followed by the constructor of Dog
.
4. Interfaces: Deep Dive
a. Syntax and Usage
An interface in Java is a completely abstract class that can have abstract methods (methods without a body) and default or static methods (methods with a body). Unlike an abstract class, an interface cannot have instance fields (variables) but can define constants. Classes implement interfaces using the implements
keyword.
Syntax of an interface:
interface Animal {
void sound(); // Abstract method
default void sleep() { // Default method
System.out.println("Animal is sleeping");
}
}
A class implements an interface by providing concrete implementations of the methods declared in the interface:
class Dog implements Animal {
@Override
public void sound() {
System.out.println("Dog barks");
}
}
b. Default Methods and Static Methods in Interfaces
- Default Methods: Introduced in Java 8, default methods allow you to provide a body for methods in interfaces. This enables backward compatibility when adding new methods to an interface without breaking the implementing classes.
Example of a default method:
interface Animal {
void sound();
default void eat() {
System.out.println("This animal is eating");
}
}
- Static Methods: Interfaces can also contain static methods, which can be called without needing an instance of the implementing class. Static methods in interfaces behave similarly to static methods in regular classes.
Example of a static method:
interface Animal {
static void describe() {
System.out.println("Animals are living organisms");
}
}
You can call static methods using the interface name:
Animal.describe();
c. Functional Interfaces and Lambda Expressions
A functional interface is an interface with exactly one abstract method. It may have multiple default or static methods. These interfaces can be used with lambda expressions to provide a clear and concise way to implement the interface.
Example of a functional interface:
@FunctionalInterface
interface Calculator {
int add(int a, int b);
}
public class Main {
public static void main(String[] args) {
Calculator calc = (a, b) -> a + b; // Lambda expression
System.out.println(calc.add(5, 10)); // Output: 15
}
}
Here, Calculator
is a functional interface, and its abstract method add
is implemented using a lambda expression.
5. Differences Between Abstract Classes and Interfaces
Both abstract classes and interfaces allow you to achieve abstraction, but there are significant differences between the two:
Feature | Abstract Class | Interface |
---|---|---|
Multiple Inheritance | A class can extend only one abstract class | A class can implement multiple interfaces |
Method Implementation | Can have both abstract and concrete methods | Can have abstract, default, and static methods |
Constructor | Can have constructors | Cannot have constructors |
Fields | Can have instance variables | Cannot have instance variables (only constants) |
Access Modifiers | Can have any access modifiers (private, protected) | All methods are public by default |
Use Case | Best for representing a “is-a” relationship | Best for representing “can-do” capabilities |
6. When to Use Abstract Classes vs Interfaces
- Use an abstract class when you want to provide a common base with shared functionality or state (fields) among related classes, and you want to control the inheritance hierarchy.
- Use an interface when you want to represent a common behavior that can be adopted by classes from different class hierarchies. Interfaces are ideal for defining “capabilities” that can be implemented by any class, regardless of its position in the class hierarchy.
7. Abstraction in Action: Code Examples
Example 1: Using Abstract Classes
abstract class Vehicle {
abstract void drive();
void fuel() {
System.out.println("Filling fuel");
}
}
class Car extends Vehicle {
@Override
void drive() {
System.out.println("Driving a car");
}
}
public class Main {
public static void main(String[] args) {
Vehicle car = new Car();
car.drive(); // Calls Car's drive method
car.fuel(); // Calls Vehicle's fuel method
}
}
Example 2: Using Interfaces
interface Shape {
void draw();
default void color() {
System.out.println("Coloring the shape");
}
}
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
public class Main {
public static void main(String[] args) {
Shape circle = new Circle();
circle.draw(); // Calls Circle's draw method
circle.color(); // Calls Shape's default color method
}
}
In both examples, abstraction is used to define a general blueprint (abstract class or interface) for specific behaviors (like drawing a circle or driving a car).
8. Summary
In this module, we’ve explored abstraction in Java, focusing on abstract classes and interfaces. Both of these concepts allow you to define common behaviors while hiding implementation details, making your code more modular, flexible, and maintainable.
We’ve also discussed key differences between abstract classes and interfaces, as well as when to use each of them. The practical examples provided demonstrate how abstraction allows for more general, reusable, and flexible code in real-world scenarios.