The Artima Developer Community
Sponsored Link

Objects and Java by Bill Venners
Chapter 3:
Expressions and Statements

Advertisement

Another difference between the source files of C++ and the source files of Java is that Java source is written in Unicode, a 16-bit international character set standard. If you create an ASCII source file, the Java compiler will treat the ASCII characters as if they were the equivalent Unicode characters. This will be discussed at greater length in Chapter 3.

This chapter will reveal to you the details of expressions and statements in the Java language. It discusses operators, expressions, literals, local variables, control-flow, and gives some general guidelines on various aspects of method implementation.

Expressions

Expressions perform operations on data and move data around. Some expressions will be evaluated for their results, some for their side effects, some for both. An expression can have three kinds of result:

  1. a value, such as the result of: (4 * i)
  2. a variable, such as the result of: i = 4
  3. nothing (in the case of an invocation of a method declared as void)
An expression that results in a variable is called an lvalue in C++ and many other languages. A variable expression in Java is the same thing, the Java Language Specification just uses the name variable instead of lvalue. Such an expression can be used on the left hand side of an assignment operator. Side effects come about when an expression includes an assignment, increment, decrement, or method invocation.

Literals

Each type in the Java programming language has literals, the way to specify unnamed constant values for the type. For example, the boolean type has two literals, true and false. Object reference types have only one literal, null.

Integer literals come in three forms: decimal, hexadecimal, and octal. The way an integer literal begins indicates the base of the number. If the number begins with a 0x or 0X, it is hexadecimal (base 16). Some examples are: 0x5, 0X00FF, and 0xcafebabe. If the number begins with only a zero, it is an octal (base 8). Some examples are: 035, 0777, 0321. If the number begins with a non-zero digit, it is decimal: 31, 255, 20. If an integer literal ends in an L or l, it is a long, otherwise it is an int. Some examples of long integer literals are: 0XCAFEBABEL, 035L, 31L. If an int literal is assigned to a variable of type short or byte, the literal is treated as if it were a short or byte type so long as the literal value is within the valid range for that type.

Floating point literals are made up of decimal digits, optionally containing a decimal point, and optionally followed by an E or e and an exponent. Some examples of floating point literals are: 3.14159, .314159e1, 34.26E23. If a floating point literal ends in a F or f, it is a float, otherwise it is a double. Optionally, a double floating point literal can end in D or d. Some examples of float literals are: 3.14159F and 3e5f. The same values expressed as doubles could look like this: 3.14159 and 3e5D.

Character literals can be any Unicode character between single quotes, such as 'A'. In addition to providing an explicit character between the single quotes, you can provide an octal or hex number preceded by a backslash. The octal number must be between '\0' and '\377'. The hex number must have four digits. It is preceded with either a \u or \uu, as in: '\u0073' and '\uu039d'. There are also several character literals represented by special escape sequences, shown in Table 3-1.

Table 3-1. Special Character Literal Escape Sequences

LiteralMeaning
\nline feed (\u000A)
\bbackspace (\u0008)
\ttab (\u0009)
\fform feed (\u000C)
\rcarriage return (\u000D)
\"double quote (\u0022)
\'single quote (\u0027)
\\backslash (\005C)

The hex version of the character literal can have either one or two u's to facilitate conversion of Unicode source files to ASCII and back again. All Java source files are Unicode by definition. When you create an Java source file in ASCII, it is treated as the first 128 Unicode characters. (The first 128 Unicode characters, \u0000 to \u007f, map to the 7-bit ASCII character set. The second 128 Unicode characters, \u0080 to \u00ff, map to the ISO-Latin-1 character set.) If you include in a Unicode source file the Greek character 'q' (lower case theta), it will be converted into the string '\u03b8' in the ASCII file. When the ASCII file is converted back to Unicode, the '\u03b8' will be changed back into a 'q'. If, however, in your Unicode file you included the character literal '\u03b8', this would be converted into '\uu03b8' in the ASCII file. If the extra u were not added, when the ASCII file was translated back to Unicode you'd end up with a 'q' where you used to have a '\u03b8'. To avoid this problem, '\uu03b8' is transformed into '\u03b8' when converting from ASCII back to Unicode.

The Unicode escape sequences are valid not only for character and string literals, they are valid anywhere in a Java source file. For example, instead of typing this into your Unicode source file:

// (Not on the CD-ROM)
class Example1a {

