Command Palette

Search for a command to run...

[Java Basics] Fields, Methods and Constructors in Java

A class describes what its objects know and what they can do. What they know is held in its fields; what they can do is its instance methods; and the code that turns a raw, zero-filled object into a usable one is its constructor.

This article covers all three, with most of the space going to constructors, because that is where the traps are: a keyword that silently demotes a constructor to an ordinary method, a no-argument constructor that vanishes the moment you write one of your own, and an initialisation order that almost no tutorial shows. Every error message and every line of output below is real, produced by OpenJDK 21.0.6.

A new call filling an object field slots, default values replaced by real ones

Article 23 covered classes, objects and new. This one is about what goes inside the class body.

Fields: the state of an object

A field is a variable declared directly in the class body, outside every method. Every object of the class gets its own copy of every field, and that set of values is the object's state.

class Account {
    String owner;
    int balance;
    double rate;
    boolean frozen;
    char tier;
    int[] history;
}

Six declarations, no initialisers, and the class compiles. That is the first difference from a local variable: a field never has to be assigned before it is read.

Default values, and where they come from

Article 6 established the rule and this is where it starts to matter: a field with no initialiser is given the default value for its type before any of your code runs.

Account a = new Account();
System.out.println("owner   " + a.owner);
System.out.println("balance " + a.balance);
System.out.println("rate    " + a.rate);
System.out.println("frozen  " + a.frozen);
System.out.println("tier    code " + (int) a.tier);
System.out.println("history " + a.history);
owner   null
balance 0
rate    0.0
frozen  false
tier    code 0
history null

Numeric fields start at zero, boolean at false, char at code point 0, and every reference type at null. That guarantee is exactly why a half-built object is dangerous rather than impossible: the object exists, every field has a value, and nothing tells you the values are meaningless. Making them meaningful is the constructor's job.

Field initialisers run for every instance

A field can carry an initialiser, and it is not evaluated once for the class — it is evaluated once per object.

class Counter {
    int value = 10;
    String label = "counter";
    java.util.List<String> log = new java.util.ArrayList<>();
}
Counter a = new Counter();
Counter b = new Counter();
a.value = 99;
a.log.add("a only");
System.out.println("a.value " + a.value + ", b.value " + b.value);
System.out.println("a.log " + a.log + ", b.log " + b.log);
System.out.println("same list object? " + (a.log == b.log));
a.value 99, b.value 10
a.log [a only], b.log []
same list object? false

new java.util.ArrayList<>() ran twice, so a and b hold two different lists, and writing through one does not touch the other. Two objects never share a field slot.

Instance methods: behaviour that reads and writes fields

An instance method is a method declared without static. It belongs to an object, is called through a reference, and can read and write that object's fields by their bare names.

A class holding one shared copy of the method code beside two heap objects, each with its own field slots, and the constructor filling them

class Counter {
    int count;
    String name;

    Counter(String name) {
        this.name = name;
    }

    void increment() {
        count++;
    }

    void add(int n) {
        count += n;
    }

    boolean isEmpty() {
        return count == 0;
    }

    String describe() {
        return name + " = " + count;
    }

    static int sum(int a, int b) {
        return a + b;
    }
}
Counter clicks = new Counter("clicks");
Counter errors = new Counter("errors");

clicks.increment();
clicks.increment();
clicks.add(5);

System.out.println(clicks.describe());
System.out.println(errors.describe());
System.out.println("errors empty? " + errors.isEmpty());
System.out.println("Counter.sum(2, 3) = " + Counter.sum(2, 3));
clicks = 7
errors = 0
errors empty? true
Counter.sum(2, 3) = 5

count++ inside increment() has no receiver written in front of it, and it still means "the count of whichever object this call was made on". clicks.increment() moved clicks.count; errors.count stayed at its default.

That is the whole difference from the static methods of article 19. A static method belongs to the class, is called as Counter.sum(2, 3), and has no object to read fields from — which is why article 19's non-static method cannot be referenced from a static context error exists at all. An instance method has an object, so a field name is enough.

