Skip to content

Java · Object-Oriented Programming

Java Classes, Objects & Constructors — A Complete Guide

8 min read Updated 2026-06-20 Share:

Practice Classes & Objects interview questions

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.

More ways to practice

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

or
Join our WhatsApp Channel