SEN1001 · Week 13 · Classes and Objects: A Deeper Look II

Composition, enum and passing references to methods

An object of another class as a field of a class (has-a), enum for fixed sets of values, and the consequences of object references going to a method as a copy: a method can change the object but cannot turn the caller's variable into another object.

compositionhas-a relationshipenumenum constantvalues()ordinal()valueOfenum constructorpass-by-valuereferencealiasingequals== vs equals

This week has three topics, and all three are about how objects relate to each other. Composition means having other objects inside an object (an employee’s birth date, a car’s engine). enum safely represents a fixed and limited set of values (days, levels, planets). Finally, you will learn exactly what changes and what does not change when you pass objects to a method. This third topic is the sneakiest source of “what is printed?” questions on the final exam.

Golden rules:

  1. Composition = has-a. A field of a class can be an object of another class.
  2. enum constants are not created with new, they are used as Day.WED and compared with ==.
  3. Java is always pass-by-value. For objects the thing copied is the reference.
  4. A method can change the inside of an object; it cannot turn the caller’s variable into another object.
  5. == checks whether it is the same object, equals checks the contents (if overridden).

Composition: the has-a relationship

class Date {
    private int day, month, year;
    public Date(int day, int month, int year) {
        this.day = day; this.month = month; this.year = year;
    }
    public int getYear() { return year; }
    public String toString() { return day + "/" + month + "/" + year; }
    ...
}
class Employee {
    private String name;
    private Date birthDate;                  // an object of another class
    public Employee(String name, Date birthDate) {
        this.name = name;
        this.birthDate = birthDate;
    }
    public Date getBirthDate() { return birthDate; }
    public String toString() { return name + ", born " + birthDate; }
}
// in main:
Date birth = new Date(14, 3, 2005);
Employee e = new Employee("Deniz", birth);
System.out.println(e);
System.out.println(e.getBirthDate().getYear());
Deniz, born 14/3/2005
2005
  • When Employee’s toString concatenated birthDate with a String, Date’s toString was called automatically.
  • e.getBirthDate().getYear() is a chain: first get the Date object, then call its method.
  • If the sentence “an employee has a date” sounds right, composition is the right choice.

The second example, handing the job to the inner object (delegation):

class Engine {
    private int cc;
    public Engine(int cc) { this.cc = cc; }
    public int getCc() { return cc; }
    public void setCc(int cc) { this.cc = cc; }
    public void run() { System.out.println("Engine " + cc + "cc running"); }
}
// Car: private String model; private Engine engine; start() { print(model + ": "); engine.run(); }
Car car = new Car("Egea", new Engine(1400));
car.start();
System.out.println(car.getEngine().getCc());
Egea: Engine 1400cc running
1400

PITFALL · a getter hands out the inner object. getBirthDate() returns not a copy of the Date but its reference. Also, the Date object given to the constructor was not copied either: the same reference was stored. When dd.setYear(1990) was done outside, the employee’s birth year also became 1990 (in verification e2.getBirthDate().getYear() → 1990). Even if the field is private, if you share a changeable inner object you cannot protect its inside.

enum

Some variables can take only one of a few values: the day of the week, a traffic light, a difficulty level. Keeping these with an int (0 = Monday) or a String ("Mon") invites mistakes: nothing stops anyone from writing day = 9 or "mon". enum defines this set as a type:

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }

Day d = Day.WED;
System.out.println(d + " " + d.ordinal() + " " + d.name());
System.out.println(d == Day.WED);
for (Day x : Day.values()) System.out.print(x + " ");
System.out.println();
System.out.println(Day.values().length);
System.out.println(Day.valueOf("FRI").ordinal());
WED 2 WED
true
MON TUE WED THU FRI SAT SUN 
7
4
ExpressionWhat it gives
Day.WEDthe constant itself
d.ordinal()declaration position, from 0
d.name(), println(d)its name: "WED"
Day.values()all constants, in order, as an array
Day.valueOf("FRI")the constant from a String; IllegalArgumentException if absent
d == Day.WEDcomparison (== is safe with enums)

new Day() cannot be written: enum classes may not be instantiated. The constants are created once at the start of the program, that is all.

switch with an enum

static String advice(Level lv) {
    switch (lv) {
        case LOW: return "Devam";       // just LOW, not Level.LOW
        case MEDIUM: return "Dikkat";
        case HIGH: return "Dur";
        default: return "?";
    }
}
// main: advice(Level.LOW) + " " + advice(Level.HIGH)
Devam Dur

An enum with fields and a constructor

An enum is actually a special class; it can have fields, a constructor and methods. Each constant calls the constructor with its own argument:

enum Coin {
    QUARTER(25), HALF(50), LIRA(100);     // ; after the constants
    private final int kurus;
    Coin(int k) { kurus = k; }            // the constructor is not written public
    public int getKurus() { return kurus; }
}
// in main:
for (Coin c : Coin.values()) System.out.print(c + "=" + c.getKurus() + " ");
System.out.println();
int total = Coin.HALF.getKurus() * 2 + Coin.QUARTER.getKurus();
System.out.println(total);
QUARTER=25 HALF=50 LIRA=100 
125

