Skip to content
Ameen Abozaid
PublishedBEG-014-L07

Demo: building the calculator one method at a time

  • Lesson 7 of 8
  • 12 min read
  • Beginner
On This Lesson 11 topics

Lesson Overview

This is a demo lesson. Instead of a new idea, you will watch (and then repeat) a program grow from nothing into a working menu-driven calculator, one small method at a time, using everything from Lessons 1 to 6: methods with parameters and return values, separate computing from printing, and a handler per task. Your lab, BEG-014-LAB1, is the same program with two additions, so treat this lesson as the warm-up: type it in, run it, break it, and improve it.

The recipe-card picture fits well. A restaurant does not have one giant recipe called "dinner". It has cards for sauce, for dough, for the garnish, and a head chef who calls them in the right order. Our calculator will have cards (add, divide, power, readNumber, printResult) and a short main that plays the head chef.

Follow along in your own editor or on the command line (javac Calculator.java, then java Calculator). Menus for creating files vary between editors, so use whichever way you normally create a Java file. This lesson builds on all earlier lessons in the module, and on Module 11 (Scanner) and Module 13 (loops).

Learning Objectives

By the end of this lesson you will be able to:

  • Build a program in layers: pure maths methods first, then input and output methods, then the loop that connects them.
  • Keep computing methods free of println and Scanner.
  • Pass a Scanner to a helper method instead of creating a new one each time.
  • Handle division by zero in the caller, before the maths method is used.
  • Spot and repair the "method that prints instead of returning" mistake.
  • Keep main short by delegating to handler methods.

Written Explanation

The plan: three layers

We build in this order, running the program after each step:

  1. Math layer: add, subtract, multiply, divide, power. Each takes numbers, returns a number, prints nothing.
  2. Input/output layer: printMenu, readNumber, printResult. These talk to the user and do no maths.
  3. Control layer: runBasic, runPower and main, which read the menu choice and connect the other layers.

The rule behind the layering is the one from Lesson 2: methods that compute return values; methods that show or read values do the printing and reading. Because of it, the maths layer works whether the result goes to the screen, a file, or (later) a web page, and each layer can be fixed without touching the others.

Step 1: the maths methods

java
static double add(double a, double b)      { return a + b; }
static double subtract(double a, double b) { return a - b; }
static double multiply(double a, double b) { return a * b; }

Test them from a temporary main (System.out.println(add(2, 3)); gives 5.0). We use double so the calculator accepts decimals.

Step 2: the planned mistake

Suppose that, in a hurry, you write add like this:

java
static double add(double a, double b) {
    System.out.println(a + b);   // prints instead of just returning
    return a + b;
}

It seems to work when you test it. The problem appears at the next feature. If you later want a "memory" that adds a result to a running total, printResult(add(a, b)) would show the number twice, once from inside add and once from printResult. A method that prints can never be reused quietly. The repair is to delete the println in add and let the caller decide how to show the value. Every maths method stays silent; only the printing layer prints.

Step 3: division and the zero problem

divide has one special case. For double values, Java does not throw an error on a zero divisor; it produces special values. A quick test on JDK 21 printed Infinity for 1.0 / 0 and NaN ("not a number") for 0.0 / 0. Integer division by zero, on the other hand, throws an ArithmeticException (1 / 0 crashed with / by zero in my run). Neither result is something a user wants to see as an answer.

So who should handle it? Two reasonable designs: the method signals the problem, or the caller checks first. Because divide has no good number to return for a zero divisor, we choose the second: the caller checks b == 0 and prints a friendly message, and divide stays a simple one-line method. Later modules introduce exceptions, which are the usual professional way for a method to report a problem; for now the check-first approach is clear and testable. The acceptance rule for the lab is the same: division by zero must be handled, not crash and not print Infinity.

Step 4: power with a loop