    public static void main(String[] args) {
        int q = 25;
        System.out.println(q);
    }
}
You could type this into a Unicode or an ASCII source file:
// In Source Packet in file expr/ex1/Example1b.java
class Example1b {

    public static void main(String[] args) {
        int \u03b8 = 25;
        System.out.println(\u03b8);
    }
}
If you wanted to be extremely cryptic, you could even type:
// In Source Packet in file expr/ex1/Example1c.java
class Example1c {

    public static void main(String[] args) {
        \u0069\u006e\u0074\u0020\u03b8\u0020\u003d
            \u0020\u0032\u0035\u003b
        System.out.println(\u03b8);
    }
}
The main() method of Example1c starts with the same statement as the previous two examples, but with every character--including spaces and the semicolon-- replaced by the equivalent Unicode escape sequence. All three versions compile and do the same thing when run: they declare an int named q, initialize it to 25, and print out its value.

String literals, which appear between double quotes, as in "appear", are actually references to instances of class String. Strings will be described in detail in Chapter 10.

Operators

Java's binary arithmetic operators--addition: +, subtraction: -, multiplication: *, division: /, and remainder: %-- operate on any numerical type. The % operator, which is called modulo in C++, is called remainder in Java. It performs the same operation: calculating the remainder of a division. You can also use the + operator to concatenate strings. String concatenation will be described in Chapter 10. [bv: may want to add more details of IEEE754 math]

Java also has unary + and -, which allow you to indicate a literal number is positive or negative, as in -3 or +4.0. If you don't specify a unary + or -, a literal number is interpreted as positive. The unary - can also be used to negate a variable, as in: negativeVal = - val.

Java also has increment: ++, and decrement: --, operators. These operators can be placed before or after the variable expression they modify. If they are placed before (a prefix operator), they modify the variable expression before its value is used. If they are placed after (a postfix operator), they modify the variable expression after its value is used.

Java's relational and equality operators are: greater than: <, less than: >, greater than or equal to: >=, less than or equal to: <=, equal to: ==, and not equal to: !=. All yield a boolean result. The unary ! operator inverts boolean value.

The conditional operators, conditional-AND: &&, and conditional-OR: ||, take boolean operands and yield a boolean result. Expressions built from these operators only evaluate as far as needed to determine the result. For example, assume salt(), pepper(), toBe(), and notToBe() are methods that return a boolean. If salt() evaluates to false in the expression:(salt() && pepper()), then pepper() is never evaluated. Likewise, if toBe() evaluates to true in the expression:(toBe() || notToBe()), then notToBe() is never evaluated. Conditional- AND and conditional-OR are also called logical-AND and logical-OR.

Java has several operators that perform operations on individual bits. The binary bitwise operators are: bitwise AND: &, bitwise inclusive OR: |, and bitwise exclusive OR (or bitwise XOR): ^. The unary bitwise complement operator, ~, inverts each bit in its operand. There are also three shift operators: shift left: <<, shift right: >>, and unsigned shift right: >>>. The shift operators shift the integer value on the left of the operator by the amount specified by the integer value on the right. Shift left and unsigned shift right fill with zeroes as they shift. Shift right fills with the highest bit (the sign bit) of the left hand value as it shifts.

The conditional operator, ? :, is a shorthand for an if-then- else construction that does one of two different things depending upon the result of a boolean expression. The expression:

variable = booleanExpr ? value1 : value2;
has the same effect as:
if (booleanExpr) {
	variable = value1;
}
else {
	variable = value2;
}
The conditional operator is also sometimes called the ternary operator or the question- colon operator.

In addition to the basic assignment operator, =, Java includes many shorthand assignment operators that allow you to write expressions such as j = j + 1 in shorthand as j += 1. The main difference between these two approaches is that in the shorthand version, the variable expression j is evaluated only once. In the longhand version, the variable expression j is evaluated twice. This difference matters in cases where the variable expression has a side effect, as in someArray[i++]. All the shorthand assignment operators are listed in Table 3-2. [bv: must add in about one guy needing to be assignable to the other and that being the type of the expression.]

Operator Precedence and Associativity

Operator precedence determines which parts of an expression are evaluated before the other parts. For example, the expression

2 + 2 * 7
evaluates to 16, not 28, because the * operator has a higher precedence than the + operator. Thus the 2 * 7 part of the expression is evaluated before the 2 + 2 part. If you wish, you can use parentheses in expressions to clarify evaluation order or to override precedence. For example, if you really wanted the result of the expression above to be 28, you could write the expression like this:
(2 + 2) * 7

