Section 4.4. Statements and Expressions


4.4. Statements and Expressions

Java statements appear inside methods and classes; they describe all activities of a Java program. Variable declarations and assignments, such as those in the previous section, are statements, as are basic language structures such as if/then conditionals and loops.

     int size = 5;     if ( size > 10 )         doSomething(  );     for( int x = 0; x < size; x++ ) { ... }

Expressions produce values; an expression is evaluated to produce a result, to be used as part of another expression or in a statement. Method calls, object allocations, and, of course, mathematical expressions are examples of expressions. Technically, since variable assignments can be used as values for further assignments or operations (in somewhat questionable programming style), they can be considered to be both statements and expressions.

     new Object(  );     Math.sin( 3.1415 );     42 * 64;

One of the tenets of Java is to keep things simple and consistent. To that end, when there are no other constraints, evaluations and initializations in Java always occur in the order in which they appear in the codefrom left to right, top to bottom. We'll see this rule used in the evaluation of assignment expressions, method calls, and array indexes, to name a few cases. In some other languages, the order of evaluation is more complicated or even implementation-dependent. Java removes this element of danger by precisely and simply defining how the code is evaluated. This doesn't mean you should start writing obscure and convoluted statements, however. Relying on the order of evaluation of expressions in complex ways is a bad programming habit, even when it works. It produces code that is hard to read and harder to modify.

4.4.1. Statements

Statements and expressions in Java appear within a code block. A code block is syntactically a series of statements surrounded by an open curly brace ({) and a close curly brace (}). The statements in a code block can include variable declarations and most of the other sorts of statements and expressions we mentioned earlier:

     {         int size = 5;         setName("Max");         ...     }

Methods, which look like C functions, are in a sense just code blocks that take parameters and can be called by their namesfor example, the method setUpDog( ):

     setUpDog( String name ) {         int size = 5;         setName( name );         ...     }

Variable declarations are limited in scope to their enclosing code block. That is, they can't be seen outside of the nearest set of braces:

     {         int i = 5;     }     i = 6;           // Compile-time error, no such variable i

In this way, code blocks can be used to arbitrarily group other statements and variables. The most common use of code blocks, however, is to define a group of statements for use in a conditional or iterative statement.

4.4.1.1 if/else conditionals

Since a code block is itself collectively treated as a statement, we define a conditional like an if/else clause as follows:

     if ( condition )         statement;     [ else         statement; ]

So, the if clause has the familiar (to C/C++ programmers) functionality of taking two different forms: a "one-liner" and a block. Here's one:

     if ( condition )         statement;

Here's the other:

     if ( condition )  {         [ statement; ]         [ statement; ]         [ ... ]     }

The condition is a Boolean expression. A Boolean expression is a true or false value or an expression that evaluates to one of those. For example i == 0 is a Boolean expression that tests whether the integer i holds the value 0.

In the second form, the statement is a code block, and all its enclosed statements are executed if the conditional succeeds. Any variables declared within that block are visible only to the statements within the successful branch of the condition. Like the if/else conditional, most of the remaining Java statements are concerned with controlling the flow of execution. They act for the most part like their namesakes in other languages.

4.4.1.2 do/while loops

The do and while iterative statements have the familiar functionality; their conditional test is also a Boolean expression:

     while ( condition )         statement;     do         statement;     while ( condition );

For example:

     while( queue.isEmpty(  ) )         wait(  );

