SEN1001 · Week 12 · Classes and Objects: A Deeper Look

this, static members, final constants and toString

this when a parameter shadows a field, this(...) chaining constructors, static fields shared by all objects, final values that do not change and toString for printing an object readably. The favorite of 'what is printed' questions on the final exam.

this referenceshadowingconstructor chaining this(...)static field (class variable)static methodinstance variablefinalconstantstatic finaltoStringmethod overriding@OverrideString.format

You know how to write classes. This week there are four tools commonly used inside classes: this, static, final and toString. Each rests on a few rules, but in the exam these rules are set up as traps with small differences: a class where this was forgotten in the constructor, a static counter, a println that calls toString automatically.

Golden rules:

  1. If a parameter has the same name as a field, you cannot reach the field without this..
  2. A static field is a single copy; all objects share it. An instance field is separate in each object.
  3. A static method cannot directly access instance fields.
  4. final is assigned once. Constants are static final and UPPER_CASE.
  5. println(object) automatically calls toString().

The this reference

Inside every instance method there is a hidden variable: this. It points to the object that called the method. In the call p.getName(), this is p.

Shadowing

Giving constructor parameters the same names as the fields is a common habit. But then, inside the method, that name refers to the parameter:

class BadPlayer {
    private String name;
    private int number;
    public BadPlayer(String name, int number) {
        name = name;          // parameter = parameter
        number = number;
    }
    ...
}
class Player {
    private String name;
    private int number;
    public Player(String name, int number) {
        this.name = name;     // field = parameter
        this.number = number;
    }
    ...
}
// in main:
BadPlayer bp = new BadPlayer("Deniz", 22);
System.out.println(bp.getName() + " " + bp.getNumber());
Player p = new Player("Deniz", 22);
System.out.println(p.getName() + " " + p.getNumber());
null 0
Deniz 22

BadPlayer compiles and runs, but the fields are never assigned. The compiler does not warn; a typical logic error.

Constructor chaining with this(…)

If there are several constructors, you can do the common work in one of them and call it from the others:

class Time {
    private int hour, minute;
    public Time() { this(0, 0); }
    public Time(int hour) { this(hour, 0); }
    public Time(int hour, int minute) {
        this.hour = hour;
        this.minute = minute;
    }
    @Override
    public String toString() {
        return String.format("%02d:%02d", hour, minute);
    }
}
// main: System.out.println(new Time() + " " + new Time(9) + " " + new Time(14, 30));
00:00 09:00 14:30

Write the this(...) call on the first line of the constructor. This is the rule of the textbook and of Java versions up to 21; javac --release 21 rejected a constructor with a line written before it, saying flexible constructors is not supported in -source 21. (This rule was relaxed with Java 25, but in the exam apply the classic rule.) Rules such as validation are written in only one constructor and not repeated.

return this

If a method returns this, the calls can be chained. The Temp example in the question bank shows this: new Temp(20).add(5).add(3) adds to the same object one after another.

static members

An instance variable is separate in each object. A static field, on the other hand, belongs to the class and there is a single copy in memory; all objects share it. That is why it is also called a class variable.

class Ticket {
    private static int count = 0;    // how many tickets were created (shared)
    private int id;                  // this ticket's number (separate in each object)
    private String owner;
    public Ticket(String owner) {
        this.owner = owner;
        count++;
        id = count;
    }
    public static int getCount() { return count; }
    public int getId() { return id; }
    public int getCountViaInstance() { return count; }
}
// in main:
System.out.println(Ticket.getCount());
Ticket a = new Ticket("A");
Ticket b = new Ticket("B");
Ticket c = new Ticket("C");
System.out.println(Ticket.getCount() + " " + a.getId() + " " + b.getId() + " " + c.getId());
System.out.println(a.getCountViaInstance());
0
3 1 2 3
3
Ticket class:  count = 3     (single copy)
a: id = 1    b: id = 2    c: id = 3     (separate in each object)
  • Even when there were no objects, Ticket.getCount() worked: static members do not need an object, they are used with the class name.
  • An instance method (getCountViaInstance) can read a static field.
  • The opposite direction does not work: a static method does not have this, so it cannot know which object’s field you mean.
public class Main {
    private int score = 5;
    public static void main(String[] args) {
        System.out.println(score);
    }
}
Main.java:5: error: non-static variable score cannot be referenced from a static context

You may have seen this error since the first weeks when writing helper methods for main: because main is static, the methods it called also had to be static.

Solve the Counter example in the question bank with a trace table: draw a single box next to the class for the static field, and a separate box next to each object for the instance field.

