SEN1002 · Week 1 · Classes and Objects

Classes and objects: review

Fields, constructors, this, encapsulation, static vs instance, toString and equals. The rest of the semester builds on this foundation.

classobjectinstance variableconstructorthisencapsulationgetter / setterstatictoStringequalsreference

This course continues SEN1001. In the first week we review the building blocks of object oriented programming. Inheritance, polymorphism and interfaces all reuse every concept on this page. The exam has three question types on this topic: “What is printed?” (especially static counters, references and the constructor chain), “Find the compile error” (private access, missing constructor, static context) and “Write a class”.

Golden rules:

  1. A variable does not hold the object, it holds the object’s address (reference). b = a does not create a new object.
  2. As soon as you write a constructor, the no-argument default constructor disappears.
  3. If a field and a parameter have the same name and you do not write this., the field does not change.
  4. What is static belongs to the class and has a single copy. A static method cannot access instance fields directly.
  5. == compares addresses, equals compares content. (If equals is overridden.)

Class, object, field

A class is a template: it defines what data it holds (fields / instance variables) and what it can do (methods). An object is a concrete instance created from this template with new. Each object’s fields belong to it alone.

public class Account {
    private String owner;          // instance variable
    private double balance;
    private static int count = 0;  // belongs to the class, single copy

    public Account(String owner, double balance) {
        this.owner = owner;
        setBalance(balance);       // validation in one place
        count++;
    }
    public Account(String owner) {
        this(owner, 0.0);          // delegate to the other constructor
    }
    public void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
    public double getBalance() { return balance; }
    public void setBalance(double b) {
        if (b >= 0) balance = b;
    }
    public static int getCount() { return count; }

    @Override
    public String toString() {
        return owner + " (" + balance + " TL)";
    }
}
Account a = new Account("Ece", 100);
Account b = new Account("Mert");
a.deposit(50);
a.deposit(-20);      // rejected
b.setBalance(-5);    // rejected
System.out.println(a);
System.out.println(b);
System.out.println(Account.getCount());
Ece (150.0 TL)
Mert (0.0 TL)
2

When you write System.out.println(a), Java automatically calls a.toString(). If you had not overridden toString, something like Account@1b6d3586 would be printed, the class name and the hash code; this number can change on every run.

Default values

If you do not assign a value to a field, Java gives it a default value. It does not do so for local variables (the ones inside methods); reading one before assigning it is a compile error.

Field typeDefault
int, long, short, byte0
double, float0.0
booleanfalse
char'\u0000' (code 0)
Every reference (String, array, object)null

Constructor

A constructor runs once when an object is created and gives the fields their initial values. Its name is the same as the class and it has no return type (not even void; if you write it, it becomes an ordinary method).

  • If you write no constructor at all, the compiler adds an empty, no-argument default constructor.
  • If you write even a single constructor, this addition is not made. new Car() no longer compiles.
  • A class can have several constructors with different parameter lists (overloading).

The this keyword

this is the object currently being worked on. It has two uses:

  1. Resolving shadowing. If a parameter has the same name as a field, a plain name inside the method refers to the parameter. To refer to the field you write this.name.
  2. Constructor chaining. this(...) calls another constructor of the same class. It must be the first line of the constructor. (The flexible constructor bodies feature that arrived with Java 25 allows lines that do not use this before it. The textbook and the exam follow the old rule: first line.)
class Box {
    int w, h;
    Box()             { this(1);    System.out.println("Box()"); }
    Box(int w)        { this(w, w); System.out.println("Box(int)"); }
    Box(int w, int h) { this.w = w; this.h = h;
                        System.out.println("Box(int,int) " + w + "x" + h); }
}
// main:
Box bx = new Box();
System.out.println(bx.w * bx.h);
Box(int,int) 1x1
Box(int)
Box()
1
new Box()
  └─ this(1)            → Box(int) starts running
       └─ this(1, 1)    → Box(int,int) runs, PRINTS, finishes
       Box(int) prints its remaining line
  Box() prints its remaining line

