Checkpoint: Java Basics

Reviewed & published by Brayan K

You've learned data types, type casting, Scanner input, the Math class, StringBuilder, ArrayList and HashMap — let's combine them. In this checkpoint you'll build one small but complete program that reads input, tallies it with a HashMap, and prints a formatted report.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

What You've Learned So Far

8 primitives, their ranges, overflow, and why double is wrong for money.

Widening is automatic, narrowing needs (type), casts truncate, and parsing turns text into numbers.

nextLine/next/nextInt/nextDouble, the newline trap, and hasNextInt validation.

abs, max/min, pow, sqrt, rounding, and random numbers.

Efficient text building and printf/String.format specifiers.

Resizable lists, key→value maps, iteration, and the tally pattern.

🛠️ The Build Challenge: Spending Tracker

Write a program that reads spending entries and prints a per-category report. Each entry is a category word followed by an amount, for example food 12.50. Reading stops when the user types done.

Start from this scaffold — fill in the TODOs, then run it. Try it yourself before revealing the solution!

import java.util.Scanner;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // GOAL: read several "category amount" lines (e.g. "food 12.50"),
        // tally the total spent per category in a HashMap, then print a
        // neatly formatted report with a grand total.
        //
        // Input ends when the user types "done".

        HashMap<String, Double> totals = new HashMap<>();

        // TODO 1: loop reading the category until the user types "done"
        // TODO 2: read the amount (a double) for each category
        // TODO 3: add the amount to that category using getOrDefault
        // TODO 4: print each category and total with printf (2 decimals)
        // TODO 5: print the grand total

        scanner.close();
    }
}

✅ Worked Solution

Give it a real attempt first. When you're ready, expand the full solution and walk through how each skill from this section fits together.

import java.util.Scanner;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        HashMap<String, Double> totals = new HashMap<>();

        System.out.println("Enter 'category amount' lines, or 'done' to finish.");

        // 1 + 2: keep reading until the user types "done"
        while (true) {
            System.out.print("> ");
            String category = scanner.next();          // one word
            if (category.equals("done")) {
                break;
            }
            // validate the amount so a typo can't crash us
            while (!scanner.hasNextDouble()) {
                System.out.print("  amount must be a number: ");
                scanner.next();                        // discard bad token
            }
            double amount = scanner.nextDouble();

            // 3: tally into the map with getOrDefault (the HashMap pattern)
            totals.put(category, totals.getOrDefault(category, 0.0) + amount);
        }

        // 4 + 5: formatted report with a grand total
        System.out.println("\n--- Spending Report ---");
        double grandTotal = 0;
        for (Map.Entry<String, Double> e : totals.entrySet()) {
            System.out.printf("%-12s $%8.2f%n", e.getKey(), e.getValue());
            grandTotal += e.getValue();
        }
        System.out.println("------------------------");
        System.out.printf("%-12s $%8.2f%n", "TOTAL", grandTotal);

        scanner.close();
    }
}

📝 Checkpoint Quiz

Test yourself across the whole section. Try to answer before expanding each one.

1. Why does the tally use HashMap<String, Double> rather than HashMap<String, double>?

Collections store objects, not primitives, so the value type must be the wrapper class Double. Autoboxing converts between double and Double automatically.

2. What does (int) 19.99 evaluate to, and how would you instead get 20?

19 — casting truncates, it doesn't round. To get 20 use Math.round(19.99) (which returns a long), then cast to int if needed.

3. After int n = scanner.nextInt(); the next scanner.nextLine() returns an empty string. Why, and what's the fix?

nextInt() leaves the trailing newline in the buffer, which the next nextLine() consumes immediately. Fix it by adding an extra scanner.nextLine(); after the numeric read.

4. With an ArrayList<Integer> holding [10, 20, 30], what does list.remove(1) leave, and why is this a famous trap?

[10, 30]. remove(1) deletes the element at index 1 (the value 20). To remove the value 1 you'd write remove(Integer.valueOf(1)).

5. Why prefer StringBuilder over + when assembling a long report in a loop?

Strings are immutable, so each + creates a new String object; in a large loop that's quadratic work. StringBuilder.append writes into one growable buffer, so it stays linear.

6. What does map.getOrDefault("x", 0) return when "x" is not in the map, and why is that useful for counting?

It returns 0 instead of null. That lets the tally pattern add 1 to a not-yet-seen key without a NullPointerException or a special first-time check.

Watch Out For

🎉 Checkpoint Complete!

You combined data types, casting, Scanner input, validation, the HashMap tally pattern, and printf formatting into one complete program. That's a real, useful skill — the same loop-read-tally-report shape powers countless tools.

Practice quiz

Why must a map be HashMap<String, Double> rather than HashMap<String, double>?

  • double is faster
  • double is not a real type
  • Collections store objects, not primitives, so the value must be the wrapper Double
  • Generics forbid all numbers

