Methods & Overloading
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
voidif it gives back nothing. - Name: a verb in camelCase, such as
withGstorprintBill. - 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.
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);
}
}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.
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));
}
}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.
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));
}
}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.
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));
}
}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-
voidmethod 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;returnsends 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
returnkeep 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
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);
}
}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?
void means the method does not give back a value.
2. Which pair of methods is a valid overload?
Overloads must differ in their parameter lists, not in the return type or parameter names.
3. A method receives an int and adds 10 to it. What happens to the caller's variable?
Java passes primitives by value, so the caller's variable is unchanged.
4. What must every recursive method have?
Without a base case, the method calls itself until a StackOverflowError.
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
