Learn Java in 10 DaysDay 6: Classes and Objects
books.chapter 6Learn Java in 10 Days

Day 6: Classes and Objects

What You'll Learn Today

  • Classes and objects fundamentals
  • Fields and methods
  • Constructors
  • Access modifiers
  • Static members
  • Records (Java 16+)

Classes and Objects

A class is a blueprint, and an object is an instance created from that blueprint.

// Defining a class (blueprint)
public class Dog {
    // Fields (attributes)
    String name;
    int age;

    // Method (behavior)
    void bark() {
        System.out.println(name + ": Woof!");
    }
}

// Creating an object (instantiation)
Dog dog1 = new Dog();
dog1.name = "Buddy";
dog1.age = 3;
dog1.bark(); // Buddy: Woof!
flowchart LR
    subgraph Class["Class (Blueprint)"]
        CD["Dog<br>name, age<br>bark()"]
    end
    subgraph Objects["Objects (Instances)"]
        O1["dog1<br>Buddy, 3 yrs"]
        O2["dog2<br>Max, 5 yrs"]
    end
    Class -->|"new"| O1
    Class -->|"new"| O2
    style CD fill:#3b82f6,color:#fff
    style O1 fill:#22c55e,color:#fff
    style O2 fill:#22c55e,color:#fff

Constructors

A constructor is a special method called when an object is created to initialize its state.

public class Dog {
    String name;
    int age;

    // Constructor
    Dog(String name, int age) {
        this.name = name; // this = the current object
        this.age = age;
    }

    void introduce() {
        System.out.printf("%s (%d years old)%n", name, age);
    }
}

// Usage
Dog dog = new Dog("Buddy", 3);
dog.introduce(); // Buddy (3 years old)

Constructor Overloading

public class Dog {
    String name;
    int age;
    String breed;

    // All-arguments constructor
    Dog(String name, int age, String breed) {
        this.name = name;
        this.age = age;
        this.breed = breed;
    }

    // Name and age only
    Dog(String name, int age) {
        this(name, age, "Unknown"); // Call another constructor
    }

    // Name only
    Dog(String name) {
        this(name, 0, "Unknown");
    }
}

Key point: You can use this() to call another constructor in the same class. This is known as constructor chaining.


Access Modifiers

public class BankAccount {
    private String owner;
    private double balance;

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

    // getter
    public double getBalance() {
        return balance;
    }

    // deposit
    public void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }

    // withdraw
    public boolean withdraw(double amount) {
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            return true;
        }
        return false;
    }
}
Modifier Same Class Same Package Subclass Anywhere
private Yes No No No
(default) Yes Yes No No
protected Yes Yes Yes No
public Yes Yes Yes Yes
flowchart TB
    subgraph Access["Access Modifier Scope"]
        Private["private<br>Same class only"]
        Default["(default)<br>Same package"]
        Protected["protected<br>+ Subclasses"]
        Public["public<br>Anywhere"]
    end
    Private --> Default --> Protected --> Public
    style Private fill:#ef4444,color:#fff
    style Default fill:#f59e0b,color:#fff
    style Protected fill:#3b82f6,color:#fff
    style Public fill:#22c55e,color:#fff

Best practice: Keep fields private and provide getter/setter methods as needed. This principle is called encapsulation.


Getters and Setters

public class Student {
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        setScore(score);
    }

    // getters
    public String getName() { return name; }
    public int getScore() { return score; }

    // setter (with validation)
    public void setScore(int score) {
        if (score >= 0 && score <= 100) {
            this.score = score;
        } else {
            throw new IllegalArgumentException("Score must be between 0 and 100");
        }
    }
}

Static Members

A static member belongs to the class itself, not to any particular object.

public class Counter {
    private static int count = 0; // shared across all objects

    private String name;

    Counter(String name) {
        this.name = name;
        count++;
    }

    // static method
    static int getCount() {
        return count;
    }

    void show() {
        System.out.printf("%s (total: %d)%n", name, count);
    }
}