Table 3-2 shows all Java's operators and their precedence. The postfix operators, shown at the top of the table, have the highest precedence. The assignment operators, shown at the bottom, have the lowest precedence.

Table 3-2. Operator precedence

Operator TypeOperator
Postfix operators[] . (params) expr++ expr--
Unary operators++expr --expr +expr -expr ~ !
Creation or castnew (type)expr
Multiplicative* / %
Additive+ -
Shift<< >> >>>
Relational< > >= <= instanceof
Equality== !=
Bitwise AND&
Bitwise exlusive OR^
Bitwise inclusive OR|
Conditional-AND&&
Conditional-OR||
Conditional?:
Assignment= += -= *= /= %= >>= <<= >>>= &= ^= |=

When multiple operators of the same precedence appear side by side in an expression, the associativity of the operators determines the order of evaluation. In Java, all binary operators except the assignment operators are left-associative. Assignment operators are right- associative. For example, i + j - k is evaluated as (i + j) - k. i = j = k is evaluated as i = (j = k).

Complex Expressions

Operator precedence is part of the Java language. You needn't be afraid to use it. But on the other hand, if you find yourself attempting to show off your knowledge of precedence, consider using parentheses to clarify what operators are operating upon what expressions. When you do rely on precedence, make sure you are as smart as you think you are. Many bugs arise from mistaken assumptions about precedence. The best is approach is somewhere in the middle. If you use too many parentheses, or not enough, the code can be difficult to understand. You should choose the form that you feel maximizes your code's readability. Remember, it's not a contest to see who can write the most cryptic code, but who can write the most lucid code.

One approach to writing complex expressions is to separate them out into several stages, placing intermediate values into variables with descriptive names. Using well-named variables to hold intermediate values of long computation expressions can help others understand what is going on. As a bonus, a programmer can check the value of intermediate results in a debugger by inspecting the intermediate variables.

Order of Evaluation

After taking into account precedence and parentheses, Java guarantees that expressions will be evaluated left to right. For example, to evaluate eat() + drink() - beMerry(), Java will first evaluate eat(), then drink(), then perform the addition, then evaluate beMerry(), and finally perform the subtraction. eat() is evaluated before drink(), because eat() is to the left of drink(), and expressions are evaluated left to right. This guarantee is important because the invocations of eat() and drink() may have side effects that would differ if they were invoked in the opposite order.

In general, every operand to an operator is evaluated before the operator is evaluated. Three exceptions are the conditional-AND: &&, the conditional-OR: ||, and the conditional operator: ?:. The right hand side of conditional-AND and conditional-OR expressions won't be evaluated if the left hand side determines the result. For example, if the left hand side of a conditional-AND expression evaluates to false, the result of the expression will definitely be false, so the right hand side is not evaluated. Likewise, if the left hand side of an conditional-OR expression evaluates to true, the result of the expression will definitely be true, so the right hand side is not evaluated.

In the case of a conditional expression, the boolean-expression is evaluated first. Depending upon the result of this evaluation, only one of the other expressions will be evaluated. For example, in the expression isHungry() ? eat() : beMerry(), isHungry() will be evaluated first. If it evaluates to true, eat() will be evaluated and beMerry() will not. Otherwise, beMerry() will be evaluated and eat() will not.

Statements

A Java method body is a series of zero or more statements. In the Java programming language, statements are the fundamental unit of execution. All statements except blocks are terminated by a semicolon. Blocks are denoted by open and close curly braces. Statements are executed for their effects; they do not have values. There are many different kinds of statements in Java:

Most of these statement types will be described below. A few will be described in later chapters.[bv: am I missing anything that was added in JDK 1.1 or 1.2?]

Blocks

A block is a series of zero or more statements between a matching set of open and close curly braces. The bodies of methods and switch statements are blocks. The bodies of if, for, while, and do-while statements may also be blocks. Also, you can also simply create a new block inside another block by enclosing code within curly braces. A block contained within another block is itself a statement of the outer block. Blocks that contain no statements are called empty.

Local Variable Declaration Statements

[bv: need to add final here.]

Declaration statements establish a name, type, and possibly an initial value for a new local variable. Local variables in Java can be declared anywhere in a method. A local variable need not be initialized where it is declared, but it must be initialized before it is used. If it isn't, the source file won't compile. For example, the following snippet of code won't compile:

