Java Arena
Step 2 of 6

Test input for a Scanner

Steps 1 and 5 of the checklist mean running a program again and again with the same input. Typing it every time is slow, and it's easy to type something a little different by mistake. Module 20 showed that a Scanner can read a String, and in module 25 the user interface was given its Scanner instead of making one. Put the two together, and any code that reads input can be tried with input written right in the code.

The trick is that the method that reads doesn't make its own Scanner: it gets one as a parameter. A hiker writes down the kilometers walked each day, one number per line, and an empty line ends the list:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        System.out.println(totalKilometers(new Scanner("12\n8\n15\n")));
        System.out.println(totalKilometers(new Scanner("7\n\n30\n")));
        System.out.println(totalKilometers(new Scanner("")));
    }

    public static int totalKilometers(Scanner scanner) {
        int total = 0;
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            if (line.isEmpty()) {
                break;
            }
            total += Integer.valueOf(line);
        }
        return total;
    }
}
Output
35
7
0
  • Inside the quotes, \n is a line break, so "12\n8\n15\n" is three lines, just as if someone had typed 12, 8 and 15, pressing Enter after each.
  • "7\n\n30\n" has an empty line after the 7, so the method stops there and never reads the 30.
  • "" has no lines at all, like input that ends right away. It's easy to write here, and awkward to try by typing.
  • In the finished program, main gives the method the keyboard: totalKilometers(new Scanner(System.in)). The method works the same either way, because it only uses the Scanner it was given.

A method that makes its own new Scanner(System.in) inside can only ever read the keyboard. Taking the Scanner as a parameter is what makes it easy to try out.

A check that says ok or wrong

Printing the results still means comparing them in your head, every time. Let the program do that too: give it the input and the result it should give, and have it print whether it did.

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        check("12\n8\n15\n", 35);
        check("7\n\n30\n", 7);
        check("", 0);
        check("5\n", 5);
    }

    public static void check(String input, int expected) {
        int result = totalKilometers(new Scanner(input));
        if (result == expected) {
            System.out.println("ok");
        } else {
            System.out.println("WRONG: expected " + expected + " but got " + result);
        }
    }

    public static int totalKilometers(Scanner scanner) {
        int total = 0;
        while (scanner.hasNextLine()) {
            String line = scanner.nextLine();
            if (line.isEmpty()) {
                break;
            }
            total += Integer.valueOf(line);
        }
        return total;
    }
}
Output
ok
ok
ok
ok

Now any change to totalKilometers can be checked in a second. Say someone changes break to continue, to skip empty lines instead of stopping at them. Then the second check prints WRONG: expected 7 but got 37, and you know at once, without typing a single number.

Each call to check is a small test: an input, the result it should give, and a comparison. Tests like these, which check one method or one class on its own, are called unit tests. Writing a check method for every program gets tedious, though. The next step uses JUnit, a library that runs your tests, does the comparing, and keeps track of which tests passed and which failed.

Your task

Challenge 1 of 3

A diary has one entry per line. Write the method countEntries(Scanner scanner, String word): it reads lines until the input ends and returns how many of them contain the word. A line that has the word twice still counts once, and capital letters count as different letters, as they do for contains. Use the Scanner the method gets as its parameter: main gives it the keyboard, and the check gives it a String. The check runs:

System.out.println(countEntries(new Scanner("ran 5 km\nrain all day\nran again\n"), "ran"));

and expects:

2

Challenge 2

countLong should count the words that have at least 5 letters, and main checks it with four inputs. But the method has a bug. What does the program print?

Challenge 3

A lap timer reads lap times in seconds, one per line, until the input ends. Its method readLaps makes its own Scanner, so it can only ever read the keyboard, and nobody has tried it with no laps at all. Do two things:

  • Change readLaps to take the Scanner as a parameter, readLaps(Scanner scanner), and make main give it new Scanner(System.in).
  • With no laps, fastest() crashes. Make it return 0 then.

The check runs:

LapTimer timer = new LapTimer();
timer.readLaps(new Scanner("62\n58\n60\n"));
System.out.println(timer.count() + " laps, fastest " + timer.fastest());
LapTimer none = new LapTimer();
none.readLaps(new Scanner(""));
System.out.println(none.count() + " laps, fastest " + none.fastest());

and expects:

3 laps, fastest 58
0 laps, fastest 0