Java is a class-based object-oriented language where objects are created from classes. OOP improves code organization, reusability, maintainability, extensibility, and testability.
Class and Object
A class is a blueprint that defines the structure and behavior of objects. It specifies fields, constructors, methods, and nested types. An object is a runtime instance of a class created using the new keyword.class Employee {
String name;
int age;
void work() {
System.out.println(name + " is working");
}
}
public class Main {
public static void main(String[] args) {
Employee employee = new Employee();
employee.name = "John";
employee.age = 30;
employee.work();
}
}
Each object has its own copy of instance variables but shares the class definition.
Object State, Behavior and Identity
Every object has three characteristics. 1. State is represented by instance variables. 2. Behavior is represented by methods. 3. Identity uniquely distinguishes one object from another, even if multiple objects contain identical data.Employee e1 = new Employee();
Employee e2 = new Employee();
System.out.println(e1 == e2); // false
Both objects have different identities even though they belong to the same class.
The Four Pillars of OOP
Java's object-oriented model is based on four fundamental principles. 1. Encapsulation 2 . Inheritance 3. Polymorphism 4. Abstraction These principles work together to create flexible and maintainable software designs.Encapsulation
Encapsulation is the process of hiding an object's internal state and exposing controlled access through methods. Fields are typically declared private and accessed using public methods.class Employee {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Encapsulation provides validation, protects internal state, reduces coupling, and allows implementation changes without affecting consumers. Encapsulation is not simply making fields private. It is about exposing only the operations required by clients while hiding implementation details.
Inheritance
Inheritance allows one class to inherit fields and methods from another class. The derived class uses the extends keyword.class Animal {
void eat() {
System.out.println("Eating");
}
}
class Dog extends Animal {
void bark() {
System.out.println("Barking");
}
}
The subclass automatically inherits accessible members from the superclass. Inheritance promotes code reuse but should model an is-a relationship. Examples:
- Dog is an Animal.
- Car is a Vehicle.
- SavingsAccount is an Account.
Inheritance should not be used simply to reuse code.
Composition over Inheritance
Modern Java design prefers composition over inheritance whenever possible. Composition represents a has-a relationship.class Engine {
}
class Car {
private Engine engine = new Engine();
}
A car has an engine. Composition provides better flexibility, lower coupling, and easier testing than deep inheritance hierarchies.
Polymorphism
Polymorphism allows one interface or superclass reference to represent multiple object types.class Animal {
void sound() {
System.out.println("Animal");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Bark");
}
}
Animal animal = new Dog();
animal.sound();
Output:
Bark
The method executed depends on the object's runtime type rather than the reference type. This is known as runtime polymorphism or dynamic method dispatch.
Compile-Time and Runtime Polymorphism
Compile-time polymorphism is achieved through method overloading.class Calculator {
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
}
The compiler determines which method to invoke.
Runtime polymorphism is achieved through method overriding.
class Animal {
void sound() {
}
}
class Dog extends Animal {
@Override
void sound() {
}
}
The JVM determines the method implementation during execution.
Method Overloading
Method overloading allows multiple methods with the same name but different parameter lists. Overloading requires at least one of the following:- Different number of parameters.
- Different parameter types.
- Different parameter order.
Return type alone cannot overload a method.
void print(int value) {
}
void print(String value) {
}
void print(int value, String text) {
}
Method Overriding
Method overriding allows a subclass to provide its own implementation of an inherited method.class Animal {
void sound() {
System.out.println("Animal");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog");
}
}
The overridden method must have the same name, parameter list, and compatible return type. The @Override annotation allows the compiler to verify that overriding is correct.
Abstraction
Abstraction hides implementation details while exposing only essential behavior. Java supports abstraction using abstract classes and interfaces.abstract class Shape {
abstract double area();
}
Concrete subclasses provide the implementation. Abstraction reduces complexity and enables interchangeable implementations. Detailed coverage of interfaces and abstract classes is discussed separately.
Association
Association represents a relationship between independent objects.class Teacher {
}
class Student {
private Teacher teacher;
}
Objects can exist independently of each other.
Aggregation
Aggregation is a weak ownership relationship. The contained object can exist independently.class Department {
private List<Employee> employees;
}
Employees continue to exist even if the department is removed.
Composition
Composition is a strong ownership relationship. The contained object cannot logically exist without its owner.class House {
private final List<Room> rooms = new ArrayList<>();
}
Rooms belong to a specific house. Composition provides better lifecycle management than aggregation.
The this Keyword
this refers to the current object. It is commonly used to distinguish instance variables from parameters and to invoke constructors.class Employee {
private String name;
Employee(String name) {
this.name = name;
}
}
The super Keyword
super refers to the immediate superclass. It is used to invoke superclass constructors and access overridden members.class Animal {
Animal() {
System.out.println("Animal");
}
}
class Dog extends Animal {
Dog() {
super();
}
}
If not explicitly called, the compiler inserts super() as the first statement of every constructor.
instanceof Operator
The instanceof operator checks whether an object belongs to a particular type.Animal animal = new Dog();
System.out.println(animal instanceof Dog);
System.out.println(animal instanceof Animal);
Output:
true
true
Java also supports Pattern Matching for instanceof, reducing explicit casting.
if (animal instanceof Dog dog) {
dog.sound();
}
Object Class
Every Java class directly or indirectly extends java.lang.Object. Common methods inherited from Object include:toString(), equals(), hashCode(), getClass(), clone(), wait(), notify(), notifyAll().
These methods form the foundation of Java's object model.
Object Equality
The == operator compares object references. The equals() method compares logical equality.String s1 = new String("Java");
String s2 = new String("Java");
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
Output:
false
true
Overriding equals() requires also overriding hashCode() to maintain the general contract.
Constructor Chaining
Constructors can invoke other constructors using this().class Employee {
private String name;
private int age;
Employee() {
this("Unknown", 0);
}
Employee(String name, int age) {
this.name = name;
this.age = age;
}
}
Constructor chaining reduces code duplication and centralizes initialization.
Final Notes
OOP is centered around modeling software using interacting objects rather than functions. Encapsulation protects state, Inheritance enables specialization, Polymorphism enables runtime flexibility, and Abstraction hides implementation details.Prefer composition over inheritance, program to interfaces rather than implementations, keep classes cohesive, minimize coupling, and design objects around behavior instead of exposing internal state.