1 of 20

Learning Java: �A Test-Driven Approach

Chapter 2.1: Conditionals

2 of 20

Conditional statements.

Conditionals are used for decision-making.

  • If something is true, we do one thing, and if it isn't true, we do something else.

​

p resolves to a boolean value.

  • Called the predicate.

​

The consequent is evaluated when p is true.

​

The alternative is evaluated when p is false.

if (p) {

… // consequent

} else {

… // alternative

}

3 of 20

Conditional statements.

Example: consider the right-hand code.

​

​

​

42 is assigned to x if the predicate is true.

​

​

​

The predicate resolves to true, because y (42) is less than 200 / 3, which is 66 (integer division).

​

​

int x = 0;�int y = 42;�int z = 200;

if (y < z / 3) {

x = 42;

} else {

x = 84;

}

4 of 20

Conditional statements.

int x = 0;�int y = 42;�int z = 200;

if (z < 100) {

x = 42;

} else if (z < 200) {

x = 900;

} else if (z < 300) {

x = 2000;

} else {

x = 10000;

}

We can have multiple alternatives in an if statement.

​

​

Example: consider the code on the right.

​

​

The variable x takes on distinct values depending on the conditions.

​

​

If you're coming from Python, this is the same as if/elif/else.

​

​

If you're coming from Racket, this is the same as a cond.

5 of 20

Conditional example.

Let's design a method for determining the letter grade of a numeric grade.

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

​

class ComputeLetterGradeTester {�

@Test

void testComputeLetterGrade() {

assertAll(

() -> assertEquals('A', computeLetterGrade(98)),

() -> assertEquals('B', computeLetterGrade(81)),

() -> assertEquals('C', computeLetterGrade(70)),

() -> assertEquals('D', computeLetterGrade(60)),

() -> assertEquals('F', computeLetterGrade(59))),

}�}

​

Remember: test cases should cover ALL possibilities!

6 of 20

Conditional example.

static char computeLetterGrade(int g) {

if (g >= 90) {

return 'A';

} else if (g >= 80) {

return 'B';

} else if (g >= 70) {

return 'C';

} else if (g >= 60) {

return 'D';

} else {

return 'F';

}

}

​

​

7 of 20

Conditional example.

static char computeLetterGrade(int g) {

if (g >= 90) {

return 'A';

} else if (g >= 80) {

return 'B';

} else if (g >= 70) {

return 'C';

} else if (g >= 60) {

return 'D';

} else {

return 'F';

}

}

​

static char computeLetterGrade(int g) {

char c = 'F';

if (g >= 90) { c = 'A'; }

if (g >= 80) { c = 'B'; }

if (g >= 70) { c = 'C'; }

if (g >= 60) { c = 'D'; }

else { c = 'F'; }

return c;

}

​

What is the difference between the two code examples?

​

​

The one on the bottom does not work! Consider an input of 96.

​

​

Sequential 'ifs' are mutually exclusive; else ifs only execute when the previous branch was not taken.

​

8 of 20

Conditional expression.

Remember, 'if' is a conditional statement! It cannot be returned itself.

  • E.g., return if (…) does not work.

​

One solution is the ternary operator/conditional expression!

  • Denoted with a ? and : symbol.

​

return p ? c : u is equivalent to: if (p) {

return c;

} else {

return u;

}

9 of 20

Conditional expression.

The conditional expression is nice when you want to assign values based on logical operations.

​

Example: assign, to x, 42 if the sum of y and z exceeds the square root of |y * z|, otherwise assign the minimum of w and u.

​

double x = (y + z) > Math.sqrt(Math.abs(y * z)) ? 42 : Math.min(w, u);

​

We could also do:�

if ((y + z) > Math.sqrt(Math.abs(y * z))) {

x = 42;

} else {

x = Math.min(w, u);

}

10 of 20

Conditional expression.

It is important to note that ternary expressions should be used sparingly!

​

While we could in theory write all if statements as ternary operations, doing so likely obfuscates the logic of our code.

​

char grade = (g >= 90) ? 'A' : (g >= 80) ? 'B' : (g >= 70) ? 'C' : (g >= 60) ? 'D' : 'F';

​

​

11 of 20

Logical operators over booleans.

