Classes, Objects & static

Java · Lesson 14 of 32 6 min read Updated

Until now, your data has lived in separate variables: a name here, a balance there. Real programs deal with things: bank accounts, books, train tickets, students. Each thing has data (an account has an owner and a balance) and behaviour (you can deposit and withdraw). A class describes one kind of thing, and an object is one actual thing made from that class.

This is the core of object-oriented programming (OOP), and Java is built around it: every line of Java code lives inside a class. In this lesson you will write classes, create objects, and learn the difference between members that belong to each object and static members that belong to the class itself.

Classes and objects

Think of a class as the printed form for a library card and objects as the filled-in cards. The class lists the fields (the data each object holds). The keyword new creates an object, and you reach its fields with a dot.

Java
public class Library {
    public static void main(String[] args) {
        Book first = new Book();
        first.title = "Wings of Fire";
        first.author = "A. P. J. Abdul Kalam";
        first.pages = 180;

        Book second = new Book();
        second.title = "The Guide";
        second.author = "R. K. Narayan";

        System.out.println(first.title + " by " + first.author + ", " + first.pages + " pages");
        System.out.println(second.title + " has " + second.pages + " pages recorded");
        System.out.println(second.issuedTo);
    }
}

class Book {
    String title;
    String author;
    int pages;
    String issuedTo;
}
Output
Wings of Fire by A. P. J. Abdul Kalam, 180 pages
The Guide has 0 pages recorded
null

This file contains two classes. Only one class in a file can be public, and the file is named after it. When you run a file directly with java Library.java, put the class with main first. Each Book object has its own copy of every field. Fields you do not set get default values, just like array elements: 0 for numbers, false for booleans and null for references.

Methods and this

Methods inside a class (without static) are instance methods: you call them on an object, and they work with that object's fields. Inside a method, the keyword this means "the object this method was called on". You can usually leave it out, as in balance += amount, but it helps when a parameter has the same name as a field.

Java
public class Wallet {
    public static void main(String[] args) {
        Account priya = new Account();
        priya.owner = "Priya";
        priya.deposit(5000);
        priya.withdraw(1200);
        priya.withdraw(9000);
        System.out.println(priya.summary());

        Account rohan = new Account();
        rohan.owner = "Rohan";
        rohan.deposit(800);
        System.out.println(rohan.summary());
    }
}

class Account {
    String owner;
    double balance;

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

    boolean withdraw(double amount) {
        if (amount > balance) {
            System.out.println("Sorry " + owner + ", only Rs " + balance + " available");
            return false;
        }
        balance -= amount;
        return true;
    }

    String summary() {
        return owner + ": Rs " + this.balance;
    }
}
Output
Sorry Priya, only Rs 3800.0 available
Priya: Rs 3800.0
Rohan: Rs 800.0

priya.deposit(5000) changes only Priya's balance; Rohan's account is a separate object. Data and the code that changes it now live together, which is the main idea of OOP. Right now anyone can still write priya.balance = 1000000;, though. You will lock that down in Encapsulation & Packages.

Objects live behind references

As with arrays, a variable of a class type holds a reference to an object, not the object itself. Two variables can refer to the same object, == compares references, and a reference can be null.

Java
public class Refs {
    public static void main(String[] args) {
        Phone a = new Phone();
        a.model = "Galaxy";
        Phone b = a;                 // same object, second name
        b.model = "Pixel";
        System.out.println(a.model);

        Phone c = new Phone();
        c.model = "Pixel";
        System.out.println(a == b);
        System.out.println(a == c);
        System.out.println(c);

        Phone lost = null;
        System.out.println(lost.model.length());
    }
}

class Phone {
    String model;
}
Output
Pixel
true
false
Phone@525b461a
Exception in thread "main" java.lang.NullPointerException: Cannot read field "model" because "lost" is null
	at Refs.main(Refs.java:16)

a and b are one phone with two names, so changing b.model changed what a sees. a and c hold the same text but are different objects, so a == c is false. Printing an object shows its class name and an internal code, unless the class defines toString (you will do that next). Using a null reference throws a NullPointerException. Since Java 14 the message tells you exactly which variable was null; if your program was compiled without debug information (as java File.java does), the name appears as something like "<local4>" instead of "lost".

static: members that belong to the class

Sometimes a value or method belongs to the class as a whole, not to any single object: the ticket price that all tickets share, or a counter of how many tickets were sold. Mark such members static. There is only one copy of a static field, and you use it through the class name: Ticket.sold.

