Friday, December 7, 2007

Evaluation Order of Operands

In order to understand the result returned by an operator, it is important to understand the evaluation order of its operands. Java states that the operands of operators are evaluated from left to right.

Java guarantees that all operands of an operator are fully evaluated before the operator is applied. The only exceptions are the short-circuit conditional operators &&, ||, and ?:.

In the case of a binary operator, if the left-hand operand causes an exception (see Section 5.5, p. 181), the right-hand operand is not evaluated. The evaluation of the left-hand operand can have side effects that can influence the value of the right-hand operand. For example, in the following code:

int b = 10;
System.out.println((b=3) + b);

the value printed will be 6 and not 13. The evaluation proceeds as follows:


(b=3) + b
3 + b b is assigned the value 3
3 + 3
6

The evaluation order also respects any parentheses, and the precedence and associativity rules of operators.

Examples illustrating how the operand evaluation order influences the result returned by an operator, can be found in Sections 3.4 and 3.7.

Precedence and Associativity Rules for Operators

Precedence and associativity rules are necessary for deterministic evaluation of expressions. The operators are summarized in Table 3.1. They are discussed in subsequent sections in this chapter.

The following remarks apply to Table 3.1:

  • The operators are shown with decreasing precedence from the top of the table.

  • Operators within the same row have the same precedence.

  • Parentheses, ( ), can be used to override precedence and associativity.

  • The unary operators, which require one operand, include the postfix increment (++) and decrement (--) operators from the first row, all the prefix operators (+, -, ++, --, ~, !) in the second row, and the prefix operators (object creation operator new, cast operator (type)) in the third row.

  • The conditional operator (? :) is ternary, that is, requires three operands.

  • All operators not listed above as unary or ternary, are binary, that is, require two operands.

  • All binary operators, except for the relational and assignment operators, associate from left to right. The relational operators are nonassociative.

  • Except for unary postfix increment and decrement operators, all unary operators, all assignment operators, and the ternary conditional operator associate from right to left.

Table 3.1. Operator Summary

Postfix operators

[] . (parameters) expression++ expression--

Unary prefix operators

++expression --expression +expression -expression ~ !

Unary prefix creation and cast

new (type)

Multiplicative

* / %

Additive

+ -

Shift

<< >> >>>

Relational

< <= > >= instanceof

Equality

== !=

Bitwise/logical AND

&

Bitwise/logical XOR

^

Bitwise/logical OR

|

Conditional AND

&&

Conditional OR

||

Conditional

?:

Assignment

= += -= *= /= %= <<= >>= >>>= &= ^= |=

Precedence rules are used to determine which operator should be applied first if there are two operators with different precedence, and these follow each other in the expression. In such a case, the operator with the highest precedence is applied first.

2 + 3 * 4 is evaluated as 2 + (3 * 4) (with the result 14) since * has higher precedence than +.

Associativity rules are used to determine which operator should be applied first if there are two operators with the same precedence, and these follow each other in the expression.

Left associativity implies grouping from left to right:

1 + 2 - 3 is interpreted as ((1 + 2) - 3), since the binary operators + and - both have same precedence and left associativity.

Right associativity implies grouping from right to left:

- - 4 is interpreted as (- (- 4)) (with the result 4), since the unary operator - has right associativity.

The precedence and associativity rules together determine the evaluation order of the operators.

The main() Method

The mechanics of compiling and running Java applications using the Java 2 SDK are outlined in Section 1.10. The Java interpreter executes a method called main in the class specified on the command line. Any class can have a main() method, but only the main() method of the class specified to the Java interpreter is executed to start a Java application.

The main() method must have public accessibility so that the interpreter can call it (see Section 4.9, p. 138). It is a static method belonging to the class, so that no object of the class is required to start the execution (see Section 4.10, p. 144). It does not return a value, that is, it is declared void (see Section 5.4, p. 176). It always has an array of String objects as its only formal parameter. This array contains any arguments passed to the program on the command line (see Section 3.23, p. 95). All this adds up to the following definition of the main() method:

public static void main(String[] args) {
// ...
}

