Unit 3 - Booleans and If-Statements

Boolean Expressions and Operators

  • A Boolean expression is a mathematical expression that evaluates to either true or false.

  • Boolean expressions are commonly used in if statements to control the flow of execution.

Relational Operators

  • Equality (==): Checks if two primitive values are equal.

    • Example: 3 == 2 (evaluates to false)
    • Only use double equals with primitive types; objects require a different approach.
  • Inequality (!=): Checks if two values are not equal.

    • Example: 3 != 2 (evaluates to true)
  • Other relational operators:

    • Greater than: >
    • Less than: <
    • Greater than or equal to: >=
    • Less than or equal to: <=
  • Example: x <= 2 (where x is a variable)

Conditional Operators

  • Conditional operators combine two or more Boolean expressions.

  • AND (&&): Both expressions must be true for the combined expression to be true.

    • Example: x <= 4 && x < 3

    • Short-circuiting: If the first expression is false, the second expression is not evaluated.

    • Significance: Prevents errors if the second expression might cause a crash.

      • Example: 5 / 0 > 3 will cause an ArithmeticException, but false && (5 / 0 > 3) will not due to short-circuiting.
  • OR (||): Either expression must be true for the combined expression to be true.

    • Example: x > 3 || x == 10

    • Short-circuiting: If the first expression is true, the second expression is not evaluated.

    • Significance: Prevents errors if the second expression might cause a crash.

      • Example: true || (5 / 0 > 3) will not cause an error due to short-circuiting.

If, Else If, and Else Statements

  • These statements control the execution of code blocks based on Boolean expressions.
  • if and else if require a Boolean expression.
  • else does not require a Boolean expression.
  • else if and else must have an if or another else if to be associated with.

If Statements

  • if statements are independent unless nested.

  • Multiple if statements can execute in a sequence.

  • Example:

    int x = 5;
    if (x < 10) { // true
        System.out.println("a"); // Executes
    }
    if (x < 4) { // false
        System.out.println("b"); // Skips
    }
    if (x < 7) { // true
        System.out.println("c"); // Executes
    }
    

Else Statements

  • else provides an alternative block of code to execute when the if condition is false.

  • Example:

    int y = 5;
    if (y > 10) { // false
        System.out.println("d"); // Skips
    } else {
        System.out.println("e"); // Executes
    }
    

Else If Statements

  • else if provides multiple conditions to check in sequence.

  • If an if or else if condition is true, its block executes, and the rest are skipped.

  • If no condition is true, the else block executes (if present).

  • Example:

    int z = 5;
    if (z > 10) { // false
        System.out.println("f"); // Skips
    } else if (z >= 4) { // true
        System.out.println("g"); // Executes
    } else {
        System.out.println("h"); // Skips
    }
    

Single-Line Code Blocks

  • Curly brackets are optional for if, else if, and else if the block contains only one line of code.

  • Indentation is used to indicate the code's association with the conditional statement.

  • Example:

    if (x > 0)
        System.out.println("Positive");
    

Common Mistakes

  • Adding a semicolon after the if, else if, or else statement.

    • This disconnects the intended code block from the conditional statement, leading to unexpected behavior or compiler errors (e.g., "else without if").
  • Attaching a Boolean expression to an else statement (only if and else if need conditions).

  • Forgetting curly brackets for multi-line code blocks in if, else if, or else statements.

Nested If, Else If, and Else Statements

  • if, else if, and else statements can be nested inside other conditional blocks.

  • Other control structures (e.g., while, for) can also be nested.

  • Example:

    if (x < 10) {
        if (y != 3) {
            System.out.println("a");
        } else if (y <= 5) {
            System.out.println("b");
        } else {
            System.out.println("c");
        }
    } else {
        if (x == 10) {
            System.out.println("d");
        } else if (x > 10) {
            System.out.println("e");
        }
    }
    
  • Tracing nested conditionals requires careful attention to the Boolean expressions and their associated blocks.

De Morgan's Laws

  • De Morgan's laws provide equivalencies for negating compound Boolean expressions.

  • Law 1: NOT (A \text{ OR } B) \equiv (NOT A) \text{ AND } (NOT B)

  • Law 2: NOT (A \text{ AND } B) \equiv (NOT A) \text{ OR } (NOT B)

  • In Java (and many other languages):

    • NOT is represented by !
    • AND is represented by &&
    • OR is represented by ||
  • Equivalents for Booleans:

    • !(A && B) is equivalent to !A || !B
    • !(A || B) is equivalent to !A && !B
  • Equivalents including numbers:

    • !(A == B) is equivalent to A != B
    • !(A > B) is equivalent to A <= B
    • !(A >= B) is equivalent to A < B
  • Example: Simplify !(a > b || b != a)

    1. Apply De Morgan's law: !(a > b) && !(b != a)
    2. Simplify: a <= b && b == a
  • For complex expressions with nested parentheses:

    • Apply De Morgan's laws level by level, starting from the outermost parentheses.
    • Example: Simplify !(a == b || !(b >= c || a < c)) -> (a != b && !(b >= c || a < c)) -> (a != b && (b < c && a >= c))

Checking Equality of Different Data Types

  • Primitive data types (e.g., boolean, char, byte, short, int, long, float, double) can be compared using the double equals operator (==).
  • Reference/Object types need different methods since using == will only check if they point to the same object in memory.

Primitive Data Types

  • Example:

    int a = 3;
    int b = 3;
    double c = 3.0;
    double d = 3.0;
    boolean e = false;
    
  • Boolean expressions:

    • a == b (true because a and b have the same value)
    • a != b (false because a and b are equal)
    • c == 3.0 (true because c has the value 3.0)
    • a == d (true because a is implicitly converted to a double for comparison)
    • c == e (Error because Java cannot directly compare numerical values to boolean values.

Object Types

  • Using == compares if two reference variables point to the same object.

  • The equals() method can perform a 'deep' comparison, checking if the data within the objects is equivalent.

    • Some classes implement equals() as a shallow comparison (same as ==).
    • Other classes implement equals() as a deep comparison (checking data equivalence).

Strings

  • Strings can be declared in two ways:

    1. Using string literals (e.g., String a = "coffee";)

      • String literals are stored in a string pool on the heap.
      • If an equivalent string already exists in the pool, the new variable will point to the existing string.
    2. Using the new String() constructor (e.g., String c = new String("coffee");)

      • Creates a new string object outside the string pool.
  • Examples:

    String a = "coffee";
    String b = "coffee";
    String c = new String("coffee");
    
  • Boolean expressions:

    • a == b (true because a and b point to the same string in the string pool)
    • a == c (false because a and c point to different objects on the heap even though they contain the same value)
    • a.equals(b) (true because the equals() method for strings does a deep comparison)
    • a.equals(c) (true because the equals() method compares the contents, not the object references)

Best Practices

  • Use == and != to compare primitive data types.
  • Use the equals() method to compare objects when a deep comparison is needed.
  • Be aware of whether the equals() method performs a shallow or deep comparison for a given class.
  • Use specialized methods (e.g., Arrays.equals()) for comparing specific object types like arrays.