We know that operators such as <, !=, and such are logical operators over numbers and characters.

​

Example: write code that assigns, to y, 100 if x is both positive and divisible by 2, otherwise assign 0.

​

​

That is very verbose!

​

Let's combine those two inner conditions

using a new logical operator.

​

int y = 0;

if (x > 0) {

if (x % 2 == 0) {

y = 100;

} else {

y = 0;

}

} else {

y = 0;

}

12 of 20

Logical operators over booleans.

Logical AND: &&

Logical OR: ||

Logical NOT: !

​

Example from before: y = x > 0 && x % 2 == 0 ? 100 : 0;

​

p && q is true if both p and q are expressions that resolve to true.

p || q is true if at least one of p or q is an expression that resolves to true.

!p flips the resulting evaluation of p, i.e., if evaluating p resolves to true, !p resolves to false, and vice versa.

​

13 of 20

Common mistake with && and ||.

As a warning, MANY programmers will accidentally omit a & or a | from the relation, typing expressions such as x & y or x | y.

​

​

& and | are bitwise operators; NOT logical operators!

​

​

We will not discuss these operators.

14 of 20

Short-circuiting.

Logical AND and OR are short-circuiting operators.

​

​

Logical AND only executes q if p is true. When p is false, it doesn't matter what q is. Both operands of AND must resolve to true.

​

​

Logical OR only executes q if p is false. When p is true, it doesn't matter what q is. Only one operand of OR must resolve to true.

​

15 of 20

Short-circuiting example.

Before we talk about short-circuiting in action, we need to explain four operators.

​

x++, x--, ++x, and --x.

​

These are the postfix increment, postfix decrement, prefix increment, and prefix decrement operators respectively.

​

Operated on l-values, or (left-hand) variables.

​

Example: x++ increments x and stores it back into x.

16 of 20

Short-circuiting example.

What do “prefix” and “postfix” have to do with this?

​

Example:

int x = 2;

int y = x++ + ++x;

// x++ USES x in the expression, then increments it.

// ++x increments x, and only then uses it in the

// expression.

// Resolves to 2 + 4 = 6. x was affected to be 4.

​

Example (using x and y from above):

int z = ++x * (x-- + --y);

// 5 * (x-- + --y) // prefix ++ happens before *.

// 5 * (5 + 5)

// 50

17 of 20

Short-circuiting example.

Consider the following sloppy code.

​

The first expression short-circuits because 0 is not less than 0. Therefore, the y on the right-hand side is not decremented.

​

The second expression also short-circuits because z is not equal to 100. Therefore, neither the x nor the y on the right-hand side are incremented.

​

Don't use prefix/postfix evaluations in conditionals.

​

​

int x = 0;

int y = 1;

int z = 2;

if (x < --y && --y == -1) {

z = 100;

}

​

if (z != 100 || y++ + ++x == 2) {

z = 200;

}

18 of 20

Negating expressions, DeMorgan's law.

Consider the following expression:

(x < y && y == -1)

​

What is its logical negation? That is, what is the result of:

​

!(x < y && y == -1)

​

Distribute the negation inward. Follow the logical negation table.

​

Operator

Negated Operator

<

>=

<=

>

>

<=

>=

<

==

!=

!=

=

&&

||

||

&&

19 of 20

Negating expressions, DeMorgan's law.

!(x < y && y == -1)

= !(x < y) || !(y == -1)

= (x >= y) || (y != -1)

​

DeMorgan's law:

!(P || Q) = (!P && !Q)

!(P && Q) = (!P || !Q)

​

Double negations cancel out.

!!P = P, !!(P && Q) = (P && Q)

​

Operator

Negated Operator

<

>=

<=

>

>

<=

>=

<

==

!=

!=

=

&&

||

||

&&

20 of 20

Negating expressions, DeMorgan's law.

Study the table - do not get the operators mixed up!

​

Many programmers think the logical negation of < is >, but that's not true!

​

Think of it like this: if x < 100, then the negation CANNOT be x > 100, because that excludes 100. Therefore, the negation of x < 100 is x >= 100.

​

Operator

Negated Operator

<

>=

<=

>

>

<=

>=

<

==

!=

!=

=

&&

||

||

&&