Java
public class StaticDemo {
    public static void main(String[] args) {
        Ticket t1 = new Ticket("Aarav", 3);
        Ticket t2 = new Ticket("Meera", 1);
        Ticket t3 = new Ticket("Kabir", 5);

        System.out.println(t1);
        System.out.println(t3);
        System.out.println("Tickets sold: " + Ticket.sold);
        System.out.println("Price for 4 adults: Rs " + Ticket.priceFor(4));
    }
}

class Ticket {
    static final int PRICE = 250;      // one value for the whole class
    static int sold = 0;               // shared counter

    final int number;                  // each ticket has its own
    String name;
    int seats;

    Ticket(String name, int seats) {   // a constructor: runs on "new"
        sold++;
        this.number = sold;
        this.name = name;
        this.seats = seats;
    }

    static int priceFor(int seats) {
        return seats * PRICE;
    }

    @Override
    public String toString() {
        return "Ticket #" + number + " for " + name + " (" + seats + " seats, Rs " + priceFor(seats) + ")";
    }
}
Output
Ticket #1 for Aarav (3 seats, Rs 750)
Ticket #3 for Kabir (5 seats, Rs 1250)
Tickets sold: 3
Price for 4 adults: Rs 1000

Two new things appear here. Ticket(String name, int seats) is a constructor: it runs automatically when you write new Ticket(...) and sets up the new object. this.name = name copies the parameter into the field of the same name. Constructors get their own lesson next: Constructors. And toString tells Java how to print a Ticket; println(t1) calls it automatically. @Override marks that it replaces a method every class inherits, which you will understand in Inheritance & Polymorphism.

The rule to remember: static code cannot use instance fields directly, because there is no particular object to read them from. Writing count++ for an instance field inside static void main fails with error: non-static variable count cannot be referenced from a static context. Instance methods, on the other hand, can use static members freely, as toString does with priceFor.

Classes such as Math contain only static methods, which is why you call Math.max(...) without creating a Math object.

Common mistakes

  • Forgetting new. Account a; a.deposit(100); does not compile, because a was never given an object. Account a = null; a.deposit(100); compiles but throws NullPointerException.
  • Making everything static. Beginners add static to silence the "static context" error. Instead, create an object and call methods on it.
  • Comparing objects with ==. It checks whether two references point to the same object, not whether the contents match.
  • Expecting a useful println(obj) without toString. Add a toString method to your classes.

Key points

  • A class defines fields (data) and methods (behaviour); new creates objects from it.
  • Each object has its own copy of instance fields; unset fields get default values.
  • this refers to the current object; instance methods work on the object they are called on.
  • Variables of class types hold references; == compares references, and null means no object.
  • static fields and methods belong to the class, are shared, and are used through the class name.
  • Static code cannot access instance fields or methods without an object.

Exercise

Create a class Student with fields name, marks (an int[]) and a static field count that counts how many students were created (increase it in a constructor). Add an instance method average() returning a double, an instance method topMark(), and a toString method. In main, create Ananya with marks 88, 92, 79 and Farhan with marks 65, 71, 80, print both, and print the count.

Show solution
Java
public class School {
    public static void main(String[] args) {
        Student ananya = new Student("Ananya", new int[]{88, 92, 79});
        Student farhan = new Student("Farhan", new int[]{65, 71, 80});
        System.out.println(ananya);
        System.out.println(farhan);
        System.out.println("Students created: " + Student.count);
    }
}

class Student {
    static int count = 0;

    String name;
    int[] marks;

    Student(String name, int[] marks) {
        this.name = name;
        this.marks = marks;
        count++;
    }

    double average() {
        int total = 0;
        for (int m : marks) {
            total += m;
        }
        return (double) total / marks.length;
    }

    int topMark() {
        int best = marks[0];
        for (int m : marks) {
            best = Math.max(best, m);
        }
        return best;
    }

    @Override
    public String toString() {
        return "%s: average %.1f, top %d".formatted(name, average(), topMark());
    }
}
Output
Ananya: average 86.3, top 92
Farhan: average 72.0, top 80
Students created: 2

count is static, so both constructors increase the same counter. average() and topMark() are instance methods because each student has different marks.

Quick Quiz

1. What does new Account() do?

2. A class has static int sold;. How many copies of sold exist when there are 50 objects?

3. Why does count++ inside public static void main fail when count is an instance field?

4. What does this mean inside an instance method?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call