انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-010-L05

التقييم المختصر (Short-circuit)

  • الدرس 5 من 6
  • 12 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

In Lesson 2 you learned that a && b is true only when both sides are true. Look at that again from Java's point of view: if a is already false, the answer is false no matter what b is. Looking at b would be wasted work. So Java does not look. This is short-circuit evaluation: the right side of && and || is evaluated only when the left side has not already decided the answer.

Think of a security guard checking two things at a door. If the first check fails, there is no point in doing the second. Even better, the first check can protect the second: "if the room exists, check what is in it". Used on purpose, short-circuiting lets you write conditions that never crash.

Why this matters: backend code constantly meets missing data, such as a zero count, an empty value, or a value that was never set. Putting the safety check on the left of && is one of the most common ways to avoid a crash, and one of the first things a reviewer looks for.

This lesson builds on Lessons 2 and 4: boolean operators and precedence, and on the division and String ideas from earlier modules.

Learning Objectives

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

  • State exactly when the right operand of && and of || is skipped.
  • Use && and || to guard a risky operation, such as division by a value that might be zero.
  • Explain why the order of operands matters with a crash example.
  • Explain why a side effect in the right operand may never run.
  • Distinguish && and || from the non-short-circuit & and |.
  • Write a null check for a String so that its method is never called on null.

Written Explanation

The two rules

ExpressionThe right side is evaluated when...Otherwise
a && ba is truea is false, so the result is false immediately
a || ba is falsea is true, so the result is true immediately

The Java Language Specification states both rules: the conditional-and operator evaluates its right operand only if the left is true, and the conditional-or operator evaluates its right operand only if the left is false. The result of the whole expression is always what the truth table from Lesson 2 gives. Short-circuiting changes only whether the right side runs.

When the right side is a plain comparison with no risk, you cannot see the difference. It becomes visible when the right side could fail or could change something.

Protecting a risky operation

Recall from Lesson 1 that 10 / d with int d = 0 throws an ArithmeticException. Put the safety check first:

java
d != 0 && 10 / d > 1

When d is 0, the left side d != 0 is false, so Java never evaluates 10 / d. The whole expression is false, and nothing crashes. When d is not zero, the left side is true, and Java goes on to check the right side. Read it as a sentence: "d is not zero, and ten divided by d is more than one".

For || the guard is the other way round. d == 0 || 10 / d > 1 reads "d is zero, or ...". If d is zero, the left is true, the right side is skipped, and the answer is true.

Order matters

Swap the operands and the guard no longer protects anything:

java
10 / d > 1 && d != 0     // crashes when d is 0

Java evaluates left to right (Lesson 4), so it evaluates 10 / d > 1 first, and with d equal to zero that throws before d != 0 is ever reached. The two expressions mean the same thing in mathematics and behave differently in Java. The rule: put the cheap, safe check on the left, and the risky one on the right.

Protecting a method call on null

A variable that holds a reference (such as a String) can hold null, meaning "no object". Calling a method on null throws a NullPointerException. The same guard works:

java
name != null && name.length() > 3

If name is null, the right side is not evaluated, and the answer is false. This is one of the most common forms of short-circuiting in real code. (The full story about null comes later; for now you only need this pattern.)

Side effects in the right operand

If the right operand contains an increment or an assignment, whether it runs depends on the left side. This is called a side effect, because it changes something besides producing a value:

java
int count = 0;
boolean r = false && count++ > 0;   // count++ is skipped: count stays 0

Code that depends on a side effect inside a condition is fragile: it does something only some of the time, and the next reader will not notice. Keep conditions free of side effects. Do the increment on its own line.

The non-short-circuit versions: & and |

Java also has single-character & and |. On boolean operands they give the same answer as && and ||, but they always evaluate both sides. The Java Language Specification describes && as working like & except for the skipping of the right operand. You will almost never want the single versions for conditions. If you type one by mistake, a guard like d != 0 & 10 / d > 1 crashes when d is zero, even though it looks almost the same. (These operators have another job with the bits of numbers, which this course does not use.)

Examples

Example 1: the guard in both positions (simple)

java
public class SafeDiv {
    public static void main(String[] args) {
        int d = 0;
        System.out.println(d != 0 && 10 / d > 1);
        System.out.println(d == 0 || 10 / d > 1);
        d = 5;
        System.out.println(d != 0 && 10 / d > 1);
        d = 20;
        System.out.println(d != 0 && 10 / d > 1);
    }
}

