Lecture 14 Classes and Objects

Joseph Haugh

University of New Mexico

Free Recall

  • Get out a sheet of paper or open a text editor
  • For 2 minutes write down whatever comes to mind about the last class
    • This could be topics you learned
    • Questions you had
    • Connections you made

What We’ve Been Writing

  • Every program you’ve written so far has looked something like this:
void main() {
    IO.println("Hello, world!");
}
  • Or with helper functions:
int square(int x) {
    return x * x;
}

void main() {
    IO.println(square(5));
}
  • Today we’re going to pull back the curtain on what Java is actually doing

The Big Reveal

  • Java is an object-oriented language
  • In Java, everything lives inside a class
  • The style you’ve been using is a shorthand feature called a compact class
  • Java is secretly wrapping your code in a class for you!
  • A full Java file looks like this:
class Main {
    void main() {
        IO.println("Hello, world!");
    }
}

Compact vs Full Class

Compact (what you wrote)

int square(int x) {
    return x * x;
}

void main() {
    IO.println(square(5));
}

Full (what Java sees)

class Main {
    int square(int x) {
        return x * x;
    }

    void main() {
        IO.println(square(5));
    }
}

So Why Do We Care?

  • The real power of classes is that you can define your own types
  • So far you’ve only used types that Java gives you: int, double, String, int[]
  • What if you wanted a type to represent a Point in 2D space?
    • You’d need an x and a y coordinate
  • What if you wanted a type to represent a BankAccount?
    • You’d need an owner name and a balance
  • Classes let you bundle related data and behavior together under a new type name

Real World Objects

  • Everything around us has:
    • State — properties that describe what it is
    • Behavior — things it can do
Object State Behavior
Dog name, breed, age bark, eat, fetch
BankAccount owner, balance deposit, withdraw
Point x, y distanceTo, move
Student name, GPA, major enroll, graduate

Classes and Objects

  • A class is a blueprint
  • An object is a thing built from that blueprint
  • You can build many objects from the same class, each with their own state
  • For example:
    • The Dog class describes what a dog is
    • Your dog Spot is an object — a specific instance of Dog
    • Your neighbor’s dog Rex is a different object — also a Dog, but with different state

Class Syntax

class ClassName {
    // Fields: the data the object holds
    <<type>> fieldName;

    // Constructor: how to create a new object
    ClassName(<<type>> param1, <<type>> param2) {
        // initialize fields
    }

    // Methods: what the object can do
    <<type>> methodName(<<type>> param) {
        // method body
    }
}

Our First Class: Point

  • Let’s define a class to represent a 2D point
  • A point has an x and y coordinate
class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
  • x and y are called fields (also called instance variables)
  • Every Point object will have its own x and y

Fields

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
  • Fields are declared inside the class but outside any method
  • They store the state of each object
  • Every object of this class gets its own copy of the fields
  • Unlike local variables, fields exist for the lifetime of the object

Constructors

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
  • Setting fields one-by-one is tedious
  • A constructor lets you set up an object when you create it
  • Constructors have the same name as the class and no return type

The this Keyword

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
  • this refers to the current object
  • When a parameter name shadows a field name, this.fieldName lets you distinguish them
  • this.x = x means: set this object’s x field to the parameter x

Using the Constructor

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
void main() {
    Point p = new Point(3, 7);
    IO.println(p.x); // 3
    IO.println(p.y); // 7
}
  • Use new to create an object — this calls the constructor
  • Use dot notation to read a field: object.field

Multiple Objects

class Point {
    int x;
    int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}
void main() {
    Point p1 = new Point(3, 7);
    Point p2 = new Point(-1, 0);

    // Each object has its own independent data
    IO.println(p1.x); // 3
    IO.println(p2.x); // -1

    // Changing one does not affect the other
    p1.x = 99;
    IO.println(p1.x); // 99
    IO.println(p2.x); // -1
}

Instance Methods

  • A class can also define methods that work with the object’s fields
  • These are called instance methods
  • Let’s add a method to compute the distance from this point to another:
class Point {
    int x;
    int y;

    // constructor ...

    double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}

Calling Instance Methods

class Point {
    int x;
    int y;
    
    // constructor ...

    double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}
void main() {
    Point origin = new Point(0, 0);
    Point p = new Point(3, 4);
    // Call the method
    double d = origin.distanceTo(p);
    IO.println(d); // 5.0
}

Calling Instance Methods

  • Instance methods are called using dot notation:

    <<objectVariable>>.<<methodName>>(<<args>>)
  • This is the same dot notation you’ve seen with Strings: s.length(), s.charAt(0)

    • Those are instance methods too!

Calling Instance Methods

class Point {
    int x;
    int y;

    // constructor ...

    double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }
}
  • When you write origin.distanceTo(p):
    • origin is the object the method runs on
    • Inside distanceTo, this refers to origin
    • other is the Point p

A More Realistic Example: BankAccount