static methodinstance method
Belongs tothe classone object
Called asCounter.sum(2, 3)clicks.increment()
Can read fieldsonly static onesthis object's fields, by name
Copies in memoryoneone, shared by every object

The last row is worth pausing on: an instance method is not copied per object. There is one compiled body for increment() no matter how many Counter objects exist. What is per-object is the field data it operates on.

Constructors: same name as the class, no return type

A constructor is the member that runs as part of new. Two rules define it and there is no third:

  1. Its name is exactly the class name.
  2. It declares no return type at all — not even void.
class Book {
    String title;
    int pages;

    Book(String title, int pages) {
        this.title = title;
        this.pages = pages;
    }
}

Anything else with a name and a parameter list in a class body is a method. The compiler decides which one you wrote purely by looking for a return type, and that is the source of the single most confusing bug in this article.

The void bug: a constructor that silently is not one

Write void in front of it and you have not written a broken constructor. You have written a perfectly ordinary method that happens to be named after the class.

class Book {
    String title;
    int pages;

    void Book() {                 // BUG: void makes this an ordinary method
        title = "Effective Java";
        pages = 412;
    }
}

public class VoidBug {
    public static void main(String[] args) {
        Book b = new Book();
        System.out.println("title " + b.title);
        System.out.println("pages " + b.pages);
    }
}
title null
pages 0

No error, no warning — javac -Xlint:all says nothing at all — and the assignments never ran. new Book() called the implicit no-argument constructor, which exists precisely because the class declares no constructor of its own. The method named Book just sits there, never called.

⚠️ If an object comes out with null and 0 in every field despite a constructor that clearly assigns them, check for a return type on that constructor before you check anything else.

The version with parameters at least fails loudly, because the implicit constructor takes no arguments:

class Note {
    String text;
    int priority;

    void Note(String text, int priority) {   // BUG: void
        this.text = text;
        this.priority = priority;
    }
}
VoidBugArgs.java:13: error: constructor Note in class Note cannot be applied to given types;
        Note n = new Note("call the bank", 2);
                 ^
  required: no arguments
  found:    String,int
  reason: actual and formal argument lists differ in length
1 error

Read required: no arguments as the tell. You wrote a two-argument constructor and the compiler says the class only has a no-argument one — which means what you wrote is not a constructor.

A constructor also cannot return a value, and the message for that is unusually clear:

Thing(int v) {
    this.v = v;
    return v;
}
CtorReturnValue.java:6: error: incompatible types: unexpected return value
        return v;
               ^
1 error

A bare return; is legal, though, and it exits the constructor early exactly like it exits a void method:

class Guarded {
    int value;

    Guarded(int value) {
        if (value < 0) {
            System.out.println("negative, leaving value at " + this.value);
            return;
        }
        this.value = value;
    }
}
7
negative, leaving value at 0
0

The object is still created; it just keeps the default in that field. Throwing is almost always the better answer, and the worked example at the end does that.

The implicit no-argument constructor

A class with no constructor of its own gets one for free. It takes no arguments and its body is empty, which is enough because the fields have already been defaulted.

public class Bare {
    int x;
    String s;
}

javap shows the constructor the compiler added:

Compiled from "Bare.java"
public class Bare {
  int x;
  java.lang.String s;
  public Bare();
}

The important half of the rule is the other half: you get it only when you declare no constructor at all. Declare one — any one — and the free no-argument constructor is gone.

class User {
    String name;
    int age;

    User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

public class NoDefaultCtor {
    public static void main(String[] args) {
        User u = new User();
        System.out.println(u.name);
    }
}
NoDefaultCtor.java:13: error: constructor User in class User cannot be applied to given types;
        User u = new User();
                 ^
  required: String,int
  found:    no arguments
  reason: actual and formal argument lists differ in length
1 error

This breaks working code the day you add your first constructor to a class other people already call with new Thing(). The fix is to add the no-argument constructor back explicitly, which is a deliberate decision rather than an accident: if a User without a name is not a valid User, not having that constructor is the point.

Overloading constructors

Constructors overload on exactly the same terms as methods, under article 20's rules: the parameter list distinguishes them, the compiler picks one at compile time from the static types of the arguments, and two constructors may not share a parameter list.

class Rectangle {
    int width;
    int height;
    String label;

