Classes & Objects Interview Questions & Answers
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.
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:
- Static fields/blocks run once when the class first loads.
- On
new: the superclass constructor runs first (viasuper), all the way up toObject. - Then instance field initializers and instance initializer blocks run.
- 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 upstaticstate that needs more than one line. - An instance initializer block (
{ ... }) runs on everynew, aftersuper()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
newgets 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.
finalclass — cannot be extended (e.g.String,Integer). Locks the design and lets the JIT optimize.finalmethod — cannot be overridden by subclasses; preserves critical behavior.finalfield — 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 ownBuilder.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
staticmembers 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:
- Mark the class
final(no subclass can add mutability). - Make all fields
private final. - Set everything in the constructor; provide no setters.
- 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 Object-Oriented Programming interview questions
More ways to practice
The self-quiz is live. Get notified when mock interviews and new question packs drop.