- A class is a blueprint or template for creating objects.
- It defines the structure, behavior, and attributes that objects of the class will have.
- Classes do not occupy memory until an object is created.
// Defining a class named 'Car'
public class Car {
// Attributes of the Car class
String make;
String model;
int year;
// Constructor to initialize Car objects
public Car(String make, String model, int year) {
this.make = make;
this.model = model;
this.year = year;
}
// Method to display car details
public void displayDetails() {
System.out.println("Make: " + make);
System.out.println("Model: " + model);
System.out.println("Year: " + year);
}
}- An object is an instance of a class.
- Objects are created based on the structure defined in the class and occupy memory.
- Objects contain actual values for the attributes defined in the class blueprint.
public class Main {
public static void main(String[] args) {
// Creating an object of the Car class
Car myCar = new Car("Toyota", "Corolla", 2020);
// Accessing the object's methods and attributes
myCar.displayDetails(); // This will print the car's details
}
}| Aspect | Class | Object |
|---|---|---|
| Definition | Blueprint or template for creating objects. | Instance of a class. |
| Memory Usage | Does not occupy memory. | Occupies memory when created. |
| Behavior | Defines behavior through methods. | Exhibits behavior defined by the class. |
| State | Does not have a state. | Has a state (attribute values). |
| Example | Car (defined as a class). |
myCar (an instance of the Car class). |
Polymorphism in Object-Oriented Programming (OOP) refers to the ability of a single interface to represent different types or forms. It allows objects to be treated as instances of their parent class, enabling flexibility and reuse.
There are two types of polymorphism in Java:
- Compile-Time Polymorphism (Static Binding)
- Runtime Polymorphism (Dynamic Binding)
Compile-time polymorphism occurs when a method is resolved during compile time. This is achieved using method overloading.
class Calculator {
int add(int a, int b) {
return a + b;
}
int add(int a, int b, int c) {
return a + b + c;
}
}
public class Main {
public static void main(String[] args) {
Calculator calc = new Calculator();
System.out.println(calc.add(5, 10)); // Output: 15
System.out.println(calc.add(5, 10, 20)); // Output: 35
}
}
Runtime polymorphism occurs when a method is resolved during runtime. This is achieved using method overriding.
class Animal {
void sound() {
System.out.println("Animal makes a sound");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
public class Main {
public static void main(String[] args) {
Animal myAnimal = new Dog(); // Upcasting
myAnimal.sound(); // Output: Dog barks
}
}
| Feature | Compile-Time Polymorphism | Runtime Polymorphism |
|---|---|---|
| Binding Time | Compile time | Runtime |
| Achieved By | Method Overloading | Method Overriding |
| Flexibility | Limited | High |
Encapsulation is one of the fundamental principles of Object-Oriented Programming (OOP). It refers to bundling data (variables) and methods (functions) that operate on the data into a single unit, typically a class. Encapsulation helps protect the internal state of an object and ensures controlled access through methods.
- Data Hiding: Restricts direct access to some of an object’s components.
- Controlled Access: Provides public methods (getters and setters) for controlled interaction with the internal data.
- Modularity: Improves code maintainability by separating concerns.
- Security: Prevents unintended interference and misuse of the object's state.
In Java, encapsulation is achieved by:
- Declaring Class Variables as Private: Ensures that the internal state is hidden from external access.
- Providing Public Getters and Setters: Allows controlled access and modification of private variables.
class Employee {
// Private variables (data hiding)
private String name;
private double salary;
// Constructor to initialize values
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
}
// Public getter for name
public String getName() {
return name;
}
// Public setter for name
public void setName(String name) {
this.name = name;
}
// Public getter for salary
public double getSalary() {
return salary;
}
// Public setter for salary
public void setSalary(double salary) {
if (salary > 0) { // Adding validation
this.salary = salary;
} else {
System.out.println("Invalid salary amount.");
}
}
}
public class Main {
public static void main(String[] args) {
// Creating an instance of Employee
Employee emp = new Employee("John Doe", 50000);
// Accessing and modifying private variables using getters and setters
System.out.println("Name: " + emp.getName());
System.out.println("Salary: " + emp.getSalary());
// Modifying the salary
emp.setSalary(60000);
System.out.println("Updated Salary: " + emp.getSalary());
// Trying to set an invalid salary
emp.setSalary(-10000); // Output: Invalid salary amount.
}
}
Encapsulation is a powerful concept in OOP that:
- Protects the internal state of an object.
- Provides a controlled interface for interaction.
- Ensures modularity, security, and maintainability in the code.
Access modifiers in Java define the visibility or accessibility of a class, method, or variable.
- public: Accessible from anywhere.
- private: Accessible only within the defining class.
- protected: Accessible within the same package and by subclasses in other packages.
- default (package-private): Accessible only within the same package.
class Example {
public int publicVar = 10; // Accessible everywhere
private int privateVar = 20; // Accessible only within this class
protected int protectedVar = 30; // Accessible within package and subclasses
int defaultVar = 40; // Accessible within the same package
}
Abstraction hides the implementation details and only shows the essential features of an object.
| Feature | Abstract Class | Interface |
|---|---|---|
| Methods | Can have abstract and concrete methods | All methods are abstract by default (before Java 8). |
| Variables | Can have instance variables | Only constants (static final). |
| Inheritance | A class can extend only one abstract class | A class can implement multiple interfaces. |
abstract class Animal {
abstract void sound();
void sleep() {
System.out.println("Sleeping...");
}
}
class Dog extends Animal {
@Override
void sound() {
System.out.println("Dog barks");
}
}
interface Shape {
void draw();
}
interface Color {
void fill();
}
class Circle implements Shape, Color {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
@Override
public void fill() {
System.out.println("Filling with color");
}
}
Use an abstract class when:
- You need to share code among multiple related classes.
- You want to define non-static or non-final fields.
| Aspect | Inheritance | Composition |
|---|---|---|
| Relationship | "is-a" relationship | "has-a" relationship |
| Flexibility | Rigid | Flexible |
- Promotes reusability.
- Reduces coupling.
Multiple inheritance allows a class to inherit from multiple classes.
class A:
def method_a(self):
print("Method from A")
class B:
def method_b(self):
print("Method from B")
class C(A, B):
pass
obj = C()
obj.method_a()
obj.method_b()
To avoid ambiguity caused by the Diamond Problem.
The this keyword refers to the current object.
class Example {
int value;
Example(int value) {
this.value = value; // Refers to the instance variable
}
void display() {
System.out.println("Value: " + this.value);
}
}
Method overriding occurs when a subclass provides a specific implementation of a method defined in its parent class.
- Method must have the same name and parameters.
- Method in the subclass must have the same or narrower access level.
- The return type must be the same or covariant.
class Parent {
void display() {
System.out.println("Display from Parent");
}
}
class Child extends Parent {
@Override
void display() {
System.out.println("Display from Child");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child(); // Upcasting
obj.display(); // Output: Display from Child
}
}
- Static Methods: Methods belonging to the class rather than any specific object. They can be accessed without creating an instance.
- Static Variables: Variables shared among all instances of a class.
- For utility or helper methods (e.g.,
Math.sqrt()). - To share common data across all objects.
- Static methods cannot access instance variables/methods directly.
- Static methods cannot be overridden.
class Example {
static int counter = 0; // Static variable
static void incrementCounter() { // Static method
counter++;
}
}
A constructor is a special method used to initialize objects.
- Default Constructor: A no-argument constructor provided by Java or defined by the programmer.
- Copy Constructor: Creates a new object by copying an existing object.
class Example {
int value;
// Default constructor
Example() {
value = 0;
}
// Copy constructor
Example(Example obj) {
this.value = obj.value;
}
}
- Shallow Copy: Copies the references, not the actual objects.
- Deep Copy: Creates a new object for each field.
import java.util.ArrayList;
class Example implements Cloneable {
ArrayList list;
Example() {
list = new ArrayList<>();
}
// Shallow Copy
Example shallowCopy() {
return this;
}
// Deep Copy
Example deepCopy() {
Example copy = new Example();
copy.list.addAll(this.list);
return copy;
}
}
- A
finalclass cannot be extended. - Used to prevent inheritance.
- Cannot be overridden in subclasses.
final class Example {
final void display() {
System.out.println("Final Method");
}
}
==compares memory addresses..equals()compares the content of objects.
String str1 = "Hello";
String str2 = new String("Hello");
System.out.println(str1 == str2); // false
System.out.println(str1.equals(str2)); // true
Reclaims memory occupied by unreferenced objects.
- JVM automatically calls garbage collection.
- Can be explicitly requested using
System.gc().
Dynamic Method Dispatch allows method resolution at runtime (runtime polymorphism).
class Parent {
void display() {
System.out.println("Parent Display");
}
}
class Child extends Parent {
@Override
void display() {
System.out.println("Child Display");
}
}
public class Main {
public static void main(String[] args) {
Parent obj = new Child();
obj.display(); // Output: Child Display
}
}
- Access parent class methods/constructors.
- Reference parent class variables.
class Parent {
Parent() {
System.out.println("Parent Constructor");
}
}
class Child extends Parent {
Child() {
super(); // Calls parent constructor
System.out.println("Child Constructor");
}
}
A design pattern to achieve loose coupling by injecting dependencies from the outside.
interface Service {
void execute();
}
class MyService implements Service {
public void execute() {
System.out.println("Executing Service");
}
}
class Client {
private Service service;
Client(Service service) {
this.service = service; // Dependency Injection
}
void doWork() {
service.execute();
}
}
| Aspect | Aggregation | Composition |
|---|---|---|
| Relationship | "Has-a" relationship | Strong "Part-of" relationship |
| Lifespan | Independent lifespan | Dependent lifespan |
class Address {
String city;
}
class Person {
Address address;
}
class Engine {
void start() {
System.out.println("Engine Starting...");
}
}
class Car {
private final Engine engine = new Engine();
void startCar() {
engine.start();
}
}
class Vehicle {
void start() {
System.out.println("Vehicle started");
}
void stop() {
System.out.println("Vehicle stopped");
}
}
class Car extends Vehicle {
void airConditioning() {
System.out.println("Car AC turned on");
}
}
class Bike extends Vehicle {
void kickStart() {
System.out.println("Bike kick-started");
}
}
class Truck extends Vehicle {
void loadCargo() {
System.out.println("Truck loaded with cargo");
}
}
- Account: Contains balance, deposit(), withdraw().
- Customer: Has Account.
- Bank: Manages multiple Customers.
- Transaction: Logs transaction details.
class Account {
private double balance;
void deposit(double amount) {
balance += amount;
}
void withdraw(double amount) {
if (balance >= amount) balance -= amount;
}
}
class Customer {
private Account account;
Customer(Account account) {
this.account = account;
}
}
class Bank {
private List customers;
}
- Product: Represents items.
- User: Customer of the platform.
- Cart: Aggregates Products.
- Order: Composed of Cart.
class Product {
String name;
double price;
}
class User {
String username;
}
class Cart {
private List products;
}
class Order {
private Cart cart;
}
class Singleton {
private static Singleton instance;
private Singleton() {}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System.out.println("Drawing Circle");
}
}
class Square implements Shape {
public void draw() {
System.out.println("Drawing Square");
}
}
class ShapeFactory {
static Shape createShape(String type) {
if (type.equals("Circle")) return new Circle();
else if (type.equals("Square")) return new Square();
return null;
}
}
interface Subscriber {
void update(String news);
}
class NewsPublisher {
private List subscribers = new ArrayList<>();
void addSubscriber(Subscriber sub) {
subscribers.add(sub);
}
void notifySubscribers(String news) {
for (Subscriber sub : subscribers) sub.update(news);
}
}
- Animal: Base class with makeSound().
- Mammal, Bird: Inherit Animal.
- ZooKeeper: Feeds animals.
class Animal {
void makeSound() {
System.out.println("Animal sound");
}
}
class Mammal extends Animal {}
class ZooKeeper {
void feedAnimal(Animal animal) {
System.out.println("Feeding animal");
}
}
class Car {
private String color;
private boolean hasSunroof;
static class Builder {
private String color;
private boolean hasSunroof;
Builder setColor(String color) {
this.color = color;
return this;
}
Builder setSunroof(boolean sunroof) {
this.hasSunroof = sunroof;
return this;
}
Car build() {
return new Car(this);
}
}
private Car(Builder builder) {
this.color = builder.color;
this.hasSunroof = builder.hasSunroof;
}
}
- ParkingSpot: Represents spots.
- Vehicle: Car, Bike, Truck.
- ParkingLot: Manages ParkingSpots.
class ParkingSpot {
boolean isOccupied;
}
class Vehicle {
String plateNumber;
}
class ParkingLot {
private List spots;
}
- Notification: Abstract class with send().
- EmailNotification, SMSNotification: Extend Notification.
- NotificationService: Sends notifications.
abstract class Notification {
abstract void send(String message);
}
class EmailNotification extends Notification {
void send(String message) {
System.out.println("Email sent: " + message);
}
}
class SMSNotification extends Notification {
void send(String message) {
System.out.println("SMS sent: " + message);
}
}
class NotificationService {
void notifyUser(Notification notification, String message) {
notification.send(message);
}
}