OOP Concepts in Java
1. What is OOP?
Object-Oriented Programming (OOP) is a programming paradigm that organises software design around data (objects) rather than functions and logic. Java is fundamentally an object-oriented language — almost everything in Java is an object.
OOP is built on four core pillars:
| Pillar | One-Line Definition | Key Java Construct |
|---|---|---|
| Encapsulation | Bundling data and methods; hiding internal details |
private fields +
getters/setters
|
| Inheritance | A class acquires properties of another class | extends |
| Polymorphism | One interface, many forms |
Overloading & @Override
|
| Abstraction | Hiding complexity, exposing only essentials |
abstract class / interface
|
Why OOP? OOP promotes code reusability, modularity, maintainability, and makes it easier to model real-world entities in software.
2. Classes and Objects
A class is a blueprint or template that defines
the state (fields) and behaviour (methods) that objects of its
type will have. An object is a concrete instance
of a class, created using the
new keyword.
// Class = blueprint
public class Car {
// Fields (state)
String brand;
String model;
int year;
double speed;
// Method (behaviour)
public void accelerate(double amount) {
speed += amount;
System.out.println(brand + " is now going at " + speed + " km/h");
}
public void brake(double amount) {
speed = Math.max(0, speed - amount);
System.out.println(brand + " slowed down to " + speed + " km/h");
}
public void displayInfo() {
System.out.println(year + " " + brand + " " + model);
}
}
// Main class to use the Car object
public class Main {
public static void main(String[] args) {
// Creating objects (instances) of Car
Car car1 = new Car(); // object 1
car1.brand = "Toyota";
car1.model = "Corolla";
car1.year = 2022;
Car car2 = new Car(); // object 2
car2.brand = "Honda";
car2.model = "Civic";
car2.year = 2023;
car1.displayInfo(); // 2022 Toyota Corolla
car1.accelerate(60); // Toyota is now going at 60.0 km/h
car2.displayInfo(); // 2023 Honda Civic
}
}
Toyota is now going at 60.0 km/h
2023 Honda Civic
Memory: Each object gets its own copy of instance fields but shares class-level (static) fields and methods.
3. Constructors
A constructor is a special method that is
automatically called when an object is created with
new. It has the same name as the class
and no return type.
Default Constructor
If you do not define any constructor, Java provides a no-argument default constructor automatically. Once you define any constructor, the default is no longer provided.
Parameterized Constructor
public class Car {
String brand;
String model;
int year;
// Default constructor
public Car() {
brand = "Unknown";
model = "Unknown";
year = 0;
}
// Parameterized constructor
public Car(String brand, String model, int year) {
this.brand = brand; // 'this' distinguishes field from parameter
this.model = model;
this.year = year;
}
// Copy constructor
public Car(Car other) {
this.brand = other.brand;
this.model = other.model;
this.year = other.year;
}
// Constructor chaining with this()
public Car(String brand) {
this(brand, "Generic", 2024); // calls parameterized constructor
}
public void display() {
System.out.println(year + " " + brand + " " + model);
}
}
public class Main {
public static void main(String[] args) {
Car c1 = new Car(); // default
Car c2 = new Car("Ford", "Mustang", 2023); // parameterized
Car c3 = new Car(c2); // copy
Car c4 = new Car("BMW"); // chained
c1.display(); // 0 Unknown Unknown
c2.display(); // 2023 Ford Mustang
c3.display(); // 2023 Ford Mustang
c4.display(); // 2024 BMW Generic
}
}
2023 Ford Mustang
2023 Ford Mustang
2024 BMW Generic
Note: this() must
be the first statement in a constructor. You cannot
call both this() and
super() in the same constructor.
4. The this Keyword
this is a reference to the
current object — the object whose method or
constructor is being called. It has three main uses:
public class Person {
String name;
int age;
// Use 1: Disambiguate fields from parameters
public Person(String name, int age) {
this.name = name; // 'this.name' = field, 'name' = parameter
this.age = age;
}
// Use 2: Call another constructor (constructor chaining)
public Person(String name) {
this(name, 0); // delegates to Person(String, int)
}
// Use 3: Pass current object as argument
public void register(Registry r) {
r.add(this); // passes the current Person object
}
public String getInfo() {
return this.name + ", age " + this.age;
}
}
class Registry {
public void add(Person p) {
System.out.println("Registered: " + p.getInfo());
}
}
public class Main {
public static void main(String[] args) {
Person p = new Person("Alice", 30);
System.out.println(p.getInfo()); // Alice, age 30
Registry reg = new Registry();
p.register(reg); // Registered: Alice, age 30
}
}
Registered: Alice, age 30
5. Encapsulation
Encapsulation means
wrapping data (fields) and code (methods) together
in a single unit and restricting direct access to
the fields from outside the class. This is achieved by declaring
fields private and providing
public
getter and setter methods.
public class BankAccount {
private String owner;
private double balance; // cannot be accessed directly from outside
public BankAccount(String owner, double initialBalance) {
this.owner = owner;
this.balance = (initialBalance >= 0) ? initialBalance : 0;
}
// Getter
public double getBalance() {
return balance;
}
// Setter with validation — this is the power of encapsulation
public void deposit(double amount) {
if (amount <= 0) {
throw new IllegalArgumentException("Deposit amount must be positive.");
}
balance += amount;
}
public void withdraw(double amount) {
if (amount <= 0 || amount > balance) {
throw new IllegalArgumentException("Invalid withdrawal amount.");
}
balance -= amount;
}
public String getOwner() { return owner; }
@Override
public String toString() {
return owner + "'s account: $" + String.format("%.2f", balance);
}
}
public class Main {
public static void main(String[] args) {
BankAccount acc = new BankAccount("Alice", 1000.0);
// acc.balance = -500; // COMPILE ERROR — field is private
acc.deposit(250.0);
acc.withdraw(100.0);
System.out.println(acc); // Alice's account: $1150.00
System.out.println(acc.getBalance()); // 1150.0
}
}
1150.0
Benefits of Encapsulation: Data validation, flexibility to change internal implementation without affecting callers, improved security, and easier unit testing.
6. Inheritance
Inheritance allows a class (child/subclass) to
acquire properties and methods of another class
(parent/superclass) using the
extends keyword. Java supports
single, multilevel, and
hierarchical inheritance (but NOT multiple class
inheritance).
// Parent (superclass)
public class Vehicle {
String brand;
int speed;
public Vehicle(String brand, int speed) {
this.brand = brand;
this.speed = speed;
}
public void move() {
System.out.println(brand + " is moving at " + speed + " km/h");
}
}
// Single Inheritance: Car extends Vehicle
public class Car extends Vehicle {
int doors;
public Car(String brand, int speed, int doors) {
super(brand, speed); // call parent constructor
this.doors = doors;
}
public void honk() {
System.out.println(brand + " goes Beep!");
}
}
// Multilevel Inheritance: ElectricCar extends Car
public class ElectricCar extends Car {
int batteryCapacity;
public ElectricCar(String brand, int speed, int doors, int battery) {
super(brand, speed, doors);
this.batteryCapacity = battery;
}
public void chargeBattery() {
System.out.println(brand + " is charging (" + batteryCapacity + " kWh battery)");
}
}
// Hierarchical Inheritance: Truck also extends Vehicle
public class Truck extends Vehicle {
double loadCapacity;
public Truck(String brand, int speed, double loadCapacity) {
super(brand, speed);
this.loadCapacity = loadCapacity;
}
public void loadCargo() {
System.out.println(brand + " loaded " + loadCapacity + " tons");
}
}
public class Main {
public static void main(String[] args) {
Car car = new Car("Toyota", 120, 4);
car.move(); // inherited from Vehicle
car.honk();
ElectricCar ev = new ElectricCar("Tesla", 200, 4, 100);
ev.move(); // inherited from Vehicle via Car
ev.honk(); // inherited from Car
ev.chargeBattery();
Truck truck = new Truck("Volvo", 80, 20.5);
truck.move();
truck.loadCargo();
}
}
Toyota goes Beep!
Tesla is moving at 200 km/h
Tesla goes Beep!
Tesla is charging (100 kWh battery)
Volvo is moving at 80 km/h
Volvo loaded 20.5 tons
Java does NOT support multiple class inheritance (a class cannot extend two classes at once) to avoid the diamond problem. Use interfaces for multiple inheritance of type.
7. Polymorphism
Polymorphism means "many forms". In Java it comes in two flavours:
Compile-time Polymorphism — Method Overloading
Same method name, different parameter lists. Resolved at compile time.
public class Calculator {
// Overloaded add methods
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
public String add(String a, String b) {
return a + b; // String concatenation
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(2, 3)); // 5
System.out.println(calc.add(2.5, 3.5)); // 6.0
System.out.println(calc.add(1, 2, 3)); // 6
System.out.println(calc.add("Hello, ", "World!")); // Hello, World!
}
}
6.0
6
Hello, World!
Runtime Polymorphism — Method Overriding
A subclass provides its own implementation of a method defined in the parent. Resolved at runtime via dynamic dispatch.
public class Animal {
public void sound() {
System.out.println("Some animal makes a sound");
}
}
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("Dog says: Woof!");
}
}
public class Cat extends Animal {
@Override
public void sound() {
System.out.println("Cat says: Meow!");
}
}
public class Cow extends Animal {
@Override
public void sound() {
System.out.println("Cow says: Moo!");
}
}
public class Main {
public static void main(String[] args) {
// Upcasting: parent reference holds child object
Animal a1 = new Dog();
Animal a2 = new Cat();
Animal a3 = new Cow();
// Runtime decides which sound() to call
a1.sound(); // Dog says: Woof!
a2.sound(); // Cat says: Meow!
a3.sound(); // Cow says: Moo!
// Polymorphic array
Animal[] animals = { new Dog(), new Cat(), new Cow() };
for (Animal a : animals) {
a.sound(); // each calls its own overridden version
}
}
}
Cat says: Meow!
Cow says: Moo!
Dog says: Woof!
Cat says: Meow!
Cow says: Moo!
Always use @Override annotation when
overriding. It tells the compiler to verify the method actually
overrides a parent method, catching typos early.
8. Abstraction
Abstraction means hiding complex implementation details and exposing only the essential features. In Java, abstraction is achieved via abstract classes and interfaces. An abstract class can have both abstract (no body) and concrete (with body) methods.
// Abstract class — cannot be instantiated directly
public abstract class Shape {
String color;
public Shape(String color) {
this.color = color;
}
// Abstract method — subclasses MUST implement this
public abstract double area();
public abstract double perimeter();
// Concrete method — shared behaviour
public void display() {
System.out.println(color + " shape | Area: " + String.format("%.2f", area())
+ " | Perimeter: " + String.format("%.2f", perimeter()));
}
}
public class Circle extends Shape {
double radius;
public Circle(String color, double radius) {
super(color);
this.radius = radius;
}
@Override
public double area() { return Math.PI * radius * radius; }
@Override
public double perimeter() { return 2 * Math.PI * radius; }
}
public class Rectangle extends Shape {
double width, height;
public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}
@Override
public double area() { return width * height; }
@Override
public double perimeter() { return 2 * (width + height); }
}
public class Main {
public static void main(String[] args) {
// Shape s = new Shape("red"); // COMPILE ERROR — abstract class
Shape c = new Circle("Red", 5.0);
Shape r = new Rectangle("Blue", 4.0, 6.0);
c.display(); // Red shape | Area: 78.54 | Perimeter: 31.42
r.display(); // Blue shape | Area: 24.00 | Perimeter: 20.00
}
}
Blue shape | Area: 24.00 | Perimeter: 20.00
9. The super Keyword
super refers to the
immediate parent class of the current object. It
is used to access parent class fields, methods, and constructors.
public class Animal {
String name;
public Animal(String name) {
this.name = name;
System.out.println("Animal constructor called for: " + name);
}
public void eat() {
System.out.println(name + " eats food.");
}
public String describe() {
return "I am an animal named " + name;
}
}
public class Dog extends Animal {
String breed;
public Dog(String name, String breed) {
super(name); // Call parent constructor — must be first line
this.breed = breed;
System.out.println("Dog constructor called. Breed: " + breed);
}
@Override
public void eat() {
super.eat(); // Call parent's eat() first
System.out.println(name + " also loves bones!");
}
@Override
public String describe() {
return super.describe() + ", breed: " + breed; // reuse parent output
}
}
public class Main {
public static void main(String[] args) {
Dog d = new Dog("Rex", "Labrador");
// Output during construction:
// Animal constructor called for: Rex
// Dog constructor called. Breed: Labrador
d.eat();
// Rex eats food.
// Rex also loves bones!
System.out.println(d.describe());
// I am an animal named Rex, breed: Labrador
}
}
Dog constructor called. Breed: Labrador
Rex eats food.
Rex also loves bones!
I am an animal named Rex, breed: Labrador
10. The Object Class
Every class in Java implicitly extends
java.lang.Object. This means every
object automatically inherits a set of methods from
Object. The three most important ones
to override are:
| Method | Default Behaviour | Why Override? |
|---|---|---|
toString() |
ClassName@hashcode (e.g., Car@1b6d3586)
|
Return a human-readable description |
equals(Object o) |
Reference equality (==) |
Define logical equality by field values |
hashCode() |
Memory address-based integer |
Must be consistent with equals() for use in
HashMap/HashSet
|
import java.util.Objects;
public class Student {
int id;
String name;
public Student(int id, String name) {
this.id = id;
this.name = name;
}
// Override toString
@Override
public String toString() {
return "Student{id=" + id + ", name='" + name + "'}";
}
// Override equals
@Override
public boolean equals(Object o) {
if (this == o) return true; // same reference
if (!(o instanceof Student)) return false;
Student s = (Student) o;
return id == s.id && Objects.equals(name, s.name);
}
// Override hashCode — always override when you override equals
@Override
public int hashCode() {
return Objects.hash(id, name);
}
}
public class Main {
public static void main(String[] args) {
Student s1 = new Student(1, "Alice");
Student s2 = new Student(1, "Alice");
Student s3 = new Student(2, "Bob");
System.out.println(s1); // Student{id=1, name='Alice'}
System.out.println(s1.equals(s2)); // true (same id and name)
System.out.println(s1.equals(s3)); // false
System.out.println(s1 == s2); // false (different references)
System.out.println(s1.hashCode() == s2.hashCode()); // true
}
}
true
false
false
true
Contract: If
a.equals(b) is true,
then
a.hashCode() == b.hashCode() must
also be true. Violating this breaks
HashMap and HashSet.
11. Packages
A package is a namespace that organises related classes and interfaces. Packages prevent name conflicts, control access, and make large projects manageable.
// File: com/myapp/model/Employee.java
package com.myapp.model; // package declaration — first line of file
public class Employee {
private int id;
private String name;
private double salary;
public Employee(int id, String name, double salary) {
this.id = id;
this.name = name;
this.salary = salary;
}
public int getId() { return id; }
public String getName() { return name; }
public double getSalary() { return salary; }
@Override
public String toString() {
return "Employee[" + id + ", " + name + ", $" + salary + "]";
}
}
// File: com/myapp/service/PayrollService.java
package com.myapp.service;
import com.myapp.model.Employee; // import specific class
// import com.myapp.model.*; // import all classes from package
public class PayrollService {
public double calculateBonus(Employee emp) {
return emp.getSalary() * 0.10;
}
}
// File: com/myapp/Main.java
package com.myapp;
import com.myapp.model.Employee;
import com.myapp.service.PayrollService;
public class Main {
public static void main(String[] args) {
Employee e = new Employee(101, "Alice", 75000);
PayrollService ps = new PayrollService();
System.out.println(e);
System.out.println("Bonus: $" + ps.calculateBonus(e));
}
}
Bonus: $7500.0
Naming Convention: Package names are all
lowercase and typically follow the reverse domain name
convention:
com.companyname.projectname.module.
Java's built-in packages start with
java. or
javax..
Abstract Class vs Interface
This distinction is one of the most commonly tested Java concepts.
| Feature | Abstract Class | Interface |
|---|---|---|
| Instantiation | Cannot be instantiated | Cannot be instantiated |
| Methods | Abstract + concrete methods | Abstract + default + static (Java 8+) |
| Fields | Any type (instance variables) |
Only
public static final constants
|
| Constructors | Yes — can have constructors | No constructors |
| Inheritance | Single inheritance only (extends) |
Multiple interfaces (implements) |
| Access modifiers | Any (public, protected, private) | Methods are public by default |
| Use when | Classes share common code/state | Defining a contract/capability |
| Keyword | extends |
implements |