Unlike while or for loops (which we'll see next), that test their conditions first, a do-while loop always executes its statement body at least once.

4.4.1.3 The for loop

The most general form of the for loop is also a holdover from the C language:

     for ( initialization; condition; incrementor )         statement;

The variable initialization section can declare or initialize variables that are limited to the scope of the for statement. The for loop then begins a possible series of rounds in which the condition is first checked and, if true, the body is executed. Following each execution of the body, the incrementor expressions are evaluated to give them a chance to update variables before the next round begins:

     for ( int i = 0; i < 100; i++ ) {         System.out.println( i )         int j = i;         ...     }

This loop will execute 100 times, printing values from 0 to 99. If the condition of a for loop returns false on the first check, the body and incrementor section will never be executed.

You can use multiple comma-separated expressions in the initialization and incrementation sections of the for loop. For example:

     for (int i = 0, j = 10; i < j; i++, j-- ) {        ...     }

You can also initialize existing variables from outside the scope of the for loop within the initializer block. You might do this if you wanted to use the end value of the loop variable elsewhere:

     int x;     for( x = 0; hasMoreValue(  ); x++ )       getNextValue(  );     System.out.println( x );

4.4.1.4 The enhanced for loop

Java 5.0 introduced a new form of the for loop (auspiciously dubbed the "enhanced for loop"). In this simpler form, the for loop acts like a "foreach" statement in some other languages, iterating over a series of values in an array or other type of collection:

     for ( varDeclaration : iterable )         statement;

The enhanced for loop can be used to loop over arrays of any type as well as any kind of Java object that implements the java.lang.Iterable interface. This includes most of the classes of the Java Collections API. We'll talk about arrays in this and the next chapter; Chapter 11 covers Java Collections. Here are a couple of examples:

     int [] arrayOfInts = new int [] { 1, 2, 3, 4 };     for( int i  : arrayOfInts )         System.out.println( i );     List<String> list = new ArrayList<String>(  );     list.add("foo");     list.add("bar");     for( String s : list )         System.out.println( s );

Again, we haven't discussed arrays or the List class and special syntax in this example. What we're showing here is the enhanced for loop iterating over an array of integers and also a list of string values. In the second case, the List implements the Iterable interface and so can be a target of the for loop.

4.4.1.5 switch statements

The most common form of the Java switch statement takes an integer-type argument (or an argument that can be automatically promoted to an integer type) and selects among a number of alternative, integer constant case branches:

     switch ( int expression )     {         case int constantExpression :             statement;         [ case int constantExpression             statement;  ]         ...         [ default :             statement;  ]     }

The case expression for each branch must evaluate to a different constant integer value at compile time. An optional default case can be specified to catch unmatched conditions. When executed, the switch simply finds the branch matching its conditional expression (or the default branch) and executes the corresponding statement. But that's not the end of the story. Perhaps counterintuitively, the switch statement then continues executing branches after the matched branch until it hits the end of the switch or a special statement called break. Here are a couple of examples:

     int value = 2;     switch( value ) {         case 1:             System.out.println( 1 );         case 2:             System.out.println( 2 );         case 3:             System.out.println( 3 );     }     // prints 2, 3

Using break to terminate each branch is more common:

     int retValue = checkStatus(  );     switch ( retVal )     {         case MyClass.GOOD :             // something good             break;         case MyClass.BAD :             // something bad             break;         default :             // neither one             break;     }

In this example, only one branch: GOOD, BAD, or the default is executed. The "fall through" behavior of the switch is justified when you want to cover several possible case values with the same statement, without resorting to a bunch of if-else statements:

     int value = getSize(  );     switch( value ) {         case MINISCULE:         case TEENYWEENIE:         case SMALL:             System.out.println("Small" );             break;         case MEDIUM:             System.out.println("Medium" );             break;         case LARGE:         case EXTRALARGE:             System.out.println("Large" );             break;     }

This example effectively groups the six possible values into three cases.

Enumerations and switch statements. Java 5.0 introduced enumerations to the language. Enumerations are intended to replace much of the usage of integer constants for situations like the one just discussed with a type-safe alternative. Enumerations use objects as their values instead of integers but preserve the notion of ordering and comparability. We'll see in Chapter 5 that enumerations are declared much like classes and that the values can be "imported" into the code of your application to be used just like constants. For example:

     enum Size { Small, Medium, Large }

You can use enumerations in switches in Java 5.0 in the same way that the previous switch examples used integer constants. In fact, it is much safer to do so because the enumerations have real types and the compiler does not let you mistakenly add cases that do not match any value or mix values from different enumerations.

     // usage     Size size = ...;     switch ( size ) {         case Small:             ...         case Medium:             ...         case Large:             ...     }

Chapter 5 provides more details about enumerations as objects.

4.4.1.6 break/continue

The Java break statement and its friend continue can also be used to cut short a loop or conditional statement by jumping out of it. A break causes Java to stop the current block statement and resume execution after it. In the following example, the while loop goes on endlessly until the condition( ) method returns true, then it stops and proceeds at the point marked "after while."

     while( true ) {         if ( condition(  ) )              break;     }     // after while

A continue statement causes for and while loops to move on to their next iteration by returning to the point where they check their condition. The following example prints the numbers 0 through 100, skipping number 33.

     for( int i=0; i < 100; i++ ) {         if ( i == 33 )             continue;         System.out.println( i );     }

The break and continue statements should be familiar to C programmers, but Java's have the additional ability to take a label as an argument and jump out multiple levels to the scope of the labeled point in the code. This usage is not very common in day-to-day Java coding but may be important in special cases. Here is an outline:

     labelOne:         while ( condition ) {             ...             labelTwo:                 while ( condition ) {                     ...                     // break or continue point                 }             // after labelTwo         }     // after labelOne

Enclosing statements, such as code blocks, conditionals, and loops, can be labeled with identifiers like labelOne and labelTwo. In this example, a break or continue without argument at the indicated position has the same effect as the earlier examples. A break causes processing to resume at the point labeled "after labelTwo"; a continue immediately causes the labelTwo loop to return to its condition test.

The statement break labelTwo at the indicated point has the same effect as an ordinary break, but break labelOne breaks both levels and resumes at the point labeled "after labelOne." Similarly, continue labelTwo serves as a normal continue, but continue labelOne returns to the test of the labelOne loop. Multilevel break and continue statements remove the main justification for the evil goto statement in C/C++.

There are a few Java statements we aren't going to discuss right now. The try, catch, and finally statements are used in exception handling, as we'll discuss later in this chapter. The synchronized statement in Java is used to coordinate access to statements among multiple threads of execution; see Chapter 9 for a discussion of thread synchronization.

4.4.1.7 Unreachable statements

On a final note, we should mention that the Java compiler flags "unreachable" statements as compile-time errors. An unreachable statement is one that the compiler determines won't be called at all. Of course, many methods may never actually be called in your code, but the compiler detects only those that it can "prove" are never called by simple checking at compile time. For example, a method with an unconditional return statement in the middle of it causes a compile-time error, as does a method with a conditional that the compiler can tell will never be fulfilled:

     if (1 < 2)         return;     // unreachable statements

4.4.2. Expressions

An expression produces a result, or value, when it is evaluated. The value of an expression can be a numeric type, as in an arithmetic expression; a reference type, as in an object allocation; or the special type, void, which is the declared type of a method that doesn't return a value. In the last case, the expression is evaluated only for its side effects, that is, the work it does aside from producing a value. The type of an expression is known at compile time. The value produced at runtime is either of this type or, in the case of a reference type, a compatible (assignable) subtype.

4.4.2.1 Operators

Java supports almost all standard C operators. These operators also have the same precedence in Java as they do in C, as shown in Table 4-3.

Table 4-3. Java operators

Precedence

Operator

Operand type

Description

1

++,

Arithmetic

Increment and decrement

1

+, -

Arithmetic

Unary plus and minus

1

~

Integral

Bitwise complement

1

!

Boolean

Logical complement

1

( type )

Any

Cast

2

*, /, %

Arithmetic

Multiplication, division, remainder

3

+, -

Arithmetic

Addition and subtraction

3

+

String

String concatenation

4

<<

Integral

Left shift

4

>>

Integral

Right shift with sign extension

4

>>>

Integral

Right shift with no extension

5

<, <=, >, >=

Arithmetic

Numeric comparison

5

instanceof

Object

Type comparison

6

==, !=

Primitive

Equality and inequality of value

6

==, !=

Object

Equality and inequality of reference

7

&

Integral

Bitwise AND

7

&

Boolean

Boolean AND

8

^

Integral

Bitwise XOR

8

^

Boolean

Boolean XOR

9

|

Integral

Bitwise OR

9

|

Boolean

Boolean OR

10

&&

Boolean

Conditional AND

11

||

Boolean

Conditional OR

12

?:

N/A

Conditional ternary operator

13

=

Any

Assignment


We should also note that the percent (%) operator is not strictly a modulo, but a remainder, and can have a negative value.

Java also adds some new operators. As we've seen, the + operator can be used with String values to perform string concatenation. Because all integral types in Java are signed values, the >> operator performs a right-arithmetic-shift operation with sign extension. The >>> operator treats the operand as an unsigned number and performs a right-arithmetic-shift with no sign extension. The new operator, as in C++, is used to create objects; we will discuss it in detail shortly.

4.4.2.2 Assignment

While variable initialization (i.e., declaration and assignment together) is considered a statement, with no resulting value, variable assignment alone is an expression:

     int i, j;          // statement     i = 5;             // both expression and statement

Normally, we rely on assignment for its side effects alone, but, as in C, an assignment can be used as a value in another part of an expression:

     j = ( i = 5 );

Again, relying on order of evaluation extensively (in this case, using compound assignments in complex expressions) can make code obscure and hard to read. Do so at your own peril.

4.4.2.3 The null value

The expression null can be assigned to any reference type. It means "no reference." A null reference can't be used to reference anything and attempting to do so generates a NullPointerException at runtime.

4.4.2.4 Variable access

The dot (.) operator is used to select members of a class or object instance. It can retrieve the value of an instance variable (of an object) or a static variable (of a class). It can also specify a method to be invoked on an object or class:

     int i = myObject.length;     String s = myObject.name;     myObject.someMethod(  );

A reference-type expression can be used in compound evaluations by selecting further variables or methods on the result:

     int len = myObject.name.length(  );     int initialLen = myObject.name.substring(5, 10).length(  );

Here we have found the length of our name variable by invoking the length( ) method of the String object. In the second case, we took an intermediate step and asked for a substring of the name string. The substring method of the String class also returns a String reference, for which we ask the length. Compounding operations like this is also called chaining method calls, which we'll mention later. One chained selection operation that we've used a lot already is calling the println( ) method on the variable out of the System class:

     System.out.println("calling println on out");

4.4.2.5 Method invocation

Methods are functions that live within a class and may be accessible through the class or its instances, depending on the kind of method. Invoking a method means to execute its body, passing in any required parameter variables and possibly getting a value in return. A method invocation is an expression that results in a value. The value's type is the return type of the method:

     System.out.println( "Hello, World..." );     int myLength = myString.length(  );

Here, we invoked the methods println( ) and length( ) on different objects. The length( ) method returned an integer value; the return type of println( ) is void.

This is all pretty simple, but in Chapter 5 we'll see that it gets a little more complex when there are methods with the same name but different parameter types in the same class or when a method is redefined in a child class, as described in Chapter 6.

4.4.2.6 Object creation

Objects in Java are allocated with the new operator:

     Object o = new Object(  );

The argument to new is the constructor for the class. The constructor is a method that always has the same name as the class. The constructor specifies any required parameters to create an instance of the object. The value of the new expression is a reference of the type of the created object. Objects always have one or more constructors, though they may not always be accessible to you.

We look at object creation in detail in Chapter 5. For now, just note that object creation is a type of expression and that the result is an object reference. A minor oddity is that the binding of new is "tighter" than that of the dot (.) selector. So you can create a new object and invoke a method in it without assigning the object to a reference type variable if you have some reason to:

     int hours = new Date(  ).getHours(  );

The Date class is a utility class that represents the current time. Here we create a new instance of Date with the new operator and call its getHours( ) method to retrieve the current hour as an integer value. The Date object reference lives long enough to service the method call and is then cut loose and garbage-collected at some point in the future (see Chapter 5 for details about garbage collection).

Calling methods in object references in this way is, again, a matter of style. It would certainly be clearer to allocate an intermediate variable of type Date to hold the new object and then call its getHours( ) method. However, combining operations like this is common.

4.4.2.7 The instanceof operator

The instanceof operator can be used to determine the type of an object at runtime. It tests to see if an object is of the same type or a subtype of the target type. This is the same as asking if the object can be assigned to a variable of the target type. The target type may be a class, interface, or array type as we'll see later. instanceof returns a boolean value that indicates whether the object matches the type:

     Boolean b;     String str = "foo";     b = ( str instanceof String );   // true, str is a String     b = ( str instanceof Object );   // also true, a String is an Object     b = ( str instanceof Date );     // false, str is not a Date or subclass

instanceof also correctly reports whether the object is of the type of an array or a specified interface (as we'll discuss later):

     if ( foo instanceof byte[] )         ...

It is also important to note that the value null is not considered an instance of any object. The following test returns false, no matter what the declared type of the variable:

     String s = null;     if ( s instanceof String )         // false, null isn't an instance of anything



    Learning Java
    Learning Java
    ISBN: 0596008732
    EAN: 2147483647
    Year: 2005
    Pages: 262

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net