Introduction | In this short lecture, we will clarify some points about the meaning of final in variable declarations, introduce two operators used in conditional expressions and discuss short-circuit evaluation in logical operators All these features allow us to write more compact and understandable code, once we understand these language features. |
More on final |
In the real world, a constant is a named value that never changes.
Examples of constants are π (pi), e, the speed of light, the mass of a
proton (we think?) etc.
In programs, a constant is a variable whose value never changes within its
scope (i.e., during the time the variable is declared).
This is a slightly more liberal definition.
So, any real-world constant is a program constant, but a program constant
doesn't have to be a real-world constant.
For example, to compute a mortgage, a program uses the current interest rate. This value is not a real-world constant, because its value changes daily. But, once the program starts computing the mortgage payments, the current interest rate is a constant in that program. We have seen that we can declare local variables as constants, by using the final access modifier (recall that final restricts how we can access the variable). When a variable is declared final it must be intialized, its value can be examined in subsequent expressions, but its value can never be changed by a state-change operator. We will use the terms constant and final variable interchangably. If we write code that tries to change a constant, the Java compiler detects and reports a syntax constraint error. In fact, we can use this rule to get some interesting information from the compiler: every statement where we change the state of a variable. We do so by changing it from a variable to a constant, and then let the compiler locate all the "errors" where we try to change its state. When we write programs, we should declare constants instead of using "magic" literals. The names of the constants will help us remember what the constant means (without having to see its values: what is 6.022141E23 or 2.99792458E8?). Using constants instead of variables makes our programs less prone to error: if we use a variable, we might accidentally change what value it stores -this is impossible with constants. Using constants also makes it easier to change our programs: in the upcoming Rocket program we can write .01 in lots of places, but if we needed to change that value to .001 (for a more accurate simulation), we might have to search our code carefully to make the correct changes (there might be other .01s in our program not refering to the time increment). If instead we declared final double dT = .01; (here dT stands for delta/change-in time) in our program, and then used the constant dT throughout our code, to change this value requires editing just this one line of code, and then recompiling the program. Although use of final in the example below may be a bit confusing, it is perfectly legal. int count = 0; int sum = 0; for (;;) { final int score = Prompt.forInt("Enter score (-1 to terminate)"); if (score == -1) break; count++; sum += score; } System.out.println("Average = " + sum/count);In this example, score is declared final and indeed, its value (once initialized) never changes in its scope: the block in which score is declared. When the block finishes, score becomes undeclared; then the for loop re-executes the block, redeclaring and reinitializing the score constant all over again. So, our use of score meets all the technical requirements for a constant. Some programmers would pronounce this code excellent; others would say that indicating final is not worth it. What do you think? Most constants specify an initializer in their declaration; but surprsingly, this is not necessary. If the initializer is omitted, it is called a blank final variable. The Java compiler is smart enought to ensure
final double d; //blank final ...code... //cannot refer to the constant d if (whatever) //value is assigned to constant d in one if branch d = ... else d = ... ...more code... //care refer to but not change the constant dAny further attempt to store a value into d will be detected and reported as an error by the Java compiler. When we learn how to write instance variables in classes, we will see more reasonable uses of blank final. |
Conditional Operators ? and : |
There are two operators that work together in Java, helping us to condense
our code by allowing us to write short expressions instead of longer
statements.
These two operators, ? and : constitute what is called a
conditional expression.
Please constrast this with if statements, which are sometimes
called conditional statements: the distinction between
expression and statement is important.
The EBNF rule for a conditional expression is
    conditional-expression <= expression ? expression : expression As a syntax constraint, the first expression must return a boolean result, and the second two expressions must return a result of the same type (it can be any type, but they must match).
We will write conditional expressions using the following form (almost
always putting them in parentheses, which makes reading them easier)
Semantically, Java first evaluates test, if it is true the result of the conditional expression is the result of evaluating expressionT; if it is false the result of the conditional expression is the result of evaluating expressionF. So, only two of the three expressions are ever evaluated. Because each conditional expression must have a unique result type, and because its value can be computed by either expressionT or expressionF, the Java compiler has a syntax constraint that requires these expressions to have the same type.
Let's look at three concrete examples of conditional expressions and the
if statements that they condense.
One can often simplify short if statements with even shorter
conditional expressions, but large if statements often cannot
be simplified.
|
Short-Circuit Evaluation |
We have learned that binary infix operators evaluate both their operands
first, and then compute their resulting value.
Actually, this ordering is correct for all but the && and ||
logical operators.
Instead, these operators use short-circuit evaluation: they always
evaluate their left operand first; if they can compute their resulting
value from this operand alone, they do so without evaluating their right
operand; if they cannot determine the resulting value from the left operand
alone, then they evaluate their right operand and compute the resulting
value
Note that if the left operand of && evaluates to false, the result must be false: false && false as well as false && true evaluate to false, so the value of the right operand is irrelevant. Note that if the left operand of || evaluates to true, the result must be true: true || false as well as true || true evaluate to true, so again the value of the right operand is irrelevant.
To see how we can use this short-circuit property when programming, assume
that a program declares int totalParts = 0, badParts = 0; and
increments the appropriate variables when a part is tested.
Next, assume that if the ratio of bad parts to total parts is ever over 5%
(or .05) we want to recognize this problem and display a message.
Because we have short-circuit evaluation, we can simply write
In a programming language without short-circuit evaluation, we would have to
write the following, more complicated code, to achieve safety from division
by zero.
As a final example, suppose that we are writing a game-playing program, and
the user must terminate the bet-play loop if his/her purse is 0 or if
he/she elects to quit (if the former is true, the user shouldn't even be
prompted about electively quitting; he/she must quit because he/she has
no more money).
With short-circuit evaluation, we can write one if statement that
captures all these semantics
Again, in a programming language without short-circuit evaluation, we could
safely write the following, more complicated code
Finally, short-circuit evaluation actually works in conditional expressions too. For example, if we write the conditional expresson (true ? 1 : 1/0) Java's result is 1; because the expression evaluates to true Java evaluates only the expression 1 and not the expression 1/0. If Java fully evaluated all expressions first, it would throw an exception. Recall the semantics of the conditional expression: Java first evaluates the test, if it is true the result of the conditional expression is the result of evaluating expressionT; if it is false the result of the conditional expression is the result of evaluating expressionF. So, it uses test to determine which other expression to evaluate, and only evaluates that one other expression. It always evaluates two of the three expressions. |
Problem Set |
To ensure that you understand all the material in this lecture, please solve
the the announced problems after you read the lecture.
If you get stumped on any problem, go back and read the relevant part of the lecture. If you still have questions, please get help from the Instructor, a CA, or any other student.
|