class BankAccount {
    String owner;
    double balance;

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

BankAccount: Adding Methods

class BankAccount {
    String owner;
    double balance;

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

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

    void withdraw(double amount) {
        balance -= amount;
    }

    double getBalance() {
        return balance;
    }
}

BankAccount: Using It

void main() {
    BankAccount account = new BankAccount("Alice", 1000.0);

    account.deposit(500.0);
    account.withdraw(200.0);

    IO.println(account.getBalance()); // 1300.0
}
  • Notice: we interact with the account through methods
  • The methods read and update the balance field
  • Each method call changes the state of that specific object

Multiple Accounts

void main() {
    BankAccount alice = new BankAccount("Alice", 1000.0);
    BankAccount bob   = new BankAccount("Bob", 500.0);

    alice.deposit(200.0);
    bob.withdraw(100.0);

    // Alice and Bob each have their own balance
    IO.println(alice.getBalance()); // 1200.0
    IO.println(bob.getBalance());   // 400.0
}
  • alice and bob are separate objects — changing one does not affect the other
  • This is the power of objects!

Access Modifiers

  • Right now anyone can directly modify the fields of our objects:

    BankAccount account = new BankAccount("Alice", 500.0);
    account.balance = -99999; // Uh oh!
  • This is dangerous — you might set a balance to an invalid value

  • Access modifiers let you control who can access fields and methods:

    • public — anyone can access it
    • private — only code inside the class can access it

Public and Private

class BankAccount {
    private String owner;
    private double balance;

    public BankAccount(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    public void deposit(double amount) {
        balance += amount;
    }

    public void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
        }
    }

    public double getBalance() {
        return balance;
    }
}

Public and Private

void main() {
    BankAccount account = new BankAccount("Alice", 500.0);

    account.balance = -99999; // COMPILE ERROR! balance is private

    account.deposit(100.0);
    IO.println(account.getBalance()); // 600.0
}
  • Making fields private forces all access to go through methods
  • The methods can validate the input before changing the state
  • This idea is called encapsulation

Getters

  • When fields are private, you often provide a getter method to read the value:
class Point {
    private int x;
    private int y;

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

    int getX() { return x; }
    int getY() { return y; }
}
void main() {
    Point p = new Point(3, 7);
    IO.println(p.getX()); // 3
    IO.println(p.getY()); // 7
}

toString

  • Printing an object right now gives a useless result:
void main() {
    Point p = new Point(3, 7);
    IO.println(p); // Prints something like: Point@3a71f4dd
}
  • That’s the reference (memory address) — same thing we saw with arrays!
  • We can fix this by adding a toString method

toString

class Point {
    private int x;
    private int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { return x; }
    public int getY() { return y; }

    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

toString

void main() {
    Point p = new Point(3, 7);
    IO.println(p.toString()); // (3, 7)
    IO.println(p);            // (3, 7) -- Java calls toString automatically!
}
  • When you pass an object to IO.println, Java automatically calls toString on it
  • Defining toString is a great habit — it makes debugging much easier

Putting It Together: Student

class Student {
    private String name;
    private double gpa;
    private int year; // 1 = freshman, 4 = senior, etc

    public Student(String name, double gpa, int year) {
        this.name = name;
        this.gpa = gpa;
        this.year = year;
    }
}

Student: Methods

class Student {
    private String name;
    private double gpa;
    private int year;

    public Student(String name, double gpa, int year) {
        this.name = name;
        this.gpa  = gpa;
        this.year = year;
    }

    public String getName()  { return name; }
    public double getGpa()   { return gpa; }
    public int    getYear()  { return year; }

    public boolean isHonorsEligible() {
        return gpa >= 3.5;
    }

    public void advanceYear() {
        if (year < 4) { 
            year ++; 
        }
    }

    public String toString() {
        return name + " (Year " + year + ", GPA: " + gpa + ")";
    }
}

Student: Using It

void main() {
    Student s1 = new Student("Alice", 3.8, 1);
    Student s2 = new Student("Bob", 2.9, 3);

    IO.println(s1); // Alice (Year 1, GPA: 3.8)
    IO.println(s2); // Bob (Year 3, GPA: 2.9)

    IO.println(s1.isHonorsEligible()); // true
    IO.println(s2.isHonorsEligible()); // false

    s1.advanceYear();
    IO.println(s1); // Alice (Year 2, GPA: 3.8)
}

Summary

Key Terms

  • Class — blueprint for a type
  • Object — an instance of a class
  • Field — data stored in each object
  • Constructor — sets up a new object
  • Instance method — behavior belonging to an object
  • this — refers to the current object
  • new — creates an object
  • private/public — controls access

Class Structure

class Name {
    <<accessMod>> <<type>> field;

    <<accessMod>> Name(type param) {
        this.field = param;
    }

    <<accessMod>> <<type>> getField() {
        return field;
    }

    <<accessMod>> String toString() {
        return "...";
    }
}