java
static double power(double base, int exponent) {
    double result = 1;
    for (int i = 0; i < exponent; i++) {
        result *= base;
    }
    return result;
}

The loop multiplies result by base, exponent times, starting from 1. That also handles exponent 0 correctly (the loop body never runs, so the answer is 1). The method assumes the exponent is zero or more; negative exponents are an extension you can add in the lab.

Step 5: the menu, input helpers, and the loop

printMenu prints the options. readNumber(Scanner in, String prompt) prints a prompt and reads a double. Notice that it receives the Scanner as a parameter. Creating a second Scanner on System.in in every helper is a source of bugs, so we create one in main and pass it down. Passing it copies the reference (Lesson 4), so every helper reads from the same input.

To keep main short we add two handler methods: runBasic for choices 1 to 4 and runPower for choice 5. Next, the whole program in its final form (factorial, the missing piece, is yours to add in the lab).

Examples

Example 1: the finished program (simple to read, realistic to run)

java
import java.util.Scanner;

public class Calculator {
    // --- math: compute and return, never print or read ---
    static double add(double a, double b) {
        return a + b;
    }

    static double subtract(double a, double b) {
        return a - b;
    }

    static double multiply(double a, double b) {
        return a * b;
    }

    static double divide(double a, double b) {
        return a / b;
    }

    static double power(double base, int exponent) {
        double result = 1;
        for (int i = 0; i < exponent; i++) {
            result *= base;
        }
        return result;
    }

    // --- input and output ---
    static void printMenu() {
        System.out.println("1) add  2) subtract  3) multiply  4) divide  5) power  0) quit");
    }

    static double readNumber(Scanner in, String prompt) {
        System.out.print(prompt);
        return in.nextDouble();
    }

    static void printResult(double value) {
        System.out.println("Result: " + value);
    }

    // --- one handler per kind of menu choice ---
    static void runBasic(Scanner in, int choice) {
        double a = readNumber(in, "First number: ");
        double b = readNumber(in, "Second number: ");
        if (choice == 1) {
            printResult(add(a, b));
        } else if (choice == 2) {
            printResult(subtract(a, b));
        } else if (choice == 3) {
            printResult(multiply(a, b));
        } else if (b == 0) {
            System.out.println("Cannot divide by zero.");
        } else {
            printResult(divide(a, b));
        }
    }

    static void runPower(Scanner in) {
        double base = readNumber(in, "Base: ");
        int exponent = (int) readNumber(in, "Exponent (whole, 0 or more): ");
        printResult(power(base, exponent));
    }

    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int choice = -1;
        while (choice != 0) {
            printMenu();
            System.out.print("Choice: ");
            choice = in.nextInt();
            if (choice >= 1 && choice <= 4) {
                runBasic(in, choice);
            } else if (choice == 5) {
                runPower(in);
            } else if (choice != 0) {
                System.out.println("Unknown choice.");
            }
        }
        System.out.println("Goodbye");
    }
}

I compiled this with JDK 21 and fed it a scripted session: add 2 and 3; divide 10 by 0; divide 10 by 4; power 2 to the 10; the unknown choice 9; quit. The program's responses (menu lines omitted, and the typed answers do not appear because the input was piped) were:

text
Choice: First number: Second number: Result: 5.0
Choice: First number: Second number: Cannot divide by zero.
Choice: First number: Second number: Result: 2.5
Choice: Base: Exponent (whole, 0 or more): Result: 1024.0
Choice: Unknown choice.
Choice: Goodbye

In an interactive session each prompt appears on screen, followed by your typing on the same line. Look at the shape: the maths layer has no println, and main is short.

Example 2: trace one menu choice through the layers (practical)

Follow "add 2 and 3" on the call stack from Lesson 5:

  1. main reads choice = 1 and calls runBasic(in, 1).
  2. runBasic calls readNumber twice (each frame returns a double), so a = 2.0, b = 3.0.
  3. runBasic evaluates printResult(add(a, b)). The inner call runs first: add(2.0, 3.0) returns 5.0; then printResult(5.0) prints Result: 5.0.
  4. runBasic returns to main; the loop prints the menu again.