Output (JDK 21):

text
false
true
true
false

With d zero, both guards avoid the division. With d = 5, 10 / 5 is 2, and 2 > 1 is true. With d = 20, 10 / 20 is 0 (integer division, Lesson 1), and 0 > 1 is false. Now the same condition written in the wrong order:

java
public class Crash {
    public static void main(String[] args) {
        int d = 0;
        System.out.println(10 / d > 1 && d != 0);
    }
}
text
Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Crash.main(Crash.java:4)

The exception message names the line of the crash. Reading the first line of such a message and the line number is the start of debugging, which a later module teaches properly.

Example 2: side effects that do or do not run (practical)

java
public class Side {
    public static void main(String[] args) {
        int count = 0;
        boolean r1 = false && count++ > 0;
        System.out.println(r1 + " count=" + count);
        boolean r2 = true || count++ > 0;
        System.out.println(r2 + " count=" + count);
        boolean r3 = true && count++ >= 0;
        System.out.println(r3 + " count=" + count);
        boolean r4 = false & count++ >= 0;
        System.out.println(r4 + " count=" + count);
        boolean r5 = true | count++ >= 0;
        System.out.println(r5 + " count=" + count);
    }
}
text
false count=0
true count=0
true count=1
false count=2
true count=3

The first two lines show a skipped right side: count stays 0. Line 3 shows && running the right side because the left was true. Lines 4 and 5 use & and |: the right side ran even though the answer was already known, so count kept going up. The increment inside the condition is only here to make the skipping visible; in real code, avoid it.

Example 3: null and zero guards in a service (realistic)

java
public class NullSafe {
    public static void main(String[] args) {
        String name = null;
        boolean safe = name != null && name.length() > 3;
        System.out.println(safe);
        String other = "Amira";
        System.out.println(other != null && other.length() > 3);
        int items = 0;
        int totalCents = 500;
        boolean expensive = items > 0 && totalCents / items > 100;
        System.out.println(expensive);
        System.out.println(name == null || name.length() == 0);
    }
}
text
false
true
false
true

This models two everyday checks: "does the customer have a usable name?" and "is the average item price above one dollar?" (with an empty cart, the average has no meaning, and the guard items > 0 prevents the division). The last line is a typical "is it missing or empty?" test. Because of ||, name.length() runs only when name is not null. Written the other way round, it would crash:

java
System.out.println(name.length() > 3 && name != null);
text
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>" is null
	at NullCrash.main(NullCrash.java:4)

The name <local1> appears because the program was compiled without extra debugging information. The exact wording of such messages can differ between Java versions and compile options.

Backend Insight

Almost every request a server handles carries data that might be missing, empty, or zero: an optional query parameter, a record that was not found, a count of zero items. Guard conditions with && and || are the quickest way to protect the code behind them. A database result that may be null, a divisor from user input, an index that might be out of range: each is checked on the left before it is used on the right.

Two review habits follow:

  • Safe check first. Reviewers read x != null && x.something() as correct and x.something() && x != null as a bug waiting to happen.
  • No side effects in conditions. Short-circuiting means a side effect may or may not run, depending on data. That is how a bug appears only for some users, and it is why the next lesson's habit, predict first and run second, matters so much for conditions.

Common Mistakes

Mistake 1: Putting the guard on the wrong side

  • Incorrect: 10 / d > 1 && d != 0
  • Correct: d != 0 && 10 / d > 1

Mistake 2: Using & or | by accident

A single character always evaluates both sides, so the guard no longer protects. In conditions, write && and ||.

Mistake 3: Using || as a guard when && is needed

d == 0 || 10 / d > 1 is safe but means something different: it is true when d is zero. Choose the operator by the meaning you want, then order the operands for safety.

Mistake 4: Hiding important work in the right operand

isValid && save(item) style conditions do something only some of the time. Put the important action on its own line.

Exercises

Try each exercise yourself before opening the solutions. Predict first.

Level A: Foundation

A1. In a && b, when is b skipped? In a || b?

أظهر الحل A1

A1. b is skipped in a && b when a is false; in a || b when a is true.

A2. int n = 0; Evaluate n != 0 && 100 / n > 5 and say whether it crashes.

أظهر الحل A2

A2. n != 0 is false, so the right side is skipped. The result is false and there is no crash.

A3. Evaluate true || false, false && true, true && false || true.

أظهر الحل A3

