Methods & Overloading

Java · Lesson 13 of 32 6 min read Updated

As programs grow, you will notice the same few lines appearing again and again: calculating GST, printing a bill line, checking a score. A method is a named block of code that does one job. You write it once and call it whenever you need it. Methods make programs shorter, easier to test and easier to read.

You have been calling methods since the first lesson (println, Math.max, name.length()). Now you will write your own, pass data into them, return results, and give several methods the same name with overloading.

Defining and calling a method

A method has a header and a body:

  • Return type: the type of value it gives back, or void if it gives back nothing.
  • Name: a verb in camelCase, such as withGst or printBill.
  • Parameters: the inputs, in brackets, each with a type and a name.
  • Body: the code in braces. return value; sends a result back to the caller.

Until you learn about objects, the methods you write sit next to main and start with static, just like main itself. You will see what static really means in Classes, Objects & static.

Java
public class Gst {
    static final double GST_RATE = 0.18;

    static double withGst(double price) {
        return price + price * GST_RATE;
    }

    static void printBill(String item, double price) {
        System.out.printf("%-10s Rs %8.2f%n", item, withGst(price));
    }

    public static void main(String[] args) {
        double headphones = withGst(1500);
        System.out.println("Headphones with GST: " + headphones);

        printBill("Keyboard", 899);
        printBill("Mouse", 450);
    }
}
Output
Headphones with GST: 1770.0
Keyboard   Rs  1060.82
Mouse      Rs   531.00

When main calls withGst(1500), the value 1500 is copied into the parameter price. The value you pass is called an argument. The method runs, return hands back the result, and main continues. Notice that printBill calls withGst too: methods can call other methods, so you build bigger jobs from small, tested pieces.

A method that promises to return a value must do so on every path. If one branch can reach the end without a return, the compiler reports error: missing return statement.

Overloading and varargs

Two methods in the same class can share a name if their parameter lists differ, in the number of parameters or their types. This is overloading. Java picks the right version from the arguments you pass. System.out.println itself is overloaded: there are versions for int, double, String and more.

A varargs parameter, written type... name, accepts any number of arguments, including none. Inside the method it is an ordinary array.

Java
public class Overload {
    static int area(int side) {
        return side * side;
    }

    static int area(int length, int width) {
        return length * width;
    }

    static double area(double radius) {
        return Math.PI * radius * radius;
    }

    static int sum(int... numbers) {
        int total = 0;
        for (int n : numbers) {
            total += n;
        }
        return total;
    }

    public static void main(String[] args) {
        System.out.println(area(5));
        System.out.println(area(12, 8));
        System.out.printf("%.2f%n", area(3.5));

        System.out.println(sum());
        System.out.println(sum(10, 20));
        System.out.println(sum(45, 12, 78, 3, 101));
    }
}
Output
25
96
38.48
0
30
239

area(5) matches the int version (a square), area(12, 8) matches the two-parameter version, and area(3.5) matches the double version (a circle). The return type alone cannot distinguish overloads: two methods named total(int a) that differ only in returning int or double fail with error: method total(int) is already defined. A varargs parameter must be the last one in the list, and a method can have only one.

Java passes arguments by value

When you call a method, Java copies each argument into the parameter. This is pass-by-value. Changing a primitive parameter inside the method has no effect outside. For an array or object, the value copied is the reference, so the method can change the object both variables point to.

Java
import java.util.Arrays;

public class PassByValue {
    static void addBonus(int marks) {
        marks += 5;
        System.out.println("Inside method: " + marks);
    }

    static void addBonusToAll(int[] allMarks) {
        for (int i = 0; i < allMarks.length; i++) {
            allMarks[i] += 5;
        }
    }

    public static void main(String[] args) {
        int rohanMarks = 70;
        addBonus(rohanMarks);
        System.out.println("After method: " + rohanMarks);

        int[] classMarks = {70, 82, 55};
        addBonusToAll(classMarks);
        System.out.println("Array after method: " + Arrays.toString(classMarks));
    }
}
Output
Inside method: 75
After method: 70
Array after method: [75, 87, 60]

rohanMarks stayed 70 because the method changed its own copy. The array changed because the method received a copy of the reference to the same array, exactly as you saw in Arrays. If you want a method to produce a new number, return it: rohanMarks = withBonus(rohanMarks);.

