FirstHack Learn
Log in Sign up free

Methods, Classes and Objects

10 min read · 37 views

Methods, Classes and Objects

Everything you have written so far has lived inside main. That works up to about fifty lines. Past that, you need a way to split a program into named pieces and to group data with the code that operates on it.

That is what methods and classes are for, and in Java they are not optional extras. Every method lives inside a class, so learning one means learning the other.

Methods

A method is a named block of code that takes inputs, does one job, and usually returns a result.

Java
public class Main {

    static int add(int a, int b) {
        return a + b;
    }

    static double average(int total, int count) {
        if (count == 0) {
            return 0.0;
        }
        return (double) total / count;
    }

    static void printLine(char symbol, int width) {
        for (int i = 0; i < width; i++) {
            System.out.print(symbol);
        }
        System.out.println();
    }

    static boolean isPrime(int n) {
        if (n < 2) return false;
        for (int i = 2; i * i <= n; i++) {
            if (n % i == 0) return false;
        }
        return true;
    }

    static int maxOf(int a, int b) {
        return (a > b) ? a : b;
    }

    static double maxOf(double a, double b) {
        return (a > b) ? a : b;
    }

    public static void main(String[] args) {
        printLine('=', 30);
        System.out.println("3 + 4 = " + add(3, 4));
        System.out.printf("Average: %.2f%n", average(250, 4));
        System.out.println("Is 29 prime? " + isPrime(29));
        System.out.println("Is 30 prime? " + isPrime(30));
        System.out.println("maxOf ints:    " + maxOf(3, 9));
        System.out.println("maxOf doubles: " + maxOf(2.5, 1.5));
        printLine('-', 30);
    }
}

The parts of static int add(int a, int b) are the modifiers, the return type, the name, and the parameter list. void as a return type means nothing is returned.

Those two maxOf methods are overloading: several methods share one name and differ in their parameter types. The compiler picks the right one from the arguments at the call. They must differ in parameter types or count; two methods differing only in return type will not compile.

Java always passes by value. For a primitive, the method gets a copy and changing it does not affect the caller. For an object, the method gets a copy of the reference, so it cannot repoint your variable, but it can modify the object both now point at.

static versus instance

This is the distinction that confuses most beginners, so here it is plainly.

A static member belongs to the class. There is exactly one of it, it exists from the moment the class is loaded, and you reach it through the class name: Math.sqrt(16), Integer.parseInt("42").

A non-static, or instance, member belongs to an object. Each object gets its own copy, it does not exist until you create the object, and you reach it through the object: student.getName().

The rule that follows is behind a very common error: a static method cannot directly use an instance member, because there is no object for it to use. main is static, so calling an instance method from main without an object gives non-static method cannot be referenced from a static context. Create an object first.

Use static for utilities that depend only on their arguments, like isPrime. Use instance members for anything that describes a particular thing.

Defining a class

A class is a blueprint describing what data an object holds (fields) and what it can do (methods). An object is one concrete thing built from that blueprint, created with new.

Java
class Student {
    private String name;
    private int rollNumber;
    private double cgpa;

    static int studentCount = 0;

    Student(String name, int rollNumber, double cgpa) {
        this.name = name;
        this.rollNumber = rollNumber;
        setCgpa(cgpa);
        studentCount++;
    }

    Student(String name, int rollNumber) {
        this(name, rollNumber, 0.0);
    }

    public String getName() {
        return name;
    }

    public double getCgpa() {
        return cgpa;
    }

    public void setCgpa(double value) {
        if (value < 0.0 || value > 10.0) {
            System.out.println("Rejected invalid CGPA: " + value);
            return;
        }
        this.cgpa = value;
    }

    public boolean hasDistinction() {
        return cgpa >= 8.5;
    }

    public void print() {
        System.out.printf("%-4d %-15s %.2f%s%n",
            rollNumber, name, cgpa, hasDistinction() ? "  *" : "");
    }
}

public class Main {
    public static void main(String[] args) {
        Student a = new Student("Ananya Rao", 101, 8.6);
        Student b = new Student("Vikram Singh", 102, 7.15);
        Student c = new Student("Meera Nair", 103);

        a.print();
        b.print();
        c.print();

        c.setCgpa(9.2);
        c.setCgpa(15.0);
        c.print();

        System.out.println("Students created: " + Student.studentCount);
        System.out.println(a.getName() + " has CGPA " + a.getCgpa());
    }
}

Several ideas are packed into that class.

A constructor has the same name as the class and no return type. It runs automatically when you write new Student(...). Because there is no way to get an object without it running, it is the right place to establish whatever the class needs to be true.

this refers to the current object. It is needed in this.name = name; because the parameter and the field share a name and the parameter would otherwise shadow the field. Renaming the parameter would avoid it, but this pattern is the common convention.

There are two constructors, which is overloading again. The second delegates to the first with this(name, rollNumber, 0.0), so the validation lives in one place. A this(...) call must be the first statement in the constructor.

studentCount is static, so there is one counter shared by all students rather than one per student. That is exactly what a count of objects should be, and you read it as Student.studentCount.

Encapsulation

The fields are private, meaning only code inside Student can touch them. Access goes through public methods: getters to read, setters to write with validation.

It is worth being clear about why this helps, because "hide the data" sounds like paperwork. The real benefit is that it shrinks how much code you have to read when a value is wrong. If cgpa were public and it holds 15.0, the culprit is anywhere in the program. Because it is private, the culprit is inside Student, which is fifty lines. That is the entire argument, and it is a good one.

Notice too that setCgpa rejects 15.0 and the constructor routes through the same setter, so there is no path into the object that skips the check.

💡Ask what your object promises

Before writing fields, finish this sentence: "a Student object always guarantees that ...". Here it is "cgpa is always between 0 and 10". That sentence tells you which fields must be private and which checks belong in the constructor and setters. If a class has no such promise, it is probably just a bundle of data and can stay simple.

One file, several classes

The example above has Student and Main in one file. Java allows this as long as only one class is public and it matches the filename. For real projects each class gets its own file, but for a single exercise this is fine and it is what an online judge expects.

Common mistakes

  • Calling an instance method from main without an object. The error is non-static method cannot be referenced from a static context. Create the object first.
  • Giving the constructor a return type. void Student(...) is an ordinary method that happens to share the name, not a constructor. Constructors have no return type.
  • Defining a constructor with arguments and then writing new Student(). Once you define any constructor, Java stops supplying the no-argument default. Add one if you need it.
  • Forgetting this when a parameter shadows a field. name = name; assigns the parameter to itself and leaves the field null. Write this.name = name;.
  • Making every field public "for now". The validation stops being enforceable and the class becomes a struct with extra ceremony.
  • Marking a helper method non-static and calling it from main. Either make the helper static or create an object.
  • Two classes marked public in one file. Only one may be, and its name must match the filename.

Once main is a short list of calls and your data lives inside classes that protect it, you have moved from writing code to designing programs. Take an exercise you solved with loose methods and rewrite it as a class with private fields, then work through the object-oriented problems on the Practice page.

Create a free account to track what you have finished.