Classes, objects and constructors
The arrays lesson ended with a deliberate disaster: three parallel arrays holding names, prices and counts, indexed in lockstep, destroyed the moment anything sorted one of them. That bug is the argument for this module.
A class is how you keep values that belong together from drifting apart.
The problem, stated properly
String[] names = {"Priya", "Arjun", "Kavita"};
int[] tiffins = {26, 18, 30};
String[] pincodes = {"411207", "411014", "411207"};
Nothing in that code says names[1] and tiffins[1] describe the same person.
The relationship lives in your head. Sort one array, insert into another, or
remove an entry from two of the three, and the data is silently wrong — no
exception, no warning, just a report that quietly lies.
A class fixes it
public class Subscriber {
String name;
int tiffins;
String pincode;
}
Three fields. The class is a description — a template. To get an actual
thing, you create an instance with new:
Subscriber s = new Subscriber();
s.name = "Priya";
s.tiffins = 26;
s.pincode = "411207";
Now the three values cannot be separated. An array of Subscriber sorts as
whole people.
Constructors
Setting fields one at a time after construction is a problem: between new and
the last assignment the object is half-built, and nothing forces you to finish.
A constructor takes the values up front.
public class Subscriber {
String name;
int tiffins;
String pincode;
Subscriber(String name, int tiffins, String pincode) {
this.name = name;
this.tiffins = tiffins;
this.pincode = pincode;
}
}
Subscriber s = new Subscriber("Priya", 26, "411207");
A constructor has the class's name and no return type, not even void. If you
write none, Java supplies an invisible no-argument one — and the moment you write
any constructor, that free one disappears. A class with only the three-argument
constructor above has no new Subscriber() any more, which is usually what you
want.
this, and the bug you get without it
this refers to the object the method is running on. In the constructor above it
is disambiguating: this.name is the field, name is the parameter.
Leave it out and watch:
class Broken {
String name;
Broken(String name) {
name = name;
}
}
System.out.println("name is [" + new Broken("Arjun").name + "]");
name is [null]
It compiles. It runs. name = name assigns the parameter to itself and never
touches the field, which keeps its default of null. IntelliJ flags it; javac
does not.
Every constructor parameter that shares a field's name needs this. — and
the right habit is to always write this. for fields, so the question never
arises.
Several constructors
Overloading lets you offer convenient shorter forms, and this(...) chains one
to another so the real work lives in one place:
Subscriber(String name, int tiffins, String pincode) {
this.name = name;
this.tiffins = tiffins;
this.pincode = pincode;
}
Subscriber(String name, String pincode) {
this(name, 26, pincode); // the standard monthly plan
}
this(...) must be the first statement in the constructor. One constructor
should do the assigning and the rest should delegate to it, or validation
added later gets added to one of them and forgotten in the others.
Methods
A method is behaviour that belongs with the data:
int billPaise() {
return tiffins * PRICE_PER_TIFFIN_PAISE;
}
boolean isInServiceArea() {
return pincode.startsWith("4112");
}
Inside a method, the fields are simply available — you are already on an object.
This is the real difference from a function taking a Subscriber as a parameter:
the method is part of the thing.
toString, and a familiar-looking piece of noise
Subscriber s = new Subscriber("Priya Deshmukh", 26, "411207");
System.out.println(s);
Subscriber@799f10e1
That is the same shape as [I@2f4948e4 from the arrays lesson, and the same
cause: every class inherits a toString() from Object that prints the type
name and an identity hash, and yours has not replaced it.
@Override
public String toString() {
return "%s (%s) — %d tiffins".formatted(name, pincode, tiffins);
}
Priya Deshmukh (411207) — 26 tiffins
Write toString on every class that holds data. It costs three lines and it
is the difference between a useful log line and Subscriber@799f10e1 at the
moment you most need to know what went wrong. The records lesson later in this
module generates it for you, which is one of several reasons records exist.
References, and the aliasing trap
A variable of a class type does not hold the object. It holds a reference to it.
Subscription one = new Subscription("Priya", 26);
Subscription two = one;
two.tiffins = 5;
System.out.println(one.tiffins);
System.out.println(one == two);
5
true
There is one object and two names for it. Changing it through two changes what
one sees, because they are the same thing. This is not copying — = on a
reference type copies the reference, never the object.
This is the same fact behind == on strings and arrays, arriving for the third
time. == on any reference type asks "the same object?". By now that should
be automatic.
Aliasing is useful — it is how you pass an object to a method without copying it — and it is a bug when you expected a copy. If you need an independent object, make one explicitly.
static: belonging to the class, not the object
public class Subscription {
static int created = 0;
static final int PRICE_PER_TIFFIN_PAISE = 8_235;
String name;
int tiffins;
Subscription(String name, int tiffins) {
this.name = name;
this.tiffins = tiffins;
created++;
}
}
created exists once, shared by every instance. name exists once per instance.
static |
instance | |
|---|---|---|
| Exists | Once per class | Once per object |
| Accessed as | Subscription.created |
s.name |
Can use this |
No | Yes |
| Created when | The class is first loaded | new runs |
main is static for this reason — it runs before any object exists, as module 1
explained. A static method cannot touch instance fields, because there is no
instance to touch.
static final with a capital-letters name is how Java writes a constant, and
PRICE_PER_TIFFIN_PAISE in one place beats 8235 scattered through the file.
Do not make everything static. A class of static methods with no state is a namespace, not an object, and if that is what you have you may not need a class at all — a point the interfaces lesson comes back to.
Check your work
Why do three parallel arrays break? Nothing in the code records that
names[1] and tiffins[1] belong together. Any operation that reorders or
resizes one and not the others silently misaligns them, with no error.
What does name = name; in a constructor do? Assigns the parameter to
itself. The field is never written and keeps its default — null for a
String. It compiles cleanly. Write this.name = name;.
What happens to the no-argument constructor once you write your own? It disappears. Java only supplies a default constructor for a class that declares none.
Where must this(...) appear? As the first statement of the constructor.
After two = one; two.tiffins = 5;, what is one.tiffins? 5. Both
variables reference one object; = copies the reference, not the object.
Why does printing an object give Subscriber@799f10e1? It inherits
Object.toString(), which prints the type name and an identity hash. Override
toString().
Why can a static method not use this? It belongs to the class and runs
without any instance, so there is no object for this to refer to.
Practice 2, the Subscriber class. A reasonable shape:
public class Subscriber {
static final int PRICE_PER_TIFFIN_PAISE = 8_235;
String name;
String pincode;
int tiffins;
Subscriber(String name, String pincode, int tiffins) {
this.name = name;
this.pincode = pincode;
this.tiffins = tiffins;
}
Subscriber(String name, String pincode) {
this(name, pincode, 26);
}
int billPaise() {
return tiffins * PRICE_PER_TIFFIN_PAISE;
}
@Override
public String toString() {
return "%s (%s) - %d tiffins, Rs %d.%02d".formatted(
name, pincode, tiffins, billPaise() / 100, billPaise() % 100);
}
}
The fields are left open here because practice 4 needs to write to one from outside. That is exactly what the next lesson closes.
Practice 4, the aliasing surprise. Passing a Subscriber to a method that
sets tiffins = 0 changes the caller's object, because the method received a
copy of the reference and both point at one object. Java is always
pass-by-value — but for reference types the value being passed is a reference.
Reassigning the parameter inside the method (s = new Subscriber(...)) does
nothing to the caller; mutating what it points at does.
Practice
-
Reproduce the parallel-array failure. Three arrays, printed as a table. Sort one of them. Print the table again and write down, in one sentence, what the report now claims.
-
Write
Subscriber. Name, pincode, tiffin count. A three-argument constructor and a two-argument one that defaults to 26 tiffins and delegates. AbillPaise()method at Rs 82.35 each. AtoString. Then build an array of three and print them. -
Cause the
thisbug deliberately. Write a constructor withname = name;, print the field, and confirm you getnullwith no complaint from the compiler. Then look at what IntelliJ says about that line. -
Prove aliasing. Write
static void cancel(Subscriber s)that setss.tiffins = 0. Call it and print the caller's object afterwards. Then adds = new Subscriber("Nobody", "000000")as the last line of the method and confirm the caller does not see that. Explain the difference to yourself — it is the whole of "Java is pass-by-value" in one exercise. -
Add a static counter. Count how many
Subscriberobjects have been created. Print it after making four. Then add a static methodresetCount()and notice that it cannot readname— and say why. -
Harder — a delivery run. A
DeliveryRunholds a date, an area, and an array ofSubscriber. Give ittotalPaise()and atoStringthat prints a readable summary. Then write amainthat builds two runs for different areas and prints both. Keep every amount in paise. You will rewrite this class twice in this module — once with encapsulation, once as a record — and comparing the three versions is the point.
Next: encapsulation, and the getter that hands a caller the keys to your internals.
Stuck on this lesson?
Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.
About the internship