Module 5 · Object-Oriented Fundamentals ⏱ 18 min

Classes & Objects

By the end of this lesson you will be able to:
  • Define a class with fields and a constructor, and create objects from it with new
  • Use this to tell a field apart from a same-named parameter
  • Encapsulate state with private fields and public getter methods

Suppose you tracked three bank accounts in a flat script. You would keep one list of owners, a parallel list of balances, and a third of account numbers — and every time you added an account you would have to append to all three, in lockstep. Forget one and the lists slide out of alignment: the second customer's balance gets charged to the first. That whole family of bug — related data drifting apart — is exactly what object-oriented programming exists to prevent.

A class bundles the data and the actions that belong to a thing into a single unit, so they can never be separated. An object is one real account produced from that blueprint.

flowchart LR
  CL["class BankAccount (blueprint)"] --> O1["new BankAccount(Mara, 100)"]
  CL --> O2["new BankAccount(Jules, 250)"]
  style CL fill:#c2410c,color:#fff
A class is the blueprint; each call to new builds a separate object with its own values.

Fields, constructors, and new

Inside a class you declare fields — the data each object carries. A constructor is the special block that sets up a brand-new object the moment it is created; it shares the class's name and has no return type. You bring an object into existence with the new keyword: new BankAccount("Mara", 100) builds one account, with its own owner and balance, separate from every other account.

Two words get used loosely but mean different things. The class is the blueprint, written once. An object — also called an instance — is a concrete value of that type. One class can spawn thousands of instances.

A class is also a type

Defining BankAccount does more than hand you a constructor — it creates a brand-new type you can use everywhere a type is expected. You can declare a variable BankAccount acc;, accept one as a method parameter like void transfer(BankAccount target, int amount), or gather many into a List<BankAccount>. Before you write any classes, the only types you have are the built-in ones (int, String, boolean), and every class you add enlarges the vocabulary your program can speak.

This is what statically typed really means: the compiler knows the type of every expression, so a call like acc.deposit(...) is checked against BankAccount's actual methods at build time.

Main.java — a class with private fields, a constructor, getters, and an instance method. Real, compiling Java.
public class Main {
    static class BankAccount {
        private String owner;
        private int balance;

        BankAccount(String owner, int openingBalance) {
            this.owner = owner;
            this.balance = openingBalance;
        }

        String getOwner() {
            return owner;
        }

        int getBalance() {
            return balance;
        }

        void deposit(int amount) {
            balance = balance + amount;
        }
    }

    public static void main(String[] args) {
        BankAccount acc = new BankAccount("Mara", 100);
        acc.deposit(50);
        System.out.println("Owner: " + acc.getOwner());
        System.out.println("Balance: " + acc.getBalance());
    }
}

Objects carry behaviour, not just data

A class is more than a bag of fields. The methods you write inside it are the actions an object can perform, and each one runs on the object you called it on. Write acc.deposit(50) and the method updates acc's own balance — which is why two accounts stay independent even though they share the same code.

Notice deposit has no static keyword. That marks it as an instance method: it belongs to an object, not to the class as a whole. Inside an instance method, a bare balance quietly means this.balance — the field of whichever object you invoked the method on.

this: the field, not the parameter

Constructors usually name their parameters exactly like the fields they fill — owner for owner. That reads cleanly, but it creates a puzzle: inside the constructor, does the bare word owner mean the incoming parameter or the field?

Java's rule is nearest wins. An unqualified name refers to the closest declaration, which here is the parameter. So owner = owner; would copy a parameter into itself and leave the field untouched — a silent no-op. Writing this.owner = owner; breaks the tie: this means the object currently being built, so the left side is unambiguously the field.

flowchart LR
  IN["new BankAccount(owner, 100)"] --> C["constructor runs"]
  C --> P["bare owner = parameter"]
  C --> F["this.owner = field"]
  P -.-> N["without this:<br/>parameter copied into itself"]
  F --> OK["field set correctly"]
  style F fill:#3776ab,color:#fff
  style N fill:#b45309,color:#fff
