Skip to content

Classes & Objects Interview Questions & Answers

17 questions Updated 2026-06-19 Share:

Java classes and objects interview questions — constructors and chaining, this vs super, static vs instance members, final, access modifiers, initializer blocks, nested classes, immutability and the ways to create an object.

Read the in-depth guideJava Classes, Objects & Constructors — A Complete Guide(opens in new tab)
17 of 17

A class is a blueprint — it defines fields (state) and methods (behavior). An object is a concrete instance of that blueprint, created with new, living on the heap with its own copy of the instance fields.

class Car { String color; }       // blueprint
Car a = new Car();                // object 1
Car b = new Car();                // object 2 — separate state
a.color = "red";                  // doesn't affect b

One class, many objects. Rule of thumb: the class is the type; the object is a value of that type sitting in memory.

Encapsulation means keeping fields private and exposing controlled access through methods, so an object protects its own invariants and you can change the internals without breaking callers.

class Account {
  private double balance;           // hidden state
  public void deposit(double amt) {
    if (amt <= 0) throw new IllegalArgumentException();
    balance += amt;                 // validated mutation
  }
  public double getBalance() { return balance; }
}

Benefits: validation in one place, freedom to refactor representation, easier debugging (state changes go through known methods), and thread-safety hooks. A public mutable field gives up all of these.

A constructor initializes a new object. It has the class's name, no return type, and runs when you use new. If you write none, the compiler supplies a default no-arg constructor.

class User {
  String name;
  User() { this("anon"); }      // no-arg, chains
  User(String name) { this.name = name; } // parameterized
}

Rules interviewers probe: defining any constructor removes the free default one; constructors can be overloaded; they're not inherited (but a subclass must call one via super); and private constructors enable singletons and factory-only construction.

  • A constructor has the same name as the class and no return type (not even void); a method has any name and a declared return type.
  • A constructor runs once, automatically, during new; a method runs when you call it, as often as you like.
  • Constructors are not inherited and can't be final/static/abstract; methods can be.
class Box {
  int size;
  Box(int size) { this.size = size; }   // constructor
  int size() { return size; }           // method (named like a getter)
}

A method named like the class but with a return type is just a method, not a constructor — a classic trick. Rule of thumb: no return type + class name = constructor.

this is a reference to the current object. Uses: disambiguate a field from a same-named parameter, call another constructor in the same class (this(...), constructor chaining), and pass the current instance to other methods.

class Point {
  int x, y;
  Point(int x, int y) {
    this.x = x;          // field vs parameter
    this.y = y;
  }
  Point() { this(0, 0); } // chain to the other constructor
}

this(...) for constructor chaining, like super(...), must be the first statement — so you can't use both in one constructor.

Construction runs top-down through the hierarchy:

  1. Static fields/blocks run once when the class first loads.
  2. On new: the superclass constructor runs first (via super), all the way up to Object.
  3. Then instance field initializers and instance initializer blocks run.
  4. Then the rest of the subclass constructor body.
class A { A() { System.out.println("A ctor"); } }
class B extends A {
  int x = init();
  int init() { System.out.println("B field"); return 1; }
  B() { System.out.println("B ctor"); }
}
// new B() prints: A ctor -> B field -> B ctor

A famous trap: calling an overridable method from a constructor runs the subclass override before the subclass's fields are initialized — so it may see null/0.

No. Both this(...) (chain to another constructor in the same class) and super(...) (chain to the parent) must be the first statement in a constructor — and there can only be one first statement, so you use at most one of them.

class Base { Base(int n) { } }
class Sub extends Base {
  Sub() { this(5); }          // delegates within Sub...
  Sub(int n) { super(n); }    // ...which then reaches super
}

The pattern is to funnel overloaded constructors through this(...) down to one "primary" constructor that finally calls super(...).

  • A static initializer block (static { ... }) runs once when the class is loaded — used to set up static state that needs more than one line.
  • An instance initializer block ({ ... }) runs on every new, after super() and before the constructor body — shared setup across overloaded constructors.
class Config {
  static final Map<String,String> DEFAULTS;
  static { DEFAULTS = new HashMap<>(); DEFAULTS.put("env", "dev"); }
  final long created;
  { created = System.currentTimeMillis(); }   // runs for each instance
}

Rule of thumb: prefer field initializers or constructors; reach for blocks only when initialization needs logic (a loop, a try/catch) that a single expression can't express.

static binds a member to the class rather than an instance:

  • static field — one shared copy across all instances.
  • static method — called on the class, has no this, can't access instance members directly (utility methods, factories).
  • static nested class — doesn't hold a reference to an outer instance.
  • static block — runs once at class load for static setup.
class MathUtil {
  static final double PI = 3.14159;
  static int square(int n) { return n * n; } // MathUtil.square(5)
}

