DEV Community

Cover image for Java Core Mastery Part 2: Advanced Concepts & Question: Prep πŸš€
Rajat Parihar
Rajat Parihar

Posted on

Java Core Mastery Part 2: Advanced Concepts & Question: Prep πŸš€

Deep dive into OOP, Collections, Multithreading, and Java 8+ features that every developer must master

Java Advanced

πŸ“‹ Table of Contents


🎨 Object-Oriented Programming

This is THE most important section for technical interviews

The Four Pillars

**Explain OOP principles with real-world examples AND show me code."

1. Encapsulation

Definition: Wrapping data and methods that operate on that data within a single unit (class).

Detailed Explanation:

// ❌ Bad: No encapsulation
class BankAccountBad {
    public double balance;

    // Anyone can do this:
    // account.balance = -1000000; // Invalid state!
}

// βœ… Good: Proper encapsulation
class BankAccount {
    private double balance;  // Hidden from outside
    private String accountNumber;

    // Controlled access through methods
    public double getBalance() {
        return balance;
    }

    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        } else {
            throw new IllegalArgumentException("Amount must be positive");
        }
    }

    public void withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
        } else {
            throw new IllegalArgumentException("Invalid withdrawal amount");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Why Encapsulation?

  1. Data Hiding - Internal state is protected
  2. Validation - Can validate data before setting
  3. Flexibility - Can change internal implementation without affecting users
  4. Maintainability - Clear interface for interacting with class

Common Mistake: "Can we achieve 100% encapsulation in Java?"

Explanation: No! Reflection API can access private members. Also, if you return mutable objects from getters, caller can modify them.

class Employee {
    private List<String> skills = new ArrayList<>();

    // ❌ Bad: Returns mutable reference
    public List<String> getSkills() {
        return skills;
    }

    // βœ… Good: Returns unmodifiable view
    public List<String> getSkillsSafe() {
        return Collections.unmodifiableList(skills);
    }

    // βœ… Better: Returns defensive copy
    public List<String> getSkillsCopy() {
        return new ArrayList<>(skills);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Inheritance

Definition: Mechanism where one class acquires properties of another class.

// Parent class (Superclass/Base class)
class Animal {
    protected String name;
    protected int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(name + " is eating");
    }

    public void sleep() {
        System.out.println(name + " is sleeping");
    }
}

// Child class (Subclass/Derived class)
class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);  // Call parent constructor
        this.breed = breed;
    }

    // Dog-specific method
    public void bark() {
        System.out.println(name + " is barking!");
    }

    // Override parent method
    @Override
    public void eat() {
        System.out.println(name + " is eating dog food");
    }
}

class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    public void meow() {
        System.out.println(name + " is meowing!");
    }
}

public class InheritanceDemo {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy", 3, "Golden Retriever");
        dog.eat();    // Dog's version
        dog.sleep();  // Inherited from Animal
        dog.bark();   // Dog-specific

        Cat cat = new Cat("Whiskers", 2);
        cat.eat();    // Animal's version
        cat.meow();   // Cat-specific
    }
}
Enter fullscreen mode Exit fullscreen mode

Types of Inheritance in Java:

// 1. Single Inheritance
class A { }
class B extends A { }

// 2. Multilevel Inheritance
class A { }
class B extends A { }
class C extends B { }  // C inherits from B, B inherits from A

// 3. Hierarchical Inheritance
class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
class Bird extends Animal { }

// ❌ Multiple Inheritance NOT allowed with classes
// class C extends A, B { }  // Compilation error!

// βœ… Multiple Inheritance allowed with interfaces
interface Flyable { }
interface Swimmable { }
class Duck implements Flyable, Swimmable { }
Enter fullscreen mode Exit fullscreen mode

Common Question: "Why doesn't Java support multiple inheritance with classes?"

Explanation: Diamond Problem

/*
      A
     / \
    B   C
     \ /
      D

If B and C both override a method from A,
which version does D inherit?
*/

// This is why it's not allowed:
class A {
    void display() { System.out.println("A"); }
}

class B extends A {
    void display() { System.out.println("B"); }
}

class C extends A {
    void display() { System.out.println("C"); }
}

// ❌ Not allowed
// class D extends B, C {
//     // Which display() to inherit?
// }
Enter fullscreen mode Exit fullscreen mode

3. Polymorphism

Definition: One interface, multiple implementations.

Two Types:

A. Compile-time Polymorphism (Method Overloading)

class Calculator {
    // Same method name, different parameters
    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;
    }

    // ❌ NOT overloading (return type doesn't matter)
    // public double add(int a, int b) {
    //     return a + b;
    // }
}
Enter fullscreen mode Exit fullscreen mode

Rules for Method Overloading:

  1. Must have different parameter lists
  2. May have different return types
  3. May have different access modifiers
  4. May throw different exceptions

B. Runtime Polymorphism (Method Overriding)

class Shape {
    public void draw() {
        System.out.println("Drawing a shape");
    }