call       x.mine   y.mine   Counter.total
start      0        0        0
x.hit()    1        0        1
y.hit()    1        1        2
y.hit()    1        2        3

Each hit increases two boxes at once: the calling object’s own box and the single box everyone shares.

When to use static? Ask yourself: “Does this information belong to a single object, or to the whole class?” A student’s grade belongs to that student: an instance field. The total number of students, the VAT rate or the next number to give out belongs to the whole class: static. The same question for methods: if the method uses an object’s fields it is an instance method; if it works only with its parameters (like Math.max) it can be static.

staticnot static
Belongs tothe classeach object
How many copies1as many as objects
AccessTicket.getCount()a.getId()
Examplecounter, constants, Math.sqrtname, balance, grade

final and constants

final says that a variable can be assigned a value once.

class Shop {
    public static final double TAX_RATE = 0.20;          // constant
    public static double priceWithTax(double price) {
        return price * (1 + TAX_RATE);
    }
}
class Circle {
    private final double radius;                         // assigned once in the constructor
    public Circle(double radius) { this.radius = radius; }
    public double area() { return Math.PI * radius * radius; }
}
// main: Shop.priceWithTax(100), Shop.TAX_RATE, new Circle(2).area() (%.2f)
120.0
0.2
12.57
  • For a constant, static final are used together: a single copy that does not change. Its name is written in capital letters with underscores: TAX_RATE, MAX_SIZE. Math.PI is such a constant too.
  • A final instance field can be different in each object but does not change after the object is created. It is assigned in the constructor.

If you try to assign to it, it does not compile:

Main.java:6: error: cannot assign a value to static final variable MAX
Main2.java:4: error: cannot assign a value to final variable radius

toString

Every class gets a toString() method from Object, Java’s root class. The default version returns a useless text: the class name, @ and a hash code (like NoToString@1b6d3586). If you write your own version (override):

@Override
public String toString() {
    return name + " #" + number;
}
// in main:
Player p2 = new Player("Can", 7);
System.out.println(p2);                 // println calls toString automatically
String msg = "Kaptan: " + p2;           // concatenation calls it too
System.out.println(msg);
Can #7
Kaptan: Can #7

Rules:

  • The signature is exactly public String toString(). No parameters, return type String, public.
  • The method returns, it does not print. Putting a println inside is a wrong habit.
  • The @Override tag is not required but is good: if you write the signature wrong, the compiler warns you.
  • String.format uses the same specifiers as printf but returns a String instead of printing to the screen; it is very useful inside toString.

Common mistakes

Wrong ideaThe truth
name = name; assigns the fieldIt assigns the parameter to itself; this.name = name;
this(...) anywhere in the constructorWrite it on the first line (required in Java 21 and earlier)
this can be used in a static methodThere is no this in static
A static field is separate in each objectA single copy, shared
An object is required for a static methodClass.method() is enough
An instance field is accessed directly from mainThrough an object: obj.field
A final field can never be assignedIt is assigned once (in the declaration or the constructor)
A constant name is camelCaseUPPER_CASE: TAX_RATE
println inside toStringReturn a String with return
println(obj) prints the fieldsIf toString is not written, ClassName@...

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1medium

What is printed?

class Box {
    private int size;
    public Box(int size) { size = size * 2; }
    public int getSize() { return size; }
}
// in main:
Box b = new Box(5);
System.out.println(b.getSize());
Show answer
0

Inside the constructor the name size refers to the parameter (shadowing). size = size * 2 only makes the parameter 10; the field never changes, it stays at the default 0. The correct version is this.size = size * 2;.

Question 2easy

Same class, but the constructor body is this.size = size * 2;. What is printed for new Box2(5).getSize()?

Show answer
10

this.size is the object's field, the size on the right is the parameter. The field becomes 5 * 2 = 10.

Question 3medium

What is printed?

class Counter {
    static int total = 0;
    private int mine = 0;
    public void hit() { mine++; total++; }
    public int getMine() { return mine; }
}
// in main:
Counter x = new Counter();
Counter y = new Counter();
x.hit();
y.hit();
y.hit();
System.out.println(x.getMine() + " " + y.getMine() + " " + Counter.total);
Show answer
1 2 3

mine is an instance variable: separate in each object (1 in x, 2 in y). total is static: it belongs to the class, there is a single copy and all objects share it. Three hits, total 3.

Question 4medium

Time has Time(int hour) which calls this(hour, 0), and toString returns String.format("%02d:%02d", hour, minute). What is printed?

Time t = new Time(7);
System.out.println(t);
Show answer
07:00

