Although the method declaration syntax of Java is quite different from that of C++, Java statement and expression syntax is like that of C. Again, the intention was to make the low-level details of Java easily accessible to C programmers, so that they can concentrate on learning the parts of the language that are really different. Java statements appear inside of 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 the basic language structures like conditionals and loops. Expressions describe 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.
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. 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, however, mean you should start writing obscure and convoluted statements. Relying on the order of evaluation of expressions is a bad programming habit, even when it works. It produces code that is hard to read and harder to modify. Real programmers, however, are not made of stone, and you may catch us doing this once or twice when we can’t resist the urge to write terse code.
As in
C or C++, statements and expressions in Java appear within a
code
block
. A code block is syntactically just a
series of statements surrounded by an open curly brace
({
) and a close curly brace
(}
). The statements in a code block can contain
variable
declarations:
{ int size = 5; setName("Max"); ... }
Methods, which look like C functions, are in a sense code blocks that take parameters and can be called by name:
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.
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
; [ elsestatement
; ]
Thus, the if
clause has the familiar (to C/C++
programmers) functionality of taking two different forms:
if (condition
)statement
;
or:
if (condition
) { [statement
; ] [statement
; ] [ ... ] }
Here the condition
is a boolean
expression. You can’t use an integer expression or a reference
type, as in C. In other words, while i==0
is
legitimate, i
is not (unless i
itself is boolean)
.
In the second form, the statement is a code block, and all of 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 C or C++.
The
do
and while
iterative statements have the familiar functionality; their
conditional test is also a boolean expression:
while (condition
)statement
; dostatement
; while (condition
);
The
for
statement also looks like it does in C:
for (initialization
;condition
;incrementor
)statement
;
The variable initialization expression can declare a new variable;
this variable is limited to the scope of the for
statement:
for (int i = 0; i < 100; i++ ) { System.out.println( i ) int j = i; ... }
Java does not support the C comma
operator, which groups multiple
expressions into a single expression.
However, you can use multiple comma-separated expressions in the
initialization and increment sections
of the for
loop. For example:
for (int i = 0, j = 10; i < j; i++, j-- ) { ... }
The Java
switch
statement takes an integer type (or an
argument that can be automatically promoted to an integer type) and
selects among a number of alternative case
branches:
switch ( intexpression
) { case intexpression
:statement
; [ case intexpression
statement
; ... default :statement
; ] }
No two of the
case
expressions can evaluate to the same
value. As in C, an optional default
case can be
specified to catch unmatched conditions. Normally, the special
statement break
is used to terminate a branch of the
switch
:
switch ( retVal ) { case myClass.GOOD : // something good break; case myClass.BAD : // something bad break; default : // neither one break; }
The Java break
statement and its friend
continue
perform unconditional jumps out of a
loop or conditional statement. They differ from the corresponding
statements in C by taking an optional label as an argument. Enclosing
statements, like code blocks and iterators, can be labeled with
identifier statements:
one: while (condition
) { ... two: while (condition
) { ... // break or continue point } // after two } // after one
In this example, a break
or
continue
without argument at the indicated
position would have the normal, C-style effect. A
break
would cause processing to resume at the
point labeled “after two”; a continue
would immediately cause the two
loop to return to
its condition test.
The statement break
two
at the
indicated point would have the same effect as an ordinary
break
, but break
one
would break both levels and resume at the
point labeled “after one.” Similarly,
continue
two
would serve as a
normal continue
, but continue
one
would return to the test of the
one
loop. Multilevel break
and
continue
statements remove the remaining
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 8, 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 there may be many methods that are actually never called in your code, but the compiler will only detect those that it can “prove” will never be called simply by checking at compile time. For example, a method with an unconditional return statement in the middle of it will cause a compile-time error. So will a method with something like this:
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 (i.e., 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 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 |
|
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 |
?: |
NA |
Conditional ternary operator |
13 |
= |
Any |
Assignment |
13 |
*=, /=, %=, +=, -=, <<=, >> =, >>>=, &=, ^=, |= |
Any |
Assignment with operation |
There are a few operators missing from the
standard C collection. For example, Java doesn’t support the
comma operator for combining expressions,
although the for
statement allows you to use it in
the initialization and increment sections. Java doesn’t allow
direct pointer manipulation, so it doesn’t support the
reference
(&
), dereference (*
), and
sizeof
operators that are familiar to C/C++
programmers.
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.
While variable initialization (i.e., declaration and assignment together) is considered a statement, with no resulting value, variable assignment alone is also 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 very obscure and hard to read. Do so at your own peril.
The expression null
can
be assigned to any reference type. It has the meaning of “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 has multiple
meanings. It can retrieve the value of an
instance variable (of some object) or
a static variable (of some class). It
can also specify a method to be invoked on an object or class. Using
the dot (.
) to access a variable in an object is
an expression that results in the value of the variable accessed.
This can be either a numeric type or a reference type:
int i; String s; i = myObject.length; s = myObject.name;
A reference-type expression can be used in further evaluations, by selecting variables or calling methods within it:
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.
A method invocation is essentially a function call: an expression that results in a value. The value’s type is the return type of the method. Thus far, we have seen methods invoked by name:
System.out.println( "Hello World..." ); int myLength = myString.length( );
Selecting which method to invoke is more complicated than it appears, because Java allows method overloading and overriding; the details are discussed in Chapter 5.
Like the result of any expression, the result of a method invocation can be used in further evaluations, as we saw earlier. You can allocate intermediate variables to make it absolutely clear what your code is doing, or you can opt for brevity where appropriate; it’s all a matter of coding style. The following are equivalent:
int initialLen = myObject.name.substring(5, 10).length( );
and:
String temp1 = myObject.name; String temp2 = temp1.substring(5, 10); int initialLen = temp2.length( );
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 which 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.
We’ll look at object creation in detail in Chapter 5. For now, just note that object creation is a
type of expression, and that the resulting object reference can be
used in general expressions. In fact, because the binding of
new
is “tighter” than that of dot
(.
), you can easily create a new object and invoke
a method in it, without assigning the object to a reference type
variable:
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.
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 run- time.
It is used to compare an object against a particular type.
instanceof
returns a boolean
value that indicates whether an object is an instance of a specified
class or a subclass of that class:
Boolean b; String str = "foo"; b = ( str instanceof String ); // true b = ( str instanceof Object ); // also true b = ( str instanceof Date ); // false, not a Date or subclass
instanceof
also correctly reports if the object is
of the type of an array or a specified interface:
if ( foo instanceof byte[] ) ...
It is also important to note that the value
null
is not considered an instance of any
object. So the following test will return false
,
no matter what the declared type of the variable:
String s = null; if ( s instanceof String ) // won't happen
Get Learning Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.