سلاسل else if والقيم الحدّية
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
An if/else chooses between two roads. Many rules need more: a score becomes one of five grades, a parcel falls into one of three weight bands, an hour of the day gets one of three greetings. The tool is an else if chain: several conditions tested one after another, with exactly one road taken.
This lesson has two goals. The first is the mechanics: how Java walks down a chain and why the order of the tests matters. The second is the skill that separates careful from careless programmers: boundary values, the exact points where the answer changes, such as 59 versus 60. Most bugs in business rules live there.
It builds on Lesson 1 (the if/else statement and boolean conditions) and on Module 10 (comparison operators). The next lesson introduces switch, a different tool for one particular kind of decision.
Learning Objectives
By the end of this lesson you will be able to:
- Write an
if/else if/elsechain and trace which branch runs for a given input. - Explain why Java runs only the first true branch of a chain.
- Order the tests of a chain so that no branch is hidden by an earlier one.
- Identify the boundary values of a rule and choose the test inputs that expose a
<versus<=error. - Use a final
elseto cover every remaining case, and explain the compiler error when a variable is left unassigned. - Spot a gap or an overlap between conditions.
Written Explanation
How a chain runs
An else if chain is just if/else where the else block contains another if. Written flat, it reads like a list of questions:
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else {
grade = 'F';
}Java works from top to bottom. It evaluates the first condition; if it is true, it runs that block and skips the rest of the chain entirely. If it is false, it moves to the next condition, and so on. The final else has no condition: it runs when every test above it was false. Only one block ever runs.
This is what makes chains compact. By the time Java reaches score >= 80, it already knows that score >= 90 was false, so the second test does not need to say score >= 80 && score < 90. Each test can lean on all the tests before it.
Order matters
Because the first true test wins, the order is part of the logic. For ranges that grow in one direction, order the tests from the most demanding to the least: >= 90, then >= 80, then >= 70. If you reverse them, score >= 70 would catch a 95 first and the A test would never be reached. Example 3 shows exactly this bug.
A useful habit: for each branch, ask "what do I already know is not true when I get here?" and make sure the condition still describes the right values.
Boundary values
A boundary is the exact value where the result changes. In the grade chain the boundaries are 90, 80, and 70. Boundary bugs come from choosing the wrong operator: score > 90 and score >= 90 give the same answer for 89 and for 95, and differ only at 90.
So test each boundary with three values: the boundary itself, one step below, and one step above where it makes sense. For the whole-number grade chain, that means 91, 90, 89, then 81, 80, 79, and so on. For decimals such as weights, "one step" is a small value like 0.01, so test 1.0 and 1.01 around a boundary at 1.
Work out the expected result by hand first, then run the code; otherwise you will unconsciously accept the output as correct.
Covering every case: gaps, overlaps, and the final else
A well-formed chain has no gaps (a value that matches no branch) and no overlaps (a value that could match two branches, where only the earlier one will ever run). Look at these two attempts for ages:
if (age < 18) { ... } else if (age > 18) { ... } // gap: 18 matches neither
if (age < 18) { ... } else if (age >= 18) { ... } // no gap, no overlapThe first one has a hole at exactly 18. The simplest cure is a final else: it catches everything that remains, including values you did not think about, such as negative or enormous numbers. If a final else would be wrong for odd values, add a first test that rejects them (if (score < 0 || score > 100)), then continue the chain.
The compiler helps in one case. If a variable must be set by the chain and some path leaves it unset, Java refuses to compile a later use of it. You will see this in Example 3.
Examples
Example 1: score to grade (simple)
int score = 89;
char grade;
if (score >= 90) {
grade = 'A';
} else if (score >= 80) {
grade = 'B';
} else if (score >= 70) {
grade = 'C';
} else if (score >= 60) {
grade = 'D';
} else {
grade = 'F';
}
System.out.println(score + " -> " + grade);For 89 the output is 89 -> B: the >= 90 test fails, >= 80 succeeds, and the rest of the chain is skipped. Running the same code with other values gave this table (verified with JDK 21):
| score | grade | why |
|---|---|---|
| 100 | A | first test |
| 90 | A | boundary: >= includes 90 |
| 89 | B | one below the boundary |
| 80 | B | boundary |
| 79 | C | one below the boundary |
| 60 | D | boundary |
| 59 | F | falls to else |
| 0 | F | falls to else |
This chain gives every number an answer, even 150 (an A) and -5 (an F); Lesson 5 returns to rejecting such values.
Example 2: greeting by hour, and a weight band (practical)
int hour = 14;
String greeting;
if (hour < 12) {
greeting = "Good morning";
} else if (hour < 18) {
greeting = "Good afternoon";
} else {
greeting = "Good evening";
}
System.out.println(hour + ": " + greeting);Output: 14: Good afternoon. Noon belongs to the afternoon because the first test is < 12. A rule written in words, "morning is before 12:00", tells you which operator to use; the code follows the sentence.
Now a delivery rule: up to 1 kg inclusive is light, up to 5 kg inclusive is medium, above is heavy, and zero or negative is invalid.
double weight = 1.0;
String band;
if (weight <= 0) {
band = "invalid";
} else if (weight <= 1) {
band = "light";
} else if (weight <= 5) {
band = "medium";
} else {
band = "heavy";
}
System.out.println(weight + " -> " + band);Output: 1.0 -> light. The boundary 1 belongs to the lower band because of <=. Try 0, 1.01, 5, and 5.01 and predict each first: the expected bands are invalid, medium, medium, heavy. The invalid-input test comes first on purpose: it filters out bad values before the ordinary ranges run.
Example 3: wrong order and a missing else (realistic)
A developer wants A for 90 and above, but orders the tests from the wrong end:
int score = 95;
String g;
if (score >= 60) {
g = "D";
} else if (score >= 90) {
g = "A";
} else {
g = "F";
}
System.out.println(score + " -> " + g);Real output:
95 -> DEvery score of 60 or more is caught by the first test, so score >= 90 is an overlap that can never win: the earlier branch covers it. The fix is to put >= 90 first. This compiles without a warning, which is why tracing by hand matters.
Next, a chain with no final else, where the variable y must hold a value:
int x = 5;
int y;
if (x > 3) { y = 1; } else if (x > 0) { y = 2; }
System.out.println(y);The compiler refuses:
L02c.java:21: error: variable y might not have been initialized
System.out.println(y);
^Java sees that if both tests were false, y would never be set. Adding a final else { y = 0; } closes the gap and the error disappears. Treat this message as helpful: it points at a case you forgot.
Backend Insight
Business rules in services are almost always tables of ranges: shipping cost by weight, a fee by order total, a risk label by score, a rate limit by plan. In a real codebase these look exactly like the chains above, and the boundary is where the money is. If "orders of 100 or more ship free" is coded with > instead of >=, every customer who spends exactly 100 is charged, and nobody notices until someone complains.
The professional habit is to put the boundary values into a small test table before writing the chain, with the expected result worked out from the written rule. Later in the course you will turn such tables into automated tests; the thinking is identical. Requirements are written in words: "up to" and "at most" include the value, "under" and "below" do not. When unclear, ask rather than guess.
Common Mistakes
Mistake 1: Boundary on the wrong side
- Incorrect:
if (weight < 1)for "up to 1 kg". - Correct:
if (weight <= 1).
The two versions differ only for exactly 1, so typical test values such as 0.5 and 3 never reveal the bug. Test the boundary.
Mistake 2: Tests in the wrong order
- Incorrect:
>= 60first, then>= 90. The 90 test is never reached. - Correct: most demanding test first.
Mistake 3: A gap between branches
- Incorrect:
age < 18andage > 18. Exactly 18 matches neither branch. - Correct:
age < 18thenage >= 18, or a finalelse.
Mistake 4: Repeating information the chain already knows
- Clumsy:
else if (score >= 80 && score < 90)after ascore >= 90test. - Better:
else if (score >= 80).
The extra check is not wrong, but it adds a second place to make a boundary mistake. Using separate if statements instead of else if is more serious: both blocks can run for one value.
Exercises
Work out expected results by hand before you run anything.
Level A: Foundation
A1. Given the chain below, what does n = 10 print? What do n = 11 and n = 0 print?
if (n > 10) { System.out.println("big"); }
else if (n > 0) { System.out.println("small"); }
else { System.out.println("none"); }A2. List the boundary values and the test inputs you would use for a rule "a ticket costs 0 for ages up to 5, 8 for ages 6 to 17, and 15 for ages 18 and over".
A3. In one sentence: why does only one branch of an else if chain ever run?
Level B: Application
B1. Write a chain that sets a String size from int cm: under 160 is short, 160 up to and including 180 is medium, above 180 is tall. List five test inputs with expected results.
B2. Write a chain for a battery level (0 to 100): 0 is empty, 1 to 20 is low, 21 to 79 is ok, 80 to 100 is full. Put an invalid-input test first for values outside 0 to 100, printing invalid.
B3. The rule says 10 or more items is a bulk order. Find the problem and fix it:
if (qty >= 1) { type = "single"; }
else if (qty >= 10) { type = "bulk"; }
else { type = "invalid"; }Level C: Challenge
C1. Tax rule for an annual income income: nothing up to 10 000 inclusive, 10 percent of the income above 10 000 up to 50 000 inclusive, and for incomes above 50 000 a flat 4 000 plus 20 percent of the amount above 50 000. Write the chain, calculate by hand the tax for 10 000, 10 001, 50 000, and 50 001.
C2. A teammate's chain has x >= 0 first and x > 100 second. State what is wrong, then rewrite it so values above 100 get label "high", values 0 to 100 get "normal", and negative values get "negative".
أظهر الحلول
A1. n = 10: 10 > 10 is false, 10 > 0 is true, so small. n = 11: big. n = 0: both tests false, so none.
A2. Boundaries are between 5 and 6, and between 17 and 18. Test 5 (cost 0), 6 (cost 8), 17 (cost 8), 18 (cost 15), plus an ordinary value such as 30 and an edge such as 0.
B1.
String size;
if (cm < 160) {
size = "short";
} else if (cm <= 180) {
size = "medium";
} else {
size = "tall";
}Tests: 159 short, 160 medium, 180 medium, 181 tall, 100 short.
B2.
String level;
if (pct < 0 || pct > 100) {
level = "invalid";
} else if (pct == 0) {
level = "empty";
} else if (pct <= 20) {
level = "low";
} else if (pct < 80) {
level = "ok";
} else {
level = "full";
}Tests: -1 invalid, 0 empty, 1 low, 20 low, 21 ok, 79 ok, 80 full, 100 full, 101 invalid.
B3. Every quantity of 1 or more is caught by the first test, so bulk can never be chosen. Reorder: qty >= 10 first (bulk), then qty >= 1 (single), then else for invalid.
C1.
double tax;
if (income <= 10000) {
tax = 0;
} else if (income <= 50000) {
tax = (income - 10000) * 0.10;
} else {
tax = 4000 + (income - 50000) * 0.20;
}Results: 10 000 gives 0; 10 001 gives 0.1; 50 000 gives 4 000; 50 001 gives 4 000.2. There is no jump at either boundary, a good sanity check for a tax rule.
C2. Every value above 100 is also >= 0, so the first test catches it and x > 100 is never reached. Rewrite:
if (x > 100) { label = "high"; }
else if (x >= 0) { label = "normal"; }
else { label = "negative"; }Mini Challenge
A library charges a late fee per book: no fee for returns on time or up to 2 days late, 1 currency unit for 3 to 7 days late, 3 for 8 to 30 days, and 10 above 30. Negative daysLate means early.
- Write the rule as four sentences first.
- Write the chain with a
double fee. - Build a table of at least eight test inputs around every boundary (2/3, 7/8, 30/31), with expected values worked out before running.
- Run the code and compare.
Check your work: every boundary has a value on each side, and you can explain each expected value from the rule, not from the output.
Summary & Next Lesson
Key takeaways
- A chain is tested top to bottom; the first true branch runs and the rest are skipped.
- Order the tests so that earlier ones do not swallow values meant for later ones.
- A boundary is where the result changes;
<versus<=differs only there, so test it with the value itself and its neighbours. - A final
elsecloses gaps. The compiler warns about unassigned variables when a path is missing. - Work out expected results by hand before running the code.
Next lesson
Some decisions are not about ranges but about one exact value with many fixed choices, such as a day number or a menu option. The next lesson, "Classic switch and fall-through", shows the tool made for this and its best-known trap.
Self-check
- In a chain, what happens after the first true condition is found?
- For
if (score >= 50), which inputs find a wrong operator fastest? - Why would
if (x > 5) {...} else if (x > 10) {...}hide a bug? - What does a final
elseguarantee? - A requirement says "up to 20 kg". Is 20 inside or outside?
أظهر الإجابات
- Its block runs and the rest of the chain is skipped.
- 49, 50, and 51: the boundary and its neighbours.
- Every
xabove 10 is already caught byx > 5, so the second branch is never reached. - That every value not matched by earlier tests still gets an outcome, so no gaps remain.
- Inside: "up to" includes the limit, so the test is
<= 20.
References
- Oracle, The Java Language Specification, Java SE 21, Chapter 14. Supports the semantics of
if/else, including thatelsepairs with the nearestif. - The outputs and the compiler message in this lesson came from compiling and running the code with JDK 21. The exact wording of messages can vary between JDK versions.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.