☕
Core Java Roadmap Phase 03
0 / 40 (0%)
☰ Syllabus Index Next: P04 →

Strings, Object Contract & Memory Internals

📂 Repository Module: Module 1_ Arrays & Strings Demos \ Memory Mechanics
📖 Textbook Alignment: Volume I — Chapter 3: Strings & StringBuilder • Chapter 5: Object: The Cosmic Superclass (equals, hashCode, toString)
Phase 3

Strings, Object Contract & Memory Internals

📂 Module 1... \\ 02_Arrays_Strings_Demos & Module 2 \\ 01_Java11_Demos
📖 Core Java Textbook Alignment: Volume I — Chapter 3: Strings & StringBuilder • Chapter 5: Object: The Cosmic Superclass (equals, hashCode, toString)

3.1 String Immutability, String Constant Pool (SCP) & Memory Mechanics

📖 Cay S. Horstmann • Core Java Volume I — Chapter 3, Section 3.6 ("Strings") • Chapter 5 ("Object Contract")
“The Java designers decided to share strings. No method in the String class can change a string. A string is an immutable sequence of Unicode characters... The equals method determines whether two objects have identical state.”
  • SCP FlyweightString literals reside in the String Constant Pool in Heap memory for maximum sharing.
⚡ Pro-Tip: equals() & hashCode() Sacred Contract
If a.equals(b) is true, then a.hashCode() == b.hashCode() MUST be true! Always generate both methods together in your IDE using the exact same business key fields.
💡 Real-World Analogy: Why Strings are Immutable (Currency Engraving)
Imagine you have a $10 bill. If you want $20, you cannot take a red pen and scribble a '2' over the '1' to change its value. That would cause chaos and counterfeiting! Instead, you must receive an entirely distinct, newly minted $20 bill.
In Java, String is designed the same way:
• When you do String s = "Hello"; s = s + " World";, Java does NOT change "Hello".
• It leaves "Hello" completely untouched in memory, and mints a brand-new String object "Hello World" on the Heap!
🖊 Architectural Memory Layout: String Pool vs new String() Heap Instances Mermaid Diagram
graph TD subgraph StackSpace ["Thread Stack"] s1["s1 = 'Java'"] s2["s2 = 'Java'"] s3["s3 = new String('Java')"] end subgraph HeapSpace ["Java Heap Memory"] subgraph SCP ["String Constant Pool (SCP)"] PoolLiteral["'Java' (Single interned instance @ 0x100)"] end HeapObj["Heap Object: new String() @ 0x500
value -> points to Pool instance"] end s1 --> PoolLiteral s2 --> PoolLiteral s3 --> HeapObj HeapObj -.-> PoolLiteral
📂 StringMemoryDemo.java — Literal vs new String()
public class StringMemoryDemo {
    public static void main(String[] args) {
        // String Literals: Reuses the single instance in String Constant Pool (SCP)
        String s1 = "Java";
        String s2 = "Java";

        // 'new' Operator: FORCES a brand-new object on Heap outside SCP
        String s3 = new String("Java");

        // == compares MEMORY ADDRESS POINTERS
        System.out.println(s1 == s2);      // true  (Both point to same SCP address!)
        System.out.println(s1 == s3);      // false (Different Heap addresses!)

        // .equals() compares CHARACTER CONTENTS
        System.out.println(s1.equals(s3)); // true  (Both hold characters 'J','a','v','a')

        // .intern() moves/finds the reference in the String Constant Pool
        String s4 = s3.intern();
        System.out.println(s1 == s4);      // true  (Now pointing to same SCP instance!)
    }
}
👁 4 Core Reasons Why Strings Are Immutable in Java
  • 1. Memory Saving The String Constant Pool (SCP) allows millions of identical string literals across your application to share a single Heap instance. If strings were mutable, changing s1 would secretly corrupt s2!
  • 2. Thread Safety Immutable objects can be shared freely across 500 concurrent worker threads with ZERO locks and ZERO synchronization overhead.
  • 3. Security File paths, network socket URLs, and database connection strings are passed as Strings. If Strings were mutable, an attacker could pass a valid file path, bypass validation, and then mutate the path to /etc/shadow on another thread!
  • 4. HashCode Caching Because a String's content never changes, its hashCode() is calculated once lazily and cached. This makes HashMap lookups with String keys lightning fast.

3.2 The equals() and hashCode() Contract (The Library Analogy)

💡 Real-World Analogy: Finding a Book in a Giant Library
Imagine you walk into a library with 1,000,000 books looking for "Core Java Vol I".
• hashCode() is the Aisle Number: It tells the librarian: "Check Aisle #42". Instead of scanning all 1,000,000 books one-by-one, you instantly jump to Aisle #42 (O(1) time!).
• equals() is the Title Verification: Once inside Aisle #42, there might be 5 books on the shelf. You check each book's title to find the exact match.

THE DISASTER OF BREAKING THE CONTRACT:
If you override equals() but FORGET to override hashCode():
You put a book into the library. Later, you create an identical book with the same title to look it up. But because hashCode() was default, it generates a different aisle number (Aisle #99)!
You walk into Aisle #99 and search every book. The librarian says: "Book not found!" — even though the exact book is sitting in Aisle #42! That is why your HashMap.get(key) returns null when the contract is broken.
📂 ImmutableEmployee.java — Complete Implementation
package com.accenture.ltt.oop;

import java.util.*;

/**
 * Standard Production Immutable Class Design:
 * 1. Declare class 'final' to prevent subclasses from adding mutable state.
 * 2. All fields are 'private final'.
 * 3. No setter methods.
 * 4. Perform defensive copying in constructor for mutable objects (Lists, Dates).
 * 5. Perform defensive copying in getters.
 */
public final class ImmutableEmployee {
    private final String empId;
    private final List<String> skills;

    public ImmutableEmployee(String empId, List<String> skills) {
        this.empId = empId;
        // Defensive copy in constructor: Prevents caller from mutating our internal list!
        this.skills = new ArrayList<>(skills);
    }

    public String getEmpId() { return empId; }

    public List<String> getSkills() {
        // Return unmodifiable view: Prevents caller from adding elements to our list!
        return Collections.unmodifiableList(skills);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ImmutableEmployee that = (ImmutableEmployee) o;
        return Objects.equals(empId, that.empId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(empId);
    }
}
👁 The 5 Golden Rules of Immutability & Defensive Copying
  • Rule 1: final Class public final class ImmutableEmployee prevents malicious or accidental subclassing that could override getters and introduce mutable state.
  • Rule 2 & 3: private final Fields & No Setters Fields are initialized once during construction and can never be reassigned. Absence of setters guarantees state is fixed for the lifetime of the object.
  • Rule 4: Defensive Copy in Constructor Line 8: this.skills = new ArrayList<>(skills); — If you wrote this.skills = skills;, the caller would keep the original list pointer outside the class and could add elements (skills.add("Hacker")), quietly mutating your "immutable" object! Defensive copying clones the data into a private internal list.
  • Rule 5: Defensive Copy in Getters Line 15: return Collections.unmodifiableList(skills); — Never return raw references to mutable collections. Returning an unmodifiable wrapper guarantees anyone calling getSkills().add(...) receives an immediate UnsupportedOperationException.
📂 Module 2.../01_Java11_Demos/.../Tester1005_String_Strip.java
package com.accenture.ltt.zeleven;

public class Tester1005_String_Strip {
    public static void main(String[] args) {
        String name = "  Mohpreet  ";

        // 1. strip(): Unicode-aware removal of leading and trailing whitespace
        System.out.println("strip(): [" + name.strip() + "]");

        // 2. stripLeading(): Removes only leading whitespaces
        System.out.println("stripLeading(): [" + name.stripLeading() + "]");

        // 3. stripTrailing(): Removes only trailing whitespaces
        System.out.println("stripTrailing(): [" + name.stripTrailing() + "]");

        // 4. isBlank(): Checks if empty or contains only whitespace codepoints
        System.out.println("\"   \".isBlank(): " + "   ".isBlank()); // true (isEmpty() would be false!)

        // 5. repeat(count): Concatenates string N times without manual loop
        System.out.println("Java ".repeat(3)); // "Java Java Java "
    }
}
👁 Java 11 Modern String Enhancements: strip() vs trim()
  • strip() vs trim() trim() was introduced in Java 1.0 and only detects ASCII whitespace (characters ≤ U+0020). strip() is the modern Unicode-aware replacement using Character.isWhitespace(c), properly stripping non-breaking spaces and international Unicode whitespace.
  • isBlank() vs isEmpty() isEmpty() checks if length == 0. " ".isEmpty() returns false! isBlank() accurately identifies strings consisting purely of whitespace characters, eliminating manual regex or Apache Commons utility dependencies.
📂 Module 1.../Part-1-Demos/.../TracingWithLogger.java
package com.accenture.ltt;

import java.util.logging.Logger;
import java.util.logging.Level;

public class TracingWithLogger {
    // Thread-safe static logger instance bound to class name
    private static final Logger LOG = Logger.getLogger(TracingWithLogger.class.getName());

    public static void main(String[] args) {
        LOG.setLevel(Level.ALL); // Capture all tracing thresholds
        LOG.info("=== Enterprise Tracing With Logger START ===");

        int[] numbers = {2, -1, 3};
        int total = process(numbers);

        LOG.info("Final computed total = " + total);
        LOG.info("=== Execution Finished Successfully ===");
    }

    static int process(int[] arr) {
        LOG.info("process() called with array length=" + arr.length);
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            LOG.fine("At index " + i + ", value=" + arr[i]);
            sum = add(sum, arr[i]);
        }
        return sum;
    }

    static int add(int a, int b) {
        if (b < 0) {
            LOG.warning("add() received negative parameter: " + b); // Highlight potential business anomaly
        } else {
            LOG.info("add() adding " + b);
        }
        return a + b;
    }
}
👁 Production Logging Levels & Diagnostic Tracing
  • Logger vs System.out.println Never use System.out.println in enterprise code. System.out causes thread blocking on stdout streams, cannot be filtered dynamically, and lacks timestamps, thread identifiers, or log level severities.
  • Log Level Hierarchy SEVERE (System crash/fatal) > WARNING (Recoverable anomaly) > INFO (Normal operational milestone) > FINE/DEBUG (Diagnostic execution steps). In production, set level to INFO to keep log volume minimal; set to FINE during diagnostic investigations.