Inside a constructor, a bare name resolves to the parameter; this. reaches the field. Forgetting this. assigns a variable to itself.

Encapsulation: the object owns its data

Mark the fields private and the outside world cannot touch them directly — it must go through the methods you choose to expose. A getter hands back a field's value; getBalance() lets callers read the balance without being able to write it. That split between reading and writing is encapsulation: the object keeps control of how its state changes, even as the rest of the program reads the values freely.

deposit is the other half of the bargain. Funneling every change through one method leaves a single place to add rules later — reject negatives, log the transaction, cap the amount.

The trap: objects are references

Here is the surprise that bites every newcomer. Assigning an object to a new variable does not copy it. Box b = a; makes b point at the same object as a — there is still only one box. Mutate it through b and the change is visible through a, because they are two names for one thing.

This is why a == b prints true: == on objects compares references, not the values inside. Two separately-built boxes holding the same number are not ==. To compare contents you call .equals() — the subject of a later lesson. Primitives like int copy on assignment; objects never do.

flowchart LR
  A["Box a"] --> OBJ[("one Box<br/>value 99")]
  B["Box b = a"] -.same object.-> OBJ
  C["Box c = new Box(99)"] --> OBJ2[("a different Box<br/>value 99")]
  style OBJ fill:#b45309,color:#fff
  style OBJ2 fill:#3776ab,color:#fff
Assigning an object makes an alias, not a copy. a and b share one object; c is a different object that merely holds the same value.
Main.java — assigning an object aliases it. Real, compiling Java.
public class Main {
    static class Box {
        int value;

        Box(int value) {
            this.value = value;
        }
    }

    public static void main(String[] args) {
        Box a = new Box(10);
        Box b = a;          // b and a are the SAME object
        b.value = 99;       // so this changes a's value too
        System.out.println("a.value: " + a.value);
        System.out.println("same object: " + (a == b));

        Box c = new Box(99);   // a separate object
        System.out.println("a == c: " + (a == c));
    }
}
Exercise

What does this program print? Read it carefully and type the exact output (two lines).

public class Main {
    static class BankAccount {
        private String owner;
        private int balance;

        BankAccount(String owner, int openingBalance) {
            this.owner = owner;
            this.balance = openingBalance;
        }

        String getOwner() {
            return owner;
        }

        int getBalance() {
            return balance;
        }

        void deposit(int amount) {
            balance = balance + amount;
        }
    }

    public static void main(String[] args) {
        BankAccount acc = new BankAccount("Mara", 100);
        acc.deposit(50);
        System.out.println("Owner: " + acc.getOwner());
        System.out.println("Balance: " + acc.getBalance());
    }
}
Exercise

What is a constructor's job?

Exercise

Inside BankAccount(String owner, ...), what does the bare word owner refer to?

Exercise

References check. y is assigned from x, then mutated. Predict all three lines before you check.

public class Main {
    static class Counter {
        int count;

        Counter(int start) {
            this.count = start;
        }
    }

    public static void main(String[] args) {
        Counter x = new Counter(0);
        Counter y = x;
        y.count = 5;
        System.out.println(x.count);
        System.out.println(y.count);
        System.out.println(x == y);
    }
}
Exercise

A class has private int balance and exposes no method that lowers it. From outside the class, what can a caller do with balance?

Recap

  • A class bundles fields and methods; new builds an instance with its own values, and the class becomes a type you can use everywhere.
  • A constructor runs once at creation to put an object into a valid starting state.
  • this.field means the field; a bare name means the nearest local — usually the parameter.
  • Mark fields private and expose getters/methods so the object controls its own state.
  • Assigning an object makes an alias, not a copy; == compares references, so two equal-valued objects are not ==, and a null reference throws if you dereference it.

Next you will meet Java's collections — the resizable containers that hold many objects like these.

Checkpoint quiz

Inside a constructor, what does this.owner = owner; do?

Why mark a field private and provide a getter instead of leaving it public?

After Box b = a; for two object variables, which is true?

Go deeper — technical resources