A3. true, false, true (&& first: true && false is false, then false || true is true).

Level B: Application

B1. int total = 80; int count = 0; Write a condition that is true only when the average total / count is at least 10 and never crashes.

أظهر الحل B1

B1. count != 0 && total / count >= 10. With count = 0 it is false; the division is skipped.

B2. What does this print? int k = 5; boolean ok = k > 10 && k++ > 0; System.out.println(ok + " " + k); Then change && to & and say what it prints.

أظهر الحل B2

B2. With &&: false 5, because k > 10 is false and k++ is skipped. With &: false 6, because both sides are evaluated and k++ runs.

B3. String code = null; Write a condition that is true when code is null or empty (length zero), without crashing.

أظهر الحل B3

B3. code == null || code.length() == 0. If code is null the left side is true and the method call is skipped.

Level C: Challenge

C1. int a = 4; int b = 0; Evaluate each and say which crash: (i) b != 0 && a / b > 1 (ii) a / b > 1 || b == 0 (iii) b == 0 || a / b > 1 (iv) a > 0 & a / b > 1

أظهر الحل C1

C1. (i) false, no crash: the guard skips the division. (ii) crashes: a / b is evaluated first and b is zero. (iii) true, no crash: the left side is true, so the right is skipped. (iv) crashes: & evaluates both sides, so the division runs even though a > 0 is already known.

C2. A rule says an order can be refunded if the order exists (order != null), it is not older than 30 days (order.ageDays <= 30), and it is paid. Write the condition with the order's days written as days, a boolean orderExists, and boolean paid, so that the cheap, safe checks come first. Explain your ordering in one sentence.

أظهر الحل C2

C2. orderExists && days <= 30 && paid. Everything on the right of orderExists is only looked at once the order is known to exist (in real code, reading order.ageDays directly requires it); cheap comparisons come before anything slower, and no side effects are used.

Mini Challenge

Write a program Guards with int items = 0; int totalCents = 1200; String note = null;.

  1. Print whether the average item price is at least 100 cents, written so that it cannot crash.
  2. Print whether the note is present and longer than 5 characters, written so that it cannot crash.
  3. Print whether the note is missing or empty.
  4. Write one version of (1) with the operands in the wrong order, but do not run it. Say in a comment what would happen and why.
  5. Change items to 3 and note to "Gift wrap" and predict the three results first.

Check your work: the three printed results with the first values should be false, false, and true; with the second values, they should be true, true, and false.

Summary & Next Lesson

Key takeaways

  • a && b skips b when a is false; a || b skips b when a is true.
  • Put the safety check on the left and the risky operation on the right: d != 0 && 10 / d > 1.
  • The same pattern protects method calls on a possibly null value.
  • A side effect in the right operand may not run; keep conditions free of them.
  • & and | always evaluate both sides, so do not use them as guards.

Next lesson

Next is the module demo, "Demo: predict-then-run expression challenge". You will put every operator from this module together, predict a set of results on paper, and then compare them with real output.

Self-check

  1. When does Java skip the right side of ||?
  2. Why is d != 0 && 10 / d > 1 safe, and why is 10 / d > 1 && d != 0 not?
  3. What is the difference between && and & on booleans?
  4. What would name != null && name.isEmpty() give if name is null?
  5. Why should you avoid count++ inside a condition?
أظهر الإجابات
  1. When the left side is true, because the whole expression must then be true.
  2. In the first, a zero d makes the left side false, so the division is skipped. In the second, the division is evaluated first and throws when d is zero.
  3. Both give the same result, but && skips the right side when the left is false, and & always evaluates both.
  4. false, with no crash: the null check fails first and the method call is skipped.
  5. It may or may not run depending on the left side, which makes the program's behavior depend on hidden conditions and is hard to read.

References

  • Oracle, The Java Language Specification, Java SE 21, Chapter 15: Expressions. Section 15.23 says && evaluates the right operand only if the left is true; section 15.24 says || evaluates its right operand only if the left is false; section 15.17.2 says integer division by zero throws ArithmeticException.
  • Oracle, The Java Tutorials: Operators. It lists && and || in the precedence table. It does not explain short-circuiting itself; that comes from the specification above.
  • All outputs and exception messages above came from compiling and running the code with JDK 21. The text of a NullPointerException message depends on the Java version and on how the code was compiled. Calling a method on null throwing NullPointerException was observed in the run, and I did not look it up on a documentation page here.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.