Classes and objects: review
Fields, constructors, this, encapsulation, static vs instance, toString and equals. The rest of the semester builds on this foundation.
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:
- A variable does not hold the object, it holds the object’s address (reference).
b = adoes not create a new object. - As soon as you write a constructor, the no-argument default constructor disappears.
- If a field and a parameter have the same name and you do not write
this., the field does not change. - What is static belongs to the class and has a single copy. A static method cannot access instance fields directly.
==compares addresses,equalscompares 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 type | Default |
|---|---|
int, long, short, byte | 0 |
double, float | 0.0 |
boolean | false |
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:
- Resolving shadowing. If a parameter has the same name as a field, a plain
nameinside the method refers to the parameter. To refer to the field you writethis.name. - 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 usethisbefore 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 modifier | Same class | Same package | Subclass | Everywhere |
|---|---|---|---|---|
private | yes | no | no | no |
| (not written, package-private) | yes | yes | if in the same package | no |
protected | yes | yes | yes | no |
public | yes | yes | yes | yes |
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 object | The class, a single copy |
| How is it called? | object.method() | Class.method() |
Is there a this? | Yes | No |
| What can it access? | Both instance and static members | Only static members (directly) |
| Typical example | balance, 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
intanddouble, 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@Overrideon a method, the compiler warns you when you get the signature wrong (for exampleequals(Point o)ortostring()). If you do not, you have silently created a new method.
Common mistakes
| Wrong idea | The truth |
|---|---|
name = name; fills the field | It assigns the parameter to itself, the field stays null. this.name = name; |
I wrote a constructor, new Car() still works | Once you write a constructor, the default constructor is not added |
Writing void on a constructor is no problem | If you write void it becomes an ordinary method, not a constructor |
b = a; creates a copy object | A second reference to the same object; when one changes, the other sees it |
| I access an instance field from a static method | Create an object first: obj.field |
| A static counter is separate in each object | A single copy, shared by all objects |
Comparing a String with == | Use equals |
equals(Point p) overrides | The parameter must be Object, otherwise it is an overload |
| I gave the method an int, the method changed it | A primitive is copied, the outside does not change |
this(...) can be on any line you like | The rule the exam follows: the first line of the constructor |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
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
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.
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
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;.
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
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.
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
== 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.
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
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.
Find the compile error.
public class Test {
private int score = 10;
public static void main(String[] args) {
System.out.println(score);
}
}Show answer
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.
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
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.
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
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.
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
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.
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
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.
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
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.
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 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).