Passing references to methods

In Java everything is pass-by-value: a copy of a value goes to the method. For a primitive, the thing copied is the number itself. For an object, the thing copied is the reference (the address of the object). This single rule has three consequences:

static void rename(Pet p) { p.setName("Boncuk"); }                        // 1) change the inside
static void replace(Pet p) { p = new Pet("Yeni"); p.setName("Degisti"); }  // 2) turn it into a new object
static void swap(Pet a, Pet b) { Pet t = a; a = b; b = t; }               // 3) swap
// in main:
Pet pet = new Pet("Pamuk");
rename(pet);
System.out.println(pet.getName());
replace(pet);
System.out.println(pet.getName());
Pet a = new Pet("A"), b = new Pet("B");
swap(a, b);
System.out.println(a.getName() + " " + b.getName());
Boncuk
Boncuk
A B
rename call:
  main.pet ──┐
             ├──► [Pet: "Pamuk" → "Boncuk"]     the same object, its inside changed
  rename.p ──┘

replace call:
  main.pet ─────► [Pet: "Boncuk"]               untouched
  replace.p ────► [Pet: "Degisti"]              a new object, lost when the method ends
What is the method doing?Does the caller see it?
Calling a method of the object through the parameter and changing its insideYes
Assigning a new object to the parameter (p = new ...)No
Swapping two parametersNo
Changing a primitive parameterNo
Creating a new object and returning itYes, if you assign the returned value

If you want to get a new object from a method, return it: Pet m = make("Minnos");.

When you see a question of this kind in the exam, ask yourself for each method call: is the method doing a setter or an element assignment through the parameter (the caller sees it), or is it assigning to the parameter itself (the caller does not see it)? This single question decides the answer.

== and equals

Pet x1 = new Pet("Tom"), x2 = new Pet("Tom");
System.out.println((x1 == x2) + " " + x1.equals(x2));
Point p1 = new Point(1, 2), p2 = new Point(1, 2);
System.out.println((p1 == p2) + " " + p1.equals(p2));
false false
false true

Pet did not override equals; the default equals from Object does the same thing as ==. Point, on the other hand, defined equals itself and compares x and y:

@Override
public boolean equals(Object o) {
    if (!(o instanceof Point)) return false;
    Point other = (Point) o;
    return x == other.x && y == other.y;
}

String’s equals is also a method overridden like this; that is why "abc".equals(s) looks at the contents.

Common mistakes

Wrong ideaThe truth
For composition make the field publicprivate field + constructor + a getter if needed
A getter gives a copy of the inner objectIt gives its reference
Day d = new Day();Day d = Day.MON;
case Day.MON:case MON:
ordinal() starts from 1From 0
valueOf("mon") worksIt is case-sensitive; an exception if absent
Java passes objects by referencePass-by-value; the thing copied is the reference
When a method says p = new Pet(...), the object in main changesIt does not change
A swap method swaps the objectsIt does not
x1 == x2 compares contentsSame object or not; use equals for contents

Test yourself

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

Question 1easy

Employee has a Date birthDate field; Date(day, month, year) has getters. What is printed?

Employee q = new Employee("Ece", new Date(5, 9, 2004));
System.out.println(q.getBirthDate().getMonth() + q.getBirthDate().getDay());
Show answer
14

getBirthDate() returns a Date object, and another method can be called on it right away (a chain). The month is 9, the day is 5; both are ints, so addition: 14.

Question 2medium

Given enum Day { MON, TUE, WED, THU, FRI, SAT, SUN }, what is printed?

System.out.println(Day.MON.ordinal() + Day.SUN.ordinal() + " " + Day.values()[2]);
Show answer
6 WED

ordinal() is the position of the constant, starting from 0: MON 0, SUN 6. 0 + 6 = 6 (int addition). values() gives the constants as an array in declaration order; index 2 is WED. An enum is printed by its name in println.

Question 3medium

Given enum Level { LOW, MEDIUM, HIGH }, what is printed?

Level lv = Level.MEDIUM;
switch (lv) {
    case LOW: System.out.print("L");
    case MEDIUM: System.out.print("M");
    case HIGH: System.out.print("H");
}
Show answer
MH

A switch can be written with an enum; the class name is not written in the cases (case LOW, not Level.LOW). It enters at MEDIUM and, because there is no break, falls into HIGH: MH.

Question 4medium

rename(Pet p) calls p.setName("Boncuk"). What is printed?

Pet q = new Pet("Zeytin");
Pet q2 = q;
rename(q2);
System.out.println(q.getName());
Show answer
Boncuk

q and q2 point to the same object. A copy of that object's reference goes to rename; the method changes the name of the same object. All three references look at a single object.

Question 5hard