PITFALL · order in the chain. The call order is outside to inside, the output order is inside to outside. The last constructor called finishes first. The super(...) chain in inheritance works exactly like this (week 2).

Encapsulation

Encapsulation means making an object’s data impossible to change directly from outside. The rule is simple: fields are private, access goes through public methods. The method that reads is called a getter (getBalance), the one that changes is called a setter (setBalance). The real job of a setter is validation: it rejects values such as a negative balance, an empty name or an invalid grade. That way the object never ends up in a broken state.

Access modifierSame classSame packageSubclassEverywhere
privateyesnonono
(not written, package-private)yesyesif in the same packageno
protectedyesyesyesno
publicyesyesyesyes

TIP · put the validation in one place. Let the constructor call the setter too (setBalance(balance)). If you write the same validation in two places, one gets updated and the other is forgotten.

static and instance

instance (not static)static
Who owns it?A separate copy for each objectThe class, a single copy
How is it called?object.method()Class.method()
Is there a this?YesNo
What can it access?Both instance and static membersOnly static members (directly)
Typical examplebalance, getBalance()counter count, Math.sqrt, main
class Counter {
    static int total = 0;
    int mine = 0;
    void hit() { total++; mine++; }
}
Counter c1 = new Counter(), c2 = new Counter();
c1.hit(); c1.hit(); c2.hit();
System.out.println(c1.mine + " " + c2.mine + " " + Counter.total);
2 1 3

Because main is static, you cannot directly call an instance field or instance method of the same class from main. You first have to create an object. This is the most common compile error in the first weeks: non-static variable score cannot be referenced from a static context.

References and parameter passing

In Java everything is passed by value (pass by value). The difference is what gets copied:

  • For primitive types such as int and double, a copy of the number is passed. A change inside the method does not affect the outside.
  • For objects, a copy of the address is passed. The method can change the fields of the object at that address, and the change is visible outside. But if it assigns another object to the parameter with new, it has only redirected its own copy.
static void fill(Cup c)    { c.ml = 250; }                   // changes the object
static void swapCup(Cup c) { c = new Cup(999); c.ml = 1; }   // only the local copy
static void bump(int n)    { n = n + 10; }                    // only the local copy

Cup cup = new Cup(100);
int n = 5;
fill(cup);
bump(n);
System.out.println(cup.ml + " " + n);
swapCup(cup);
System.out.println(cup.ml);
250 5
250

toString and equals

Every class, even if you do not write it, derives from the Object class and gets the toString() and equals(Object) methods from it. The default versions of both are often useless to you:

  • Default toString(): ClassName@hashcode.
  • Default equals(o): this == o, that is, an address comparison.

To compare content you override equals. The parameter type must be Object; if you write equals(Point p) it is an overload, not an override, and it will not be called where you expect.

class Point {
    private final int x, y;
    Point(int x, int y) { this.x = x; this.y = y; }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;              // same object
        if (!(o instanceof Point)) return false; // wrong type or null
        Point p = (Point) o;
        return x == p.x && y == p.y;
    }
    @Override
    public String toString() { return "(" + x + ", " + y + ")"; }
}
Point p1 = new Point(2, 3), p2 = new Point(2, 3);
System.out.println(p1 == p2);
System.out.println(p1.equals(p2));
System.out.println(p1);
false
true
(2, 3)

String follows the same logic. "java" == new String("java") gives false, equals gives true. Because String literals are kept in a shared pool (string pool), == between two literals sometimes comes out true; that is luck, do not rely on it.

PITFALL · write @Override. If you put @Override on a method, the compiler warns you when you get the signature wrong (for example equals(Point o) or tostring()). If you do not, you have silently created a new method.

Common mistakes

