You mark a paper 78 and your grading program prints a C. The line above the grade says score: 78, so the input is right. The grade is one step too low, though. The run ended normally, with exit status 0, no compiler warning and no runtime error. Nothing in the terminal points at the program.
Key takeaways
switchleaves a case only when it reaches abreak, so a missingbreakruns the next case's statements too.- In the starter,
case 7assignsBandcase 6overwrites it withC, so a score of 78 printsgrade: C. - Grouped labels like
case 10:andcase 9:fall through on purpose, which is the same mechanism used deliberately. - The arrow form (
case 8, 7 -> "B";) arrived in Java 14 and cannot fall through, because an arm has no statements to fall into. score / 10puts 89 and 80 in one bucket, so a threshold rule such as 85 and up needs anifchain, not aswitch.- A checker with a fixed input certifies one score, not the grading rule: feed it 95 and it reports
0 of 2 checks passed.
The program that prints C
The file holds a single class with one int score and a switch on score / 10. Each case picks a letter for one band of scores, and default handles everything else.
// Grade.java
public class Grade {
public static void main(String[] args) {
int score = 78;
String grade;
switch (score / 10) {
case 10:
case 9:
grade = "A";
break;
case 8:
grade = "B";
break;
case 7:
grade = "B";
case 6:
grade = "C";
break;
default:
grade = "D";
}
System.out.println("score: " + score);
System.out.println("grade: " + grade);
}
}
Compile and run it with the command below.
$ javac Grade.java && java Grade
score: 78
grade: C
The score line is right. Since score is an int, 78 / 10 is integer division and gives 7, so the switch enters case 7. Read that case in the source and it looks correct, because it assigns B. Nothing in the output says which case ran or what it did. You get only the final letter, and the final letter is wrong.
What break actually does
Without a break, control does not leave the switch at the end of a case. It runs on into the statements of the next case instead. Here case 7 assigns B, then control continues into case 6, which overwrites it with C. The break after case 6 is the first one that stops the run.
The top of the switch uses the same behavior on purpose. case 10: has no statements and no break, so it falls into case 9:, and both labels share one A. Falling through is a tool when you write it deliberately. It is a bug when you only forgot to stop it.
The compiler stays silent about the difference. Both versions are legal Java, so the program compiled, ran, and exited with status 0. You have to notice the missing break yourself, or find a way to make the output speak up.
A check you can run
The checker reads the two lines the program prints and compares them against what the grade should be for 78. It counts the passes, reports any line that does not match, and flags an extra line.
// CheckGrade.java
import java.util.List;
import java.util.Scanner;
public class CheckGrade {
public static void main(String[] args) {
List<String> expected = List.of("score: 78", "grade: B");
Scanner in = new Scanner(System.in);
int passed = 0;
for (int i = 0; i < expected.size(); i++) {
String got = in.hasNextLine() ? in.nextLine() : "<nothing>";
if (got.equals(expected.get(i))) {
passed++;
System.out.println("PASS line " + (i + 1) + ": " + got);
} else {
System.out.println("FAIL line " + (i + 1) + ": expected " + expected.get(i) + ", got " + got);
}
}
if (in.hasNextLine()) {
System.out.println("FAIL extra line: " + in.nextLine());
}
System.out.println();
System.out.println(passed + " of " + expected.size() + " checks passed");
if (passed != expected.size()) {
System.exit(1);
}
}
}
Compile it and pipe the program into it with this command.
$ javac CheckGrade.java && java Grade | java CheckGrade
PASS line 1: score: 78
FAIL line 2: expected grade: B, got grade: C
1 of 2 checks passed
Only the first line passes. The second line is the whole bug: the checker wanted grade: B and got grade: C. The checker also exits with status 1 until both lines pass, so the failure is visible to anything that reads the exit status.
The form that cannot fall through
Java 14 added the arrow form of switch, and it is a standard feature in the Java 21 used here. Each -> arm holds one outcome, and there is no statement under it to fall into. The same program in that form looks like this.
// Grade.java
public class Grade {
public static void main(String[] args) {
int score = 78;
String grade = switch (score / 10) {
case 10, 9 -> "A";
case 8, 7 -> "B";
case 6 -> "C";
default -> "D";
};
System.out.println("score: " + score);
System.out.println("grade: " + grade);
}
}
Compile and run it.
$ javac Grade.java && java Grade
score: 78
grade: B
Then run the same checker against it.
$ java Grade | java CheckGrade
PASS line 1: score: 78
PASS line 2: grade: B
2 of 2 checks passed
Three things changed. Each -> arm produces one outcome. One arm can carry several labels, as in case 10, 9 -> "A";. And there is no way to fall into the next arm, so the mistake from the first version cannot be written here.
When the boundary is not a multiple of ten
The arrow form removes fall-through, but it does not change the arithmetic. score / 10 puts 89 and 80 in the same bucket, because both divide to 8. The arrow version prints grade: B for a score of 89. A rule that says 85 and up is an A cannot be written as a case on score / 10. An if chain with score >= 85 states that rule directly, as the next program does with the score set to 89.
// GradeIf.java
public class GradeIf {
public static void main(String[] args) {
int score = 89;
String grade;
if (score >= 85) {
grade = "A";
} else if (score >= 70) {
grade = "B";
} else if (score >= 60) {
grade = "C";
} else {
grade = "D";
}
System.out.println("score: " + score);
System.out.println("grade: " + grade);
}
}
$ javac GradeIf.java && java GradeIf
score: 89
grade: A
That is an A for the same 89 the arrow switch graded as a B. The two constructs answer different questions when the boundary is not a multiple of ten. Choose switch when the values are discrete and few. Choose if when the rule is a threshold.
Before the next article
Open the fixed file and change int score = 78; to 95. Run it and pipe it into the same checker, unchanged.
You will see score: 95 and grade: A, which is the right grade for 95. The checker still reports 0 of 2 checks passed, because it expects score: 78. Its input is fixed, so it certifies one input and not the rule.
If a test only ever feeds one score, which part of the grading rule is still unproven?