OOPS Concepts
A class is a blueprint or template for creating objects. It defines the properties (fields) and behaviors (methods) that the objects created from the class can have. In Java, a class is defined using the class keyword.
public class Car { // Fields (properties) String color; String model;
// Methods (behaviors) void drive() { System.out.println("The car is driving."); }
void stop() { System.out.println("The car has stopped."); }}Objects
Section titled “Objects”An object is an instance of a class. It is created from a class and has its own set of values for the properties defined in the class. In Java, an object is created using the new keyword.
public class Main { public static void main(String[] args) { // Creating an object of the Car class Car myCar = new Car(); myCar.color = "Red"; myCar.model = "Tesla Model S";
// Calling methods on the object myCar.drive(); // Output: The car is driving. myCar.stop(); // Output: The car has stopped.
System.out.println("Car Model: " + myCar.model); // Output: Car Model: Tesla Model S System.out.println("Car Color: " + myCar.color); // Output: Car Color: Red }}Fundamental OOPS Concepts
Section titled “Fundamental OOPS Concepts”- Encapsulation
- Inheritance
- Polymorphism
- Abstraction
Encapsulation in Java
Section titled “Encapsulation in Java”Encapsulation is one of the four fundamental OOP concepts in Java. It’s the practice of bundling data (variables) and the methods that operate on that data within a single unit (class), while restricting direct access to some of the object’s components.
Key aspects of encapsulation:
Section titled “Key aspects of encapsulation:”- Information hiding: Encapsulation hides the internal state of an object by making variables private, preventing unauthorized direct access from outside the class.
- Access control: Using access modifiers (private, protected, public, default) to control the visibility and accessibility of class members.
- Data protection: Encapsulation prevents the object’s data from being modified unexpectedly by providing controlled access through methods.
- API definition: By determining what’s exposed and what’s hidden, encapsulation defines the public interface (API) of a class.
public class BankAccount { // Private variables - hidden internal state private String accountNumber; private double balance; private String ownerName;
// Constructor public BankAccount(String accountNumber, String ownerName) { this.accountNumber = accountNumber; this.ownerName = ownerName; this.balance = 0.0; }
// Public getters - controlled access to data public String getAccountNumber() { return accountNumber; }
public String getOwnerName() { return ownerName; }
public double getBalance() { return balance; }
// Public methods - controlled operations on data public void deposit(double amount) { if (amount > 0) { balance += amount; } }
public boolean withdraw(double amount) { if (amount > 0 && balance >= amount) { balance -= amount; return true; } return false; }}Benefits of encapsulation:
Section titled “Benefits of encapsulation:”- Controlled data access: Changes to the object’s state happen through well-defined methods
- Data validation: Input can be validated before changing object state
- Flexibility: Internal implementation can change without affecting external code
- Maintainability: Easier to debug and maintain when data access is controlled
- Security: Sensitive data can be protected from unauthorized access
Encapsulation is a key pillar of good object-oriented design, allowing you to create robust and maintainable code by controlling how data is accessed and modified.
Encapsulation vs Data Hiding
Section titled “Encapsulation vs Data Hiding”Encapsulation is the bundling of data and methods that operate on that data within a single unit (like a class), whereas data hiding, a core component of encapsulation, restricts direct access to the internal data of that unit, protecting it from external modification.
Inheritance
Section titled “Inheritance”Inheritance enables a class to acquire properties and behaviors from another class, promoting code reuse and establishing an “is-a” relationship.
Key aspects:
- Achieved using the
extendskeyword - Subclasses inherit accessible members of superclass
- Method overriding allows customized behavior
public class Vehicle { protected String brand;
public void start() { System.out.println("Vehicle starting"); }}
public class Car extends Vehicle { private int numDoors;
@Override public void start() { System.out.println("Car engine starting"); }}Polymorphism
Section titled “Polymorphism”Polymorphism allows objects to be treated as instances of their parent class rather than their actual class, enabling one interface to represent different underlying forms.
Key aspects:
- Method overriding (runtime polymorphism)
- Method overloading (compile-time polymorphism)
- Reference variable of parent type can refer to child object
// Method overriding exampleVehicle vehicle = new Car(); // Car reference through Vehicle typevehicle.start(); // Calls Car's implementation
// Method overloading examplepublic class Calculator { 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; }}Abstraction
Section titled “Abstraction”Abstraction focuses on essential qualities rather than specific details, hiding complex implementation details and showing only functionality to users.
Key aspects:
- Implemented using abstract classes and interfaces
- Defines what a class must do, not how it does it
- Simplifies complex systems by breaking them into manageable parts
// Abstract class examplepublic abstract class Shape { abstract double calculateArea();
public void display() { System.out.println("Area: " + calculateArea()); }}
// Interface examplepublic interface Drawable { void draw(); default void setColor(String color) { System.out.println("Setting color to " + color); }}Abstract Classes vs Interfaces
Section titled “Abstract Classes vs Interfaces”Abstract Classes
Section titled “Abstract Classes”An abstract class is a class that cannot be instantiated and is designed to be subclassed. It can contain both abstract methods (methods without a body) and concrete methods (methods with implementation).
Key Characteristics:
Section titled “Key Characteristics:”- Partial Implementation: Can contain both abstract and concrete methods
- Constructor: Can have constructors (though they can only be called from subclasses)
- Access Modifiers: Methods can have any access modifier (public, protected, private, default)
- Fields: Can have instance variables (fields) with any access modifier
- Inheritance: A class can extend only one abstract class (single inheritance for classes)
- Purpose: Used when related classes need to share code but also have unique implementations
public abstract class Database { protected String connectionString; private boolean isConnected;
// Constructor public Database(String connectionString) { this.connectionString = connectionString; this.isConnected = false; }
// Abstract method - must be implemented by subclasses public abstract void executeQuery(String query);
// Concrete method - shared implementation public void connect() { System.out.println("Connecting to " + connectionString); isConnected = true; }
public void disconnect() { if (isConnected) { System.out.println("Disconnecting from database"); isConnected = false; } }
// Protected method - accessible to subclasses protected boolean isConnected() { return isConnected; }}
// Concrete subclasspublic class MySQLDatabase extends Database { public MySQLDatabase(String connectionString) { super(connectionString); // Call to parent constructor }
@Override public void executeQuery(String query) { if (isConnected()) { System.out.println("Executing MySQL query: " + query); } else { System.out.println("Not connected to database"); } }}Interfaces
Section titled “Interfaces”An interface is a completely abstract type that defines a contract for classes to implement. It specifies what a class must do, but not how it does it.
Key Characteristics:
Section titled “Key Characteristics:”- All Abstract Methods: Traditionally, all methods are implicitly abstract and public (prior to Java 8)
- No Constructors: Cannot have constructors
- Constants Only: Can only have constants (public static final fields)
- Multiple Inheritance: A class can implement multiple interfaces
- Default Methods: Since Java 8, can have default and static methods with implementations
- Private Methods: Since Java 9, can have private methods for code reuse within the interface
- Purpose: Used to define a contract that unrelated classes can implement
public interface Payable { // Constants double MINIMUM_WAGE = 15.0; // implicitly public static final
// Abstract methods double calculatePay(); // implicitly public abstract void processPay();
// Default method (Java 8+) default void printPayStub() { System.out.println("Payment amount: $" + calculatePay()); }
// Static method (Java 8+) static boolean isValidPayAmount(double amount) { return amount >= MINIMUM_WAGE; }
// Private method (Java 9+) private void internalHelperMethod() { // Code reuse within interface }}
// Implementationpublic class Employee implements Payable { private double hourlyRate; private int hoursWorked;
public Employee(double hourlyRate, int hoursWorked) { this.hourlyRate = hourlyRate; this.hoursWorked = hoursWorked; }
@Override public double calculatePay() { return hourlyRate * hoursWorked; }
@Override public void processPay() { System.out.println("Processing payment for employee"); }
// We can use the default implementation of printPayStub() // or override it if needed}When to Use Each
Section titled “When to Use Each”-
Use Abstract Classes When:
- You want to share code among closely related classes
- You need to declare non-public members
- You want to provide a partial implementation
- Your classes need to share common state or behavior
-
Use Interfaces When:
- You want to define a contract for unrelated classes
- You need multiple inheritance
- You want to specify behavior without implementation details
- You’re designing for API stability
Functional Interfaces (Java 8+)
Section titled “Functional Interfaces (Java 8+)”A functional interface is an interface with exactly one abstract method. These interfaces can be used with lambda expressions and method references.
// Functional interface (marked with @FunctionalInterface annotation)@FunctionalInterfacepublic interface Transformer<T, R> { R transform(T input);
// Can still have default and static methods default void printInfo() { System.out.println("This is a transformer"); }}
// Usage with lambda expressionpublic class FunctionalInterfaceExample { public static void main(String[] args) { // Implementation using lambda expression Transformer<String, Integer> lengthFinder = s -> s.length();
// Using the implementation int length = lengthFinder.transform("Hello, World!"); System.out.println("Length: " + length); // Output: Length: 13
// Method reference equivalent Transformer<String, Integer> lengthFinder2 = String::length; }}Predefined functional interfaces
Section titled “Predefined functional interfaces”Java provides several predefined functional interfaces in the java.util.function package:
Predicate<T>: Tests if an input is true or falseFunction<T, R>: Applies a function to an input and returns a resultConsumer<T>: Performs an action on an inputSupplier<T>: Returns a result without an inputBiFunction<T, U, R>: Applies a function to two inputs and returns a resultUnaryOperator<T>: Applies a function to a single input of the same typeBinaryOperator<T>: Applies a function to two inputs of the same type
Usage Example:
Section titled “Usage Example:”// Using PredicatePredicate<String> isEmpty = s -> s.isEmpty();
// Using FunctionFunction<String, Integer> length = s -> s.length();
// Using ConsumerConsumer<String> print = s -> System.out.println(s);
// Using SupplierSupplier<String> randomString = () -> "Random";
// Using BiFunctionBiFunction<String, String, String> concat = (s1, s2) -> s1 + s2;
// Using UnaryOperatorUnaryOperator<String> reverse = s -> new StringBuilder(s).reverse().toString();
// Using BinaryOperatorBinaryOperator<String> append = (s1, s2) -> s1 + s2;Types of Inheritance in Java
Section titled “Types of Inheritance in Java”Java supports various types of inheritance patterns, each with specific use cases and characteristics.
1. Single Inheritance
Section titled “1. Single Inheritance”A class inherits from only one superclass. This is the most common form of inheritance in Java.
public class Animal { public void eat() { System.out.println("Animal is eating"); }}
public class Dog extends Animal { // Single inheritance public void bark() { System.out.println("Dog is barking"); }}2. Multilevel Inheritance
Section titled “2. Multilevel Inheritance”A class inherits from a class which itself inherits from another class, forming a chain of inheritance.
public class Animal { public void eat() { System.out.println("Animal is eating"); }}
public class Mammal extends Animal { public void breathe() { System.out.println("Mammal is breathing"); }}
public class Dog extends Mammal { // Multilevel inheritance public void bark() { System.out.println("Dog is barking"); }}
// UsageDog dog = new Dog();dog.eat(); // From Animaldog.breathe(); // From Mammaldog.bark(); // From Dog3. Hierarchical Inheritance
Section titled “3. Hierarchical Inheritance”Multiple classes inherit from a single superclass.
public class Animal { public void eat() { System.out.println("Animal is eating"); }}
public class Dog extends Animal { public void bark() { System.out.println("Dog is barking"); }}
public class Cat extends Animal { // Another subclass of Animal public void meow() { System.out.println("Cat is meowing"); }}4. Multiple Inheritance (Through Interfaces)
Section titled “4. Multiple Inheritance (Through Interfaces)”Java doesn’t support multiple inheritance of classes (to avoid the “diamond problem”), but it allows multiple inheritance through interfaces.
public interface Swimmer { void swim();}
public interface Flyer { void fly();}
// Duck implements multiple interfacespublic class Duck implements Swimmer, Flyer { @Override public void swim() { System.out.println("Duck is swimming"); }
@Override public void fly() { System.out.println("Duck is flying"); }}5. Hybrid Inheritance
Section titled “5. Hybrid Inheritance”A combination of two or more types of inheritance. In Java, this is typically achieved using a mix of class inheritance and interface implementation.
public class Animal { public void eat() { System.out.println("Animal is eating"); }}
public interface Swimmer { void swim();}
public interface Flyer { void fly();}
public class Bird extends Animal implements Flyer { @Override public void fly() { System.out.println("Bird is flying"); }}
public class Penguin extends Bird implements Swimmer { @Override public void fly() { System.out.println("Penguin cannot fly"); }
@Override public void swim() { System.out.println("Penguin is swimming"); }}The Diamond Problem
Section titled “The Diamond Problem”Java avoids the “diamond problem” (ambiguity that arises when a class inherits from two classes with a common ancestor) by not supporting multiple inheritance of classes.
However, with interfaces, Java 8+ provides a solution using the super keyword to explicitly call the desired implementation:
public interface A { default void show() { System.out.println("A's show"); }}
public interface B extends A { default void show() { System.out.println("B's show"); }}
public interface C extends A { default void show() { System.out.println("C's show"); }}
// This would cause a compilation error without explicit overridepublic class D implements B, C { // Must override to resolve the conflict @Override public void show() { B.super.show(); // Explicitly call B's implementation // or C.super.show(); to call C's implementation }}Inner Classes in Java
Section titled “Inner Classes in Java”Inner classes are classes defined within other classes. They allow for logical grouping of classes and increased encapsulation.
1. Member Inner Classes
Section titled “1. Member Inner Classes”A non-static class defined at the member level of another class. It has access to all members (including private) of the outer class.
public class OuterClass { private int outerField = 10;
// Member inner class public class InnerClass { public void display() { // Can access private members of outer class System.out.println("Outer field value: " + outerField); } }
public void createInner() { InnerClass inner = new InnerClass(); inner.display(); }}
// Creating an instance of inner class from outsideOuterClass outer = new OuterClass();OuterClass.InnerClass inner = outer.new InnerClass();inner.display();2. Static Nested Classes
Section titled “2. Static Nested Classes”A static class defined at the member level of another class. It cannot access non-static members of the outer class directly.
public class OuterClass { private static int staticOuterField = 20; private int instanceOuterField = 30;
// Static nested class public static class StaticNestedClass { public void display() { // Can access static members of outer class System.out.println("Static outer field: " + staticOuterField);
// Cannot access instance members directly // System.out.println(instanceOuterField); // Error } }}
// Creating an instance of static nested classOuterClass.StaticNestedClass nested = new OuterClass.StaticNestedClass();nested.display();3. Local Inner Classes
Section titled “3. Local Inner Classes”A class defined within a method or block. It can access all members of the enclosing class and final or effectively final local variables of the enclosing method.
public class OuterClass { private int outerField = 40;
public void method(final int param) { final int localVar = 50; int effectivelyFinalVar = 60; // Effectively final (not modified after initialization)
// Local inner class class LocalInnerClass { public void display() { // Can access outer class members System.out.println("Outer field: " + outerField);
// Can access final or effectively final local variables System.out.println("Method parameter: " + param); System.out.println("Local variable: " + localVar); System.out.println("Effectively final: " + effectivelyFinalVar); } }
// Create and use the local inner class LocalInnerClass local = new LocalInnerClass(); local.display(); }}4. Anonymous Inner Classes
Section titled “4. Anonymous Inner Classes”A class defined without a name, typically used to create a one-time implementation of an interface or extension of a class.
public interface Clickable { void onClick();}
public class Button { private Clickable clickHandler;
public void setClickHandler(Clickable handler) { this.clickHandler = handler; }
public void click() { if (clickHandler != null) { clickHandler.onClick(); } }}
// Using anonymous inner classButton button = new Button();
// Anonymous inner class implementing Clickablebutton.setClickHandler(new Clickable() { @Override public void onClick() { System.out.println("Button clicked!"); }});
button.click(); // Output: Button clicked!
// With Java 8+ lambda (for functional interfaces)button.setClickHandler(() -> System.out.println("Button clicked with lambda!"));Benefits of Inner Classes
Section titled “Benefits of Inner Classes”- Encapsulation: Inner classes can access private members of the outer class
- Logical grouping: Related classes can be kept together
- More readable and maintainable code: Helper classes can be defined close to where they’re used
- Reduced code complexity: Anonymous classes reduce the need for separate class files for simple implementations
Constructor Chaining in Java
Section titled “Constructor Chaining in Java”Constructor chaining is the process of calling one constructor from another constructor within the same class or from a parent class.
1. Constructor Chaining Within the Same Class (this())
Section titled “1. Constructor Chaining Within the Same Class (this())”Using this() to call another constructor in the same class.
public class Person { private String name; private int age; private String address;
// Primary constructor public Person(String name, int age, String address) { this.name = name; this.age = age; this.address = address; }
// Calls the primary constructor public Person(String name, int age) { this(name, age, "Unknown"); // Constructor chaining }
// Calls the two-parameter constructor public Person(String name) { this(name, 0); // Constructor chaining }
// Default constructor public Person() { this("John Doe"); // Constructor chaining }
public void displayInfo() { System.out.println("Name: " + name + ", Age: " + age + ", Address: " + address); }}2. Constructor Chaining to Parent Class (super())
Section titled “2. Constructor Chaining to Parent Class (super())”Using super() to call a constructor in the parent class.
public class Vehicle { private String make; private String model; private int year;
public Vehicle(String make, String model, int year) { this.make = make; this.model = model; this.year = year; }
public Vehicle() { this("Unknown", "Unknown", 2023); }
public void displayInfo() { System.out.println("Vehicle: " + year + " " + make + " " + model); }}
public class Car extends Vehicle { private int numDoors; private String engineType;
public Car(String make, String model, int year, int numDoors, String engineType) { super(make, model, year); // Call to parent constructor this.numDoors = numDoors; this.engineType = engineType; }
public Car(String make, String model, int year) { this(make, model, year, 4, "Gasoline"); // Call to another constructor in same class }
public Car() { super(); // Call to parent's default constructor this.numDoors = 4; this.engineType = "Gasoline"; }
@Override public void displayInfo() { super.displayInfo(); // Call parent method System.out.println("Doors: " + numDoors + ", Engine: " + engineType); }}Rules for Constructor Chaining
Section titled “Rules for Constructor Chaining”this()orsuper()must be the first statement in a constructor- You cannot use both
this()andsuper()in the same constructor - Constructor chaining helps avoid code duplication
- If a class doesn’t explicitly call a parent constructor, the compiler adds an implicit call to the parent’s default constructor
- If the parent class doesn’t have a default constructor, the child class must explicitly call one of the parent’s constructors
Constructor Execution Order
Section titled “Constructor Execution Order”When creating an object, constructors are executed in a specific order:
- Static initializers of the parent class
- Static initializers of the child class
- Instance initializers of the parent class
- Parent class constructor
- Instance initializers of the child class
- Child class constructor
public class Parent { static { System.out.println("1. Parent static initializer"); }
{ System.out.println("3. Parent instance initializer"); }
public Parent() { System.out.println("4. Parent constructor"); }}
public class Child extends Parent { static { System.out.println("2. Child static initializer"); }
{ System.out.println("5. Child instance initializer"); }
public Child() { super(); // This is implicit if not written System.out.println("6. Child constructor"); }
public static void main(String[] args) { new Child(); }}Interview Questions on Constructor Chaining
Section titled “Interview Questions on Constructor Chaining”-
What happens if a parent class doesn’t have a default constructor?
- Child classes must explicitly call one of the parent’s constructors using
super()
- Child classes must explicitly call one of the parent’s constructors using
-
Can you call a constructor from a method?
- No, constructors can only be called from other constructors using
this()orsuper()
- No, constructors can only be called from other constructors using
-
What’s the difference between initialization blocks and constructors?
- Initialization blocks run for every constructor, while constructors can have different implementations
- Initialization blocks run before the constructor body
-
Can you have recursive constructor calls?
- No, it would cause a compilation error (recursive constructor invocation)
-
What is the purpose of constructor chaining?
- To reuse code and avoid duplication
- To ensure proper initialization of objects
- To provide multiple ways to create objects with different parameters