Because static methods aren't tied to an object, they can't be overridden (only hidden) and don't participate in runtime polymorphism.

  • An instance member belongs to each object — every new gets its own copy, accessed through a reference.
  • A static member belongs to the class — one copy shared by all instances, accessed through the class name.
class Counter {
  static int total;     // shared across all Counters
  int id;               // unique per Counter
  Counter() { id = ++total; }
}
new Counter(); new Counter();
Counter.total;          // 2 — shared

Rule of thumb: if a value or behavior is conceptually about the type (a constant, a factory, a running count), make it static; if it's about one object's state, keep it an instance member.

  • final class — cannot be extended (e.g. String, Integer). Locks the design and lets the JIT optimize.
  • final method — cannot be overridden by subclasses; preserves critical behavior.
  • final field — assign-once; must be set by the end of construction.
final class Constants { }          // no subclassing
class Base {
  final void critical() { }        // subclasses can't override
  final int id;
  Base(int id) { this.id = id; }   // final field set in ctor
}

Common reasons: immutability (a class meant to be immutable is often final), security/invariant protection, and clarity of intent. Note final on a reference fixes the reference, not the object it points to.

From most to least restrictive:

  • private — same class only.
  • (default / package-private) — same package only (no keyword).
  • protected — same package plus subclasses (even in other packages).
  • public — everywhere.
public class Api {
  private int secret;       // class only
  int packageScoped;        // package
  protected int forSubs;    // package + subclasses
  public int open;          // everyone
}

Default the most restrictive that works (usually private fields) — it minimizes coupling. Note protected also grants package access, which surprises people who think it's "subclasses only."

Getters/setters keep fields private while exposing access through methods, so you can add validation, change the internal representation, make a field read-only, or add logging/lazy-loading without changing callers.

class Temperature {
  private double celsius;
  public double getFahrenheit() { return celsius * 9 / 5 + 32; } // derived
  public void setCelsius(double c) {
    if (c < -273.15) throw new IllegalArgumentException();        // validated
    this.celsius = c;
  }
}

A public field locks the API to a raw value forever. Rule of thumb: expose behavior, not data — but don't add a getter/setter for every field reflexively; only where encapsulation buys something (records are better for pure data).

  • Static nested class — declared static; no link to an outer instance, a namespaced helper.
  • Inner (non-static) class — holds an implicit reference to its enclosing instance; can access its outer fields.
  • Local class — declared inside a method.
  • Anonymous class — an unnamed inner class created and instantiated at once.
class Outer {
  static class Helper { }            // static nested
  class Inner { }                    // inner — needs an Outer instance
  Runnable r = new Runnable() {      // anonymous
    public void run() { }
  };
}

Gotcha: a non-static inner class keeps the outer object alive (potential memory leak), so prefer static nested classes unless you actually need the enclosing instance.

  • new — the normal path, invokes a constructor.
  • Factory method — e.g. List.of(), Integer.valueOf(), your own Builder.build().
  • Class.getDeclaredConstructor().newInstance() — reflection.
  • clone() — copy an existing object.
  • Deserialization — reconstruct from bytes (bypasses constructors).
User a = new User();                          // new
List<Integer> b = List.of(1, 2);              // factory
User c = User.class.getDeclaredConstructor().newInstance(); // reflection

Note that reflection and deserialization can create objects without running a constructor, which is why immutability/singletons sometimes need extra guards.

A private constructor blocks outside new, which enables several patterns:

  • Singleton — one shared instance handed out by a static accessor.
  • Static factory only — force creation through named methods (of, valueOf) that can cache or pick a subtype.
  • Utility class — a class of only static members that should never be instantiated.
final class MathUtil {
  private MathUtil() { }                 // no instances
  static int square(int n) { return n * n; }
}
enum Config { INSTANCE; }                // the preferred singleton

Rule of thumb: if a class shouldn't be instantiated freely (utility, singleton, factory-controlled), hide the constructor — but for singletons an enum is the safest implementation.

Make the state impossible to change after construction:

  1. Mark the class final (no subclass can add mutability).
  2. Make all fields private final.
  3. Set everything in the constructor; provide no setters.
  4. Defensively copy mutable fields in and out (don't leak internal refs).
final class Range {
  private final int[] bounds;
  Range(int[] bounds) { this.bounds = bounds.clone(); } // copy in
  int[] bounds() { return bounds.clone(); }             // copy out
}

Immutables are inherently thread-safe, cacheable, and safe as map keys — String, Integer, and LocalDate are all built this way. (A record gives you most of this for free.)

More ways to practice

The self-quiz is live. Get notified when mock interviews and new question packs drop.

or
Join our WhatsApp Channel