// Usage
Counter a = new Counter("A");
Counter b = new Counter("B");
Counter c = new Counter("C");
System.out.println(Counter.getCount()); // 3
Type Belongs To Access Pattern
Instance member Object obj.method()
Static member Class ClassName.method()

Note: Static methods cannot access this or non-static members.


The toString Method

Customize the string representation of an object.

public class Dog {
    private String name;
    private int age;

    Dog(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return String.format("Dog{name='%s', age=%d}", name, age);
    }
}

Dog dog = new Dog("Buddy", 3);
System.out.println(dog); // Dog{name='Buddy', age=3}

Records (Java 16+)

Records provide a concise way to define classes whose sole purpose is to hold data.

// Traditional class (lots of boilerplate)
public class Point {
    private final int x;
    private final int y;
    Point(int x, int y) { this.x = x; this.y = y; }
    int x() { return x; }
    int y() { return y; }
    // + equals, hashCode, toString...
}

// Record (equivalent functionality in one line)
record Point(int x, int y) {}

Records automatically generate:

  • A constructor
  • Accessor methods (x(), y())
  • equals(), hashCode(), toString()
record Student(String name, int score) {
    // You can add custom methods
    String grade() {
        if (score >= 90) return "A";
        if (score >= 80) return "B";
        if (score >= 70) return "C";
        return "D";
    }
}

var s = new Student("Alice", 85);
System.out.println(s.name());   // Alice
System.out.println(s.score());  // 85
System.out.println(s.grade());  // B
System.out.println(s);          // Student[name=Alice, score=85]

Recommendation: Use records for classes that simply hold data.


Hands-On: Bank Account System

public class BankAccount {
    private static int nextId = 1;

    private final int id;
    private String owner;
    private double balance;

    public BankAccount(String owner, double initialBalance) {
        this.id = nextId++;
        this.owner = owner;
        this.balance = initialBalance;
    }

    public BankAccount(String owner) {
        this(owner, 0);
    }

    public void deposit(double amount) {
        if (amount <= 0) {
            System.out.println("Deposit amount must be positive.");
            return;
        }
        balance += amount;
        System.out.printf("[Account %d] Deposited $%,.2f -> Balance: $%,.2f%n", id, amount, balance);
    }

    public boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance) {
            System.out.println("Withdrawal failed.");
            return false;
        }
        balance -= amount;
        System.out.printf("[Account %d] Withdrew $%,.2f -> Balance: $%,.2f%n", id, amount, balance);
        return true;
    }

    public void transfer(BankAccount to, double amount) {
        if (this.withdraw(amount)) {
            to.deposit(amount);
            System.out.printf("-> Transferred $%,.2f from %s to %s%n", amount, this.owner, to.owner);
        }
    }

    @Override
    public String toString() {
        return String.format("Account %d: %s (Balance: $%,.2f)", id, owner, balance);
    }

    public static void main(String[] args) {
        var alice = new BankAccount("Alice", 1000.00);
        var bob = new BankAccount("Bob", 500.00);

        System.out.println(alice);
        System.out.println(bob);
        System.out.println();

        alice.deposit(300.00);
        bob.withdraw(100.00);
        alice.transfer(bob, 250.00);

        System.out.println();
        System.out.println(alice);
        System.out.println(bob);
    }
}

Summary

Concept Description
Class A blueprint for creating objects
Object An instance created from a class
Constructor Initializes an object upon creation
this A reference to the current object
Access modifiers private / protected / public
Encapsulation Protecting fields by making them private
static A member that belongs to the class
Record A concise data-holding class (Java 16+)

Key Takeaways

  1. Keep fields private (encapsulation)
  2. Use constructors to properly initialize objects
  3. static members are shared across the entire class
  4. Use records for data-only classes

Exercises

Exercise 1: Basic

Create a Book class (with title, author, and page count), instantiate several books, and display their information.

Exercise 2: Applied

Create a ShoppingCart class that supports adding items, removing items, and calculating the total price.

Challenge

Using record, define a Money record (amount and currency) and implement addition and subtraction methods for the same currency. Throw an error if the currencies differ.


References


Next up: In Day 7, you'll learn about Inheritance and Polymorphism -- how to extend and reuse classes effectively.