Lesson 05 · 10 min read · Java for placements

Classes, objects and constructors

Fields, methods, this, constructor overloading and chaining, static against instance, and the order in which initialisation actually happens.

A class is a template; an object is one thing built from it. Everything else in this lesson is detail about when memory is allocated and when code runs.

A class

public class Student {
    // fields — the state of one object
    private String name;
    private String roll;
    private int marks;

    // constructor — runs when an object is created
    public Student(String name, String roll) {
        this.name = name;
        this.roll = roll;
        this.marks = 0;
    }

    // methods — the behaviour
    public String label() {
        return roll + " — " + name;
    }

    public void record(int score) {
        if (score < 0 || score > 100) {
            throw new IllegalArgumentException("marks out of range: " + score);
        }
        this.marks = score;
    }

    public int getMarks() {
        return marks;
    }
}
Student s = new Student("Raja", "21B81A05H4");
s.record(87);
System.out.println(s.label() + " scored " + s.getMarks());

What new actually does

Four steps, and knowing them in order answers several questions at once:

  1. Allocate memory on the heap for the object's fields.
  2. Initialise fields to defaults0, false, null.
  3. Run field initialisers and instance initialiser blocks, top to bottom.
  4. Run the constructor body.

Then new evaluates to a reference to that object, which is what gets stored in s. The object is on the heap; s is a reference on the stack.

Student a = new Student("Raja", "05H4");
Student b = a;                          // second reference, same object
b.record(90);
System.out.println(a.getMarks());       // 90 — one object

Student c = new Student("Raja", "05H4");
System.out.println(a == c);             // false — different objects

Same as everywhere else: == compares references. Lesson 11 covers making equals mean what you want.

this

this is a reference to the current object. Two real uses:

Disambiguating a shadowed field, which is why constructors are full of it:

public Student(String name) {
    this.name = name;      // field = parameter; without `this` you assign the parameter to itself
}

Calling another constructor in the same class, which must be the first statement:

public class Student {
    private String name;
    private String roll;
    private int marks;

    public Student(String name, String roll, int marks) {   // the real one
        this.name = name;
        this.roll = roll;
        this.marks = marks;
    }

    public Student(String name, String roll) {
        this(name, roll, 0);          // constructor chaining
    }

    public Student() {
        this("unknown", "-");
    }
}

This is constructor chaining, and it is how you avoid duplicating validation across three constructors. this(...) must be the first statement, and you cannot use both this(...) and super(...) in one constructor — the parent is reached through the chain.

Constructor rules

  • Same name as the class, no return type.
  • If you write no constructor, the compiler supplies a no-argument default constructor that does nothing.
  • If you write any constructor, that default disappears. So adding a two-argument constructor can break new Student() elsewhere in the codebase — a genuine and commonly-asked consequence.
  • Constructors can be overloaded, and can be private (used by the singleton and builder patterns).
  • Constructors are not inherited.
class A {
    A(int x) { }
}

class B extends A {
    // compile error: no A() to call implicitly
}

Because a subclass constructor implicitly calls super() — the parent's no-argument constructor — and A no longer has one. Fix it by calling the right one explicitly:

class B extends A {
    B() { super(5); }
}

static

A static member belongs to the class, not to any object. One copy, shared:

public class Student {
    private static int count = 0;          // one, shared
    private static final String COLLEGE = "MLRIT";   // constant

    private String name;                   // one per object

    public Student(String name) {
        this.name = name;
        count++;                           // every object increments the same counter
    }

    public static int getCount() {         // callable without an object
        return count;
    }
}
new Student("Raja");
new Student("Anu");
System.out.println(Student.getCount());   // 2 — called on the CLASS

Rules that get asked directly:

  • A static method cannot use this or touch instance fields — there is no instance.
  • An instance method can use static fields freely.
  • A static method cannot be overridden. It can be hidden by a static method with the same signature in a subclass, and which one runs is decided by the reference type at compile time, not by the object. This is why you should call static methods on the class name.
  • static final in capitals is the Java convention for a constant.
public class Util {
    public static int square(int x) {
        return x * x;              // no instance state needed — correctly static
    }
}

main is static for exactly this reason: it must run before any object exists.

Initialisation order

This is a favourite "what does this print?" question. The order is fixed:

public class Demo {
    static int s = trace("1. static field");
    static { trace("2. static block"); }

    int i = trace("4. instance field");
    { trace("5. instance block"); }

    Demo() { trace("6. constructor"); }

    static int trace(String msg) {
        System.out.println(msg);
        return 0;
    }

    public static void main(String[] args) {
        trace("3. main starts");
        new Demo();
        new Demo();
    }
}

Output:

1. static field
2. static block
3. main starts
4. instance field
5. instance block
6. constructor
4. instance field
5. instance block
6. constructor

Two things to say about it. Static members initialise once, when the class is first loaded — before main. Instance members initialise on every new, in source order, before the constructor body. With inheritance, the parent's chain completes before the child's begins.

Method overloading

Same name, different parameter list. Resolved at compile time by the argument types:

public class Printer {
    void show(int x)            { System.out.println("int"); }
    void show(double x)         { System.out.println("double"); }
    void show(String x)         { System.out.println("String"); }
    void show(int x, int y)     { System.out.println("two ints"); }
    void show(int... xs)        { System.out.println("varargs"); }
}
Printer p = new Printer();
p.show(5);        // int
p.show(5.0);      // double
p.show('a');      // int  ← char widens to int; there is no show(char)
p.show(5, 6);     // two ints
p.show(1, 2, 3);  // varargs
p.show();         // varargs — an empty array

p.show('a') printing "int" is the classic trick question. When there is no exact match, Java prefers widening (charint) over boxing (charCharacter) over varargs, in that order.

The return type is not part of the signature. Two methods differing only in return type will not compile.

Getters, setters and why

private int marks;                       // nobody outside can touch it

public int getMarks() { return marks; }

public void setMarks(int marks) {
    if (marks < 0 || marks > 100) {
        throw new IllegalArgumentException("out of range");
    }
    this.marks = marks;
}

The field is private, so every write goes through one place where the rule lives. That is encapsulation with a purpose, rather than boilerplate — and if a setter has no validation and no prospect of any, a plain public field would have been honest.

Since Java 16, a class that only carries data can be a record, which writes the constructor, accessors, equals, hashCode and toString for you:

public record Point(int x, int y) { }

Point p = new Point(3, 4);
System.out.println(p.x());               // 3 — accessor is x(), not getX()
System.out.println(p);                   // Point[x=3, y=4]
System.out.println(p.equals(new Point(3, 4)));   // true

Records are implicitly final and their fields are immutable.

What to take into an interview

  • new allocates, defaults the fields, runs field initialisers and blocks, then the constructor.
  • The object lives on the heap; the reference lives on the stack.
  • this.x = x disambiguates; this(...) chains constructors and must be the first statement.
  • Writing any constructor removes the compiler's default no-argument one.
  • Constructors are not inherited, and a subclass constructor implicitly calls super().
  • static belongs to the class: one copy, no this, cannot be overridden (only hidden).
  • Static initialisers run once at class load, before main; instance ones run on every new.
  • Overloading resolves at compile time, preferring widening, then boxing, then varargs.
  • Return type is not part of the signature.
Lesson 5 of 12 · all 12 are free to read, no account. Course contents