    Rectangle() {
        this.width = 1;
        this.height = 1;
        this.label = "unit";
    }

    Rectangle(int side) {
        this.width = side;
        this.height = side;
        this.label = "square";
    }

    Rectangle(int width, int height) {
        this.width = width;
        this.height = height;
        this.label = "rectangle";
    }

    int area() {
        return width * height;
    }

    public String toString() {
        return label + " " + width + "x" + height + ", area " + area();
    }
}
System.out.println(new Rectangle());
System.out.println(new Rectangle(4));
System.out.println(new Rectangle(3, 5));
unit 1x1, area 1
square 4x4, area 16
rectangle 3x5, area 15

Duplicating a parameter list is a compile error, and renaming the parameters does not help, because parameter names are not part of a signature:

DupCtor.java:10: error: constructor Pair(int,int) is already defined in class Pair
    Pair(int x, int y) {
    ^
1 error

What is wrong with the Rectangle above is not the overloading — it is that the assignment logic is written out three times. Add a fourth field and there are three places to remember.

Chaining constructors with this(...)

this(...) calls another constructor of the same class. Write the full initialisation once and let the shorter constructors delegate into it.

class Rect {
    int width;
    int height;
    String label;

    Rect(int width, int height, String label) {
        this.width = width;
        this.height = height;
        this.label = label;
    }

    Rect(int side) {
        this(side, side, "square");
    }

    Rect() {
        this(1, 1, "unit");
    }

    int area() {
        return width * height;
    }

    public String toString() {
        return label + " " + width + "x" + height + ", area " + area();
    }
}
unit 1x1, area 1
square 4x4, area 16
rectangle 3x5, area 15

Identical output, one place that assigns fields. This is the constructor form of article 20's forwarding pattern, and the JDK uses it everywhere.

this(...) must be the first statement

There is one hard rule: a this(...) call has to be the very first statement in the constructor body. Not the first assignment, not the first interesting line — the first statement.

Three overloaded constructors delegating through this into the fullest one, with the first-statement rule and the real javac error

Box(int side) {
    System.out.println("making a square");
    this(side, side, "square");
}
ThisNotFirst.java:14: error: call to this must be first statement in constructor
        this(side, side, "square");
            ^
1 error

The same message appears if you try to call this(...) from an ordinary method, which is a reasonable-looking way to write a reset() and is not allowed:

ThisInMethod.java:9: error: call to this must be first statement in constructor
        this(0);
            ^
1 error

The rule is not arbitrary. Field initialisers and instance initialiser blocks are compiled into every constructor that does not start with this(...), so exactly one constructor in a delegation chain runs them. If a statement could run before the delegation, it would run before those initialisers, on an object whose fields were still all zero.

Chaining is easy to see in a trace. Here is a three-constructor Session with a field initialiser and an instance block, constructed with the shortest constructor:

class Session {
    String user = trace("field initialiser", "guest");
    int timeoutMs;
    boolean tls;

    {
        System.out.println("instance block");
    }

    Session(String user, int timeoutMs, boolean tls) {
        System.out.println("Session(String, int, boolean)");
        this.user = user;
        this.timeoutMs = timeoutMs;
        this.tls = tls;
    }

    Session(String user, int timeoutMs) {
        this(user, timeoutMs, true);
        System.out.println("Session(String, int)");
    }

    Session(String user) {
        this(user, 5000);
        System.out.println("Session(String)");
    }

    static String trace(String msg, String value) {
        System.out.println(msg);
        return value;
    }

    public String toString() {
        return "Session[user=" + user + ", timeoutMs=" + timeoutMs + ", tls=" + tls + "]";
    }
}
Session s = new Session("ana");
System.out.println(s);
field initialiser
instance block
Session(String, int, boolean)
Session(String, int)
Session(String)
Session[user=ana, timeoutMs=5000, tls=true]

Two things to take from that trace. The field initialiser and the instance block ran once, and they ran inside Session(String, int, boolean) — the only constructor that does not delegate. And the bodies finished in reverse order of the calls, because this(...) is a call: the innermost constructor returns first.

A constructor cannot call itself

Delegation must terminate, and javac proves it statically rather than letting you find out with a StackOverflowError. Direct self-calls and cycles are both rejected:

class Loop {
    int n;

    Loop() {
        this();
    }

    Loop(int n) {
        this(n, 0);
    }

    Loop(int n, int m) {
        this(n);
        this.n = n + m;
    }
}
RecursiveCtor.java:5: error: recursive constructor invocation
        this();
        ^
RecursiveCtor.java:13: error: recursive constructor invocation
        this(n);
        ^
2 errors

The first error is the obvious one. The second is the cycle: Loop(int) delegates to Loop(int, int), which delegates back. A chain has to end at a constructor that assigns fields itself.

Parameters that shadow fields

Naming a parameter after the field it sets is standard Java style, and it means the parameter shadows the field inside that constructor: the bare name x refers to the parameter. Which makes the most useless statement in the language compile without a murmur.

class Point {
    int x;
    int y;

    Point(int x, int y) {
        x = x;              // BUG: assigns the parameter to itself
        y = y;
    }

    Point(int x, int y, boolean fixed) {
        this.x = x;         // the fix
        this.y = y;
    }
}
Point broken = new Point(3, 4);
System.out.println("broken  x=" + broken.x + " y=" + broken.y);
Point good = new Point(3, 4, true);
System.out.println("good    x=" + good.x + " y=" + good.y);
broken  x=0 y=0
good    x=3 y=4

javac -Xlint:all reports nothing here either. x = x reads the parameter and writes the parameter; the field is never touched and keeps its default. this.x is what reaches past the shadow to the field, and this.x = x; is the line you write in almost every constructor you will ever write. Article 25 covers this as a keyword in its own right — for now, read it as "this object".

Initialisation order: what new really runs

Fields, initialisers and the constructor body do not run in the order most people assume. The rule is precise: field initialisers and instance initialiser blocks run in source order, all of them, before the constructor body.

The class body in source order beside the real interleaved output, with the constructor body running last

Print statements settle it. The class below interleaves a field initialiser, an instance block, another field initialiser and another instance block, then a constructor:

class Widget {
    int a = trace("field a = 1", 1);

    { System.out.println("instance block 1"); }

    int b = trace("field b = 2", 2);

    { System.out.println("instance block 2"); }

    Widget() {
        System.out.println("constructor body, a=" + a + " b=" + b);
    }

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

public class InitOrder {
    public static void main(String[] args) {
        System.out.println("before new");
        new Widget();
        System.out.println("after new");
    }
}
before new
field a = 1
instance block 1
field b = 2
instance block 2
constructor body, a=1 b=2
after new

Field initialisers and instance blocks are not two separate phases; they are one phase, in the order they appear in the file. The constructor body is a second phase that always comes last, which is why it can already read a and b and why anything it assigns wins.

Source order is enforced in the other direction too. An initialiser cannot read a field declared below it:

class Config {
    int total = base + 10;      // base is declared below
    int base = 5;
}
ForwardRef.java:2: error: illegal forward reference
    int total = base + 10;      // base is declared below
                ^
1 error

The bytecode says the same thing

javap -c shows what the compiler actually does with initialisers: it copies them into the top of every constructor.

public class Copied {
    int a = 1;

    Copied() {
        a = 10;
    }

    Copied(int a) {
        this.a = a;
    }
}
  Copied();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield      #7                  // Field a:I
       9: aload_0
      10: bipush        10
      12: putfield      #7                  // Field a:I
      15: return

  Copied(int);
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield      #7                  // Field a:I
       9: aload_0
      10: iload_1
      11: putfield      #7                  // Field a:I
      14: return

Both constructors store 1 into a and then immediately store the constructor's own value over it. That is the initialiser, duplicated. Now change Copied() to delegate instead:

public class Chained {
    int a = 1;

    Chained() {
        this(10);
    }

    Chained(int a) {
        this.a = a;
    }
}
  Chained();
    Code:
       0: aload_0
       1: bipush        10
       3: invokespecial #1                  // Method "<init>":(I)V
       6: return

The initialiser is gone from the delegating constructor — three instructions, all delegation. That is the mechanical reason this(...) has to be first, and the reason chaining runs the initialisers exactly once. Constructors are named <init> in bytecode, which is also why they show up that way in stack traces.

Instance initialiser blocks

An instance initialiser block is a bare { ... } in the class body. It runs on every new, in source order with the field initialisers, before the constructor body. Its one real use is setup shared by several constructors:

class Report {
    String title;
    java.util.List<String> rows;
    String generatedBy;

    {
        rows = new java.util.ArrayList<>();
        generatedBy = "report-tool";
        System.out.println("shared setup ran");
    }

    Report(String title) {
        this.title = title;
    }

    Report(String title, String firstRow) {
        this.title = title;
        rows.add(firstRow);
    }

    public String toString() {
        return title + " " + rows + " by " + generatedBy;
    }
}
System.out.println(new Report("Q3"));
System.out.println(new Report("Q4", "opening line"));
shared setup ran
Q3 [] by report-tool
shared setup ran
Q4 [opening line] by report-tool

Both constructors got the list and the tool name without either one mentioning them.

Usually you should not write one. A field initialiser handles a single-expression default more readably, and this(...) chaining handles shared setup while keeping the code in a constructor where a reader looks for it. The block is worth reaching for when the setup needs several statements and cannot be expressed as one initialiser — and even then, a helper method called from the fullest constructor is often clearer. It has one genuinely unique use, the double-brace idiom for anonymous subclasses, which belongs to a much later article.

Objects that hold other objects

A field can have another class as its type. Nothing new is needed: the field holds a reference, it defaults to null, and the constructor's job is to point it at something.

class Address {
    String city;
    String country;

    Address(String city, String country) {
        this.city = city;
        this.country = country;
    }

    public String toString() {
        return city + ", " + country;
    }
}

class Customer {
    String name;
    Address address;

    Customer(String name, String city, String country) {
        this.name = name;
        this.address = new Address(city, country);
    }

    Customer(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public String toString() {
        return name + " (" + address + ")";
    }
}
Customer a = new Customer("Ana", "Hanoi", "VN");
Address shared = new Address("Berlin", "DE");
Customer b = new Customer("Bo", shared);

System.out.println(a);
System.out.println(b);

shared.city = "Munich";
System.out.println("after shared.city = \"Munich\": " + b);
Ana (Hanoi, VN)
Bo (Berlin, DE)
after shared.city = "Munich": Bo (Munich, DE)

The two constructors differ in a way worth understanding. The first creates the Address, so nothing outside can reach it. The second stores the caller's reference, so the caller still holds the same object and can change it under you — which the last two lines demonstrate. Neither is wrong; the choice is whether the object owns its parts or borrows them.

Forgetting to point a reference field at anything is the classic runtime failure of this section:

class Cart {
    String owner;
    java.util.List<String> items;      // never initialised

    Cart(String owner) {
        this.owner = owner;
    }

    void add(String item) {
        items.add(item);
    }
}
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.add(Object)" because "this.items" is null
	at Cart.add(NullField.java:10)
	at NullField.main(NullField.java:17)

It compiles, because null is a perfectly good value for the field. The helpful NullPointerException names this.items outright, so the fix is unambiguous: give the field an initialiser, or assign it in the constructor.

Returning this for chaining

An instance method whose return type is the class can end with return this;. The caller then gets the same object back and can call the next method on the result.

class Query {
    String table = "";
    String where = "";
    int limit = -1;

    Query from(String table) {
        this.table = table;
        return this;
    }

    Query where(String condition) {
        this.where = condition;
        return this;
    }

    Query limit(int n) {
        this.limit = n;
        return this;
    }

    String build() {
        String sql = "SELECT * FROM " + table;
        if (!where.isEmpty()) sql += " WHERE " + where;
        if (limit >= 0) sql += " LIMIT " + limit;
        return sql;
    }
}
String sql = new Query().from("orders").where("total > 100").limit(10).build();
System.out.println(sql);

Query q = new Query();
Query same = q.from("users");
System.out.println("same object? " + (q == same));
SELECT * FROM orders WHERE total > 100 LIMIT 10
same object? true

same object? true is the point: nothing was copied. Each call mutated the one Query and handed the same reference back. StringBuilder works exactly this way, which is why sb.append("a").append("b") compiles. Use it for setup-style APIs; a method that computes an answer should return the answer.

A worked class: two constructors, validation and toString

Putting it together — fields, a field initialiser, two constructors with one of them chaining, validation that refuses to build a broken object, instance methods, and a toString.

class BankAccount {
    String owner;
    long balanceCents;
    java.util.List<String> log = new java.util.ArrayList<>();

    BankAccount(String owner, long openingCents) {
        if (owner == null || owner.isBlank()) {
            throw new IllegalArgumentException("owner must not be blank");
        }
        if (openingCents < 0) {
            throw new IllegalArgumentException("opening balance must not be negative, got " + openingCents);
        }
        this.owner = owner;
        this.balanceCents = openingCents;
        log.add("opened at " + money(openingCents));
    }

    BankAccount(String owner) {
        this(owner, 0);
    }

    void deposit(long cents) {
        if (cents <= 0) {
            throw new IllegalArgumentException("deposit must be positive, got " + cents);
        }
        balanceCents += cents;
        log.add("deposit " + money(cents));
    }

    boolean withdraw(long cents) {
        if (cents <= 0 || cents > balanceCents) {
            log.add("refused withdrawal of " + money(cents));
            return false;
        }
        balanceCents -= cents;
        log.add("withdraw " + money(cents));
        return true;
    }

    static String money(long cents) {
        return String.format("%d.%02d", cents / 100, Math.abs(cents % 100));
    }

    public String toString() {
        return "BankAccount[owner=" + owner + ", balance=" + money(balanceCents)
                + ", events=" + log.size() + "]";
    }
}
BankAccount a = new BankAccount("Ana", 25_000);
a.deposit(5_000);
System.out.println(a.withdraw(100_000));
System.out.println(a.withdraw(10_000));
System.out.println(a);
System.out.println(a.log);

BankAccount b = new BankAccount("Bo");
System.out.println(b);

new BankAccount("Cam", -1);
false
true
BankAccount[owner=Ana, balance=200.00, events=4]
[opened at 250.00, deposit 50.00, refused withdrawal of 1000.00, withdraw 100.00]
BankAccount[owner=Bo, balance=0.00, events=1]
Exception in thread "main" java.lang.IllegalArgumentException: opening balance must not be negative, got -1
	at BankAccount.<init>(BankDemo.java:11)
	at BankDemo.main(BankDemo.java:62)

Four things are doing real work here. The log field initialiser means every constructor gets a list without either of them saying so. BankAccount(String) chains, so the validation lives in one place. The validation throws before assigning anything, so an invalid BankAccount never becomes a reference anyone can hold — which is the single most valuable habit a constructor can have. And the stack trace names BankAccount.<init>, the bytecode name for a constructor, so a failure inside one is easy to spot in a log.

toString() is the other habit worth adopting immediately. Delete it from the class above and the same System.out.println(a) prints BankAccount@76ed5528 — the class name, an @, and an identity hash that changes between runs. With it, every debug print and every log line becomes readable.

Common mistakes and the errors they produce

MistakeWhat happensMessage
void on a no-argument constructorcompiles, fields keep their defaultsnone
void on a constructor with parameterscompile errorconstructor Note in class Note cannot be applied to given types;
new Foo() after adding Foo(int)compile errorconstructor User in class User cannot be applied to given types;
this(...) after any other statementcompile errorcall to this must be first statement in constructor
this(...) inside an ordinary methodcompile errorcall to this must be first statement in constructor
A constructor that reaches itselfcompile errorrecursive constructor invocation
return v; in a constructorcompile errorincompatible types: unexpected return value
Two constructors with the same parameter listcompile errorconstructor Pair(int,int) is already defined in class Pair
An initialiser reading a field declared below itcompile errorillegal forward reference
x = x; instead of this.x = x;compiles, field keeps its defaultnone
Never initialising a reference fieldcompiles, throws when usedNullPointerException ... because "this.items" is null

The three with no message are the ones to watch, and all three share a symptom: the object exists but its fields hold null and 0. When you see that, the bug is almost never in the code that reads the object.

FAQ

What is the difference between a constructor and a method in Java?

A constructor has the class's name and no return type, cannot be called by name, and runs as part of new. A method has any name, always declares a return type, and is called on an existing object or class. Write void in front of a constructor and it becomes a method — same name, completely different member.

Do I have to write a constructor in Java?

No. A class with no declared constructor gets an implicit no-argument one whose body is empty, and javap will show it. You lose that the moment you declare any constructor of your own, so a class that needs both a parameterised constructor and new Thing() must declare the no-argument one explicitly.

Why does my constructor seem not to run?

Check for a return type on it. void Book() is a method named Book, not a constructor, so new Book() runs the implicit empty constructor and every field keeps its default. javac reports nothing, not even with -Xlint:all. The other cause is x = x; instead of this.x = x;, which also compiles silently and also leaves the field at its default.

Can a constructor return a value?

No. return v; inside one is error: incompatible types: unexpected return value. A bare return; is legal and exits the constructor early, leaving any not-yet-assigned field at its default; throwing an exception is usually the better way to reject bad arguments, because it stops the caller from getting a half-built object at all.

Why do I need this.x = x when x = x compiles?

Because a parameter named x shadows the field x for the whole constructor body, so x = x reads the parameter and writes it straight back. The field is never involved and stays at its default, with no warning from the compiler. this.x explicitly names the field on the current object, so this.x = x; copies the parameter into it.

What is the difference between a field initialiser and an instance initialiser block?

Only syntax and capacity. int a = 1; is one expression attached to one field; { ... } is a block that can run several statements and touch several fields. They are not separate phases: the compiler runs both in source order, interleaved exactly as written, before the constructor body — and copies both into every constructor that does not begin with this(...).

Conclusion

Fields hold the state, one set per object; instance methods read and write that state through bare field names, with one shared copy of the code in the class; and the constructor is what turns default values into real ones. The rest is a short list of facts worth memorising, all of them demonstrated above.

A constructor has the class's name and no return type, and void silently turns it into a method. You get a free no-argument constructor only until you declare one yourself. Constructors overload like methods, and this(...) lets the short ones delegate into the full one — as the first statement, never in a cycle. Field initialisers and instance blocks run in source order, once, before the constructor body of the constructor that does not delegate. A parameter shadows the field it is named after, so this.x = x; is not decoration. And a constructor that validates before it assigns is a constructor no caller can misuse.

Next in this series: the this, static and final keywords — what this actually refers to and when you can leave it out, what static changes about fields and methods, static initialiser blocks, and what final freezes on a variable, a field and a parameter.

Related Posts

[Java Basics] The this, static and final Keywords in Java

The Java keywords this, static and final on JDK 21 — what this binds to, why a static field is shared by every object, static initialiser order, compile-time constant inlining shown with javap, and why final never makes an object immutable.

[Java Basics] Nested Loops, break and continue in Java

Nested loops in Java and the two keywords that cut them short: how many times the inner body runs, break leaving only the innermost loop, continue skipping the update in a while loop, labelled break and continue, and the switch-inside-a-loop trap.

[Java Basics] Multidimensional and Jagged Arrays in Java

How 2D arrays really work in Java - an array of arrays, not a rectangle: creating and traversing a grid, jagged rows, deepToString, deepEquals, shallow vs deep copy, 3D arrays and a worked matrix example, every output compiled and run on JDK 21.

[Java Basics] Read and Write Text Files in Java

Reading and writing text files in Java: FileReader and FileWriter, why BufferedReader and BufferedWriter matter, try-with-resources, the modern Files and Path API, relative paths, the real exceptions when a file is missing, and the character encoding that decides whether the round trip survives.