Early returns and recursion

A return ends the method immediately. Checking for bad input at the top and returning early (a guard clause) keeps the main logic flat and readable, instead of deeply nested if blocks.

A method can also call itself. This is recursion. Every recursive method needs a base case that stops the chain; otherwise it calls itself until the program crashes with StackOverflowError.

Java
public class Guard {
    static String admissionResult(String name, int age, double percent) {
        if (name == null || name.isBlank()) {
            return "Rejected: name missing";
        }
        if (age < 17) {
            return "Rejected: " + name + " is too young";
        }
        if (percent < 60) {
            return "Waitlisted: " + name;
        }
        return "Admitted: " + name;
    }

    static long factorial(int n) {
        if (n <= 1) {
            return 1;                    // base case stops the recursion
        }
        return n * factorial(n - 1);     // the method calls itself
    }

    public static void main(String[] args) {
        System.out.println(admissionResult("Farhan", 18, 84.5));
        System.out.println(admissionResult("Isha", 16, 91.0));
        System.out.println(admissionResult("", 19, 70.0));
        System.out.println(admissionResult("Kabir", 18, 55.0));
        System.out.println("5! = " + factorial(5));
        System.out.println("20! = " + factorial(20));
    }
}
Output
Admitted: Farhan
Rejected: Isha is too young
Rejected: name missing
Waitlisted: Kabir
5! = 120
20! = 2432902008176640000

factorial(5) becomes 5 * factorial(4), then 5 * 4 * factorial(3), and so on until factorial(1) returns 1. It returns long because factorials grow fast: 20! is the largest that fits in a long. Recursion is elegant for problems that contain smaller copies of themselves, such as folders inside folders, but a loop is usually simpler for counting.

Common mistakes

  • Ignoring the return value. withGst(500); on its own line calculates a result and throws it away. Store or use it: double total = withGst(500);.
  • Missing return on some path. Every path in a non-void method must return a value.
  • Overloading by return type only. The parameter lists must differ.
  • Expecting a method to change a primitive argument. Java passes a copy. Return the new value instead.
  • Recursion without a base case. The program ends with StackOverflowError.

Key points

  • A method has a return type (or void), a name, parameters and a body; return sends back a result.
  • Arguments are copied into parameters (pass-by-value); for objects and arrays, the reference is copied.
  • Overloaded methods share a name but differ in their parameter lists.
  • type... name (varargs) accepts any number of arguments as an array.
  • Guard clauses with early return keep methods flat; recursive methods need a base case.

Exercise

Write Cricket.java with these methods: strikeRate(int runs, int balls) returning a double (runs per 100 balls, and 0 if balls is 0); an overloaded strikeRate(int[] runs, int[] balls) that totals both arrays and reuses the first method; and printSummary(String name, int runs, int balls) that prints one formatted line. Test it with Rohan: 45 runs off 30 balls, and with his whole series: runs {45, 82, 12} off balls {30, 61, 15}.

Show solution
Java
public class Cricket {
    static double strikeRate(int runs, int balls) {
        if (balls == 0) {
            return 0;
        }
        return runs * 100.0 / balls;
    }

    static double strikeRate(int[] runs, int[] balls) {
        int totalRuns = 0;
        int totalBalls = 0;
        for (int i = 0; i < runs.length; i++) {
            totalRuns += runs[i];
            totalBalls += balls[i];
        }
        return strikeRate(totalRuns, totalBalls);
    }

    static void printSummary(String name, int runs, int balls) {
        System.out.printf("%s: %d off %d, strike rate %.2f%n",
                name, runs, balls, strikeRate(runs, balls));
    }

    public static void main(String[] args) {
        printSummary("Rohan", 45, 30);
        double series = strikeRate(new int[]{45, 82, 12}, new int[]{30, 61, 15});
        System.out.printf("Series strike rate: %.2f%n", series);
    }
}
Output
Rohan: 45 off 30, strike rate 150.00
Series strike rate: 131.13

100.0 makes the division decimal. The array version adds up the totals and then calls the simple version, so the zero-balls check lives in one place. new int[]{45, 82, 12} creates an array right inside the method call.

Quick Quiz

1. What does a method with return type void return?

2. Which pair of methods is a valid overload?

3. A method receives an int and adds 10 to it. What happens to the caller's variable?

4. What must every recursive method have?

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