// In Source Packet in file expr/ex2/Example2a.java
class Example2a {
    public static void main(String[] args) {
        // THIS WON'T COMPILE.
        int i;
        if (i < 100) {
            //...
        }
    }
}
This code won't compile because i is used before it is initialized. To fix this problem, you could initialize i when you declare it:
// In Source Packet in file expr/ex2/Example2b.java
class Example2b {
    public static void main(String[] args) {
        // This compiles fine.
        int i = 5;
        if (i < 100) {
            //...
        }
    }
}
Alternatively, you could assign a value to i after you declare it, but before you use it:
// In Source Packet in file expr/ex2/Example2c.java
class Example2c {
    public static void main(String[] args) {
        // This compiles fine.
        int i;
        i = 5;
        if (i < 100) {
            //...
        }
    }
}

The "i" in "int i" and the "i = 5" in "int i = 5" are called declarators. As in C++, you can place multiple local variable declarators, separated by commas, into the same local variable declaration statement, as in:

int i, j = 5, k;

The scope of a local variable extends from the point where the local variable is declared to the end of the block in which it is declared. No other local variable of the same name can be declared within a local variable's scope. For example, the following won't compile, because a second local variable i is declared within the scope of a first local variable i:

// In Source Packet in file expr/ex2/Example2d.java
class Example2d {
    public static void main(String[] args) {
        // THIS WON'T COMPILE.
        int i = 4;
        if (i < 10) {
            int i = 3;  // Can't use name i again.
            //...
        }
    }
}

The Empty Statement

	;

[note to editors: the above semicolon is intended to be the entire text of this section. If I were to explain the semicolon in a sentence, it would be: "The empty statement, represented by a semicolon and nothing else, does nothing." The lone semicolon above is an empty (English) statement about the empty (Java) statement. All the programmers who reviewed this chapter "got it."]

Expression Statements

Expression statements are valid Java expressions that are terminated by a semicolon. Unlike C and C++, not all kinds of valid expressions can be expression statements. In Java, there are four kinds of expression statements:

All expressions except invocations of methods that don't return a value (methods declared as void) yield a value upon evaluation. When an expression is used as an expression statement, the end value of the expression is discarded. Such expressions are evaluated simply for their side effects, as in:
++i;
The full details of expressions will be expressed later in this chapter.

The if and if-else Statements

The simplest control flow construct in Java is the if statement, which can have an optional else clause. Here is the format without the else clause:

if (boolean-expression)
	statement
The statement can be a simple statement terminated by a semicolon or a block enclosed in curly braces. Alternatively, you can add an else:
if (boolean-expression)
	statement1
else
	statement2

As in C++, an else in Java matches the closest if.

Note that Java's if statement differs from that of C++ in that the expression contained in the parentheses must be boolean. In Java, if i is an int, then if (i) won't compile. You must say if (i != 0). This is also true of the expressions in the while, do, and for loops. This feature of Java enables the compiler to catch accidental use of = instead of == inside an expression. For example, if you accidentally type i = 3 instead of i == 3 in an if expression, it won't compile:

// In Source Packet in file expr/ex2/Example2e.java
class Example2e {
    public static void main(String[] args) {
        int i = 4;
        // In Java, THIS WON'T COMPILE, because the resulting
        // value of the expression isn't boolean.
        if (i = 3) {
            //...
        }
    }
}

The while and do-while Statements

The while statement looks like this:

while (boolean-expression)
	statement