Wrong ideaThe truth
name = name; fills the fieldIt assigns the parameter to itself, the field stays null. this.name = name;
I wrote a constructor, new Car() still worksOnce you write a constructor, the default constructor is not added
Writing void on a constructor is no problemIf you write void it becomes an ordinary method, not a constructor
b = a; creates a copy objectA second reference to the same object; when one changes, the other sees it
I access an instance field from a static methodCreate an object first: obj.field
A static counter is separate in each objectA single copy, shared by all objects
Comparing a String with ==Use equals
equals(Point p) overridesThe parameter must be Object, otherwise it is an overload
I gave the method an int, the method changed itA primitive is copied, the outside does not change
this(...) can be on any line you likeThe rule the exam follows: the first line of the constructor

Test yourself

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

Question 1easy

What is printed?

class Counter {
    static int total = 0;
    int mine = 0;
    void hit() { total++; mine++; }
}
// in main:
Counter c1 = new Counter(), c2 = new Counter();
c1.hit(); c1.hit(); c2.hit();
System.out.println(c1.mine + " " + c2.mine + " " + Counter.total);
Show answer
2 1 3

mine is an instance field: every object has its own copy. c1 was incremented twice and c2 once: 2 and 1. total is static: there is a single copy, and all three calls incremented the same counter: 3.

Question 2medium

What is printed?

class Student {
    private String name;
    public Student(String name) { name = name; }
    public String getName() { return name; }
}
// in main:
System.out.println(new Student("Ali").getName());
Show answer
null

Inside the constructor, the name parameter shadows the field. name = name; assigns the parameter to itself and never touches the field. The field keeps its default value null. Fix: this.name = name;.

Question 3easy

What is printed?

Account x = new Account("Can", 10);   // deposit adds to balance
Account y = x;
y.deposit(5);
System.out.println(x.getBalance());
System.out.println(x == y);
Show answer
15.0 and true

y = x does not create a new object. It copies the address of the same object (aliasing). A deposit made through y changes the object that x sees: 15.0 (balance is a double). Both references point to the same object, so == is true.

Question 4medium

What is printed?

String s1 = "java";
String s2 = new String("java");
String s3 = "java";
System.out.println(s1 == s2);
System.out.println(s1.equals(s2));
System.out.println(s1 == s3);
Show answer
false, true, true

== compares references. new String always creates a new object, not at the same address as s1: false. equals looks at the content: true. s1 and s3 are the same literal, and Java keeps a single copy of literals in the string pool: true. In the exam, always compare Strings with equals.

Question 5medium

Find the compile error.

class Car {
    private String model;
    public Car(String model) { this.model = model; }
}
public class Test {
    public static void main(String[] args) {
        Car c = new Car();
    }
}
Show answer
new Car() does not compile: there is no no-argument constructor

As soon as you write even one constructor in a class, the compiler does not add the default constructor. Car only knows Car(String). javac: constructor Car in class Car cannot be applied to given types; required: String, found: no arguments. Fix: new Car("Clio"), or add public Car() { ... } to the class.

Question 6medium

Find the compile error.

public class Test {
    private int score = 10;
    public static void main(String[] args) {
        System.out.println(score);
    }
}
Show answer
the instance field score cannot be accessed from static main

main is static: when it runs there is no Test object, while score is a field of each object. javac: non-static variable score cannot be referenced from a static context. Fix: Test t = new Test(); System.out.println(t.score); or make the field static.

Question 7easy

What is printed?

class Defaults { int i; double d; boolean b; String s; }
// in main:
Defaults df = new Defaults();
System.out.println(df.i + " " + df.d + " " + df.b + " " + df.s);
Show answer
0 0.0 false null

Fields (instance variables) get a default value even if you do not assign them: numbers 0 / 0.0, boolean false, references null. Local variables get no default; reading one before assigning it is a compile error.

Question 8hard

What is printed?

class Box {
    int w, h;
    Box() { this(1); System.out.println("Box()"); }
    Box(int w) { this(w, w); System.out.println("Box(int)"); }
    Box(int w, int h) { this.w = w; this.h = h;
        System.out.println("Box(int,int) " + w + "x" + h); }
}
// in main:
Box bx = new Box();
System.out.println(bx.w * bx.h);
Show answer
Box(int,int) 1x1 / Box(int) / Box() / 1