The above requirements do not exclude specification of additional modifiers (see Section 4.10, p. 144) or any throws clause (see Section 5.9, p. 201). The main() method can also be overloaded like any other method (see p. 116). The Java interpreter ensures that the main() method, that complies with the above definition is the starting point of the program execution.

Java Source File Structure

The structure of a skeletal Java source file is depicted in Figure 2.2. A Java source file can have the following elements that, if present, must be specified in the following order:

  1. An optional package declaration to specify a package name. Packages are discussed in Section 4.6.

  2. Zero or more import declarations. Since import declarations introduce class and interface names in the source code, they must be placed before any type declarations. The import statement is discussed in Section 4.6.

  3. Any number of top-level class and interface declarations. Since these declarations belong to the same package, they are said to be defined at the top level, which is the package level.

    The classes and interfaces can be defined in any order. Class and interface declarations are collectively known as type declarations. Technically, a source file need not have any such definitions, but that is hardly useful.

    The Java 2 SDK imposes the restriction that at the most one public class definition per source file can be defined. If a public class is defined, the file name must match this public class. If the public class name is NewApp, then the file name must be NewApp.java.

    Classes are discussed in Section 4.2, and interfaces are discussed in Section 6.4.


Note that except for the package and the import statements, all code is encapsulated in classes and interfaces. No such restriction applies to comments and white space.

Primitive Data Types
Figure 2.1 gives an overview of the primitive data types in Java.

Figure 2.1. Primitive Data Types in Java


Primitive data types in Java can be divided into three main categories:

Integral types— represent signed integers (byte, short, int, long) and unsigned character values (char)

Floating-point types (float, double)— represent fractional signed numbers

Boolean type (boolean)— represent logical values

Primitive data values are not objects. Each primitive data type defines the range of values in the data type, and operations on these values are defined by special operators in the language (see Chapter 3).

Each primitive data type also has a corresponding wrapper class that can be used to represent a primitive value as an object. Wrapper classes are discussed in Section 10.3.

Integer Types
Table 2.9. Range of Integer Values Data Type
Width (bits)
Minimum value MIN_VALUE
Maximum value MAX_VALUE

byte
8
-27 (-128)
27-1 (+127)

short
16
-215 (-32768)
215-1 (+32767)

int
32
-231 (-2147483648)
231-1 (+2147483647)

long
64
-263 (-9223372036854775808L)
263-1 (+9223372036854775807L)



Integer data types are byte, short, int, and long (see Table 2.9). Their values are signed integers represented by 2's complement (see Section G.4, p. 598).

Character Type
Table 2.10. Range of Character Values Data Type
Width (bits)
Minimum Unicode value
Maximum Unicode value

char
16
0x0 (\u0000)
0xffff (\uffff)



Characters are represented by the data type char (see Table 2.10). Their values are unsigned integers that denote all the 65536 (216) characters in the 16-bit Unicode character set. This set includes letters, digits, and special characters.

The first 128 characters of the Unicode set are the same as the 128 characters of the 7-bit ASCII character set, and the first 256 characters of the Unicode set correspond to the 256 characters of the 8-bit ISO Latin-1 character set.

Floating-point Types
Table 2.11. Range of Floating-point Values Data Type
Width (bits)
Minimum Positive Value MIN_VALUE
Maximum Positive Value MAX_VALUE

float
32
1.401298464324817E-45f
3.402823476638528860e+38f

double
64
4.94065645841246544e-324
1.79769313486231570e+308



Floating-point numbers are represented by the float and double data types.

Floating-point numbers conform to the IEEE 754-1985 binary floating-point standard. Table 2.11 shows the range of values for positive floating-point numbers, but these apply equally to negative floating-point numbers with the '-' sign as prefix. Zero can be either 0.0 or -0.0.

Since the size for representation is finite, certain floating-point numbers can only be represented as approximations. For example, the value of the expression (1.0/3.0) is represented as an approximation due to the finite number of bits used.

Boolean Type
Table 2.12. Boolean Values Data Type
Width
True Value Literal
False Value Literal

boolean
not applicable
true
false



The data type boolean represents the two logical values denoted by the literals true and false (see Table 2.12).

Boolean values are produced by all relational (see Section 3.9), conditional (see Section 3.12) and boolean logical operators (see Section 3.11), and are primarily used to govern the flow of control during program execution.

