Java Part 9: Object-oriented Programming In Java (OOP Pillars)
Please Subscribe Youtube| Like Facebook | Follow Twitter
Object-Oriented Programming (OOP Pillars) In Java
In this article, we will provide a detailed overview of Object-Oriented Programming In Java (OOP Pillars) and provide examples of how they are used in Java programming.
The Pillars of Object-Oriented Programming
OOP is built upon four key pillars: encapsulation, inheritance, polymorphism, and abstraction. In this article we explore them as in previous article we explored classes and objects.
Encapsulation
Encapsulation is one of the fundamental principles of object-oriented programming. It refers to the bundling of data and methods within a class, where the data is kept private and can only be accessed through public methods. In Java, encapsulation is achieved using access modifiers, which specify the visibility and accessibility of class members (variables and methods) from other parts of the program.
There are four levels of encapsulation in Java, determined by different access modifiers:
Public: Members declared as public are accessible from anywhere, both within the class itself and from other classes in the same or different packages. They have the highest level of accessibility.
Protected: Members declared as protected are accessible within the same class, subclasses (even if they are in different packages), and other classes within the same package. Protected members are not accessible outside the package if they are not inherited.
Default (Package-Private): Members declared without an explicit access modifier (i.e., no access modifier specified) are considered to have default accessibility. Default members are accessible within the same class and other classes within the same package, but not accessible outside the package.
Private: Members declared as private are only accessible within the same class. They have the most restricted level of accessibility and are not accessible from any other class, even subclasses.
Example
Below example code snippet that demonstrates encapsulation levels using different access modifiers:
public class EncapsulationExample {
public static void main(String[] args) {
Car car = new Car();
// Accessing and modifying encapsulated members
car.setMake("Toyota"); // Using the public setter method for 'make'
car.model = "Corolla"; // Direct access to 'model' since it's protected (within the same package)
car.color = "Blue"; // Direct access to 'color' since it's default (within the same package)
car.year = 2022; // Direct access to 'year' since it's public
// Accessing encapsulated members using getter methods
String carMake = car.getMake(); // Using the public getter method for 'make'
// Displaying car details
System.out.println("Car Details:");
car.displayDetails();
}
}
class Car {
private String make; // Private member
protected String model; // Protected member
String color; // Default (Package-Private) member
public int year; // Public member
// Public getter and setter methods for the private member 'make'
public String getMake() {
return make;
}
public void setMake(String make) {
this.make = make;
}
// Method to display the details of the car
public void displayDetails() {
System.out.println("Make: " + make);
System.out.println("Model: " + model);
System.out.println("Color: " + color);
System.out.println("Year: " + year);
}
}
Output
Car Details: Make: Toyota Model: Corolla Color: Blue Year: 2022
In this example, we have a Car class that represents a car object. The class has four member variables: make, model, color, and year. Each member variable is declared with a different access modifier to demonstrate different encapsulation levels.
make is declared as private, so it can only be accessed within the Car class. Getter and setter methods (getMake() and setMake()) are used to interact with the private member variable.
model is declared as protected, allowing it to be accessed within the same package as well as by subclasses (even if they are in different packages).
color is declared without an explicit access modifier, making it default or package-private. It can be accessed within the same package.
year is declared as public, making it accessible from anywhere.
The EncapsulationExample class demonstrates how encapsulated members of the Car class are accessed and modified using appropriate access methods or by directly accessing them based on their access levels.
Overall, this example showcases different encapsulation levels in Java and how access modifiers control the visibility and accessibility of class members to achieve encapsulation.
Inheritance
Inheritance is a fundamental concept in object-oriented programming that allows classes to inherit properties and behaviors from other classes. It promotes code reuse, modularity, and extensibility. In Java, inheritance is achieved using the “extends” keyword. Java supports single inheritance, meaning a class can inherit from only one superclass.
Super keyword
The super keyword in Java is used to refer to the parent class (superclass) from within a subclass. It can be used to access the superclass’s members (variables or methods), invoke the superclass’s constructor, or differentiate between superclass(parent) and subclass(child) members with the same name.
Here’s an example code demonstrating inheritance
// Main class
public class Main {
public static void main(String[] args) {
Car car = new Car("Toyota", 4);
car.start(); // Overridden method with super invocation
car.displayBrand(); // Accessing superclass and subclass variables
}
}
// Parent class
class Vehicle {
protected String brand;
public Vehicle(String brand) {
this.brand = brand;
}
public void start() {
System.out.println("Starting the vehicle.");
}
public String getBrand() {
return brand;
}
}
// Child class inheriting from Vehicle
class Car extends Vehicle {
private int numDoors;
public Car(String brand, int numDoors) {
super(brand); // Invoking the superclass constructor
this.numDoors = numDoors;
}
public void start() {
super.start(); // Invoking the superclass method
System.out.println("Warming up the engine.");
}
public void displayBrand() {
System.out.println("Brand: " + super.getBrand()); // Accessing superclass variable using getter method
System.out.println("Brand: " + this.brand); // Accessing subclass variable
}
}
Output
Starting the vehicle. Warming up the engine. Brand: Toyota Brand: Toyota
The example code showcases inheritance in Java. It includes a parent class Vehicle and a child class Car that inherits from Vehicle.
The Vehicle class has a protected brand variable, a constructor that accepts a brand parameter, a start() method that prints a message, and a getBrand() method that returns the brand value.
The Car class extends Vehicle and introduces a private numDoors variable. It has a constructor that takes both brand and numDoors parameters, invokes the superclass constructor using super(brand), and assigns the numDoors value. It also overrides the start() method to add a specific behavior and includes a displayBrand() method to demonstrate accessing the brand variable using super.getBrand() (inherited from the superclass) and this.brand (in the subclass).
The Main class contains the main() method, where an instance of Car is created, and its methods are invoked to observe the behavior of inheritance, method overriding, and accessing superclass and subclass variables.
This example illustrates the usage of the extends keyword to establish an inheritance relationship between classes, allowing child classes to inherit and extend the properties and behaviors of the parent class.
Polymorphism
Polymorphism is a fundamental concept in object-oriented programming that allows objects of different classes to be treated as objects of a common superclass. It provides flexibility and extensibility by enabling objects to be used interchangeably and to exhibit different behaviors based on their actual class at runtime.
There are two main types of polymorphism in Java
Method Overloading (Compile-time Polymorphism)
Method overloading is a feature in Java that allows a class to have multiple methods with the same name but different parameters. It enables developers to define several methods with different input parameters but the same method name, allowing for flexibility and code reusability.
Method Overriding (Runtime Polymorphism)
Method overriding occurs when a subclass provides its own implementation of a method that is already defined in its superclass. The subclass method must have the same name, return type, and parameters as the method in the superclass. This allows the subclass to provide specialized behavior while maintaining the same method signature.
The method signature in Java is a combination of the method name and the parameter list. It specifies the unique identifier of a method and helps distinguish it from other methods in the same class. The method signature does not include the return type, access modifiers, or exceptions. It is determined by the method name and the types, order, and number of parameters accepted by the method.
Example:
public class PolymorphismExample {
public static void main(String[] args) {
Shape shape1 = new Circle();
Shape shape2 = new Square();
shape1.draw(); // Output: Drawing a circle. (Method Overriding)
shape1.draw("Red"); // Output: Drawing a shape with color: Red (Method Overloading)
shape2.draw(); // Output: Drawing a square. (Method Overriding)
shape2.draw("Blue"); // Output: Drawing a shape with color: Blue (Method Overloading)
Circle circle = new Circle();
circle.draw(5); // Output: Drawing a circle with radius: 5 (Method Overloading)
Square square = new Square();
square.draw(4.5); // Output: Drawing a square with side: 4.5 (Method Overloading)
}
}
class Shape {
// Method Overriding
public void draw() {
System.out.println("Drawing a shape.");
}
// Method Overloading
public void draw(String color) {
System.out.println("Drawing a shape with color: " + color);
}
}
class Circle extends Shape {
// Method Overriding
@Override
public void draw() {
System.out.println("Drawing a circle.");
}
// Method Overloading
public void draw(int radius) {
System.out.println("Drawing a circle with radius: " + radius);
}
}
class Square extends Shape {
// Method Overriding
@Override
public void draw() {
System.out.println("Drawing a square.");
}
// Method Overloading
public void draw(double side) {
System.out.println("Drawing a square with side: " + side);
}
}
Output
Drawing a circle. Drawing a shape with color: Red Drawing a square. Drawing a shape with color: Blue Drawing a circle with radius: 5 Drawing a square with side: 4.5
In this example, we have a Shape class as the superclass, and Circle and Square classes as subclasses. The Shape class has two overloaded draw() methods—one without any parameters and another with a String parameter for specifying the color. The Circle class overrides the draw() method from the superclass and introduces an additional overloaded draw() method that takes an int parameter for specifying the radius. Similarly, the Square class overrides the draw() method from the superclass and introduces an additional overloaded draw() method that takes a double parameter for specifying the side length.
The main() method demonstrates polymorphism by creating instances of Circle and Square and storing them in variables of type Shape. The methods are called on these variables, invoking the appropriate overridden or overloaded methods based on the actual object type.
Abstraction
Abstraction is a fundamental concept in object-oriented programming that focuses on hiding unnecessary implementation details and exposing only the essential features and behaviors of an object. It allows us to create abstract classes and interfaces that define a common structure and behavior for a group of related classes, without providing the actual implementation.
Abstract Class
An abstract class is a class that cannot be instantiated and can only be used as a superclass for other classes. It may contain both abstract and non-abstract methods. Abstract methods are declared without an implementation and must be overridden by the concrete subclasses.
Interface
An interface is a collection of abstract methods that define a contract for implementing classes. It is similar to an abstract class but can only contain abstract methods and constants. Classes can implement one or more interfaces to inherit their abstract methods.
By using abstraction through abstract classes and interfaces, we can define common behaviors and structures, enforce method implementation in subclasses, and achieve loose coupling and code modularity.
Example
Now, let’s proceed with an example code that demonstrates abstraction using an abstract class and an interface.
public class AbstractionExample {
public static void main(String[] args) {
Vehicle car = new Car("Toyota");
car.displayBrand(); // Output: Brand: Toyota
car.start(); // Output: Car is starting.
Engine engine = new Car("Honda");
engine.run(); // Output: Car engine is running.
}
}
// Abstract class representing a vehicle
abstract class Vehicle {
private String brand;
public Vehicle(String brand) {
this.brand = brand;
}
public abstract void start(); // Abstract method
public void displayBrand() {
System.out.println("Brand: " + brand);
}
}
// Interface representing a vehicle engine
interface Engine {
void run();
}
// Concrete class representing a car
class Car extends Vehicle implements Engine {
public Car(String brand) {
super(brand);
}
@Override
public void start() {
System.out.println("Car is starting.");
}
@Override
public void run() {
System.out.println("Car engine is running.");
}
}
Output
Brand: Toyota Car is starting. Car engine is running.
In this example, we have an abstract class called Vehicle representing a general concept of a vehicle. It has an abstract method start() that must be implemented by its subclasses. The Vehicle class also has a non-abstract method displayBrand() that displays the brand of the vehicle.
Next, we have an interface called Engine that defines the contract for implementing classes to have an run() method.
The Car class is a concrete class that extends the Vehicle abstract class and implements the Engine interface. It provides its own implementation for the start() method from the Vehicle class and the run() method from the Engine interface.
In the main() method of the AbstractionExample class, we create an instance of Car and store it in a variable of type Vehicle. We invoke the displayBrand() method to display the brand of the vehicle, and the start() method to start the car. Additionally, we create another instance of Car and store it in a variable of type Engine. We invoke the run() method to simulate the engine running.
This example demonstrates abstraction by utilizing an abstract class and an interface to define common behaviors and structures, enforce method implementations, and achieve loose coupling and code modularity.
Differences between an abstract class and an interface in Java
Term | Abstract Class | Interface |
---|---|---|
Definition | A class that cannot be instantiated and may contain both concrete and abstract methods, as well as member variables | A reference type that defines a contract for classes to implement, containing only abstract methods and constants |
Inheritance | Can be extended by a subclass using the ‘extends’ keyword | Can be implemented by a class using the ‘implements’ keyword |
Members | Can have both abstract and non-abstract methods | Can only have abstract methods and constants |
Method Bodies | Can have method bodies (concrete implementation) | Cannot have method bodies (only method signatures) |
Variables | Can have instance variables and constants | Can only have constants (public static final variables) |
Constructor | Can have a constructor | Cannot have a constructor |
Multiple | Cannot extend multiple classes | Can implement multiple interfaces |
Conclusion
Encapsulation, inheritance, polymorphism, and abstraction are four fundamental concepts in Java object-oriented programming. Together, they provide a powerful and flexible foundation for creating modular, maintainable, and extensible code.
Java Beginner Tutorial Series
- Java Part 1: Setup And Introduction
- Java Part 2: Understanding Basic Data Types And Variables In Java
- Java Part 3: Operators And Expressions In Java
- Java Part 4: Control Flow Statements In Java
- Java Part 5: Methods In Java
- Java Part 6: Arrays In Java
- Java Part 7: String Manipulation In Java
- Java Part 8: Object-Oriented Programming In Java (Classes And Objects)
- Java Part 9: Object-oriented Programming In Java (OOP Pillars)
- Java Part 10: Exception Handling In Java