replace(Pet p) does p = new Pet("Yeni"); p.setName("Degisti"); and rename sets the name to Boncuk. What is printed?

Pet q = new Pet("Fistik");
replace(q);
rename(q);
replace(q);
System.out.println(q.getName());
Show answer
Boncuk

Inside replace, p = new Pet(...) turns only the method's local copy to the new object; q in main is still on the old object. The object that replace changed is lost when the method ends. Only the effect of rename remains.

Question 6medium

What is printed?

static void modify(int[] arr, int v) { arr[0] = 100; v = 100; }
// in main:
int[] nums = {1, 2, 3};
int k = 5;
modify(nums, k);
System.out.println(nums[0] + " " + k);
Show answer
100 5

Both are pass-by-value. For the array, the thing copied is the reference; the method changes an element of the same array. For the int, the thing copied is the value itself; changing v does not touch k.

Question 7hard

Car has a private Engine engine field and getEngine() returns it; Engine has getCc() and setCc(int). What is printed?

Car c = new Car("Clio", new Engine(1000));
Engine en = c.getEngine();
en.setCc(1600);
System.out.println(c.getEngine().getCc());
Show answer
1600

The getter returns not a copy of the object but its reference. en is the very engine inside the car; changing it changes the car's engine. Even if the field is private, if you hand out a changeable object, its inside is not protected.

Question 8medium

enum Coin { QUARTER(25), HALF(50), LIRA(100); ... } has getKurus(). What is printed?

System.out.println(Coin.valueOf("QUARTER").getKurus() + Coin.values().length);
Show answer
28

valueOf("QUARTER") finds the constant from the String; its kurus value is 25. values().length is the number of constants: 3. 25 + 3 = 28.

Question 9medium

Point overrides equals to compare x and y. What is printed?

Point a = new Point(3, 4), b = new Point(3, 4), c = a;
System.out.println((a == b) + " " + a.equals(b) + " " + (a == c));
Show answer
false true true

== checks whether it is the same object: a and b are two separate objects → false. Because equals is overridden, it compares the contents → true. c = a is the same object → true.

Question 10medium

Given enum Level { LOW, HIGH }: (a) does Level x = new Level(); compile? (b) what happens at runtime with Level.valueOf("MID")?

Show answer
(a) No: enum classes may not be instantiated · (b) IllegalArgumentException

The enum constants are fixed; new ones cannot be produced with new, and the compiler says enum classes may not be instantiated. If valueOf is called with a name that does not exist, it compiles but crashes while running with No enum constant Level.MID.

Question 11hard

Write an enum Planet with constants EARTH(9.81) and MARS(3.71), a private final double field gravity, a constructor and a getter.

Show answer
EARTH(9.81), MARS(3.71); private final double gravity; Planet(double g) { gravity = g; }

A ; is put after the constants; the fields and methods come after it. An enum constructor is not written public (it is automatically private).enum Planet { EARTH(9.81), MARS(3.71); private final double gravity; Planet(double g) { gravity = g; } public double getGravity() { return gravity; }}Planet.MARS + " " + Planet.MARS.getGravity() → MARS 3.71.

Question 12medium

Write a class Car that has a model (String) and an Engine (composition), a constructor taking both, and a start() method that prints the model followed by the result of the engine's run().

Show answer
private Engine engine; ... public void start() { System.out.print(model + ": "); engine.run(); }

Car has an Engine (has-a). Car does not do the job itself, it hands it to the engine (delegation).class Car { private String model; private Engine engine; public Car(String model, Engine engine) { this.model = model; this.engine = engine; } public void start() { System.out.print(model + ": "); engine.run(); }}new Car("Egea", new Engine(1400)).start() → Egea: Engine 1400cc running.

Verification: All outputs were run with JDK 26 (verify/SEN1001/W13.java; the classes Date, Employee, Engine, Car, Pet, Point and the enums Day, Level, Planet, Coin are in the same file). The enum errors were obtained with javac and java under verify/SEN1001/err/en/.

Review card

Composition (has-a): a field is an object of another class Employee has-a Date, Car has-a Engine Chain: e.getBirthDate().getYear() the inner object's toString is called automatically inside the outer one's toString A getter gives the REFERENCE of the inner object → it can be changed from outside enum Day { MON, TUE, WED } Day d = Day.WED; there is NO new Day() d.ordinal() position from 0 d.name() / println(d) → "WED" Day.values() array Day.valueOf("WED") valueOf with a wrong name → IllegalArgumentException (runtime) enum comparison == switch(d) { case MON: ... } (no Day. written) enum field+constructor: EARTH(9.81), MARS(3.71); private final double g; Planet(double g) {...} Java is ALWAYS pass-by-value: for objects the thing copied is the REFERENCE A method can change the inside of an object (p.setName) → the caller sees it If a method turns the parameter into a new object (p = new ...) → the caller does not see it swap(a, b) does not work with objects either == same object? equals content (same as == if not overridden)