Answer: Collections store objects, not primitives, so the value must be the wrapper Double. Generic type arguments must be reference types. Autoboxing converts between double and Double automatically.

What does (int) 19.99 evaluate to?

  • 19
  • 20
  • 19.99
  • Compile error

Answer: 19. Casting a double to int truncates toward zero — it does not round. (int)19.99 is 19; use Math.round to get 20.

After int n = scanner.nextInt();, the next scanner.nextLine() often returns an empty string. Why?

  • nextInt() consumes the whole line
  • nextLine() is broken
  • The buffer is cleared automatically
  • nextInt() leaves the trailing newline, which nextLine() then consumes

Answer: nextInt() leaves the trailing newline, which nextLine() then consumes. nextInt() reads the number but leaves the newline in the buffer. Add an extra scanner.nextLine() to skip it.

With an ArrayList<Integer> holding [10, 20, 30], what does list.remove(1) leave?

  • [20, 30]
  • [10, 30]
  • [10, 20]
  • [10, 20, 30]

Answer: [10, 30]. remove(int) deletes by INDEX, so remove(1) removes the value 20, leaving [10, 30].

To remove the VALUE 1 (not index 1) from an ArrayList<Integer>, you call:

  • list.remove(Integer.valueOf(1))
  • list.remove(1)
  • list.delete(1)
  • list.removeValue(1)

Answer: list.remove(Integer.valueOf(1)). remove(int) is by index; remove(Object) is by value. Integer.valueOf(1) forces the by-value overload.

Why prefer StringBuilder over + when assembling a long report in a loop?

  • + does not compile in loops
  • StringBuilder rounds numbers
  • Strings are immutable, so each + creates a new object (quadratic work); StringBuilder uses one buffer
  • There is no difference

Answer: Strings are immutable, so each + creates a new object (quadratic work); StringBuilder uses one buffer. Each + builds a new String. StringBuilder.append writes into one growable buffer, keeping the work linear.

What does map.getOrDefault("x", 0) return when "x" is not in the map?

  • null
  • 0
  • -1
  • Throws NullPointerException

Answer: 0. It returns the supplied default (0) instead of null, enabling the tally pattern without a null check.

Which printf specifier correctly prints a double amount to 2 decimal places?

  • %d
  • %s
  • %2d
  • %.2f

Answer: %.2f. %.2f formats a floating-point value to two decimals. %d with a double throws an exception.

How should you compare a Scanner-read String token to the sentinel "done"?

  • category == "done"
  • category.equals("done")
  • category = "done"
  • category.compareTo("done")

Answer: category.equals("done"). Use .equals for String content; == compares references and can be false even for equal text.

The tally line totals.put(cat, totals.getOrDefault(cat, 0.0) + amount) works because getOrDefault:

  • Deletes the key first
  • Throws if the key is missing
  • Returns 0.0 for an unseen category so the first amount adds to zero
  • Always returns the amount

Answer: Returns 0.0 for an unseen category so the first amount adds to zero. For a new category it returns 0.0, so the first amount starts the total; later amounts accumulate.

Continue this course

Frequently asked questions

Why does the solution use scanner.next() instead of nextLine()?

next() reads a single whitespace-delimited token, which fits the 'category amount' input format perfectly: one next() grabs the category word, and nextDouble() grabs the number. Using nextLine() here would read the whole line as one String and we'd have to split it manually. Mixing next()/nextDouble() also avoids the leftover-newline trap that bites nextLine() after numeric reads.

Why is the map declared as HashMap<String, Double> and not <String, double>?

Collections can only store objects, never primitives, so the value type must be the wrapper class Double rather than the primitive double. Autoboxing means you can still do arithmetic and pass plain doubles in and out; Java converts between double and Double automatically.

What does getOrDefault(category, 0.0) achieve in the tally?

The first time a category appears it isn't in the map yet, so get would return null and adding to null would crash. getOrDefault returns 0.0 for an unseen category, so the first amount is added to zero, and every later amount is added to the running total. It removes the need for a separate 'have I seen this key before?' check.

What do the printf specifiers %-12s and $%8.2f do?

%-12s prints the category left-justified in a 12-character column so the values line up. $%8.2f prints a literal dollar sign, then the amount right-justified in an 8-character field with exactly 2 decimal places. Together they produce a clean, aligned table. %n is a portable newline.

How could I make this program more robust or feature-rich?

You could store entries in an ArrayList as you read them for an itemised log, sort the report by amount using a list of map entries, reject negative amounts, support quantities, or read from a file with a Scanner wrapping a File. Each builds directly on the data types, casting, Scanner, Math, StringBuilder, ArrayList, and HashMap skills from this section.