Java's while loop behaves like C++'s while loop (the difference between the two is that Java's expression must be boolean.) The boolean- expression is evaluated first. If the expression is true, statement is executed. This process is repeated until the expression evaluates to false (or a break, continue, or return is executed, or an exception is thrown).

The do-while statement differs from the while in that the statement is always executed at least once. In a while loop, if the boolean-expression evaluates to false the first time, statement is never executed. Here is what a do-while looks like:

do
	statement
while (boolean-expression);

The for Statement

The for loop is used to iterate through a sequence of values. It takes the same form as in C++:

for (init-expr; boolean-expr; incr-expr)
	statement

The init-expr is an initialization expression, such as i = 0. You can optionally initialize multiple variables in init- expr by separating each initialization expression by a comma, as in i = 0, j = 0. You can even declare a new variable in the init- expr:

for (int i = 0; i < 10; ++i) {
    //...
}

The scope of this variable i is only within the statement portion of the for itself (in this example, a block). This contrasts with C++, which also allows you to declare a variable in the init-expr portion of a for loop, but that variable has a scope as if it were declared just above the for loop. In Java, the variable has a scope as if it were declared inside the statement of the for loop.

The incr-expr is usually an increment expression such as ++i. Like the init-expr, it can contain multiple statements separated by commas, as in: ++i, ++j.

The for loop executes by first executing init- expr, then evaluating boolean-expr. If boolean-expr evaluates to true, statement is executed. After statement is executed, incr-expr is executed and boolean-expr is checked again. The process of boolean-expr check, statement execution, incr-expr execution repeats until boolean-expr evaluates to false (or a break, continue, or return is executed, or an exception is thrown).

Each of the three components in a for loop's parentheses are optional. If you leave out the boolean-expr, it is assumed to be true. Thus, the customary way to write an infinite loop is:

// a "forever" loop
for (;;) {
    //...
}

The switch Statement

The switch statement gives an alternative to a cascade of if- else constructs:

// In Source Packet in file expr/ex3/Example3a.java
class Example3a {
    public static void main(String[] args) {
        int i = (int) (Math.random() * 5.0);
        if (i == 0) {
            System.out.println("Zero");
        }
        else if (i == 1) {
            System.out.println("One");
        }
        else if (i == 2 || i == 3) {
            System.out.println("Two or Three");
        }
        else {
            System.out.println("Other");
        }
    }
}
The equivalent switch statement is:
// In Source Packet in file expr/ex3/Example3b.java
class Example3b {
    public static void main(String[] args) {
        int i = (int) (Math.random() * 5.0);
        switch (i) {

        case 0:
            System.out.println("Zero");
            break;

        case 1:
            System.out.println("One");
            break;

        case 2:
        case 3:
            System.out.println("Two or Three");
            break;

        default:
            System.out.println("Other");
            break;
        }
    }
}
A switch's expression (i in this example) must be an int. The case labels must be compile-time constants. They can either be literals, as shown here, or static final fields of some class. A break statement is used in a switch to indicate the processing for that case is done. You need not have a break for every case. You can "fall through" to the code for the next case label, as was done by case 2 in this example.

The break and continue Statements

You can use break to exit while, do- while, for, and switch statements. When a break is executed, the execution continues just after the end of the current simple statement or block. [bv: actually, I think this statement is too simple. break hops to the end of the current while, do-while, or switch block, right?]

You can use continue inside a while, do- while, and for loop. When a continue is executed, the rest of the loop's body is skipped and the boolean-expression is again evaluated.

You can optionally use a label to indicate which loop or switch you want a break to operate on, or which loop you want a or continue to operate on. This enables you to easily break out of nested loops or switch statements, or to continue nested loops. To do so you must label the beginning of the loop or switch statement:

label: statement
Here's an example:
// In Source Packet in file expr/ex4/Example4.java
class Example4 {
    public static void main(String[] args) {

dance:  for (int i = 0; i < 10; ++i) {
            System.out.println(i + ": Swing your partner...");
            for (int j = 0; j < 10; ++j) {
                System.out.println(j + ": Round and round...");
                if (i == 5 && j == 5) {
                    break dance;
                }
            }
        }
        // Execution continues here after break dance is encountered.
        System.out.println("Now, twirl on your back.");
    }
}

When the break dance statement is executed, the for loop labeled dance (the outer loop) is exited.

The return Statement

The return statement returns from a method, potentially returning a value. If the method is declared as void, you must use a simple return statement:

return;
Otherwise, you must indicate a return value with a type that matches the return type of the method. For example, a method declared with a boolean return type could have the following statement:
return true;

Other Statements

A handful of other statements will be covered in later chapters. Chapter 11 will describe the throw and try statements. Chapter 13 will describe the synchronized statement.

Reachability

With one exception, statements in a Java program must be reachable. There must be at least one path of execution that will "reach" each statement. For example, the following code won't compile, because the ++i is unreachable:

// In Source Packet in file expr/ex5/Example5a.java
class Example5a {
    public static void main(String[] args) {
        // THIS WON'T COMPILE
        int i = 0;
        while (false) {
            ++i;
        }
    }
}
The exception to the reachability rule is the if statement. The following code does compile:
// In Source Packet in file expr/ex5/Example5b.java
class Example5b {
    public static void main(String[] args) {
        // This compiles fine.
        int i = 0;
        if (false) {
            ++i;
        }
    }
}
The reason the if statement is special is so it can be used for conditional compilation. (Because Java doesn't have a preprocessor like C++, it can't use C++'s method for conditional compilation.) The above code would compile, but the if (false) { ++i; } code would not appear in the class file.

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2019 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use