Home If statements
Branching semantics
Java allows you to combine if/else statements and branch in several different ways. Select the method that best suits what kind of branch you want to execute. Expand for more details.
More precisely, you should:
- Use
if / if
if each branch is independent: any, all, or none of the if statements could execute - Use
if / else if
if either zero or exactly one of the statement bodies will execute. - Use
if / else
if each branch is mutually exclusive: exactly one of the statement bodies will execute.
As an example, let's say we want to write a method which takes in a GPA and returns the corresponding letter grade. We have multiple different valid ways we could implement this method. We could use independent if statements:
// version A: multiple independent if statements public static String getLetterGrade(double gpa) { if (gpa > 3.5) { return "A"; } if (gpa > 2.5) { return "B"; } if (gpa > 1.8) { return "C"; } if (gpa > 0.7) { return "D"; } return "F"; }
Or we could use else if statements with no attached else:
// version B: if / else-if, no else public static String getLetterGrade(double gpa) { if (gpa > 3.5) { return "A"; } else if (gpa > 2.5) { return "B"; } else if (gpa > 1.8) { return "C"; } else if (gpa > 0.7) { return "D"; } return "F"; }
Or we could use if/else:
// version C: if / else public static String getLetterGrade(double gpa) { if (gpa > 3.5) { return "A"; } else if (gpa > 2.5) { return "B"; } else if (gpa > 1.8) { return "C"; } else if (gpa > 0.7) { return "D"; } else { return "F"; } }
Which of these three versions are preferred?
Well, simply from a mechanical standpoint, it doesn't make a huge difference: Java will end up doing the exact same thing in all three cases. So, the deciding factor really comes down to the underlying intent of our code. What do we intend to mean? Do we intend to mean that these checks are independent? That the first couple are exclusive but the last statement isn't? That every statement is mutually exclusive?
We probably mean the latter: you can only have one letter grade, after all. Therefore, version C would be the best in this case.
Let's consider another example. Let's say we have a method where want to calculate the resistance of two resistors that are placed in parallel, but want to return early if either of the resistors has a resistivity of zero ohms. Well, again, we have a dilemma:
// version A public double resistivity(double r1, double r2) { if (r1 == 0) { return 0; } if (r2 == 0) { return 0; } double inverse = 1 / r1 + 1 / r2; return 1 / inverse; } // version B public double resistivity(double r1, double r2) { if (r1 == 0) { return 0; } else if (r2 == 0) { return 0; } double inverse = 1 / r1 + 1 / r2; return 1 / inverse; } // version C public double resistivity(double r1, double r2) { if (r1 == 0) { return 0; } else if (r2 == 0) { return 0; } else { double inverse = 1 / r1 + 1 / r2; return 1 / inverse; } }
If we ignore the fact that we probably should be combining those two initial if statements into a single one, which version is best?
Well, in this case, those two initial checks are really independent from each other. Either of those two variables could be equal to zero at any given time, so version A would probably be best. Unlike the previous example where each branch was "equal", in this version the first two branches are merely "fail-early" branches that look for some specific criteria and terminate early if it looks like the method is about to fail. (In this case, we would have ended up dividing by zero if it weren't for those two checks).
Factor out common code
If you have common code at the beginning or end of a conditional, pull that code to avoid duplication and redundancy.
When writing if statements, be careful to make sure that you don't have redundancy between multiple branches.
For example, this would be bad style due to the redundancy:
if (x % 2 == 0) { int half = x / 2; System.out.println("half of x is " + half); System.out.println("x was even"); } else { int half = x / 2; System.out.println("half of x is " + half); System.out.println("x was odd"); }
Notice that the first two lines of both branches are doing the exact same things: lines 2-3 are the same as 6-7. While we could fix this by pulling those lines into a helper method, a better alternative would be to pull them out of the if statement entirely. A fixed version would look like this:
int half = x / 2; System.out.println("half of x is " + half); if (x % 2 == 0) { System.out.println("x was even"); } else { System.out.println("x was odd"); }
Typically, this sort of redundancy will take place at during the first few lines of your branches, or at the last few lines. For example, lines 3 and 6 are redundant in the following example:
if (x > 10) { x = x % 10; System.out.println("Result: " + x); } else { x = x * 2; System.out.println("Result: " + x); }
We would want to fix that to look like this:
if (x > 10) { x = x % 10; } else { x = x * 2; } System.out.println("Result: " + x);
Avoid unnecessary tests
Avoid unnecessary tests. If you already know that something is true, then you shouldn't bother to have a test for it. Such tests just make your code longer and harder to understand.
As an example, let's take our GPA to grade method from earlier. The following would be a bad way of implementing it:
// version C: if / else public static String getLetterGrade(double gpa) { if (gpa > 3.5) { return "A"; } else if (gpa <= 3.5 && gpa > 2.5) { return "B"; } else if (gpa <= 2.5 && gpa > 1.8) { return "C"; } else if (gpa <= 1.8 && gpa > 0.7) { return "D"; } else { return "F"; } }
Remember, the moment Java finds a true conditional, it will execute that branch and will
not continue to check the other branches. More specifically, what Java will do is literally
run the checks on lines 3, 5, 7, and 9 in precisely that order. If it turns out that the
check on line 5 is the first one to evaluate to true, Java will immediately run that branch
and ignore checks on lines 7 and 9 (and the last else
statement).
We can, and should, exploit this sequential property of if statements to write a cleaner version of our code. The cleaner version will look like this:
public static String getLetterGrade(double gpa) { if (gpa > 3.5) { return "A"; } else if (gpa > 2.5) { return "B"; } else if (gpa > 1.8) { return "C"; } else if (gpa > 0.7) { return "D"; } else { return "F"; } }
So, as an example, we would not need to check if the GPA is greater then 3.5 on line 5. If that were true, the first branch would have already triggered, making that specific test in the second branch unnecessary.
Empty blocks
You should never have empty blocks in your code – never have at set of curly braces that contain nothing inside.
Let's say that we want to write a method that prints something out if some condition is NOT true. A naive way of writing this might be like this:
if (myVar > 0) {
} else {
System.out.println("Error: value is negative.");
}
While this works, the empty block on lines 1-3 is sloppy and should be removed entirely. (It's sort of like an unnecessary appendage, just sort of dangling there).
We can do this by negating the condition, and swapping the two blocks. The
negation of myVar > 0
is !(myVar > 0)
, or more
concisely, myVar <= 0
.
After performing that inversion, our code now looks like this:
if (myVar <= 0) {
System.out.println("Error: value is negative.");
} else {
}
This is better, but still bad since we still have an empty block. Thankfully, the fix in
this case is pretty simple – remove the else
block entirely.
if (myVar <= 0) {
System.out.println("Error: value is negative.");
}