Methods in Java — exercises with converters, statistics and a guessing game (and the return the compiler couldn’t see)
My first method with an if / else if gave me missing return statement, and I spent a good while staring at it, because to me the two conditions covered every case: either it’s greater than or equal to 5, or it’s less than 5. What else is there? Turns out the compiler doesn’t do that math, and what seems obvious to you looks to it like a path with no way out.
Today it’s your turn to put methods to the test. There are three exercises that build up gradually: a converter to warm up, a method that works with arrays and, to finish, a game where several methods call each other. Before the solutions there’s a section on typical mistakes, and in the third exercise you’ll have to use overloading for real, not just see it in the theory.
Table of Contents
Methods in Java exercises — Basic level
Exercise 1 — Temperature converter
Write a program that asks for a temperature in degrees Celsius and shows it in Fahrenheit and Kelvin. Each conversion must go in its own method, and another method must check that the temperature is possible: nothing can be below absolute zero (-273.15 °C).
Expected output with 36.6 °C:
=== TEMPERATURE CONVERTER === Temperature in °C: 36.6 --- Result --- 36.6 °C = 97.9 °F 36.6 °C = 309.75 K
With -300 the program must warn that such a temperature doesn’t exist and finish without converting anything:
Temperature in °C: -300 ✗ There is no temperature below -273.15 °C
The formulas are F = C × 9/5 + 32 and K = C + 273.15.
Writing celsius * (9 / 5) + 32 with whole numbers. In Java, 9 / 5 between two integers is an integer division and gives 1, not 1.8, so your converter would return celsius + 32 and nobody would notice until comparing it with a thermometer. For the division to keep its decimals, at least one of the two numbers has to be a double: 9.0 / 5.0.
💡 Hints:
- Write three methods:
isValidTemperature(returnsboolean),celsiusToFahrenheitandcelsiusToKelvin(returndouble) - Validate first, and if it’s not valid cut the program with
returninmain, just like the validations in control flow - The conversion methods don’t print anything: they only calculate and return.
mainis the one that prints
Methods in Java exercises — Intermediate level
Exercise 2 — The week’s statistics
Write a program that asks for the maximum temperature of each of the 7 days of a week (integers) and stores them in an array. Then it must show the highest temperature, the lowest, the average, and how many days were above the average. Each calculation must be a method that takes the array.
Expected output with temperatures 21, 24, 19, 27, 25, 18 and 22:
=== WEEK'S TEMPERATURES === Day 1: 21 Day 2: 24 Day 3: 19 Day 4: 27 Day 5: 25 Day 6: 18 Day 7: 22 --- Statistics --- Highest: 27 °C Lowest: 18 °C Average: 22.29 °C Days above average: 3
For the maximum and the minimum, initialize the variable with data[0] and start the loop at i = 1. If you initialize it to 0, a winter week with every temperature below zero would give a maximum of 0 that was never in your data.
These methods receive the original array, not a copy. Since they only read its values, nothing goes wrong. If one day a method modifies them, the change will also show up in main, and you’ll see why in the Python Tutor section.
💡 Hints:
- Four methods:
maximum,minimum,averageandcountGreaterThan, allpublic staticand with anint[]as a parameter - For the average, watch out for the division: if two
intvalues are divided, you lose the decimals. Convert the sum todoublebefore dividing with(double) sum countGreaterThancan take a seconddouble thresholdparameter, so you pass the average in frommaininstead of recalculating it inside
Methods in Java exercises — Final challenge
Exercise 3 — Guess the number
Write a game in which the program “thinks” of a number from 1 to 100 and the player has 7 attempts to guess it. After each miss, the program says whether the secret number is higher or lower. When the round ends it asks if you want to play again, and at the end it shows your best round (the one with the fewest attempts).
To keep the output reproducible, the secret number will always be 42. If a guess is outside 1-100, it’s flagged and it doesn’t count as an attempt.
Expected output of a complete session:
=== GUESS THE NUMBER === Attempt 1/7 (1-100): 150 ✗ It must be between 1 and 100 Attempt 1/7 (1-100): 50 Lower ↓ Attempt 2/7 (1-100): 25 Higher ↑ Attempt 3/7 (1-100): 42 ✓ You got it! Play again? (y/n): y Attempt 1/7 (1-100): 42 ✓ You got it! Play again? (y/n): n Best round: 1 attempt
If you don’t win any round, the program must end with You didn't win any round.
The program must be organized into these methods:
readIntin two versions (overloading): one that just reads an integer, and another that also takes a minimum and a maximum and repeats until the value is within the rangehint, which takes the guess and the secret and returns the help textplayRound, which plays an entire round and returns how many attempts it used, or-1if it didn’t succeed
Once the game works, replace the 42 with a random number and you’ll have a real game: int secret = new Random().nextInt(100) + 1; (with import java.util.Random;). nextInt(100) gives a number from 0 to 99, and the + 1 shifts it to 1-100.
The range version of readInt doesn’t need to repeat the reading code: it can call the simple version. It’s a very common way to use overloading, because each version builds on the previous one and you don’t duplicate anything.
💡 Hints:
playRoundis aforfrom 1 tomaxAttempts: in each pass it reads the guess, and if it’s right it doesreturn n(exiting the entire method right there). If the loop ends without a hit, it returns-1- The “play again” loop in
mainis ado-while, because the first round is always played - To keep track of the best round, use a
bestvariable that starts at-1and only updates whenusedis different from-1and, in addition,besthasn’t been set yet orusedis smaller - To read the
y/nanswer usesc.next()and compare with.equalsIgnoreCase("y"), never with==
What’s wrong here?
Before the solutions, three common mistakes from this part of the syllabus. Can you spot the problem in each one?
Mistake 1
public static String classify(int grade) {
if (grade >= 5) {
return "Pass";
} else if (grade < 5) {
return "Fail";
}
}
// Doesn't compile: "missing return statement"
To you the two conditions cover every case, but the compiler doesn’t reason with numbers: it only sees that the last possible path is an if without an else, and assumes that none of the conditions might be met, in which case the method would end without returning anything. The fix is to close with an else or a final return:
public static String classify(int grade) {
if (grade >= 5) {
return "Pass";
}
return "Fail"; // ← final path: it always returns something
}
Mistake 2
public class Main {
public static int twice(int x) {
return x * 2;
}
public int triple(int x) {
return x * 3;
}
public static void main(String[] args) {
System.out.println(twice(4)); // works
System.out.println(triple(4)); // ← error
}
}
// "non-static method triple(int) cannot be referenced from a static context"
main is static, and from a static method you can’t call one that isn’t. You already know that, for now, static is “that’s just how it’s done”: here you see the consequence of forgetting it. Just add static to triple. When we reach classes and objects you’ll understand why that rule exists.
Mistake 3
public static int doubleIt(int x) {
return x * 2;
}
public static void main(String[] args) {
int n = 5;
doubleIt(n);
System.out.println(n); // prints 5, not 10
}
doubleIt(n) does calculate 10, but that value gets lost because nobody stores it. And n doesn’t change because the method receives a copy of the value, not the variable itself. What a method returns has to be picked up:
n = doubleIt(n); // ← now n is 10
Commented solutions
Solution Exercise 1
import java.util.Scanner;
public class TemperatureConverter {
// private: it's an internal check, only this class uses it.
// boolean: the answer is yes or no.
private static boolean isValidTemperature(double celsius) {
// -273.15 °C is absolute zero: below it, nothing exists
return celsius >= -273.15;
}
// Takes degrees Celsius, returns degrees Fahrenheit. It doesn't print anything: it only calculates
public static double celsiusToFahrenheit(double celsius) {
// 9.0 / 5.0 with decimals. With 9 / 5 it would be an integer division and give 1
return celsius * 9.0 / 5.0 + 32;
}
public static double celsiusToKelvin(double celsius) {
return celsius + 273.15;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
System.out.println("=== TEMPERATURE CONVERTER ===");
System.out.print("Temperature in °C: ");
double celsius = sc.nextDouble();
// We validate BEFORE converting and cut with return, just like in control flow
if (!isValidTemperature(celsius)) {
System.out.println("✗ There is no temperature below -273.15 °C");
sc.close();
return;
}
System.out.println();
System.out.println("--- Result ---");
// main is the one that prints; the methods only calculate and return the value
System.out.printf("%.1f °C = %.1f °F%n", celsius, celsiusToFahrenheit(celsius));
System.out.printf("%.1f °C = %.2f K%n", celsius, celsiusToKelvin(celsius));
sc.close();
}
}
The key decision is that the conversion methods don’t print. If they had printed inside, tomorrow you couldn’t reuse them to, say, store the result in a variable or compare two temperatures. A method that returns the value works for anything; one that prints only works for printing.
Solution Exercise 2
import java.util.Scanner;
public class WeekStats {
public static int maximum(int[] data) {
int max = data[0]; // we start from the first real value, NOT from 0
for (int i = 1; i < data.length; i++) { // we start at 1: we already have position 0
if (data[i] > max) {
max = data[i];
}
}
return max; // the return goes OUTSIDE the loop: inside, it would exit on the first pass
}
public static int minimum(int[] data) {
int min = data[0];
for (int i = 1; i < data.length; i++) {
if (data[i] < min) {
min = data[i];
}
}
return min;
}
public static double average(int[] data) {
int sum = 0;
for (int i = 0; i < data.length; i++) {
sum += data[i];
}
// (double) converts the sum BEFORE dividing; without it, 156 / 7 would give 22 instead of 22.28...
return (double) sum / data.length;
}
// threshold is a double because we'll pass it the average, which has decimals
public static int countGreaterThan(int[] data, double threshold) {
int counter = 0;
for (int i = 0; i < data.length; i++) {
if (data[i] > threshold) {
counter++;
}
}
return counter;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int[] temperatures = new int[7];
System.out.println("=== WEEK'S TEMPERATURES ===");
for (int i = 0; i < temperatures.length; i++) {
System.out.print("Day " + (i + 1) + ": "); // i + 1 so the user sees 1..7, not 0..6
temperatures[i] = sc.nextInt();
}
// We store the average in a variable: we use it twice, so we calculate it only once
double average = average(temperatures);
System.out.println();
System.out.println("--- Statistics ---");
System.out.println("Highest: " + maximum(temperatures) + " °C");
System.out.println("Lowest: " + minimum(temperatures) + " °C");
System.out.printf("Average: %.2f °C%n", average);
System.out.println("Days above average: " + countGreaterThan(temperatures, average));
sc.close();
}
}
Notice that the local variable average and the method average(...) live side by side with the same name. Java tells them apart without trouble: one has parentheses and the other doesn’t. Still, in a large program it’s best not to name them the same, so you don’t confuse yourself.
Solution Exercise 3
import java.util.Scanner;
public class GuessTheNumber {
// ---------- Overloading: two versions of readInt ----------
// Version 1: reads an integer, any integer
private static int readInt(Scanner sc, String message) {
System.out.print(message);
return sc.nextInt();
}
// Version 2: repeats until it's between min and max.
// It doesn't duplicate the reading code: it calls version 1
private static int readInt(Scanner sc, String message, int min, int max) {
int value = readInt(sc, message);
while (value < min || value > max) {
System.out.println("✗ It must be between " + min + " and " + max);
value = readInt(sc, message);
}
return value;
}
// Returns the help text depending on whether the guess fell short or went over
private static String hint(int guess, int secret) {
if (guess < secret) {
return "Higher ↑";
}
return "Lower ↓"; // if we get here, the guess is greater (equal is handled earlier)
}
// Plays ONE round. Returns the attempts used if it succeeds, or -1 if it runs out
private static int playRound(Scanner sc, int secret, int maxAttempts) {
for (int n = 1; n <= maxAttempts; n++) {
// An out-of-range guess never gets here: readInt repeats it and doesn't use up the turn
int guess = readInt(sc, "Attempt " + n + "/" + maxAttempts + " (1-100): ", 1, 100);
if (guess == secret) {
System.out.println("✓ You got it!");
return n; // exits the whole method, with the number of attempts used
}
System.out.println(hint(guess, secret));
}
// If the for ends, it means the attempts ran out without a hit
System.out.println("✗ Out of attempts. The number was " + secret);
return -1;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
final int SECRET = 42; // final = can't be modified; uppercase by convention
final int MAX_ATTEMPTS = 7;
int best = -1; // -1 = "I haven't won any yet"
String another;
System.out.println("=== GUESS THE NUMBER ===");
do {
int used = playRound(sc, SECRET, MAX_ATTEMPTS);
// We update the best round only if it won (used != -1) and it's the first or beats the previous
if (used != -1 && (best == -1 || used < best)) {
best = used;
}
System.out.print("Play again? (y/n): ");
another = sc.next(); // next() reads a single word, enough for "y" or "n"
System.out.println();
} while (another.equalsIgnoreCase("y"));
if (best == -1) {
System.out.println("You didn't win any round");
} else {
// Ternary for singular/plural: "1 attempt" but "3 attempts"
System.out.println("Best round: " + best + (best == 1 ? " attempt" : " attempts"));
}
sc.close();
}
}
Look at how the work is divided. main only coordinates: it launches rounds and keeps track of the best one. playRound knows the rules of the game, but doesn’t know how a number is read or which hint to give, because it delegates that to readInt and hint. If tomorrow you want to change the hint text, you touch a four-line method and don’t have to search for anything inside the loop.
See it with Python Tutor
Select Java from the dropdown and paste this into pythontutor.com:
public class Demo {
public static void tryToChange(int x, int[] arr) {
x = 99; // changes the COPY of x
arr[0] = 99; // changes the ORIGINAL array
}
public static void main(String[] args) {
int n = 1;
int[] data = {1, 2, 3};
tryToChange(n, data);
System.out.println(n); // 1 or 99?
System.out.println(data[0]); // 1 or 99?
}
}
Step through it. When the tryToChange frame opens, you’ll see that x is a brand-new variable with its own 1, independent of n, and that’s why setting x = 99 doesn’t affect main‘s n. In contrast, arr doesn’t contain the array: it contains an arrow pointing to the same array as data. Both arrows end at the same box, so when you do arr[0] = 99 you’re changing the original, and data[0] is 99 when you get back. It’s the visual explanation of the two things that have come up today: why doubleIt(n); without storing the result changes nothing (Mistake 3), and why the statistics methods in Exercise 2 could alter your array if you weren’t careful.
Summary
- A method that returns a value is for calculating; if it prints inside, it’s only good for printing. When in doubt, let it return and let the caller print
- Every path of a method with a return type has to end in a
return: the compiler doesn’t do math, it only checks that no path is left without an exit - Primitive types (
int,double…) are passed by value: the method works with a copy. If you want the result, pick it up withn = method(n) - Arrays are passed as an arrow to the original: a method that modifies them changes the caller’s data
- Overloading lets you use the same name with different parameters, and one version can build on another to avoid repeating code
- A division between two
intvalues is an integer division: to keep decimals, at least one has to be adouble - From a
staticmethod you can only call otherstaticones, for now
Cheat sheet — methods in Java
// ============================================
// CHEAT SHEET — Methods in Java
// Sergio Learns · sergiolearns.com
// ============================================
// ANATOMY OF A METHOD
public static double calculateAverage(int a, int b) {
// │ │ │ │ └── parameters: type + name, one by one
// │ │ │ └── method name
// │ │ └── type it returns (void if it returns nothing)
// │ └── static: for now, "that's just how it's done" (explained with classes)
// └── visibility: public (from outside) or private (this class only)
return (a + b) / 2.0;
}
// RETURNING A VALUE — every path must end in a return
public static String classify(int grade) {
if (grade >= 5) {
return "Pass";
}
return "Fail"; // final path: it always returns something
}
// VOID METHOD — does something, returns nothing
public static void greet(String name) {
System.out.println("Hello, " + name);
}
// PICKING UP WHAT A METHOD RETURNS — otherwise it's lost
n = doubleIt(n); // ← correct
doubleIt(n); // ← the result is discarded
// PASS BY VALUE (primitive types) — the method receives a COPY
static void change(int x) { x = 99; } // doesn't affect the original variable
// ARRAYS AS A PARAMETER — the method receives the ORIGINAL
static void change(int[] a) { a[0] = 99; } // DOES affect the caller's array
static double average(int[] data) { ... } // only reads: no problem
// OVERLOADING — same name, different parameters
static int add(int a, int b) { return a + b; }
static double add(double a, double b) { return a + b; }
static int add(int a, int b, int c) { return a + b + c; }
// ONE VERSION BUILT ON ANOTHER — without duplicating code
static int read(Scanner sc) { return sc.nextInt(); }
static int read(Scanner sc, int min, int max) {
int v = read(sc); // reuses the simple one
while (v < min || v > max) { v = read(sc); }
return v;
}
// RETURN IN THE MIDDLE OF A LOOP — exits the whole method (no break needed)
static boolean containsDigit(String s) {
for (int i = 0; i < s.length(); i++) {
if (Character.isDigit(s.charAt(i))) {
return true;
}
}
return false; // if the loop ends, there were none
}
// INTEGER DIVISION vs WITH DECIMALS
9 / 5 // 1 (int by int)
9.0 / 5 // 1.8 (one double is enough)
(double) sum / n // converts BEFORE dividing
// SCANNER — just ONE in main, passed as a parameter; never close it inside a method
static int readGrade(Scanner sc, String message) { ... }
// ERRORS YOU'LL SEE
// "missing return statement" → some path returns nothing
// "non-static ... from a static context" → the method is missing static
Keep goingAll Java (en) articlesDid this article help you? Here is how you can support SergioLearns →