this(hour, 0) calls the two-parameter constructor in the same class: hour = 7, minute = 0. When println receives an object, it automatically calls toString(). %02d pads the number to two digits with a zero: 07.

Question 5easy

Player.toString() returns name + " #" + number. What is printed?

Player p = new Player("Ali", 10);
System.out.println("#" + p + "#");
Show answer
#Ali #10#

When an object is used in String concatenation, Java calls its toString() method: "Ali #10". A # is added at the start and at the end.

Question 6hard

Ticket has private static int count; each constructor call does count++; id = count;. Assume count starts at 0. What is printed?

Ticket t = new Ticket("X");
Ticket u = new Ticket("Y");
System.out.println(t.getId() + u.getId() + Ticket.getCount());
Show answer
5

t's id is 1, u's is 2, count is 2. All three are ints and there is no String at the start: addition is done, 1 + 2 + 2 = 5. If you wanted concatenation, you would put "" + at the start: "122".

Question 7easy

Shop has public static final double TAX_RATE = 0.20; and priceWithTax(p) returns p * (1 + TAX_RATE). What is printed?

System.out.println(Shop.priceWithTax(50) + " " + (int) Shop.priceWithTax(10));
Show answer
60.0 12

A static method is called with the class name, no object is needed. 50 * 1.2 = 60.0 (double). 10 * 1.2 = 12.0, 12 with the cast.

Question 8hard

What is printed?

class Temp {
    private int value;
    public Temp(int value) { this.value = value; }
    public Temp add(int d) { value += d; return this; }
    public String toString() { return value + "C"; }
}
// in main:
Temp tm = new Temp(20).add(5).add(3);
System.out.println(tm);
Show answer
28C

return this returns the object that called the method itself, so the calls can be chained (method chaining). First 5, then 3 is added to the same object: 28.

Question 9medium

Which lines do not compile?

public class Main {
    private int score = 5;
    public static final int MAX = 10;
    public static void main(String[] args) {
        System.out.println(score);   // line A
        System.out.println(MAX);     // line B
        MAX = 20;                    // line C
    }
}
Show answer
line A and line C

A: main is static; it is not clear whose score it is → non-static variable score cannot be referenced from a static context. B: a static field can be read from a static method. C: a final variable cannot be assigned a second time → cannot assign a value to static final variable MAX.

Question 10easy

A class has no toString method. What does System.out.println(obj) print?

  1. Nothing
  2. The values of all fields
  3. Class name, @ and a hexadecimal hash code (e.g. NoToString@1b6d3586)
  4. A compile-time error
Show answer
c

Every class gets a default toString from Object: ClassName@hashCode. The code may change on each run. If you want to see the fields, you have to write toString yourself (override).

Question 11easy

Write a constructor for Player (fields private String name; private int number;) whose parameters have the same names as the fields.

Show answer
public Player(String name, int number) { this.name = name; this.number = number; }

The parameter names shadow the fields; to reach the field, this. is a must.public Player(String name, int number) { this.name = name; this.number = number;}The version written without this (name = name;) printed null 0 in verification.

Question 12medium

Write a toString method for Player that returns the name, a space, # and the number (e.g. Can #7).

Show answer
public String toString() { return name + " #" + number; }

The signature must be exactly public String toString(): public, return type String, no parameters. The method does not print, it returns.@Overridepublic String toString() { return name + " #" + number;}System.out.println(new Player("Can", 7)); → Can #7.

Verification: All outputs were run with JDK 26 (verify/SEN1001/W12.java; the classes Player, BadPlayer, Time, Ticket, Shop, Circle, Box, Box2, Counter and Temp are in the same file). The static and final compile errors were obtained with javac under verify/SEN1001/err/st/; the this(...) rule was also tried separately with javac --release 21.

Review card

this = the object that called the method itself Parameter has the same name as a field → this.name = name; (name = name; does nothing!) this(...) calls another constructor in the same class, write it on the FIRST line of the constructor return this; → chained calls: new Temp(20).add(5).add(3) static field: belongs to the class, ONE copy, shared by all objects (counter, id generation) instance field: a separate copy in each object static method: Class.method() cannot access instance fields/methods directly (static context error) an instance method can read and change a static field final: assigned once, then does not change constant: public static final double TAX_RATE = 0.20; Constant names are UPPER_CASE a final field can be assigned in the constructor toString: public String toString() { return ...; } println(obj) and "x" + obj call it automatically without toString → ClassName@1b6d3586 String.format("%02d:%02d", h, m) → like printf but returns a String