Classes and objects in Java
Every Java program is built from classes, and almost everything you do at runtime
happens to objects. Before you can reason about inheritance or polymorphism, you need a
firm grip on the basics: what a class actually defines, how an object comes to life with
new, and the rules that govern constructors, static members, and visibility. Interviews
lean on this material because it reveals whether you understand the object model or just
memorized syntax. This guide walks through the mechanics and the design judgment that goes
with them.
A class is a blueprint, an object is an instance
A class is a blueprint — it declares fields (state) and methods (behavior).
An object is a concrete instance of that blueprint, created with new and living
on the heap with its own copy of the instance fields. One class can produce many objects,
each with independent state.
class Car {
String color; // field — per-object state
void paint(String c) { // method — behavior
color = c;
}
}
Car a = new Car(); // object 1
Car b = new Car(); // object 2 — separate state
a.paint("red"); // doesn't touch b's color
The class is the type; the object is a value of that type sitting in memory. A useful test: if you find yourself saying "the X" you usually mean a class; "an X" or "this X" you mean an object.
Constructors: how an object is initialized
A constructor initializes a new object. It has the class's name, no return type
(not even void), and runs automatically when you use new. If you write none, the
compiler hands you a default no-arg constructor — but the moment you declare any
constructor, that freebie disappears.
class User {
String name;
int age;
User() { this("anon", 0); } // no-arg, delegates
User(String name, int age) { // primary constructor
this.name = name;
this.age = age;
}
}
Constructors can be overloaded (several signatures), they're not inherited, and
they can't be final, static, or abstract. A constructor named like the class but
with a return type is just a method — a classic trick question. Rule of thumb: no
return type + class name = constructor.
Constructor chaining with this() and super()
Real classes often have several constructors. Rather than duplicate setup, you chain
them: this(...) delegates to another constructor in the same class, and super(...)
calls a constructor in the parent class. Both must be the first statement in the
constructor body — and since there's only one first statement, you use at most one of
them.
class Base {
Base(int n) { /* parent setup */ }
}
class Sub extends Base {
Sub() { this(5); } // delegate within Sub...
Sub(int n) { super(n); } // ...which then reaches the parent
}
The idiom is to funnel every overloaded constructor through this(...) down to one
primary constructor that finally calls super(...). If you write no explicit
super(...), the compiler inserts a call to the parent's no-arg constructor — which fails
to compile if the parent has none. Centralizing validation in the primary constructor means
you write the checks once.
this vs super
this is a reference to the current object. You use it to disambiguate a field from a
same-named parameter, to chain constructors via this(...), and to pass the current
instance to other methods. super refers to the parent portion of the object — handy
for calling a parent constructor or a parent method you've overridden.
class Point {
int x, y;
Point(int x, int y) {
this.x = x; // field vs parameter
this.y = y;
}
Point() { this(0, 0); } // constructor chaining
}
The two are mirror images: this looks at this object's own members, super reaches one
level up the hierarchy. Within classes-and-objects work you'll mostly meet this for field
disambiguation and constructor delegation.
Static vs instance members
By default a field or method is an instance member — every object gets its own copy,
accessed through a reference. The static keyword binds a member to the class itself:
one shared copy for all instances, accessed through the class name. A static method has
no this, so it can't touch instance fields directly — it's for utilities, factories,
and constants.
class Counter {
static int total; // one shared copy across all Counters
int id; // unique per object
Counter() { id = ++total; } // bumps the shared count
}
new Counter(); // id = 1
new Counter(); // id = 2
int howMany = Counter.total; // 2 — read via the class name
Rule of thumb: if a value or behavior is 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. Because static methods aren't tied to an object, they participate in no runtime
polymorphism.
final: assign-once and locked-down
final means "cannot change," but what it locks depends on where it sits. A final
field is assign-once and must be set by the end of construction — ideal for immutable
state. A final method can't be overridden by subclasses, and a final class
can't be extended at all (String and Integer are both final).
class Account {
final long id; // must be set during construction
Account(long id) { this.id = id; } // assigned exactly once
}
final class Constants { } // no subclass can extend this
A subtle point: final on a reference fixes the reference, not the object it points to —
a final List can still have elements added. Marking genuinely-constant fields final
documents intent, prevents accidental reassignment, and is the backbone of immutable
designs.
Access modifiers: controlling visibility
Java has four levels of access, from most to least restrictive: private (same class
only), package-private (the default — no keyword — same package only), protected
(same package plus subclasses anywhere), and public (everywhere). Choosing the tightest
level that works keeps coupling low and gives you freedom to refactor internals.
public class Api {
private int secret; // this class only
int packageScoped; // same package (default)
protected int forSubs; // package + subclasses
public int open; // everyone
}
Default to private fields and widen only when a real caller needs access. A common
surprise: protected also grants package access, so it's broader than "subclasses
only." Encapsulating fields behind methods lets you add validation or change representation
without breaking callers.
Initializer blocks
Most setup belongs in field initializers or constructors, but sometimes initialization
needs real logic — a loop, a try/catch — that a single expression can't express. Java
offers two block forms. A static initializer block (static { ... }) runs once
when the class first loads; an instance initializer block ({ ... }) runs on every
new, after super() and before the constructor body, sharing setup across overloaded
constructors.
class Config {
static final Map<String, String> DEFAULTS;
static { // runs once at class load
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 the initialization genuinely needs statements. Knowing the run order — static block once, then per-instance the block before the constructor body — is a frequent interview probe.
Creating objects: new and the heap
The normal way to make an object is new, which allocates memory on the heap, runs the
matching constructor, and returns a reference you store in a variable (which lives on the
stack). The reference is how you reach the object; the object itself stays on the heap until
no references remain, at which point the garbage collector reclaims it.
User a = new User(); // allocates on the heap, runs constructor
User b = a; // copies the reference, not the object
b.name = "Bo"; // a.name is now "Bo" too — same object
Beyond new, objects can also be made via factory methods (List.of(...)), the
reflection API, clone(), and deserialization — and the last two can bypass
constructors entirely, which is why immutable types and singletons sometimes need extra
guards. For everyday code, new and factory methods are what you reach for.
Recap
A class is the blueprint; an object is a heap-resident instance with its own copy
of the instance fields. Constructors initialize objects — same name as the class, no
return type — and you chain them with this(...) (same class) and super(...)
(parent), each required to be the first statement so only one appears. this references the
current object, super the parent portion. static members belong to the class (one
shared copy, no this); instance members belong to each object. final locks fields,
methods, or whole classes; the four access modifiers control visibility, with private
as the sensible default. Reach for initializer blocks only when setup needs real logic,
and remember that new allocates on the heap and hands back a reference. Master these
fundamentals and the harder OOP topics — inheritance, polymorphism, immutability — all
build cleanly on top.