When you create an instance of a Java class, what happens behind the scenes? In addition to constructors, Java supports two types of initialization blocks: static initialization blocks and instance initialization blocks. Understanding their order of execution is crucial for setting up state safely.

In this guide, we will trace the execution sequence of these blocks using a BaseballTeam class example and highlight common traps developers make when implementing object equality comparison.

Timeline of static block, instance block, and constructor execution order
Real-World Analogy: Setting Up a Sports Team

Imagine opening a new baseball stadium and scheduling games:

  • Static Initialization (static { ... }): You build the stadium, lay down the turf, and install the lights. This only happens once, when the stadium is opened.
  • Instance Initialization ({ ... }): Every time a team arrives to play, they must put on their jerseys and clean the locker room. This happens for every game.
  • Constructor (BaseballTeam()): The umpire blows the whistle, and the match begins.

1. The Execution Sequence Trace

Let's look at the implementation of BaseballTeam:

public class BaseballTeam {
    private String city, mascot;
    private int noOfPlayers;

    // Static Block
    static {
        System.out.println("Test Static initialization block");
    }

    // Instance Block
    {
        System.out.println("Test Initialization Block");
    }

    public BaseballTeam() {
        System.out.println("Hello World !!");
    }

    public static void main(String[] args) {
        System.out.println("Main Method ");
        new BaseballTeam();
    }
}

When you run the main method, here is the exact order of messages printed to the console:

  1. Test Static initialization block — The JVM loads the class into memory. This executes before the main method starts.
  2. Main Method — The main method starts.
  3. Test Initialization Block — The instance block executes because we called new BaseballTeam(). It runs before the constructor body.
  4. Hello World !! — Finally, the constructor body completes its execution.

2. Traps in the equals() and hashcode() methods

The BaseballTeam class also contains a bug in its comparison logic:

public boolean equals(Object obj) {
    if (!(obj instanceof BaseballTeam))
        return false;

    BaseballTeam other = (BaseballTeam) obj;
    return (this.city == other.city || this.mascot == other.mascot);
}

public int hashcode() { // Misspelled lowercase 'c'
    return noOfPlayers;
}
  • The OR (||) Trap: In equals(), it returns true if the cities match OR the mascots match. This means two different teams (e.g. "Boston Red Sox" and "Chicago Red Sox") would be considered equal because their mascot fields are identical!
  • The Reference (==) Trap: Comparing strings with == compares their memory address instead of character contents. Use .equals() for strings.
  • Misspelled hashcode(): The lowercase `c` means it does not override Object.hashCode(), breaking HashMap entries.

Conclusion

Remember that:

  • Static blocks run once upon class loading.
  • Instance blocks run for every object creation, preceding the constructor body.
  • Always match your comparison logic using && for mandatory unique fields and use .equals() for String data.