At the deepest point the stack is add over runBasic over main. This is a good moment to run your debugger or add a temporary Thread.dumpStack() to see it for yourself.

Example 3: improving it without breaking it (realistic)

Real programs change. Say a user asks for a modulus operation, or you want to refuse negative exponents. Because of the layers, each change touches one place:

  • New operation (remainder): add static double remainder(double a, double b) to the maths layer, add a number to the menu text, add a branch in main and runBasic. Nothing else moves.
  • Negative exponent: in runPower, before calling power, print "Exponent must be 0 or more." and return if exponent < 0. Again power itself is unchanged.
  • Two decimals: change only printResult, using System.out.printf("Result: %.2f%n", value); from Module 11. Every operation then prints two decimals at once.

Backend Insight

The three layers are a tiny version of how backend services are organised: a layer that receives requests (here: main and readNumber), a layer with the business rules (the maths methods), and a layer that formats the response (printResult). In a real service the middle layer knows nothing about the web or the console, which is why it can be tested alone and reused. Splitting by responsibility is the point of this module. "Validate before you compute" is also the first line of defence in backend code.

Common Mistakes

Mistake 1: Computing and printing in the same method

The planned mistake above. If a maths method contains println, it cannot be reused for any other output.

Mistake 2: Creating a new Scanner in every helper

Each new Scanner(System.in) can buffer input independently, leading to lost or skipped input. Create one in main and pass it to the helpers.

Mistake 3: Letting Infinity or NaN reach the user

With double arithmetic, dividing by zero does not crash; it quietly prints Infinity or NaN. Check the divisor and show a message.

Mistake 4: Duplicated logic

Copy-pasting the "read two numbers" lines into every operation is what the lab's acceptance criteria forbid. Put them in readNumber and one handler.

Mistake 5: Skipping validation of the menu input

This demo uses nextInt and nextDouble directly, so typing a letter would crash it. Module 11 showed how to validate input; add that to your lab version once the methods work.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. Which methods in the finished program are "maths layer", and what do they have in common?

Show solution A1

A1. add, subtract, multiply, divide and power. They take values, return a value, and neither print nor read input.

A2. What does power(3, 0) return, and why does the loop produce it?

Show solution A2

A2. It returns 1.0. The result starts as 1 and the loop body runs zero times because i < 0 is false immediately.

A3. Why is the Scanner passed to readNumber as a parameter?

Show solution A3

A3. So all helpers read from the one Scanner that main created. A parameter is a copy of the reference, so every helper uses the same input source and no input is lost.

Level B: Application

B1. Add a remainder(double a, double b) method (use the % operator) and menu option 6 for it. Which existing methods must you change, and which stay untouched?

Show solution B1

B1.

java
static double remainder(double a, double b) {
    return a % b;
}

Change printMenu (new text), main (send choice 6 to runBasic) and runBasic (a branch that prints remainder(a, b), plus the zero-divisor check, since % 0 is also a problem). The other maths and input/output methods stay untouched.

B2. Make runPower refuse a negative exponent with a message instead of calling power. Show the new runPower.

Show solution B2

B2.

java
static void runPower(Scanner in) {
    double base = readNumber(in, "Base: ");
    int exponent = (int) readNumber(in, "Exponent (whole, 0 or more): ");
    if (exponent < 0) {
        System.out.println("Exponent must be 0 or more.");
        return;
    }
    printResult(power(base, exponent));
}

B3. Change the output so every result prints with two decimals. Which single method do you edit?

Show solution B3

B3. Edit only printResult: System.out.printf("Result: %.2f%n", value);.

Level C: Challenge