Table 2.13 summarizes the pertinent facts about the primitive data types: their width or size, which indicates the number of the bits required to store a primitive value; their range (of legal values), which is specified by the minimum and the maximum values permissible; and the name of the corresponding wrapper class.

Table 2.13. Summary of Primitive Data Types Data Type
Width (bits)
Minimum Value, Maximum Value
Wrapper Class

boolean
not applicable
true, false (no ordering implied)
Boolean

byte
8
-27, 27-1
Byte

short
16
-215, 215-1
Short

char
16
0x0, 0xffff
Character

int
32
-231, 231-1
Integer

long
64
-263, 263-1
Long

float
32
±1.40129846432481707e-45f, ±3.402823476638528860e+38f
Float

double
64
\'b14.94065645841246544e-324, \'b11.79769313486231570e+308
Double

Java Programs

A Java program is a collection of one or more classes, with one of them containing the program's execution starting point. A Java source file can contain more than one class definition. The Java 2 SDK enforces the rule that at the most one class in the source file has public accessibility. The name of the source file is comprised of the name of this public class with .java as extension. Each class definition in a source file is compiled into a separate class file, containing Java byte code. The name of this file is comprised of the name of the class with .class as an extension. All programs must be compiled before they can be run. The Java 2 SDK provides tools for this purpose, as explained in 1.10 Sample Java Application.

Sample Java Application

An application is what is normally called a program: source code that is compiled and directly executed. In order to create an application in Java, the program must have a class that defines a method called main. The main() method in the class is the starting point for the execution of any application.

Essential Elements of a Java Application

Example 1.4 is an example of an application in which a client uses the CharStack class to reverse a string of characters.

Example 1.4 An Application
// Source Filename: Client.java
public class Client {

public static void main(String[] args) {

// Create a stack
CharStack stack = new CharStack(40);

// Create a string to push on the stack
String str = "!no tis ot nuf era skcatS";
int length = str.length();
System.out.println("Original string: " + str);

// Push the string char by char onto the stack
for (int i = 0; i stack.push(str.charAt(i));
}

System.out.print("Reversed string: ");
// Pop and print each char from the stack
while (!stack.isEmpty()) {
System.out.print(stack.pop());
}
System.out.println();
}
}


}

Output from the program:

Original string: !no tis ot nuf era skcatS
Reversed string: Stacks are fun to sit on!

The public class Client defines a method with the name main. To start the application, the main() method in this public class is invoked by the Java interpreter, also called the Java Virtual Machine (JVM). The main() method should be declared as follows:

public static void main(String[] args) {
// ...
}

The main() method has public accessibility, that is, it is accessible from any class. The keyword static means the method belongs to the class. The keyword void means the method does not return any value. The parameter list, String[] args, is an array of strings used to pass information to the main() method when the application is started.

Compiling and Running an Application

Java source files can be compiled using the Java compiler tool javac, which is part of the Java 2 SDK.

The source file Client.java contains the definition of the Client class. The source file can be compiled by giving the following input at the command line:

>javac Client.java

This creates the class file, Client.class, containing the Java byte code for the Client class. The Client class uses the CharStack class, and if the file CharStack.class does not already exist, the compiler will also compile the source file CharStack.java.

Compiled classes can be executed by the Java interpreter java, which is also part of the Java 2 SDK. Example 1.4 can be run by giving the following input at the command line:

>java Client

Note that only the name of the class is specified, resulting in starting the execution of the main() method from the specified class. The application in Example 1.4 terminates when the execution of the main() method is completed.

Aggregation

When building new classes from existing classes using aggregation, a composite object is built from other constituent objects that are its parts.

Java supports aggregation of objects by reference, since objects cannot contain other objects explicitly. The fields can only contain values of primitive data types or references to other objects. Each object of the CharStack class has a field to store the reference to an array object that holds the characters. Each stack object also has a field of primitive data type int to store the index value that denotes the top of stack. This is reflected in the definition of the CharStack class, which contains an instance variable for each of these parts. In contrast to the constituent objects whose references are stored in fields, the values of primitive data types are stored in the fields of the composite object. The aggregation relationship is depicted by the UML showing that each object of the CharStack class will have one array object of char associated with it.