this(...) is the first line of the constructor and runs first. Box() → Box(1) → Box(1, 1). The innermost one finishes and prints first, then on the way back Box(int), and last Box(). The fields are 1 and 1: 1 * 1 = 1.

Question 9hard

What is printed?

class Cup { int ml; Cup(int ml) { this.ml = ml; } }
static void fill(Cup c) { c.ml = 250; }
static void swapCup(Cup c) { c = new Cup(999); c.ml = 1; }
static void bump(int n) { n = n + 10; }
// in main:
Cup cup = new Cup(100);
int n = 5;
fill(cup);
bump(n);
System.out.println(cup.ml + " " + n);
swapCup(cup);
System.out.println(cup.ml);
Show answer
250 5, then 250

Java always copies values (pass by value). For an int, a copy of the number is passed: bump does not change the n outside, which stays 5. For an object, a copy of the address is passed: fill changes the field of the same object, 250. swapCup assigns a new object to the parameter; that only points the local copy somewhere else, and cup in main still points to the old object: 250.

Question 10medium

What is printed?

class P { int x, y; P(int x, int y) { this.x = x; this.y = y; } }
// in main:
P a = new P(2, 3), b = new P(2, 3);
System.out.println(a.equals(b));
Show answer
false

Class P did not override equals, so the equals inherited from Object runs. Object's equals does the same thing as ==: two separate objects, false. If you want content comparison, you must write equals yourself.

Question 11easy

Find the compile error.

class Wallet {
    private double money = 50;
}
public class Test {
    public static void main(String[] args) {
        Wallet w = new Wallet();
        w.money = 100;
    }
}
Show answer
money is private, it cannot be accessed from class Test

A private field is visible only from inside its own class. javac: money has private access in Wallet. That is the point of encapsulation: to change it from outside, Wallet should offer a setter (for example setMoney) and validate the value.

Question 12medium

Write a class Rectangle with private width and height (double), a constructor that rejects non-positive values with IllegalArgumentException, a getArea() method and a toString() that returns Rectangle[3.0 x 4.5] style text.

Show answer
The class below

The fields are private, the validation is in the setters, and the constructor calls the setters (validation in one place):

public class Rectangle {
    private double width, height;

    public Rectangle(double width, double height) {
        setWidth(width);
        setHeight(height);
    }
    public void setWidth(double width) {
        if (width <= 0) throw new IllegalArgumentException("width must be > 0");
        this.width = width;
    }
    public void setHeight(double height) {
        if (height <= 0) throw new IllegalArgumentException("height must be > 0");
        this.height = height;
    }
    public double getArea() { return width * height; }

    @Override
    public String toString() {
        return "Rectangle[" + width + " x " + height + "]";
    }
}
Printing new Rectangle(3, 4.5) gives Rectangle[3.0 x 4.5] area=13.5 (together with the area); new Rectangle(-1, 2) throws an exception with the message width must be > 0.

Verification: All code examples and answers were run with JDK 26 (java verify/SEN1002/W01.java); the compile-error questions were tried with javac (verify/SEN1002/err/E01a-d.java; E01d also with --release 21 for the flexible constructor rule).

Review card

class = template, object = instance created with new. A variable holds the ADDRESS, not the object. Field defaults: int 0, double 0.0, boolean false, reference null. Local variable: NO default. Constructor: same name as the class, no return type. If you write one, the default constructor is GONE. this.x = x → resolves shadowing. name = name; does not change the field (stays null). this(...) must be the first line (before Java 25; later versions allow lines that do not use this); the innermost finishes first. Encapsulation: private field, access through get/set, validation in the setter. static field: a single copy belonging to the class (counter). instance field: separate for each object. No this inside a static method, and it cannot access instance fields directly. b = a → same object (alias). If one changes it, the other sees it too. Java is pass-by-value: copy of the int / copy of the address. Assigning new to a parameter does not change the outside. == compares addresses, equals compares content (if overridden). String: always equals. Non-overridden equals = ==. Without toString, ClassName@hash is printed.