C1. Refactor the if/else if chain in runBasic into a method static double calculate(int choice, double a, double b) that returns the result, so that runBasic becomes: read two numbers, check the zero case, print calculate(...). Discuss one advantage and one cost of this change.

Show solution C1

C1.

java
static double calculate(int choice, double a, double b) {
    if (choice == 1) return add(a, b);
    if (choice == 2) return subtract(a, b);
    if (choice == 3) return multiply(a, b);
    return divide(a, b);
}

static void runBasic(Scanner in, int choice) {
    double a = readNumber(in, "First number: ");
    double b = readNumber(in, "Second number: ");
    if (choice == 4 && b == 0) {
        System.out.println("Cannot divide by zero.");
    } else {
        printResult(calculate(choice, a, b));
    }
}

Advantage: calculate is a pure function, so it can be checked on its own and runBasic is simpler. Cost: one more method to read and one more place that must know the menu numbers, so a change to the menu has to be made in two places.

C2. Write static boolean isDivisionSafe(double divisor) returning true when the divisor is not 0. Use it in runBasic and say why that name reads better than the raw b == 0 test.

Show solution C2

C2.

java
static boolean isDivisionSafe(double divisor) {
    return divisor != 0;
}

The name states the intention, and the rule can later change in one place. It still passes a tiny divisor such as 0.0000000001, where the quotient is huge. For a beginner calculator an exact zero test is the expected rule.

Mini Challenge

Without looking at the lesson code, rebuild the calculator from scratch in a new file in this order, running after each step: (1) the five maths methods, tested from a temporary main; (2) printMenu, readNumber, printResult; (3) the loop in main with runBasic and runPower; (4) the zero-divisor check. Then add your own sixth operation.

Check your work: no maths method contains println or Scanner; dividing by 0 prints a message; there is exactly one Scanner; and main is under 20 lines.

Summary & Next Lesson

Key takeaways

  • Build in layers: maths methods that return, input/output methods that read and print, and a short control layer.
  • A maths method that prints cannot be reused; return the value and print in the caller.
  • Handle the divide-by-zero case before dividing, so the user never sees a crash or Infinity.
  • Create one Scanner and pass it to helpers; the reference is copied, but it points to the same input.
  • Handler methods keep main short and each change local to one method.

Next lesson

The final lesson, "Demo: tracing recursion on the call stack", uses a debugger to watch factorial(4) pile up and unwind, so you can finish the recursive factorial the lab asks for with confidence.

Self-check

  1. Which layer of the calculator should contain System.out.println?
  2. Why check for a zero divisor before calling divide?
  3. Why is the Scanner passed as a parameter?
  4. What does power(2, 3) do in the loop, step by step?
  5. What is the main advantage of keeping add silent?
Show answers
  1. The input/output layer (printMenu, printResult and the handlers), not the maths methods.
  2. With double, dividing by zero gives Infinity or NaN rather than a clear message, and integer division would crash; checking first lets us show a friendly message.
  3. So that every helper reads from the same Scanner created in main.
  4. result starts at 1, then becomes 2, 4 and 8 after three multiplications by 2, and 8.0 is returned.
  5. The caller can reuse the value (print it, store it, format it, test it) without unwanted output.

References

  • Oracle, Defining Methods. Verified: method declaration parts and naming conventions used in the demo.
  • Oracle, Passing Information to a Method or a Constructor. Verified: references are passed by value, which is why the shared Scanner works.
  • Behaviours claimed about double and int division by zero (Infinity, NaN, / by zero), the calculator's output and the compiler results come from running code with JDK 21 (javac 21.0.10). I did not fetch the JLS paragraph on floating-point division, so the general rule is stated from that observed behaviour.
  • The advice that several Scanner objects on one input can lose data is a common-practice statement that I did not verify against documentation.

Practice

Apply this lesson with the module's lab, exercises and quiz.

Lab and exercises Quiz

Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.

Continue to next lesson

Type to search levels, phases, modules and lessons.