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 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.
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 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:
{
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 names—for
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.
We can define an if/else
clause as follows:
if
(
condition
)
statement
;
[
else
statement
;
]
(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
;
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.
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
(
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.
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
(
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
);
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
=
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 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
)
{
case
constantExpression
:
statement
;
[
case
constantExpression
:
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:
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 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:
enum
Size
{
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.
// usage
Size
size
=
...;
switch
(
size
)
{
case
Small:
...
case
Medium:
...
case
Large:
...
}
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
(
int
i
=
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:
int
i
,
j
;
// statement
i
=
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:
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"
);
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..."
);
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
(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:
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.
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 ); // 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
(
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
Get Learning Java, 4th Edition 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.