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.
intsize=5;if(size>10)doSomething();for(intx=0;x<size;x++){...}
Expressions produce values; an expression is evaluated to produce a result that is 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, because 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.
newObject();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 code—from 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.
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:
{intsize=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 names—for
example, the method setUpDog():
setUpDog(Stringname){intsize=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:
{inti=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.
We can define an if/else clause as follows:
if(condition)statement;[elsestatement;]
(The whole of the preceding example is itself a statement and
could be nested within another if/else clause.) The if clause has the common functionality of
taking two different forms: a “one-liner” or a block. The block form
is as follows:
if(condition){[statement;][statement;][...]}else{[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 statements are in code blocks, and all
their enclosed statements are executed if the corresponding (if or
else) branch is taken. Any variables declared within each block are
visible only to the statements within the block. 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.
The do and while iterative statements have the familiar
functionality; their conditional test is also a Boolean
expression:
while(condition)statement;dostatement;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.
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
statement (or block) 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(inti=0;i<100;i++){System.out.println(i);intj=i;...}
This loop will execute 100 times, printing values from 0 to 99.
Note that the variable j is local
to the block (visible only to statements within it) and will not be
accessible to the code “after” the for loop. 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(inti=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:
intx;for(x=0;hasMoreValue();x++)getNextValue();System.out.println(x);
Java’s auspiciously dubbed “enhanced for loop” acts like the “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=newint[]{1,2,3,4};for(inti:arrayOfInts)System.out.println(i);List<String>list=newArrayList<String>();list.add("foo");list.add("bar");for(Strings: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 thus can be a target
of the for loop.
The most common form of the Java switch statement takes an integer (or a
numeric type argument that can be automatically “promoted” to an
integer type), a string type argument, or an “enum” type (discussed
shortly) and selects among a number of alternative, constant
case
branches:[8]
switch(expression){caseconstantExpression:statement;[caseconstantExpression:statement;]...[default:statement;]}
The case expression for each branch must evaluate to a different
constant integer or string value at compile time. Strings are compared
using the String equals() method,
which we’ll discuss in more detail in Chapter 10. 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:
intvalue=2;switch(value){case1:System.out.println(1);case2:System.out.println(2);case3:System.out.println(3);}// prints 2, 3!
Using break to terminate each
branch is more common:
intretValue=checkStatus();switch(retVal){caseMyClass.GOOD:// something goodbreak;caseMyClass.BAD:// something badbreak;default:// neither onebreak;}
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:
intvalue=getSize();switch(value){caseMINISCULE:caseTEENYWEENIE:caseSMALL:System.out.println("Small");break;caseMEDIUM:System.out.println("Medium");break;caseLARGE:caseEXTRALARGE:System.out.println("Large");break;}
This example effectively groups the six possible values into three cases.
Enumerations are intended to replace much of the usage of integer constants for situations like the one just discussed with a typesafe 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:
enumSize{Small,Medium,Large}
You can use enumerations in switches 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.
// usageSizesize=...;switch(size){caseSmall:...caseMedium:...caseLarge:...}
Chapter 5 provides more details about enumerations.
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, triggering a break statement that stops the loop 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 99, skipping number
33.
for(inti=0;i<100;i++){if(i==33)continue;System.out.println(i);}
The break and continue statements look like those in the C
language, but Java’s forms 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.
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
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.
Java supports almost all standard operators from the C language. 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 |
|---|---|---|---|
Arithmetic | |||
1 | Arithmetic | ||
Integral | Bitwise complement | ||
Boolean | Logical complement | ||
1 | | Any | Cast |
2 | Arithmetic | ||
3 | Arithmetic | Addition and subtraction | |
3 | + | String | |
4 | Integral | Left shift | |
4 | Integral | Right shift with sign extension | |
4 | Integral | Right shift with no extension | |
5 | Arithmetic | Numeric comparison | |
5 | | Object | Type comparison |
Primitive | |||
6 | ==, != | Object | Equality and inequality of reference |
Integral | Bitwise AND | ||
7 | & | Boolean | Boolean AND |
Integral | Bitwise XOR | ||
8 | ^ | Boolean | Boolean XOR |
Integral | Bitwise OR | ||
9 | | | Boolean | Boolean OR |
10 | Boolean | Conditional AND | |
11 | Boolean | ||
12 | N/A | ||
13 | Any |
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 can be used
to perform 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 is used to create objects; we will discuss it in detail
shortly.
While variable initialization (i.e., declaration and assignment together) is considered a statement with no resulting value, variable assignment alone is an expression:
inti,j;// statementi=5;// both expression and statement
Normally, we rely on assignment for its side effects alone, but 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.
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.
The dot (.) operator
is used to select members of a class or object instance. (We’ll talk
about those in detail in the following chapters.) 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:
inti=myObject.length;Strings=myObject.name;myObject.someMethod();
A reference-type expression can be used in compound evaluations by selecting further variables or methods on the result:
intlen=myObject.name.length();intinitialLen=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");
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 statements, 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...");intmyLength=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 (no value).
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.
Objects in Java are allocated with the new operator:
Objecto=newObject();
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:
inthours=newDate().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.
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:
Booleanb;Stringstr="foo";b=(strinstanceofString);// true, str is a Stringb=(strinstanceofObject);// also true, a String is an Object//b = ( str instanceof Date ); // The compiler is smart enough to catch this!
instanceof also correctly
reports whether the object is of the type of an array or a specified
interface (as we’ll discuss later):
if(fooinstanceofbyte[])...
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:
Strings=null;if(sinstanceofString)// false, null isn't an instance of anything