    public double area() {
        return 0.0;
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a circle");
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double length, width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public void draw() {
        System.out.println("Drawing a rectangle");
    }

    @Override
    public double area() {
        return length * width;
    }
}

public class PolymorphismDemo {
    public static void main(String[] args) {
        // Polymorphic reference
        Shape shape1 = new Circle(5.0);
        Shape shape2 = new Rectangle(4.0, 6.0);

        // Runtime decides which method to call
        shape1.draw();  // "Drawing a circle"
        shape2.draw();  // "Drawing a rectangle"

        System.out.println("Circle area: " + shape1.area());
        System.out.println("Rectangle area: " + shape2.area());

        // Array of shapes (powerful!)
        Shape[] shapes = {
            new Circle(3.0),
            new Rectangle(2.0, 5.0),
            new Circle(4.5)
        };

        for (Shape shape : shapes) {
            shape.draw();
            System.out.println("Area: " + shape.area());
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Rules for Method Overriding:

  1. Must have same method signature
  2. Return type must be same or covariant
  3. Access modifier must be same or less restrictive
  4. Cannot override final, static, or private methods
  5. Exception thrown can be same, subclass, or none

Common Mistake:

class Parent {
    public Number getValue() {  // Returns Number
        return 10;
    }
}

class Child extends Parent {
    @Override
    public Integer getValue() {  // βœ… Covariant return type
        return 20;
    }
}
Enter fullscreen mode Exit fullscreen mode

Upcasting and Downcasting (VERY IMPORTANT!)

This is where many developers get confused. Let's break it down with examples.

Understanding Reference Type vs Object Type

class Animal {
    String name = "Animal";

    public void eat() {
        System.out.println("Animal is eating");
    }

    public void sleep() {
        System.out.println("Animal is sleeping");
    }
}

class Dog extends Animal {
    String name = "Dog";  // Variable hiding (not overriding!)
    String breed = "Golden Retriever";

    @Override
    public void eat() {
        System.out.println("Dog is eating");
    }

    public void bark() {
        System.out.println("Dog is barking");
    }
}

public class UpcastingDowncasting {
    public static void main(String[] args) {
        // Normal case: Reference type = Object type
        Dog dog1 = new Dog();
        dog1.eat();    // βœ… Dog is eating
        dog1.bark();   // βœ… Dog is barking
        dog1.sleep();  // βœ… Animal is sleeping (inherited)
        System.out.println(dog1.breed);  // βœ… Golden Retriever

        // UPCASTING: Parent reference, Child object
        Animal animal = new Dog();  // βœ… Implicit upcasting

        /*
        KEY CONCEPT:
        - Reference type (Animal) determines WHAT you can call
        - Object type (Dog) determines WHICH implementation runs
        */

        animal.eat();    // βœ… Dog is eating (Dog's implementation)
        animal.sleep();  // βœ… Animal is sleeping (inherited method)

        // ❌ CANNOT access Dog-specific methods
        // animal.bark();  // Compilation Error!
        // animal.breed;   // Compilation Error!

        // Variable access is based on REFERENCE TYPE
        System.out.println(animal.name);  // "Animal" (not "Dog"!)

        // DOWNCASTING: Cast back to access Dog-specific members
        if (animal instanceof Dog) {
            Dog dog2 = (Dog) animal;  // βœ… Explicit downcasting
            dog2.bark();   // βœ… Now works!
            System.out.println(dog2.breed);  // βœ… Now works!
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Dog is eating
Dog is barking
Animal is sleeping
Golden Retriever
Dog is eating
Animal is sleeping
Animal
Dog is barking
Golden Retriever
Enter fullscreen mode Exit fullscreen mode

Why Does This Happen?

/*
MEMORY LAYOUT:

HEAP:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Dog Object         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Animal part:            β”‚
β”‚   name = "Animal"       β”‚
β”‚   eat() β†’ overridden    β”‚
β”‚   sleep()               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Dog part:               β”‚
β”‚   name = "Dog"          β”‚
β”‚   breed = "G.Retriever" β”‚
β”‚   bark()                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↑
         β”‚
    animal reference (Animal type)
    ↓
Can only see Animal part through this reference!
*/
Enter fullscreen mode Exit fullscreen mode

Common Mistakes with Upcasting

class Parent {
    int x = 10;

    public void display() {
        System.out.println("Parent display");
    }
}

class Child extends Parent {
    int x = 20;  // Variable hiding
    int y = 30;  // Child-specific variable

    @Override
    public void display() {
        System.out.println("Child display");
    }

    public void childMethod() {
        System.out.println("Child method");
    }
}

public class CommonMistakes {
    public static void main(String[] args) {
        Parent p = new Child();  // Upcasting

        // 1. Method calls - Uses OBJECT type (Child)
        p.display();  // βœ… "Child display" (polymorphism works!)

        // 2. Variable access - Uses REFERENCE type (Parent)
        System.out.println(p.x);  // βœ… 10 (NOT 20!)

        // 3. Child-specific members - NOT accessible
        // System.out.println(p.y);  // ❌ Compilation Error
        // p.childMethod();  // ❌ Compilation Error

        // 4. Need downcasting to access child members
        Child c = (Child) p;
        System.out.println(c.x);  // βœ… 20
        System.out.println(c.y);  // βœ… 30
        c.childMethod();  // βœ… "Child method"
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

Child display
10
20
30
Child method
Enter fullscreen mode Exit fullscreen mode

Downcasting Dangers

public class DowncastingDanger {
    public static void main(String[] args) {
        Animal animal1 = new Dog();  // Dog object
        Animal animal2 = new Animal();  // Animal object

        // Safe downcasting
        if (animal1 instanceof Dog) {
            Dog dog = (Dog) animal1;  // βœ… Safe
            dog.bark();
        }

        // Unsafe downcasting
        // Dog dog2 = (Dog) animal2;  // ❌ ClassCastException at runtime!

        // ALWAYS check with instanceof before downcasting
        if (animal2 instanceof Dog) {
            Dog dog2 = (Dog) animal2;
            dog2.bark();
        } else {
            System.out.println("animal2 is not a Dog!");
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Practical Example: Why Upcasting Matters

class Shape {
    public void draw() {
        System.out.println("Drawing shape");
    }
}

class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public void draw() {
        System.out.println("Drawing circle with radius: " + radius);
    }

    public double getRadius() {
        return radius;
    }
}

class Rectangle extends Shape {
    private double length, width;

    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }

    @Override
    public void draw() {
        System.out.println("Drawing rectangle: " + length + "x" + width);
    }
}

public class WhyUpcastingMatters {
    // βœ… Good: Can accept any Shape
    public static void drawShape(Shape shape) {
        shape.draw();  // Polymorphism!
    }

    // ❌ Bad: Needs separate method for each shape
    // public static void drawCircle(Circle circle) { }
    // public static void drawRectangle(Rectangle rect) { }

    public static void main(String[] args) {
        Shape[] shapes = {
            new Circle(5.0),
            new Rectangle(4.0, 6.0),
            new Circle(3.0)
        };

        // Single loop handles all shapes!
        for (Shape shape : shapes) {
            drawShape(shape);  // Upcasting happens automatically

            // If you need specific behavior, downcast
            if (shape instanceof Circle) {
                Circle circle = (Circle) shape;
                System.out.println("  Radius: " + circle.getRadius());
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways on References

1. Method Calls (Polymorphism)

Parent p = new Child();
p.method();  // Calls Child's implementation βœ…
Enter fullscreen mode Exit fullscreen mode

2. Variable Access (No Polymorphism)

Parent p = new Child();
System.out.println(p.variable);  // Parent's variable ⚠️
Enter fullscreen mode Exit fullscreen mode

3. Member Access (Reference Type Decides)

Parent p = new Child();
// p.childMethod();  // ❌ Can't access
// p.childVariable;  // ❌ Can't access

Child c = (Child) p;  // Downcast first
c.childMethod();  // βœ… Now accessible
Enter fullscreen mode Exit fullscreen mode

4. The Golden Rule

Reference Type β†’ What you CAN call (compile-time)
Object Type β†’ Which implementation RUNS (runtime)
Enter fullscreen mode Exit fullscreen mode

4. Abstraction

Definition: Hiding implementation details and showing only functionality.

// Abstract class
abstract class Vehicle {
    protected String brand;

    public Vehicle(String brand) {
        this.brand = brand;
    }

    // Abstract method (no implementation)
    public abstract void start();
    public abstract void stop();

    // Concrete method (with implementation)
    public void displayBrand() {
        System.out.println("Brand: " + brand);
    }
}

class Car extends Vehicle {
    public Car(String brand) {
        super(brand);
    }

    @Override
    public void start() {
        System.out.println(brand + " car starting with key");
    }

    @Override
    public void stop() {
        System.out.println(brand + " car stopping with brake");
    }
}

class Motorcycle extends Vehicle {
    public Motorcycle(String brand) {
        super(brand);
    }

    @Override
    public void start() {
        System.out.println(brand + " motorcycle kick-starting");
    }

    @Override
    public void stop() {
        System.out.println(brand + " motorcycle stopping");
    }
}

public class AbstractionDemo {
    public static void main(String[] args) {
        // ❌ Cannot instantiate abstract class
        // Vehicle v = new Vehicle("Generic");

        Vehicle car = new Car("Toyota");
        Vehicle bike = new Motorcycle("Harley");

        car.start();
        bike.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

The this Keyword

class Employee {
    private String name;
    private int age;

    // 1. Differentiate between instance variable and parameter
    public Employee(String name, int age) {
        this.name = name;  // this.name refers to instance variable
        this.age = age;
    }

    // 2. Call another constructor
    public Employee(String name) {
        this(name, 0);  // Calls Employee(String, int)
    }

    // 3. Pass current object as parameter
    public void display() {
        printEmployee(this);
    }

    private void printEmployee(Employee emp) {
        System.out.println(emp.name + ", " + emp.age);
    }

    // 4. Return current object
    public Employee setName(String name) {
        this.name = name;
        return this;  // Method chaining
    }

    public Employee setAge(int age) {
        this.age = age;
        return this;
    }

    public static void main(String[] args) {
        // Method chaining using 'this'
        Employee emp = new Employee("John")
                        .setName("John Doe")
                        .setAge(30);
    }
}
Enter fullscreen mode Exit fullscreen mode

The super Keyword

class Parent {
    protected int value = 10;

    public Parent() {
        System.out.println("Parent constructor");
    }

    public Parent(int value) {
        this.value = value;
        System.out.println("Parent parameterized constructor");
    }

    public void display() {
        System.out.println("Parent display");
    }
}

class Child extends Parent {
    private int value = 20;

    public Child() {
        super();  // 1. Call parent constructor
        System.out.println("Child constructor");
    }

    public Child(int parentValue, int childValue) {
        super(parentValue);  // Must be first statement
        this.value = childValue;
    }

    @Override
    public void display() {
        super.display();  // 2. Call parent method
        System.out.println("Child display");
    }

    public void showValues() {
        System.out.println("Child value: " + this.value);  // 20
        System.out.println("Parent value: " + super.value);  // 3. Access parent variable
    }
}
Enter fullscreen mode Exit fullscreen mode

Access Modifiers

// File: AccessModifiers.java
package com.example;

public class AccessModifiers {
    public int publicVar = 1;        // Accessible everywhere
    protected int protectedVar = 2;  // Same package + subclasses
    int defaultVar = 3;              // Same package only (package-private)
    private int privateVar = 4;      // Same class only

    public void display() {
        // All accessible within same class
        System.out.println(publicVar);
        System.out.println(protectedVar);
        System.out.println(defaultVar);
        System.out.println(privateVar);
    }
}

// File: SamePackageClass.java
package com.example;

class SamePackageClass {
    void test() {
        AccessModifiers obj = new AccessModifiers();
        System.out.println(obj.publicVar);     // βœ…
        System.out.println(obj.protectedVar);  // βœ…
        System.out.println(obj.defaultVar);    // βœ…
        // System.out.println(obj.privateVar); // ❌ Compilation error
    }
}

// File: DifferentPackageClass.java
package com.other;
import com.example.AccessModifiers;

class DifferentPackageClass {
    void test() {
        AccessModifiers obj = new AccessModifiers();
        System.out.println(obj.publicVar);     // βœ…
        // System.out.println(obj.protectedVar); // ❌
        // System.out.println(obj.defaultVar);   // ❌
        // System.out.println(obj.privateVar);   // ❌
    }
}

// File: SubclassInDifferentPackage.java
package com.other;
import com.example.AccessModifiers;

class SubclassInDifferentPackage extends AccessModifiers {
    void test() {
        // βœ… Can access through inheritance (without object)
        System.out.println(publicVar);     // βœ…
        System.out.println(protectedVar);  // βœ… (through inheritance)
        // System.out.println(defaultVar);   // ❌
        // System.out.println(privateVar);   // ❌

        // ⚠️ CRITICAL: Cannot access protected through object reference!
        SubclassInDifferentPackage obj = new SubclassInDifferentPackage();
        // System.out.println(obj.protectedVar); // ❌ Compilation error!

        // This is also NOT allowed
        AccessModifiers parent = new AccessModifiers();
        // System.out.println(parent.protectedVar); // ❌ Compilation error!
    }
}
Enter fullscreen mode Exit fullscreen mode

Protected Access - The Tricky Part (MUST UNDERSTAND!)

This is one of the most misunderstood concepts in Java. Let me explain with a detailed example:

// File: Parent.java
package com.parent;

public class Parent {
    protected int protectedVar = 100;

    protected void protectedMethod() {
        System.out.println("Protected method");
    }
}

// File: Child.java
package com.child;
import com.parent.Parent;

public class Child extends Parent {
    public void demonstrateProtectedAccess() {
        // βœ… WAY 1: Access directly (through inheritance)
        System.out.println(protectedVar);  // Works!
        protectedMethod();  // Works!

        // βœ… WAY 2: Access through 'this'
        System.out.println(this.protectedVar);  // Works!
        this.protectedMethod();  // Works!

        // βœ… WAY 3: Access through 'super'
        System.out.println(super.protectedVar);  // Works!
        super.protectedMethod();  // Works!

        // ❌ WAY 4: Access through object reference - DOES NOT WORK!
        Child childObj = new Child();
        // System.out.println(childObj.protectedVar);  // ❌ Compilation Error!
        // childObj.protectedMethod();  // ❌ Compilation Error!

        // ❌ WAY 5: Access through parent object - DOES NOT WORK!
        Parent parentObj = new Parent();
        // System.out.println(parentObj.protectedVar);  // ❌ Compilation Error!
        // parentObj.protectedMethod();  // ❌ Compilation Error!
    }
}
Enter fullscreen mode Exit fullscreen mode

Why This Behavior?

/*
RULE FOR PROTECTED ACCESS IN DIFFERENT PACKAGE:

Protected members are accessible in subclass ONLY through:
1. Direct access (inherited members)
2. 'this' reference
3. 'super' reference

NOT accessible through:
1. Object references (even of same subclass)
2. Parent class object references

REASON: Protected is meant for inheritance, not for general access
through objects. It's about "is-a" relationship, not "has-a".
*/
Enter fullscreen mode Exit fullscreen mode

Complete Example Showing All Cases:

// File: Vehicle.java
package com.vehicles;

public class Vehicle {
    protected String brand = "Generic";
    protected int speed = 0;

    protected void accelerate() {
        speed += 10;
        System.out.println("Accelerating to " + speed);
    }
}

// File: Car.java
package com.cars;
import com.vehicles.Vehicle;

public class Car extends Vehicle {
    public void testProtectedAccess() {
        System.out.println("=== Testing Protected Access ===");

        // βœ… CASE 1: Direct access (through inheritance)
        System.out.println("Brand: " + brand);  // Works!
        System.out.println("Speed: " + speed);  // Works!
        accelerate();  // Works!

        // βœ… CASE 2: Through 'this'
        System.out.println("Brand via this: " + this.brand);  // Works!
        this.accelerate();  // Works!

        // βœ… CASE 3: Through 'super'
        System.out.println("Brand via super: " + super.brand);  // Works!
        super.accelerate();  // Works!

        // ❌ CASE 4: Through object of same class
        Car anotherCar = new Car();
        // System.out.println(anotherCar.brand);  // ❌ ERROR!
        // anotherCar.accelerate();  // ❌ ERROR!

        // ❌ CASE 5: Through parent class object
        Vehicle vehicle = new Vehicle();
        // System.out.println(vehicle.brand);  // ❌ ERROR!
        // vehicle.accelerate();  // ❌ ERROR!

        // ❌ CASE 6: Through upcasted reference
        Vehicle upcastedCar = new Car();
        // System.out.println(upcastedCar.brand);  // ❌ ERROR!
        // upcastedCar.accelerate();  // ❌ ERROR!
    }

    public static void main(String[] args) {
        Car car = new Car();
        car.testProtectedAccess();
    }
}
Enter fullscreen mode Exit fullscreen mode

Memory Layout Explanation:

When you have: Car extends Vehicle

MEMORY:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     Car Object          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Vehicle part (inherited)β”‚
β”‚   brand = "Generic"     β”‚ ← Protected variable
β”‚   speed = 0             β”‚ ← Protected variable
β”‚   accelerate()          β”‚ ← Protected method
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Car part                β”‚
β”‚   (Car-specific stuff)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Access Rules in Different Package:
1. Through inheritance (brand, this.brand, super.brand) βœ…
   - You're accessing YOUR OWN inherited copy

2. Through object (obj.brand) ❌
   - You're trying to access SOMEONE ELSE'S copy
   - Java says: "Protected is for YOUR inheritance, not others"
Enter fullscreen mode Exit fullscreen mode

Practical Example:

// File: BankAccount.java
package com.bank;

public class BankAccount {
    protected double balance = 1000.0;

    protected void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
        }
    }
}

// File: SavingsAccount.java
package com.accounts;
import com.bank.BankAccount;

public class SavingsAccount extends BankAccount {
    public void myTransactions() {
        // βœ… Can access my own inherited balance
        System.out.println("My balance: " + balance);
        withdraw(100);  // βœ… Works

        // ❌ Cannot access other account's balance through object
        SavingsAccount otherAccount = new SavingsAccount();
        // System.out.println(otherAccount.balance);  // ❌ Error!
        // Why? To protect encapsulation! You shouldn't access
        // protected members of other objects directly.
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaway:

/*
GOLDEN RULE FOR PROTECTED ACCESS:

In a DIFFERENT package, protected members are accessible ONLY when:
1. You're in a subclass
2. You access through inheritance (no object reference)

Think of it as: "Protected is for ME (through inheritance),
                not for THEM (through objects)"

Same Package: No restrictions (works like public)
Different Package: Only through inheritance, not through objects
*/
Enter fullscreen mode Exit fullscreen mode

Access Modifier Table:

Modifier Same Class Same Package Subclass Universe
public βœ… βœ… βœ… βœ…
protected βœ… βœ… βœ… ❌
default βœ… βœ… ❌ ❌
private βœ… ❌ ❌ ❌

Note: For protected in "Subclass" column - accessible only through inheritance (this/super), NOT through object references!


πŸ“ Strings Deep Dive

**Why are Strings immutable? What's the String pool?"

String Immutability

public class StringImmutability {
    public static void main(String[] args) {
        String s1 = "Hello";
        s1.concat(" World");  // Creates new String, doesn't modify s1
        System.out.println(s1);  // "Hello" (unchanged)

        s1 = s1.concat(" World");  // Now s1 points to new String
        System.out.println(s1);  // "Hello World"

        // Memory visualization:
        /*
        HEAP:
        "Hello" object ← s1 initially points here
        "Hello World" object ← s1 points here after concat

        The original "Hello" remains unchanged!
        */
    }
}
Enter fullscreen mode Exit fullscreen mode

Why Immutable?

  1. String Pool Optimization - Reuse same String object
  2. Security - Strings used in sensitive operations (passwords, URLs, file paths)
  3. Thread Safety - No synchronization needed
  4. Hashcode Caching - Hashcode calculated once and cached

String Pool (Intern Pool)

public class StringPool {
    public static void main(String[] args) {
        // Literal - Goes to String pool
        String s1 = "Hello";
        String s2 = "Hello";
        System.out.println(s1 == s2);  // true (same reference)

        // new keyword - Goes to heap
        String s3 = new String("Hello");
        System.out.println(s1 == s3);  // false (different references)
        System.out.println(s1.equals(s3));  // true (same content)

        // intern() - Move to pool
        String s4 = s3.intern();
        System.out.println(s1 == s4);  // true (now same reference)

        /*
        MEMORY LAYOUT:

        STRING POOL (in Heap):
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚  "Hello"    β”‚ ← s1, s2, s4 point here
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

        REGULAR HEAP:
        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
        β”‚ new String  β”‚ ← s3 points here
        β”‚  "Hello"    β”‚
        β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        */
    }
}
Enter fullscreen mode Exit fullscreen mode

String vs StringBuilder vs StringBuffer

public class StringComparison {
    public static void main(String[] args) {
        // 1. String (Immutable, Thread-safe by default)
        String str = "Hello";
        str += " World";  // Creates new object each time
        // Performance: O(nΒ²) for n concatenations

        // 2. StringBuilder (Mutable, NOT thread-safe)
        StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");  // Modifies same object
        // Performance: O(n) for n concatenations

        // 3. StringBuffer (Mutable, Thread-safe)
        StringBuffer sbf = new StringBuffer("Hello");
        sbf.append(" World");  // Synchronized methods
        // Performance: O(n) but slower than StringBuilder due to synchronization

        // Performance test
        long startTime, endTime;

        // String concatenation (SLOW)
        startTime = System.nanoTime();
        String s = "";
        for (int i = 0; i < 10000; i++) {
            s += "a";  // Creates 10000 new objects!
        }
        endTime = System.nanoTime();
        System.out.println("String: " + (endTime - startTime) / 1000000 + " ms");

        // StringBuilder (FAST)
        startTime = System.nanoTime();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            builder.append("a");
        }
        endTime = System.nanoTime();
        System.out.println("StringBuilder: " + (endTime - startTime) / 1000000 + " ms");
    }
}
Enter fullscreen mode Exit fullscreen mode

When to Use What?

Scenario Use
Fixed string, no modifications String
Single-threaded string building StringBuilder
Multi-threaded string building StringBuffer
Simple concatenations (<< 100) String (compiler optimizes)

Common String Methods

public class StringMethods {
    public static void main(String[] args) {
        String str = "  Hello World  ";

        // Length
        System.out.println(str.length());  // 15

        // Character access
        System.out.println(str.charAt(2));  // ' '

        // Substring
        System.out.println(str.substring(2, 7));  // "Hello"

        // Trim
        System.out.println(str.trim());  // "Hello World"

        // Case conversion
        System.out.println(str.toUpperCase());
        System.out.println(str.toLowerCase());

        // Replace
        System.out.println(str.replace("World", "Java"));

        // Split
        String[] words = str.trim().split(" ");
        for (String word : words) {
            System.out.println(word);
        }

        // Contains, startsWith, endsWith
        System.out.println(str.contains("World"));  // true
        System.out.println(str.trim().startsWith("Hello"));  // true
        System.out.println(str.trim().endsWith("World"));  // true

        // equals vs equalsIgnoreCase
        String s1 = "hello";
        String s2 = "HELLO";
        System.out.println(s1.equals(s2));  // false
        System.out.println(s1.equalsIgnoreCase(s2));  // true

        // isEmpty vs isBlank (Java 11+)
        System.out.println("".isEmpty());  // true
        System.out.println("  ".isEmpty());  // false
        System.out.println("  ".isBlank());  // true (Java 11+)

        // Format
        String formatted = String.format("Name: %s, Age: %d", "John", 30);
        System.out.println(formatted);
    }
}
Enter fullscreen mode Exit fullscreen mode

equals() vs ==

public class StringEquality {
    public static void main(String[] args) {
        String s1 = "Hello";
        String s2 = "Hello";
        String s3 = new String("Hello");

        // == compares references
        System.out.println(s1 == s2);  // true (same object in pool)
        System.out.println(s1 == s3);  // false (different objects)

        // equals() compares content
        System.out.println(s1.equals(s2));  // true
        System.out.println(s1.equals(s3));  // true

        // Question: trap
        Integer i1 = 127;
        Integer i2 = 127;
        System.out.println(i1 == i2);  // true (cached)

        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);  // false (not cached)
        // Integer cache: -128 to 127
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”’ Arrays Explained

Array Basics

public class ArrayBasics {
    public static void main(String[] args) {
        // Declaration and initialization
        int[] arr1 = new int[5];  // Default values: 0
        int[] arr2 = {1, 2, 3, 4, 5};
        int[] arr3 = new int[]{1, 2, 3, 4, 5};

        // Access
        System.out.println(arr2[0]);  // 1
        arr2[0] = 10;  // Modify

        // Length
        System.out.println(arr2.length);  // 5 (property, not method)

        // Enhanced for loop
        for (int num : arr2) {
            System.out.print(num + " ");
        }
        System.out.println();

        // Anonymous array
        printArray(new int[]{1, 2, 3});
    }

    static void printArray(int[] arr) {
        for (int num : arr) {
            System.out.print(num + " ");
        }
        System.out.println();
    }
}
Enter fullscreen mode Exit fullscreen mode

2D Arrays

public class TwoDArray {
    public static void main(String[] args) {
        // Rectangular array
        int[][] matrix = new int[3][3];
        int[][] matrix2 = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

        // Access
        System.out.println(matrix2[1][1]);  // 5

        // Jagged array (different column sizes)
        int[][] jagged = new int[3][];
        jagged[0] = new int[2];  // 2 columns
        jagged[1] = new int[4];  // 4 columns
        jagged[2] = new int[3];  // 3 columns

        // Traverse
        for (int i = 0; i < matrix2.length; i++) {
            for (int j = 0; j < matrix2[i].length; j++) {
                System.out.print(matrix2[i][j] + " ");
            }
            System.out.println();
        }

        // Enhanced for loop
        for (int[] row : matrix2) {
            for (int num : row) {
                System.out.print(num + " ");
            }
            System.out.println();
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Arrays Utility Class

import java.util.Arrays;

public class ArraysUtility {
    public static void main(String[] args) {
        int[] arr = {5, 2, 8, 1, 9};

        // Sort
        Arrays.sort(arr);
        System.out.println(Arrays.toString(arr));  // [1, 2, 5, 8, 9]

        // Binary search (array must be sorted)
        int index = Arrays.binarySearch(arr, 5);
        System.out.println("Index of 5: " + index);

        // Fill
        int[] arr2 = new int[5];
        Arrays.fill(arr2, 10);
        System.out.println(Arrays.toString(arr2));  // [10, 10, 10, 10, 10]

        // Copy
        int[] copy = Arrays.copyOf(arr, arr.length);
        int[] copyRange = Arrays.copyOfRange(arr, 1, 4);

        // Equals
        System.out.println(Arrays.equals(arr, copy));  // true

        // Compare
        System.out.println(Arrays.compare(arr, copy));  // 0 (equal)
    }
}
Enter fullscreen mode Exit fullscreen mode

Array vs ArrayList

import java.util.ArrayList;

public class ArrayVsArrayList {
    public static void main(String[] args) {
        // Array: Fixed size
        int[] arr = new int[5];
        // arr[5] = 10;  // ArrayIndexOutOfBoundsException

        // ArrayList: Dynamic size
        ArrayList<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list.size());  // 3

        // Performance
        // Array: O(1) access, fixed size
        // ArrayList: O(1) access, O(n) resize (amortized O(1) add)
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ”Œ Interfaces & Abstract Classes

Interface

// Interface: Contract
interface Drawable {
    // All methods are public abstract by default
    void draw();
    void resize(int width, int height);

    // Default method (Java 8+)
    default void display() {
        System.out.println("Displaying drawable");
    }

    // Static method (Java 8+)
    static void info() {
        System.out.println("This is Drawable interface");
    }

    // Constants (public static final by default)
    int MAX_SIZE = 1000;
}

class Circle implements Drawable {
    @Override
    public void draw() {
        System.out.println("Drawing circle");
    }

    @Override
    public void resize(int width, int height) {
        System.out.println("Resizing circle");
    }

    // Can override default method
    @Override
    public void display() {
        System.out.println("Displaying circle");
    }
}

// Multiple interfaces
interface Printable {
    void print();
}

class Document implements Drawable, Printable {
    @Override
    public void draw() {
        System.out.println("Drawing document");
    }

    @Override
    public void resize(int width, int height) {
        System.out.println("Resizing document");
    }

    @Override
    public void print() {
        System.out.println("Printing document");
    }
}
Enter fullscreen mode Exit fullscreen mode

Abstract Class vs Interface

// Abstract class: Partial implementation
abstract class Animal {
    protected String name;

    // Constructor allowed
    public Animal(String name) {
        this.name = name;
    }

    // Concrete method
    public void eat() {
        System.out.println(name + " is eating");
    }

    // Abstract method
    public abstract void makeSound();
}

class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " barks");
    }
}

// Interface: Full abstraction
interface Swimmable {
    void swim();
}

class Duck extends Animal implements Swimmable {
    public Duck(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " quacks");
    }

    @Override
    public void swim() {
        System.out.println(name + " is swimming");
    }
}
Enter fullscreen mode Exit fullscreen mode

Abstract Class vs Interface:

Feature Abstract Class Interface
Methods Both abstract & concrete Abstract, default, static (Java 8+)
Variables Any type public static final only
Constructor Yes No
Multiple Inheritance No Yes
Access Modifiers All public only
When to use "is-a" relationship "can-do" capability

Common Question: "When to use abstract class vs interface?"

Explanation:

  • Abstract Class: Common base implementation, "is-a" relationship (Dog is-a Animal)
  • Interface: Capabilities, "can-do" relationship (Duck can-swim, can-fly)

(Continuing with remaining sections...)


πŸ“¦ Packages

Package Basics

// File: com/example/myapp/Calculator.java
package com.example.myapp;

public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

// File: com/example/myapp/Main.java
package com.example.myapp;

// Same package - no import needed
public class Main {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(5, 3));
    }
}

// File: com/example/other/Test.java
package com.example.other;

import com.example.myapp.Calculator;  // Different package - import needed

public class Test {
    public static void main(String[] args) {
        Calculator calc = new Calculator();
        System.out.println(calc.add(10, 20));
    }
}
Enter fullscreen mode Exit fullscreen mode

Built-in Packages

// java.lang - Automatically imported
// String, System, Math, Integer, etc.

// java.util - Collections, Date, etc.
import java.util.ArrayList;
import java.util.HashMap;

// java.io - Input/Output
import java.io.File;
import java.io.IOException;

// java.sql - Database
import java.sql.Connection;
import java.sql.DriverManager;

// Wildcard import (imports all classes)
import java.util.*;  // Not recommended for production
Enter fullscreen mode Exit fullscreen mode

⚠️ Exception Handling

Exception Hierarchy

                 Throwable
                /         \
           Error          Exception
           /               /        \
    OutOfMemoryError  IOException  RuntimeException
    StackOverflowError             /         |         \
                        NullPointer  ArrayIndexOutOfBounds  ArithmeticException
Enter fullscreen mode Exit fullscreen mode

Checked vs Unchecked Exceptions

import java.io.*;

public class ExceptionTypes {
    public static void main(String[] args) {
        // 1. Checked Exception (Compile-time)
        // Must handle with try-catch or declare with throws
        try {
            FileReader fr = new FileReader("file.txt");
        } catch (FileNotFoundException e) {
            System.out.println("File not found");
        }

        // 2. Unchecked Exception (Runtime)
        // Optional to handle
        int[] arr = {1, 2, 3};
        // System.out.println(arr[5]);  // ArrayIndexOutOfBoundsException

        // 3. Error
        // Don't catch errors (JVM issues)
        // recursiveMethod();  // StackOverflowError
    }
}
Enter fullscreen mode Exit fullscreen mode

try-catch-finally

public class ExceptionHandling {
    public static void main(String[] args) {
        // Basic try-catch
        try {
            int result = 10 / 0;
        } catch (ArithmeticException e) {
            System.out.println("Cannot divide by zero");
        }

        // Multiple catch blocks
        try {
            String str = null;
            System.out.println(str.length());
            int[] arr = new int[5];
            System.out.println(arr[10]);
        } catch (NullPointerException e) {
            System.out.println("Null pointer");
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Array index out of bounds");
        } catch (Exception e) {  // Catch-all (must be last)
            System.out.println("Some exception occurred");
        }

        // finally: Always executes
        try {
            System.out.println("Try block");
            return;  // Even with return, finally executes
        } catch (Exception e) {
            System.out.println("Catch block");
        } finally {
            System.out.println("Finally block");  // This WILL execute
        }

        // try-with-resources (Java 7+)
        try (FileReader fr = new FileReader("file.txt")) {
            // Resource automatically closed
        } catch (IOException e) {
            e.printStackTrace();
        }
        // No need for finally to close resource
    }
}
Enter fullscreen mode Exit fullscreen mode

throw vs throws

public class ThrowThrows {
    // throws: Declares that method might throw exception
    public void method1() throws IOException {
        throw new IOException("File error");  // throw: Actually throws exception
    }

    // Custom exception
    public void validateAge(int age) throws InvalidAgeException {
        if (age < 18) {
            throw new InvalidAgeException("Age must be 18 or above");
        }
    }
}

// Custom exception class
class InvalidAgeException extends Exception {
    public InvalidAgeException(String message) {
        super(message);
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸ“š Collections Framework

Collection Hierarchy

                    Collection (interface)
                  /          |          \
              List          Set         Queue
             /   \         /   \
      ArrayList LinkedList HashSet TreeSet
Enter fullscreen mode Exit fullscreen mode

List Interface

import java.util.*;

public class ListDemo {
    public static void main(String[] args) {
        // ArrayList: Dynamic array
        List<String> arrayList = new ArrayList<>();
        arrayList.add("Apple");
        arrayList.add("Banana");
        arrayList.add("Apple");  // Duplicates allowed
        arrayList.add(null);  // Null allowed

        System.out.println(arrayList.get(0));  // Random access O(1)
        arrayList.remove(0);  // O(n)

        // LinkedList: Doubly linked list
        List<String> linkedList = new LinkedList<>();
        linkedList.add("First");
        linkedList.add("Second");

        // When to use what?
        // ArrayList: Fast random access, slow insertion/deletion
        // LinkedList: Slow random access, fast insertion/deletion
    }
}
Enter fullscreen mode Exit fullscreen mode

Set Interface

import java.util.*;

public class SetDemo {
    public static void main(String[] args) {
        // HashSet: No order, no duplicates
        Set<String> hashSet = new HashSet<>();
        hashSet.add("Apple");
        hashSet.add("Banana");
        hashSet.add("Apple");  // Duplicate ignored
        System.out.println(hashSet);  // [Banana, Apple] (unordered)

        // LinkedHashSet: Insertion order maintained
        Set<String> linkedHashSet = new LinkedHashSet<>();
        linkedHashSet.add("Apple");
        linkedHashSet.add("Banana");
        linkedHashSet.add("Cherry");
        System.out.println(linkedHashSet);  // [Apple, Banana, Cherry]

        // TreeSet: Sorted order
        Set<Integer> treeSet = new TreeSet<>();
        treeSet.add(5);
        treeSet.add(1);
        treeSet.add(3);
        System.out.println(treeSet);  // [1, 3, 5] (sorted)
    }
}
Enter fullscreen mode Exit fullscreen mode

Map Interface

import java.util.*;

public class MapDemo {
    public static void main(String[] args) {
        // HashMap: Key-value pairs, no order
        Map<String, Integer> hashMap = new HashMap<>();
        hashMap.put("Apple", 100);
        hashMap.put("Banana", 150);
        hashMap.put("Apple", 120);  // Overwrites previous value

        System.out.println(hashMap.get("Apple"));  // 120
        System.out.println(hashMap.containsKey("Banana"));  // true

        // Iteration
        for (Map.Entry<String, Integer> entry : hashMap.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }

        // TreeMap: Sorted by keys
        Map<String, Integer> treeMap = new TreeMap<>();
        treeMap.put("Zebra", 1);
        treeMap.put("Apple", 2);
        treeMap.put("Mango", 3);
        System.out.println(treeMap);  // {Apple=2, Mango=3, Zebra=1}
    }
}
Enter fullscreen mode Exit fullscreen mode

Comparable vs Comparator

import java.util.*;

// Comparable: Natural ordering (inside class)
class Student implements Comparable<Student> {
    String name;
    int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Student other) {
        return this.age - other.age;  // Sort by age
    }

    @Override
    public String toString() {
        return name + "(" + age + ")";
    }
}

// Comparator: Custom ordering (outside class)
class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student s1, Student s2) {
        return s1.name.compareTo(s2.name);  // Sort by name
    }
}

public class ComparableVsComparator {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("John", 22));
        students.add(new Student("Alice", 20));
        students.add(new Student("Bob", 21));

        // Natural ordering (Comparable)
        Collections.sort(students);
        System.out.println(students);  // Sort by age

        // Custom ordering (Comparator)
        Collections.sort(students, new NameComparator());
        System.out.println(students);  // Sort by name

        // Lambda (Java 8+)
        Collections.sort(students, (s1, s2) -> s1.age - s2.age);
    }
}
Enter fullscreen mode Exit fullscreen mode

⏱️ Multithreading

Creating Threads

// Method 1: Extend Thread class
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

// Method 2: Implement Runnable interface (Preferred)
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
}

public class ThreadCreation {
    public static void main(String[] args) {
        // Method 1
        MyThread t1 = new MyThread();
        t1.start();

        // Method 2
        Thread t2 = new Thread(new MyRunnable());
        t2.start();

        // Lambda (Java 8+)
        Thread t3 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        });
        t3.start();
    }
}
Enter fullscreen mode Exit fullscreen mode

Synchronization

class Counter {
    private int count = 0;

    // Synchronized method
    public synchronized void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class SynchronizationDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();

        // Without synchronization: Race condition
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });

        t1.start();
        t2.start();

        t1.join();  // Wait for t1 to finish
        t2.join();  // Wait for t2 to finish

        System.out.println("Count: " + counter.getCount());  // 2000 (with sync)
    }
}
Enter fullscreen mode Exit fullscreen mode

πŸš€ Java 8+ Features

Lambda Expressions

import java.util.*;

public class LambdaDemo {
    public static void main(String[] args) {
        // Traditional way
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Before Java 8
        for (Integer num : numbers) {
            System.out.println(num);
        }

        // Java 8: Lambda
        numbers.forEach(num -> System.out.println(num));

        // Method reference
        numbers.forEach(System.out::println);
    }
}
Enter fullscreen mode Exit fullscreen mode

Stream API

import java.util.*;
import java.util.stream.*;

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Filter even numbers and square them
        List<Integer> result = numbers.stream()
                .filter(n -> n % 2 == 0)
                .map(n -> n * n)
                .collect(Collectors.toList());

        System.out.println(result);  // [4, 16, 36, 64, 100]

        // Sum
        int sum = numbers.stream()
                .reduce(0, (a, b) -> a + b);
        System.out.println("Sum: " + sum);
    }
}
Enter fullscreen mode Exit fullscreen mode

🧠 Memory Management

Stack vs Heap

public class MemoryManagement {
    public static void main(String[] args) {
        int x = 10;  // Stack
        String str = new String("Hello");  // Reference in stack, object in heap

        /*
        STACK:
        main() frame
        β”œβ”€ x: 10
        └─ str: reference β†’ "Hello" object in heap

        HEAP:
        String object: "Hello"
        */
    }
}
Enter fullscreen mode Exit fullscreen mode

Garbage Collection

public class GarbageCollectionDemo {
    public static void main(String[] args) {
        String s1 = new String("Hello");
        String s2 = new String("World");

        s1 = null;  // Object eligible for GC
        s2 = null;  // Object eligible for GC

        // Request GC (not guaranteed)
        System.gc();

        // finalize() called before GC (deprecated in Java 9+)
    }
}
Enter fullscreen mode Exit fullscreen mode

🎯 Final Question: Tips

Most Important Concepts to Master:

  1. OOP principles with examples
  2. String immutability and pool
  3. Collections: HashMap internals
  4. Exception handling hierarchy
  5. Multithreading and synchronization
  6. Java 8 streams and lambda
  7. Memory management

How to Learn Effectively:

  1. Start with simple explanation
  2. Connect to real-world examples
  3. Write and run code examples
  4. Understand trade-offs and when to use what
  5. Practice explaining concepts out loud

πŸ‘¨β€πŸ’» Author

Rajat


That's Part 2! Master these concepts and you're ready for any Java interview! πŸš€

Made with ❀️ for aspiring developers

Top comments (0)