Chapter     2

Learning Language Fundamentals

Aspiring Android app developers need to understand the Java language in which an app’s source code is written. This chapter introduces you to this language by focusing on its fundamentals. Specifically, you’ll learn about application structure, comments, identifiers (and reserved words), types, variables, expressions (and literals), and statements.

Note  The American Standard Code for Information Interchange (ASCII) has traditionally been used to encode a program’s source code. Because ASCII is limited to the English language, Unicode (http://unicode.org/) was developed as a replacement. Unicode is a computing industry standard for consistently encoding, representing, and handling text that’s expressed in most of the world’s writing systems. Because Java supports Unicode, non-English-oriented symbols can be integrated into or accessed from Java source code; you’ll see examples in this chapter.

Learning Application Structure

Chapter 1 introduced you to three small Java applications. Each application exhibited a similar structure that I employ throughout this book. Before developing Java applications, you need to understand this structure, which Listing 2-1 presents. Throughout this chapter, I present code fragments that you can paste into this structure to create working applications.

Listing 2-1. Structuring a Java Application

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

An application is based on a class declaration. (I discuss classes in Chapter 3.) The declaration begins with a header consisting of public, followed by class, followed by X, where X is a placeholder for the actual name, for example, HelloWorld. The header is followed by a pair of braces ({ and }) that denote the class’s body.

Between these braces is a special method declaration (I discuss methods in Chapter 3), which defines the application’s entry point. It starts with a header that consists of public, followed by static, followed by void, followed by main, followed by (String[] args). A pair of braces follows this header and denotes the method’s body. The ... represents code that you specify to execute.

You can pass a sequence of arguments to the application when executing it at the command line. These string-based arguments are stored in the args array (a string is a character sequence delimited by double quote {"} characters). I introduce arrays later in this chapter and further discuss them in Chapter 3. There’s nothing special about args: I could choose another name for it, for example, arguments.

You must store this class declaration in a file whose name matches X and has a .java file extension. You would then compile the source code as follows:

javacX.java

X is a placeholder for the actual class name. Also, the “.java” file extension is mandatory.

Assuming that compilation succeeds, which results in a classfile named X.class being created, you would subsequently run the application as follows:

javaX

Replace X with the actual class name. Don’t specify the “.class” file extension.

If you need to pass command-line arguments to the application, specify them after the class name according to the following pattern:

javaX arg1 arg2 arg3...

Here, arg1, arg2, and arg3 are placeholders for three command-line arguments. The trailing ... signifies additional arguments (if any).

Finally, if you need to specify a sequence of words as a single argument, place these words between double quotes to prevent java from treating them as separate arguments, like so:

javaX"These words constitute a single argument."

Note  The word public that precedes class isn’t mandatory. When public isn’t specified, you don’t have to store the class declaration in a file whose name is the same as the class name. However, you still must specify the class name when running the application.

Learning Comments

Source code needs to be documented so that you (and any others who have to maintain it) can understand it, now and later. Source code should be documented while being written and whenever it’s modified. If these modifications impact existing documentation, the documentation must be updated so that it accurately explains the code.

Java provides the comment feature for embedding documentation in source code. When the source code is compiled, the Java compiler ignores all comments; no bytecodes are generated. Single-line, multiline, and Javadoc comments are supported.

Single-Line Comments

A single-line comment occupies all or part of a single line of source code. This comment begins with the // character sequence and continues with explanatory text. The compiler ignores everything from // to the end of the line in which // appears.

The following example presents a single-line comment:

System.out.println(Math.sqrt(10 * 10 + 20 * 20)); // Output distance from (0, 0) to (10, 20).

This example calculates the distance between the (0, 0) origin and the point (10, 20) in the Cartesian x/y plane. It uses the formula distance = square root(x*x+y*y), where x is 10 and y is 20, for this task. Java provides a Math class whose sqrt() method returns the square root of its single numeric argument. (I discuss Math in Chapter 7 and arguments in Chapter 3.)

Note  Single-line comments are useful for inserting short but meaningful explanations of source code into this code. Don’t use them to insert unhelpful documentation. For example, when declaring a variable, don’t insert a meaningless comment such as // This variable stores integer values.

Multiline Comments

A multiline comment, occupies one or more lines of source code. This comment begins with the /* character sequence, continues with explanatory text, and ends with the */ character sequence. Everything from /*  through */ is ignored by the compiler.

The following example demonstrates a multiline comment:

/*
   A year is a leap year when it's divisible by 400, or divisible by 4 and
   not also divisible by 100.
*/
System.out.println(year % 400 == 0 || (year % 4 == 0 && year % 100 != 0));

This example assumes the existence of an integer variable (discussed later) named year that stores an arbitrary four-digit year. It evaluates a complex expression (discussed later) that determines whether the year is leap or not. The expression returns true (leap year) or false (not leap year), which is output by System.out.println(). The multiline comment explains what constitutes a leap year.

Caution  You cannot place one multiline comment inside another. For example, /*/* Nesting multiline comments is illegal! */*/ isn’t a valid multiline comment.

Javadoc Comments

Java supports a third kind of comment that simplifies the specification of external HTML-based documentation. You’ll find this Javadoc comment feature helpful in the preparation of technical documentation for other developers who rely on your Java applications, libraries, and other Java-based software products.

A Javadoc comment occupies one or more lines of source code. This comment begins with the /** character sequence, continues with explanatory text, and ends with the */ character sequence. Everything from /** through */ is ignored by the compiler.

The following example demonstrates a Javadoc comment:

/**
 *  Application entry point
 *
 *  @param args array of command-line arguments passed to this method
 */
public static void main(String[] args)
{
   // TODO code application logic here
}

This example begins with a Javadoc comment that describes the main() method. Sandwiched between /** and */ is a description of the method and the @paramJavadoc tag (an @-prefixed instruction to the javadoc tool).

The following list identifies several commonly used tags:

  • @author identifies the source code’s author.
  • @deprecated identifies a source code entity (such as a method) that should no longer be used.
  • @param identifies one of a method’s parameters.
  • @see provides a see-also reference.
  • @since identifies the software release where the entity first originated.
  • @return identifies the kind of value that the method returns.
  • @throws documents an exception thrown from a method. (I discuss exceptions in Chapter 5.)

Listing 2-2 presents Chapter 1’s DumpArgs application source code with Javadoc comments that describe the DumpArgs class and its main() method.

Listing 2-2. Documenting an Application Class and Its main() Method

/**
   Dump all command-line arguments to standard output.
 
   @author Jeff Friesen
*/
 
public class DumpArgs
{
   /**
      Application entry point.
 
      @param args array of command-line arguments.
   */
 
   public static void main(String[] args)
   {
      System.out.println("Passed arguments:");
      for (int i = 0; i < args.length; i++)
         System.out.println(args[i]);
   }
}

You can extract these documentation comments into a set of HTML files by using the JDK’s javadoc tool as follows:

javadoc DumpArgs.java

javadoc responds by outputting the following messages:

Loading source file DumpArgs.java...
Constructing Javadoc information...
Standard Doclet version 1.7.0_06
Building tree for all the packages and classes...
Generating DumpArgs.html...
Generating package-frame.html...
Generating package-summary.html...
Generating package-tree.html...
Generating constant-values.html...
Building index for all the packages and classes...
Generating overview-tree.html...
Generating index-all.html...
Generating deprecated-list.html...
Building index for all classes...
Generating allclasses-frame.html...
Generating allclasses-noframe.html...
Generating index.html...
Generating help-doc.html...

It also generates several files, including the index.html documentation entry-point file. Point your browser to this file, and you should see a page similar to that shown in Figure 2-1.

9781430264545_Fig02-01.jpg

Figure 2-1. The entry-point page into DumpArg’s documentation describes this class

Note  Appendix B provides another (and a more extensive) example involving Javadoc comments and the javadoc tool.

Learning Identifiers

Source code entities such as classes and methods need to be named so that they can be referenced from elsewhere in the code. Java provides the identifiers feature for this purpose.

An identifier consists of letters (A-Z, a-z, or equivalent uppercase/lowercase letters in other human alphabets), digits (0-9 or equivalent digits in other human alphabets), connecting punctuation characters (such as the underscore), and currency symbols (such as the dollar sign, $). This name must begin with a letter, a currency symbol, or a connecting punctuation character; and its length cannot exceed the line in which it appears.

Examples of valid identifiers include the following:

  • π  (some editors might have problems with such symbols)
  • i
  • counter
  • j2
  • first$name
  • _for

Examples of invalid identifiers include the following:

  • 1name  (starts with a digit)
  • first#name  (# isn’t a valid identifier symbol)

Note  Java is a case-sensitive language, which means that identifiers differing only in case are considered separate identifiers. For example, temperature and Temperature are separate identifiers.

Almost any valid identifier can be chosen to name a class, method, or other source code entity. However, some identifiers are reserved for special purposes; they are known as reserved words. Java reserves the following identifiers:

abstract      assert        boolean       break         byte
case          catch         char          class         const
continue      default       do            double        else
enum          extends       false         final         finally
float         for           goto          if            implements
import        instanceof    int           interface     long
native        new           null          package       private
protected     public        return        short         static
strictfp      super         switch        synchronized  this
throw         throws        transient     true          try
void          volatile      while

The compiler outputs an error message when you attempt to use any of these reserved words outside of their usage contexts. Also, const and goto are not used by the Java language.

Listing 2-1 revealed several identifiers: public, class, X (a placeholder for an identifier), static, void, main, String, and args. Identifiers public, class, static, and void are also reserved words.

Note  Most of Java’s reserved words are also known as keywords. The three exceptions are false, null, and true, which are examples of literals (values specified verbatim).

Learning Types

Applications process data items, such as integers, floating-point values, characters, and strings. Data items are classified according to various characteristics. For example, integers are whole numbers without fractions. Also, a string is a sequence of characters that’s treated as a unit and possesses a length that identifies the number of characters in the sequence.

Java uses the term type to describe classifications of data items. A type identifies a set of data items (and their representation in memory) and a set of operations that transform these data items into other data items of that set. For example, the integer type identifies numeric values with no fractional parts and integer-oriented math operations, such as adding two integers to yield another integer.

Note  Java is a strongly typed language, which means that every expression, variable, and so on has a type known to the compiler. This capability helps the compiler detect type-related errors at compile time rather than having these errors manifest themselves at runtime. Expressions and variables are discussed later in this chapter.

Java recognizes primitive types, user-defined types, and array types. These types are defined in the following sections.

Primitive Types

A primitive type is a type that’s defined by the language and whose values are not objects. Java supports the Boolean, character, byte integer, short integer, integer, long integer, floating-point, and double precision floating-point primitive types. They are described in Table 2-1.

Table 2-1. Primitive Types

image

Table 2-1 describes each primitive type in terms of its reserved word, size, minimum value, and maximum value. A “--” entry indicates that the column in which it appears isn’t applicable to the primitive type described in that entry’s row.

The size column identifies the size of each primitive type in terms of the number of bits (binary digits; each digit is either 0 or 1) that a value of that type occupies in memory. Except for Boolean (whose size is implementation dependent; one Java implementation might store a Boolean value in a single bit, whereas another implementation might require an 8-bit byte for performance efficiency), each primitive type’s implementation has a specific size.

BINARY VS. DECIMAL

Computers process numbers encoded via the binary number system, which is a base-2 number system in which there are only 2 digits: 0 and 1. In contrast, people process numbers according to the decimal number system, which is a base-10 number system in which there are 10 digits: 0 through 9.

It’s occasionally necessary to convert between binary integers and decimal integers. To convert from decimal to binary, you repeatedly follow these steps.

  1. Set the integer quotient to the decimal integer.
  2. If the quotient is 0, then stop.
  3. Calculate quotient = quotient / 2. Output the remainder.
  4. Go to Step 2.

For example, suppose you want to calculate the binary equivalent of decimal integer 19. According to the previous steps, you would need to perform these calculations:

19 / 2 = 9 Remainder 1

9 / 2 = 4 Remainder 1

4 / 2 = 2 Remainder 0

2 / 2 = 1 Remainder 0

1 / 2 = 0 Remainder 1

The first remainder in this list refers to the least significant digit of the resulting binary number, which is 10011 in this example.

To convert from binary to decimal, process the integer’s digits from right to left. Each of these digits represents a power of 2 where the rightmost digit’s power is 0, the digit to its left has power 1, the digit to its left has power 2, and so on in an increasing sequence.

For example, 10011 corresponds to 1 x 2^4 + 0 x 2^3 + 0 x 2^2 + 1 x 2^1 + 1 x 2^0. Because any number to the power 0 equals 1, this expression equates to 2^4 + 2^1 + 1, which equates to 16 + 2 + 1, which equals 19.

The minimum value and maximum value columns identify the smallest and largest values that can be represented by each type. Except for Boolean (whose only values are true and false), each primitive type has a minimum value and a maximum value.

The minimum and maximum values of the character type refer to Unicode. Unicode 0 is shorthand for “the first Unicode code point;” a code point is an integer that represents a symbol (such as A) or a control character (such as newline or tab) or that combines with other code points to form a symbol.

Note  The character type’s limits imply that this type is unsigned (all character values are positive). In contrast, each numeric type is signed (it supports positive and negative values).

The minimum and maximum values of the byte integer, short integer, integer, and long integer types reveal that there is one more negative value than positive value (0 is typically not regarded as a positive value). The reason for this imbalance has to do with how integers are represented.

Java represents an integer value as a combination of a sign bit (the leftmost bit; 0 for a positive value and 1 for a negative value) and magnitude bits (all remaining bits to the right of the sign bit). When the sign bit is 0, the magnitude is stored directly. However, when the sign bit is 1, the magnitude is stored using twos-complement representation in which all 1s are flipped to 0s, all 0s are flipped to 1s, and 1 is added to the number behind the minus sign.

Twos-complement is used so that negative integers can naturally coexist with positive integers. For example, adding the representation of -1 to +1 yields 0. Figure 2-2 illustrates byte integer 2’s direct representation and byte integer -2’s twos-complement representation.

9781430264545_Fig02-02.jpg

Figure 2-2. The binary representation of two byte-integer values begins with a sign bit

The minimum and maximum values of the floating-point and double precision floating-point types refer to Institute of Electrical and Electronics Engineers (IEEE) 754, which is a standard for representing floating-point values in memory. Check out Wikipedia’s “IEEE 754-2008” entry (http://en.wikipedia.org/wiki/IEEE_754) to learn more about this standard.

Note  Developers who argue that Java should support objects only aren’t happy about the inclusion of primitive types in the language. However, Java was designed to include primitive types to overcome the speed and memory limitations of early 1990s-era devices, to which Java was originally targeted.

User-Defined Types

A user-defined type is a type that’s often used to model a real-world concept (such as a color or a bank account). The developer defines it using a class, an interface, an enum, or an annotation type; and its values are objects. (I discuss classes in Chapter 3, interfaces in Chapter 4, and enums and annotation types in Chapter 6.)

For example, you could create a Color class to model colors; its values could describe colors as red/green/blue components and its methods (see Chapter 3) could return these components.

Note  You can think of Chapter 1’s HelloWorld, DumpArgs, and EchoText classes as examples of user-defined types. However, these classes aren’t used to create objects but describe applications instead.

Java’s String class defines the string user-defined type and is a member of the standard class library. Its values describe character sequences, and its methods perform string operations such as joining two strings. Unlike other user-defined types, String enjoys language support for initializing String variables and joining strings into a single string. You’ll see examples of this later in the chapter.

User-defined types are also known as reference types because a variable of that type stores a reference (a memory address or some other identifier) to a region of memory that stores an object of that type. In contrast, variables of primitive types store the values directly; they don’t store references to these values.

Array Types

An array type is a special reference type that signifies an array, a region of memory that stores values in equal-size and contiguous slots, which are commonly referred to as elements. This type consists of the element type (a primitive type, user-defined type, or array type) and one or more pairs of square brackets that indicate the number of dimensions (extents). A single pair of brackets signifies a one-dimensional array (a vector), two pairs of brackets signify a two-dimensional array (a table), three pairs of brackets signify a one-dimensional array of two-dimensional arrays (a vector of tables), and so on. For example, int[] signifies a one-dimensional array (with int as the element type), and double[][] signifies a two-dimensional array (with double as the element type).

Learning Variables

Applications manipulate values that are stored in memory, which is symbolically represented in source code through the use of the variables feature. A variable is a named memory location that stores some type of value. A variable that stores a reference is often referred to as a reference variable.

Variables must be declared before they’re used. A declaration minimally consists of a type name, optionally followed by a sequence of square bracket pairs, followed by a name, optionally followed by a sequence of square bracket pairs, and terminated with a semicolon character (;). Consider the following examples:

int counter;         // Declare integer variable counter.
double temperature;  // Declare double precision floating-point variable temperature.
String firstName;    // Declare String variable firstName.
int[] ages;          // Declare one-dimensional integer array variable ages.
char gradeLetters[]; // Declare one-dimensional character array variable gradeLetters.
float[][] matrix;    // Declare two-dimensional floating-point array variable matrix.
double p;            // Declare double precision floating-point variable p.

No string is yet associated with firstName, and no arrays are yet associated with ages, gradeLetters, and matrix.

Note  Square brackets can appear after the type name or after the variable name, but not in both places. For example, the compiler reports an error when it encounters int[] x[];. It is common practice to place the square brackets after the type name (as in int[] ages;) instead of after the variable name (as in char gradeLetters[];), unless the array is being declared in a context such as int x, y[], z;.

You can declare multiple variables on one line by separating each variable from its predecessor with a comma, as demonstrated by the following example:

int x, y[], z;

This example declares three variables named x, y, and z. Each variable shares the same type, which happens to be integer. Unlike x and z, which store single integer values, y[] signifies a one-dimensional array whose element type is integer; each element stores an integer value. No array is yet associated with y.

The square brackets must appear after the variable name when the array is declared on the same line as the other variables. If you place the square brackets before the variable name, as in int x, []y, z;, the compiler reports an error. If you place the square brackets after the type name, as in int[] x, y, z;, all three variables signify one-dimensional arrays of integers.

Learning Expressions

The previously declared variables were not explicitly initialized to any values. As a result, they are either initialized to default values (such as 0 for int and 0.0 for double) or remain uninitialized, depending on the contexts in which they appear (declared within classes or declared within methods). In Chapter 3, I discuss variable contexts in terms of local variables, parameters, and fields.

Java provides the expressions feature for initializing variables and for other purposes. An expression is a combination of literals, variable names, method calls, and operators. At runtime, it evaluates to a value whose type is referred to as the expression’s type. If the expression is being assigned to a variable, this type must agree with the variable’s type; otherwise, the compiler reports an error.

Java recognizes simple expressions and compound expressions. These types are defined in the following sections.

Simple Expressions

A simple expression is a literal (a value expressed verbatim), the name of a variable (containing a value), or a method call (returning a value). Java supports several kinds of literals: string, Boolean true and false, character, integer, floating-point, and null.

Note  A method call that doesn’t return a value—the called method is known as a void method—is a special kind of simple expression, for example, System.out.println("Hello, World!");. This standalone expression cannot be assigned to a variable. Attempting to do so (as in int i = System.out.println("X");) causes the compiler to report an error.

A string literal consists of a sequence of Unicode characters surrounded by a pair of double quotes, for example, "The quick brown fox jumps over the lazy dog." It might also contain escape sequences, which are special syntax for representing certain printable and nonprintable characters that cannot otherwise appear in the literal. For example, "The quick brown "fox" jumps over the lazy dog." uses the " escape sequence to surround fox with double quotes.

Table 2-2 describes all supported escape sequences.

Table 2-2. Escape Sequences

Escape Syntax

Description

\

Backslash

"

Double quote

'

Single quote



Backspace

f

Form feed

Newline (also referred to as line feed)

Carriage return

Horizontal tab

Finally, a string literal might contain Unicode escape sequences, which are special syntax for representing Unicode characters. A Unicode escape sequence begins with u and continues with four hexadecimal digits (0-9, A-F, a-f) with no intervening space. For example, u0041 represents capital letter A, and u20ac represents the European Union’s euro currency symbol.

A Booleanliteral consists of reserved word true or reserved word false.

A character literal consists of a single Unicode character surrounded by a pair of single quotes ('A' is an example). You can also represent, as a character literal, an escape sequence (''', for example) or a Unicode escape sequence (such as 'u0041').

An integer literal consists of a sequence of digits. If the literal is to represent a long integer value, it must be suffixed with an uppercase L or lowercase l (L is easier to read). If there is no suffix, the literal represents a 32-bit integer (an int).

Integer literals can be specified in the decimal, hexadecimal, and octal formats:

  • The decimal format is the default format, for example, 127.
  • The hexadecimal format requires that the literal begin with 0x or 0X and continue with hexadecimal digits (0-9, A-F, a-f), for example, 0x7F.
  • The octal format requires that the literal be prefixed with 0 and continue with octal digits (0-7), for example, 0177.

A floating-point literal consists of an integer part, a decimal point (represented by the period [.]), a fractional part, an exponent (starting with letter E or e), and a type suffix (letter D, d, F, or f). Most parts are optional, but enough information must be present to differentiate the floating-point literal from an integer literal. Examples include 0.1 (double precision floating-point), 89F (floating-point), 600D (double precision floating-point), and 13.08E+23 (double precision floating-point).

Finally, the null literal is assigned to a reference variable to indicate that the variable doesn’t refer to an object.

Listing 2-3 presents a SimpleExpressions application that uses literals to initialize the previously presented variables.

Listing 2-3. Using Literals to Initialize Variables

public class SimpleExpressions
{
   public static void main(String[] args)
   {
      int counter = 10;
      double temperature = 98.6; // Assume Fahrenheit scale.
      String firstName = "Mark";
      int[] ages = { 52, 28, 93, 16 };
      char gradeLetters[] = { 'A', 'B', 'C', 'D', 'F' };
      float[][] matrix = { { 1.0F, 2.0F, 3.0F }, { 4.0F, 5.0F, 6.0F }};
      int x = 1, y[] = { 1, 2, 3 }, z = 3;
      double π = 3.14159;
      System.out.println(counter);
      System.out.println(temperature);
      System.out.println(ages.length);
      System.out.println(gradeLetters.length);
      System.out.println(matrix.length);
      System.out.println(x);
      System.out.println(y.length);
      System.out.println(z);
      System.out.println(π);
   }
}

The first example assigns 32-bit integer literal 10 to 32-bit integer variable counter. The second example assigns double precision floating-point literal 98.6 to double precision floating-point variable temperature. The third example assigns string literal "Mark" to String variable firstName.

The fourth through seventh examples use array initializers (such as { 52, 28, 93, 16 }) to initialize arrays that are assigned to the ages, gradeletters, matrix, and y array variables. An array initializer consists of a brace-and-comma-delimited list of expressions, which (as the matrix example shows) may be array initializers. The matrix example results in a table that looks like the following:

1.0F 2.0F 3.0F
4.0F 5.0F 6.0F

Each array variable is associated with a .length property that returns the number of elements in the array. For example, because ages contains 4 elements, ages.length returns 4. Similarly, because matrix contains 2 rows, matrix.length returns 2. I’ll have more to say about this property and also show you how to access array elements later in this chapter.

When you attempt to save this listing using an editor such as Windows Notepad, you’ll probably be prompted to change the encoding from extended ASCII to Unicode (unless you’ve previously done so); otherwise, the π symbol will be lost. To compile the saved source code, you’ll then need to include the -encoding Unicode option, as follows:

javac -encoding Unicode SimpleExpressions.java

You can then run this application via the following command line:

java SimpleExpressions

You should observe the following output:

10
98.6
4
5
2
1
3
3
3.14159

ORGANIZING VARIABLES IN MEMORY

Perhaps you’re curious about how variables are organized in memory. Figure 2-3 presents one possible high-level organization for the counter, ages, and matrix variables, along with the arrays assigned to ages and matrix.

9781430264545_Fig02-03.jpg

Figure 2-3. The counter variable stores a 4-byte integer value, whereas ages and matrix store 4-byte references to their respective arrays

Figure 2-3 reveals that each of counter, ages, and matrix is stored at a memory address (starting at a fictitious 20001000 value in this example) that’s divisible by 4 (each variable stores a 4-byte value); that counter’s 4-byte value is stored at this address; and that each of the ages and matrix 4-byte memory locations stores the 32-bit address of its respective array (64-bit addresses would most likely be used on 64-bit virtual machines). Also, a one-dimensional array is stored as a list of values, whereas a two-dimensional array is stored as a one-dimensional row array of addresses, where each address identifies a one-dimensional column array of values for that row.

Although Figure 2-3 implies that array addresses are stored in ages and matrix, which equates references with addresses, a Java implementation might equate references with handles (integer values that identify slots in a list). This alternative is presented in Figure 2-4 for ages and its referenced array.

9781430264545_Fig02-04.jpg

Figure 2-4. A handle is stored in ages, and the list entry identified by this handle stores the address of the associated array

Handles make it easy to move around regions of memory during garbage collection (discussed in Chapter 3). If multiple variables referenced the same array via the same address, each variable’s address value would have to be updated when the array was moved. However, if multiple variables referenced the array via the same handle, only the handle’s list entry would need to be updated. A downside to using handles is that accessing memory via these handles can be slower than directly accessing this memory via an address. Regardless of how references are implemented, this implementation detail is hidden from the Java developer to promote portability.

In addition to assigning literals to variables, you can also assign variables and the results of method calls to variables, like so:

int counter2 = counter;            // Assign previous counter variable value to counter2.
boolean isLeap = isLeapYear(2012); // Assign Boolean result of calling isLeapYear(2012) to isLeap.

These examples have assumed that only those expressions whose types are the same as the types of the variables that they are initializing can be assigned to those variables. However, under certain circumstances, it’s possible to assign an expression having a different type. For example, Java permits you to assign certain integer literals to short integer variables, as in short s = 20;, and assign a short integer expression to an integer variable, as in int i = s;.

Java permits the former assignment because 20 can be represented as a short integer (no information is lost). In contrast, Java would complain about short s = 40000; because integer literal 40000 cannot be represented as a short integer (32767 is the maximum positive integer that can be stored in a short integer variable). Java permits the latter assignment because no information is lost when Java converts from a type with a smaller set of values to a type with a wider set of values.

Java supports the following primitive-type conversions via widening conversion rules:

  • Byte integer to short integer, integer, long integer, floating-point, or double precision floating-point
  • Short integer to integer, long integer, floating-point, or double precision floating-point
  • Character to integer, long integer, floating-point, or double precision floating-point
  • Integer to long integer, floating-point, or double precision floating-point
  • Long integer to floating-point or double precision floating-point
  • Floating-point to double precision floating-point

Listing 2-4 presents a SimpleExpressions application that demonstrates these additional insights into simple expressions.

Listing 2-4. Learning More About Simple Expressions

public class SimpleExpressions
{
   public static void main(String[] args)
   {
      int counter = 30;
      int counter2 = counter;
      System.out.println(counter);
 
      short s = 20;
      System.out.println(s);
      int i = s;
      System.out.println(i);
 
      // short s2 = 40000; // possible loss of precision error
 
      int i2 = -1;
      double d = i2;
      System.out.println(d);
   }
}

This application demonstrates assigning one variable to another and assigning literal values to variables where the types don’t match. For example, in the double d = i2; example, a widening conversion rule converts the 32-bit integer value stored in variable i2 to a double precision floating-point value that’s assigned to variable d.

Compile Listing 2-4 as follows:

javac SimpleExpressions.java

Unlike in the previous SimpleExpressions application, it isn’t necessary to specifying -encoding Unicode because this listing contains no characters apart from those characters that can be represented by the extended ASCII character set (which is a subset of Unicode).

Run this application via the following command line:

java SimpleExpressions

You should observe the following output:

30
20
20
-1.0

Note  When converting from a smaller integer to a larger integer, Java copies the smaller integer’s sign bit into the extra bits of the larger integer.

In Chapter 4, I discuss the widening conversion rules for performing type conversions in the contexts of user-defined and array types.

Compound Expressions

A compound expression is a sequence of simple expressions and operators, where an operator (a sequence of instructions symbolically represented in source code) transforms its operand expression value(s) into another value. For example, -6 is a compound expression consisting of operator - and integer literal 6 as its operand. This expression transforms 6 into its negative equivalent. Similarly, x + 5 is a compound expression consisting of variable name x, integer literal 5, and operator + sandwiched between these operands. Variable x’s value is fetched and added to 5 when this expression is evaluated. The sum becomes the value of the expression.

Note  When x’s type is byte integer or short integer, this variable’s value is widened to an integer. However, when x’s type is long integer, floating-point, or double precision floating-point, 5 is widened to the appropriate type. The addition operation is performed after the widening conversion takes place.

Java supplies many operators, which are classified by the number of operands that they take. A unary operator takes only one operand (unary minus [-] is an example), a binary operator takes two operands (addition [+] is an example), and Java’s single ternary operator (conditional [?:]) takes three operands.

Operators are also classified as prefix, postfix, and infix. A prefix operator is a unary operator that precedes its operand (as in -6), a postfix operator is a unary operator that trails its operand (as in x++), and an infix operator is a binary or ternary operator sandwiched between the binary operator’s two or the ternary operator’s three operands (as in x + 5).

Table 2-3 presents all supported operators in terms of their symbols, descriptions, and precedence levels; I discuss the concept of precedence at the end of this section. Various operator descriptions refer to “integer type,” which is shorthand for specifying any of byte integer, short integer, integer, or long integer unless “integer type” is qualified as a 32-bit integer. Also, “numeric type” refers to any of these integer types along with floating-point and double precision floating-point.

Table 2-3. Operators

imageimageimageimageimage

Table 2-3’s operators can be classified as additive, array index, assignment, bitwise, cast, conditional, equality, logical, member access, method call, multiplicative, object creation, relational, shift, and unary minus/plus.

Additive Operators

The additive operators consist of addition (+), subtraction (-), postdecrement (--), postincrement (++), predecrement (--), preincrement (++), and string concatenation (+). Addition returns the sum of its operands (such as 6 + 4 returns 10), subtraction returns the difference between its operands (such as 6 - 4 returns 2 and 4 - 6 returns -2), postdecrement subtracts 1 from its variable operand and returns the variable’s prior value (such as x--), postincrement adds 1 to its variable operand and returns the variable’s prior value (such as x++), predecrement subtracts 1 from its variable operand and returns the variable’s new value (such as --x), preincrement adds 1 to its variable operand and returns the variable’s new value (such as ++x), and string concatenation merges its string operands and returns the merged string (such as "A" + "B" returns "AB").

The addition, subtraction, postdecrement, postincrement, predecrement, and preincrement operators can yield values that overflow or underflow the limits of the resulting value’s type. For example, adding two large positive 32-bit integer values can produce a value that cannot be represented as a 32-bit integer value. The result is said to overflow. Java doesn’t detect overflows and underflows.

Java provides a special widening conversion rule for use with string operands and the string concatenation operator. When either operand isn’t a string, the operand is converted to a string prior to string concatenation. For example, when presented with "A" + 5, the compiler generates code that first converts 5 to "5" and then performs the string concatenation operation, resulting in "A5".

Listing 2-5 presents a CompoundExpressions application that lets you start experimenting with the additive operators.

Listing 2-5. Experimenting with the Additive Operators

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      int age = 65;
      System.out.println(age + 32);
      System.out.println(++age);
      System.out.println(age--);
      System.out.println("A" + "B");
      System.out.println("A" + 5);
      short x = 32767;
      System.out.println(++x);
   }
}

Listing 2-5’s main() method first declares a 32-bit integer age variable that’s initialized to 32-bit integer value 65. It then outputs the result of an expression that adds age’s value to 32-bit integer value 32.

The preincrement and postdecrement operators are now demonstrated. First, preincrement adds 1 to age and the result is output. Then, age’s current value is output and this variable is then decremented via postdecrement. What age values do you think are output?

The next two expression examples demonstrate string concatenation. First, "B" is concatenated to "A" and the resulting AB is output. Then, 32-bit integer value 5 is converted to a one-character string consisting of character 5, which is then concatenated to "A". The resulting A5 is output.

At this point, overflow is demonstrated. First, a 16-bit short integer variable named x is declared and initialized to the largest positive short integer: 32767. The preincrement operator is then applied to x and the result (-32768) is output.

Compile Listing 2-5, as follows:

javac CompoundExpressions.java

Assuming successful compilation, execute the following command to run this application:

java CompoundExpressions

You should observe the following output:

97
66
66
AB
A5
-32768

Array Index Operator

The array index operator ([]) accesses an array element by presenting the location of that element as an integer index. This operator is specified after an array variable’s name, such as ages[0].

Indexes are relative to 0, which implies that ages[0] accesses the first element, whereas ages[6] accesses the seventh element. The index must be greater than or equal to 0 and less than the length of the array; otherwise, the virtual machine throws ArrayIndexOutOfBoundsException (consult Chapter 5 to learn about exceptions).

An array’s length is returned by appending “.length” to the array variable. For example, ages.length returns the length of (the number of elements in) the array that ages references. Similarly, matrix.length returns the number of row elements in the matrix two-dimensional array, whereas matrix[0].length returns the number of column elements assigned to the first row element of this array. (A two-dimensional array is essentially a one-dimensional row array of one-dimensional column arrays.)

Listing 2-6 presents a CompoundExpressions application that lets you start experimenting with the array index operator.

Listing 2-6. Experimenting with the Array Index Operator

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      int[] ages = { 52, 28, 93, 16 };
      char gradeLetters[] = { 'A', 'B', 'C', 'D', 'F' };
      float[][] matrix = { { 1.0F, 2.0F, 3.0F }, { 4.0F, 5.0F, 6.0F }};
      System.out.println(ages[0]);
      System.out.println(gradeLetters[2]);
      System.out.println(matrix[1][2]);
      System.out.println(ages['u0002']);
      ages[1] = 19;
      System.out.println(ages[1]);
   }
}

Listing 2-6’s main() method first declares and assigns arrays to variables ages, gradeLetters, and matrix. It then uses the array index operator to access the first element in the ages array (ages[0]), the third element in the gradeLetters array (gradeLetters[2]), and the third column element in the second row element of the matrix table array (matrix[1][2]).

Array indexes must be integer values. These values can be of byte integer, short integer, or integer type. However, they cannot be of long integer type because that could result in a loss of precision. The maximum number of elements that can be stored in an array is a bit less than the largest positive 32-bit integer; a long integer can be much larger than this value.

main() next demonstrates that you can also specify a character as an index value (ages['u0002']). This is legal because Java supports a character-to-integer widening rule; it converts a character value to an integer value, which is then used as an index into the array (ages[2]). However, you should avoid using characters as array indexes because they’re not intuitive and are potentially error prone. For example, what element is accessed by ages['A']? The answer is the 66th element; A’s Unicode value is 65 and the first array index is 0. Given the previous four-element ages array, ages['A'] would result in ArrayIndexOutOfBoundsException.

Finally, main() demonstrates that you can also use the array index operator to assign a value to an array element. In this case, integer 19 is stored in the second array element, which is subsequently accessed and output.

Compile Listing 2-6 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

52
C
6.0
93
19

Assignment Operators

The assignment operator (=) assigns an expression’s result to a variable (as in int x = 4;). The types of the variable and expression must agree; otherwise, the compiler reports an error.

Java also supports several compound assignment operators that perform a specific operation and assign its result to a variable. For example, in pennies += 50;, the += operator evaluates the numeric expression on its right (50) and adds the result to the contents of the variable on its left (pennies). The other compound assignment operators behave in a similar way.

Bitwise Operators

The bitwise operators consist of bitwise AND (&), bitwise complement (), bitwise exclusive OR (^), and bitwise inclusive OR (|). These operators are designed to work on the binary representations of their character or integral operands. Because this concept can be hard to understand if you haven’t previously worked with these operators in another language, check out Listing 2-7.

Listing 2-7. Experimenting with the Bitwise Operators

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      System.out.println(∼181);
      System.out.println(26 & 183);
      System.out.println(26 ^ 183);
      System.out.println(26 | 183);
   }
}

Compile Listing 2-7 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

-182
18
173
191

To make sense of these values, it helps to examine their 32-bit binary representations:

  • 181 corresponds to 00000000000000000000000010110101
  • 26 corresponds to 00000000000000000000000000011010
  • 183 corresponds to 00000000000000000000000010110111

When you specify ∼181, you end up flipping all of the bits: ∼00000000000000000000000010110101 results in 11111111111111111111111101001010. According to twos-complement representation, an integer whose leading bit is 1 is regarded to be negative, which is why ∼181 equates to -182.

The expression 26 & 183 can be represented in binary as follows:

00000000000000000000000000011010
&
00000000000000000000000010110111
--------------------------------
00000000000000000000000000010010

The resulting binary value equates to 18.

The expression 26 ^ 183 can be represented in binary as follows:

00000000000000000000000000011010
^
00000000000000000000000010110111
--------------------------------
00000000000000000000000010101101

The resulting binary value equates to 173.

The expression 26 | 183 can be represented in binary as follows:

00000000000000000000000000011010
|
00000000000000000000000010110111
--------------------------------
00000000000000000000000010111111

The resulting binary value equates to 191.

Cast Operator

The cast operator—(type)—attempts to convert the type of its operand to type. This operator exists because the compiler will not allow you to convert a value from one type to another in which information will be lost without specifying your intention to do so (via the cast operator). For example, when presented with short s = 1.65 + 3;, the compiler reports an error because attempting to convert a 64-bit double precision floating-point value to a 16-bit signed short integer results in the loss of the fraction .65, so s would contain 4 instead of 4.65.

Recognizing that information loss might not always be a problem, Java permits you to state your intention explicitly by casting to the target type. For example, short s = (short) 1.65 + 3; tells the compiler that you want 1.65 + 3 to be converted to a short integer, and that you realize that the fraction will disappear.

The following example provides another demonstration of the need for a cast operator:

char c = 'A';
byte b = c;

The compiler reports an error about loss of precision when it encounters byte b = c;. The reason is that c can represent any unsigned integer value from 0 through 65535, whereas b can only represent a signed integer value from -128 through +127. Even though 'A' equates to +65, which can fit within b’s range, c could just have easily been initialized to 'u0323', which wouldn’t fit.

The solution to this problem is to introduce a (byte) cast operator as follows, which causes the compiler to generate code to cast c’s character type to byte integer:

byte b = (byte) c;

Java supports the following primitive-type conversions via cast operators:

  • Byte integer to character
  • Short integer to byte integer or character
  • Character to byte integer or short integer
  • Integer to byte integer, short integer, or character
  • Long integer to byte integer, short integer, character, or integer
  • Floating-point to byte integer, short integer, character, integer, or long integer
  • Double precision floating-point to byte integer, short integer, character, integer, long integer, or floating-point

A cast operator isn’t always required when converting from more to fewer bits and where no data loss occurs. For example, when it encounters byte b = 100;, the compiler generates code that assigns integer 100 to byte integer variable b because 100 can easily fit into the 8-bit storage location assigned to this variable.

Listing 2-8 presents a CompoundExpressions application that lets you start experimenting with the cast operator.

Listing 2-8. Experimenting with the Cast Operator

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      short s = (short) 1.65 + 3;
      System.out.println(s);
 
      char c = 'A';
      byte b = (byte) c;
      System.out.println(b);
 
      b = 100;
      System.out.println(b);
 
      s = 'A';
      System.out.println(s);
 
      s = (short) 'uac00';
      System.out.println(s);
   }
}

Listing 2-8’s main() method first uses the (short) cast operator to narrow the double precision floating-point expression 1.65 + 3 to a 16-bit short integer that’s ultimately assigned to short integer variable s. After outputting s’s value (4), this method demonstrates the mandatory (byte) cast operator when converting from a 16-bit unsigned character type to an 8-bit signed byte integer type.

As previously mentioned, the (byte) cast operator isn’t always required. For example, when assigning a 32-bit signed integer in the range of -128 through +127 to a byte integer variable, (byte) can be omitted because no information will be lost. Assigning 100 to b demonstrates this scenario.

In a similar way, various 16-bit unsigned character values (such as 'A') can be assigned to a 16-bit signed short integer variable without loss of information, and so the (short) cast operator can be avoided. However, other 16-bit unsigned character values don’t fit into this range and must be cast to a short integer before assignment ('uac00', for example).

Compile Listing 2-8 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

4
65
100
65
-21504

Conditional Operators

The conditional operators consist of conditional AND (&&), conditional OR (||), and conditional (?:). The first two operators always evaluate their left operand (a Boolean expression that evaluates to true or false) and conditionally evaluate their right operand (another Boolean expression). The third operator evaluates one of two operands based on a third Boolean operand.

Conditional AND always evaluates its left operand and evaluates its right operand only when its left operand evaluates to true. For example, age > 64 && stillWorking first evaluates age > 64. If this subexpression is true, stillWorking is evaluated, and its true or false value (stillWorking is a Boolean variable) serves as the value of the overall expression. If age > 64 is false, stillWorking isn’t evaluated.

Conditional OR always evaluates its left operand and evaluates its right operand only when its left operand evaluates to false. For example, value < 20 || value > 40 first evaluates value < 20. If this subexpression is false, value > 40 is evaluated, and its true or false value serves as the overall expression’s value. If value < 20 is true, value > 40 isn’t evaluated.

Conditional AND and conditional OR boost performance by preventing the unnecessary evaluation of subexpressions, which is known as short-circuiting. For example, if its left operand is false, there is no way that conditional AND’s right operand can change the fact that the overall expression will evaluate to false.

If you aren’t careful, short-circuiting can prevent side effects (the results of subexpressions that persist after the subexpressions have been evaluated) from executing. For example, age > 64 && ++numEmployees > 5 increments numEmployees for only those employees whose ages are greater than 64. Incrementing numEmployees is an example of a side effect because the value in numEmployees persists after the subexpression ++numEmployees > 5 has evaluated.

The conditional operator is useful for making a decision by evaluating and returning one of two operands based upon the value of a third operand. The following example converts a Boolean value to its integer equivalent (1 for true and 0 for false):

boolean b = true;
int i = b ? 1 : 0; // 1 assigns to i

Listing 2-9 presents a CompoundExpressions application that lets you start experimenting with the conditional operators.

Listing 2-9. Experimenting with the Conditional Operators

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      int age = 65;
      boolean stillWorking = true;
      System.out.println(age > 64 && stillWorking);
      age--;
      System.out.println(age > 64 && stillWorking);
      int value = 30;
      System.out.println(value < 20 || value > 40);
      value = 10;
      System.out.println(value < 20 || value > 40);
      int numEmployees = 6;
      age = 65;
      System.out.println(age > 64 && ++numEmployees > 5);
      System.out.println("numEmployees = " + numEmployees);
      age = 63;
      System.out.println(age > 64 && ++numEmployees > 5);
      System.out.println("numEmployees = " + numEmployees);
      boolean b = true;
      int i = b ? 1 : 0; // 1 assigns to i
      System.out.println("i = " + i);
      b = false;
      i = b ? 1 : 0; // 0 assigns to i
      System.out.println("i = " + i);
   }
}

Compile Listing 2-9 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

true
false
false
true
true
numEmployees = 7
false
numEmployees = 7
i = 1
i = 0

Equality Operators

The equality operators consist of equality (==) and inequality (!=). These operators compare their operands to determine whether they are equal or unequal. The former operator returns true when equal and the latter operator returns true when unequal. For example, each of 2 == 2 and 2 != 3 evaluates to true, whereas each of 2 == 4  and 4 != 4 evaluates to false.

You have to be careful when comparing floating-point expressions for equality. For example, what does System.out.println(0.3 == 0.1 + 0.1 + 0.1); output? If you guessed that the output is true, you would be wrong. Instead, the output is false.

The reason for this nonintuitive output is that 0.1 cannot be represented exactly in memory. The error compounds when this value is added to itself. For example, if you executed System.out.println(0.1 + 0.1 + 0.1);, you would observe 0.30000000000000004, which doesn’t equal 0.3.

When it comes to object operands (I discuss objects in Chapter 3), these operators don’t compare their contents. Instead, object references are compared. For example, "abc" == "xyz" doesn’t compare a with x. Because string literals are really String objects (Chapter 7 discusses the String class), == compares the references to these objects.

Logical Operators

The logical operators consist of logical AND (&), logical complement (!), logical exclusive OR (^), and logical inclusive OR (|). Although these operators are similar to their bitwise counterparts, whose operands must be integer/character, the operands passed to the logical operators must be Boolean. For example, !false returns true. Also, when confronted with age > 64 & stillWorking, logical AND evaluates both subexpressions; there’s no short-circuiting. This same pattern holds for logical exclusive OR and logical inclusive OR.

Listing 2-10 presents a CompoundExpressions application that lets you start experimenting with the logical operators.

Listing 2-10. Experimenting with the Logical Operators

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      System.out.println(!false);
      int age = 65;
      boolean stillWorking = true;
      System.out.println(age > 64 & stillWorking);
      System.out.println();
      
      boolean result = true & true;
      System.out.println("true & true: " + result);
      result = true & false;
      System.out.println("true & false: " + result);
      result = false & true;
      System.out.println("false & true: " + result);
      result = false & false;
      System.out.println("false & false: " + result);
      System.out.println();
 
      result = true | true;
      System.out.println("true | true: " + result);
      result = true | false;
      System.out.println("true | false: " + result);
      result = false | true;
      System.out.println("false | true: " + result);
      result = false | false;
      System.out.println("false | false: " + result);
      System.out.println();
 
      result = true ^ true;
      System.out.println("true ^ true: " + result);
      result = true ^ false;
      System.out.println("true ^ false: " + result);
      result = false ^ true;
      System.out.println("false ^ true: " + result);
      result = false ^ false;
      System.out.println("false ^ false: " + result);
      System.out.println();
 
      int numEmployees = 1;
      age = 65;
      System.out.println(age > 64 & ++numEmployees > 2);
      System.out.println(numEmployees);
   }
}

After outputting the results of the !false and age > 64 & stillWorking expressions, main() outputs three truth tables that show how logical AND, logical inclusive OR, and logical exclusive OR behave when their operands are true or false. It then demonstrates that short-circuiting is ignored by incrementing numEmployees when age > 64 returns true.

Compile Listing 2-10 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

true
true
 
true & true: true
true & false: false
false & true: false
false & false: false
 
true | true: true
true | false: true
false | true: true
false | false: false
 
true ^ true: false
true ^ false: true
false ^ true: true
false ^ false: false
 
false
2

Member Access Operator

The member access operator (.) is used to access a class’s members or an object’s members. For example, String s = "Hello"; int len = s.length(); returns the length of the string assigned to variable s. It does so by calling the length() method member of the String class. In Chapter 3, I discuss member access in more detail.

Arrays are special objects that have a single length member. When you specify an array variable followed by the member access operator, followed by length, the resulting expression returns the number of elements in the array as a 32-bit integer. For example, ages.length returns the length of (the number of elements in) the array that ages references.

Method Call Operator

The method call operator () is used to signify that a method (discussed in Chapter 3) is being called. Also, it identifies the number, order, and types of arguments that are passed to the method to be picked up by the method’s parameters. For example, in the System.out.println("Hello"); method call, () signifies that a method named println is being called with one argument: "Hello".

Multiplicative Operators

The multiplicative operators consist of multiplication (*), division (/), and remainder (%). Multiplication returns the product of its operands (such as 6 * 4 returns 24), division returns the quotient of dividing its left operand by its right operand (such as 6 / 4 returns 1), and remainder returns the remainder of dividing its left operand by its right operand (such as 6 % 4 returns 2).

The multiplication, division, and remainder operators can yield values that overflow or underflow the limits of the resulting value’s type. For example, multiplying two large positive 32-bit integer values can produce a value that cannot be represented as a 32-bit integer value. The result is said to overflow. Java doesn’t detect overflows and underflows.

Dividing a numeric value by 0 (via the division or remainder operator) also results in interesting behavior. Dividing an integer value by integer 0 causes the operator to throw an ArithmeticException object (Chapter 5 covers exceptions). Dividing a floating-point/double precision floating-point value by 0 causes the operator to return +infinity or -infinity, depending on whether the dividend is positive or negative. Finally, dividing floating-point 0 by 0 causes the operator to return NaN (Not a Number).

Listing 2-11 presents a CompoundExpressions application that lets you start experimenting with the multiplicative operators.

Listing 2-11. Experimenting with the Multiplicative Operators

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      short age = 65;
      System.out.println(age * 1000);
      System.out.println(1.0 / 0.0);
      System.out.println(10 % 4);
      System.out.println(3 / 0);
   }
}

Compile Listing 2-11 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

65000
Infinity
2
Exception in thread "main" java.lang.ArithmeticException: / by zero
        at CompoundExpressions.main(CompoundExpressions.java:9)

Object Creation Operator

The object creation operator (new) creates an object from a class, and it also creates an array from an initializer. These topics are discussed in Chapter 3.

Relational Operators

The relational operators consist of greater than (>), greater than or equal to (>=), less than (<), less than or equal to (<=), and type checking (instanceof). The former four operators compare their operands and return true when the left operand is (respectively) greater than, greater than or equal to, less than, or less than or equal to the right operand. For example, each of 5.0 > 3, 2 >= 2, 16.1 < 303.3, and 54.0 <= 54.0 evaluates to true.

The type-checking operator is used to determine if an object belongs to a specific type, returning true when this is the case. For example, "abc" instanceof String returns true because "abc" is a String object. I discuss this operator more fully in Chapter 4.

Shift Operators

The shift operators consist of left shift (<<), signed right shift (>>), and unsigned right shift (>>>). Left shift shifts the binary representation of its left operand leftward by the number of positions specified by its right operand. Each shift is equivalent to multiplying by 2. For example, 2 << 3 shifts 2’s binary representation left by three positions; the result is equivalent to multiplying 2 by 8.

Each of signed and unsigned right shift shifts the binary representation of its left operand rightward by the number of positions specified by its right operand. Each shift is equivalent to dividing by 2. For example, 16 >> 3 shifts 16’s binary representation right by three positions; the result is equivalent to dividing 16 by 8.

The difference between signed and unsigned right shift is what happens to the sign bit during the shift. Signed right shift includes the sign bit in the shift, whereas unsigned right shift ignores the sign bit. As a result, signed right shift preserves negative numbers, but unsigned right shift doesn’t. For example, -4 >> 1 (the equivalent of -4 / 2) evaluates to -2, whereas –4 >>> 1 evaluates to 2147483646.

Listing 2-12 presents a CompoundExpressions application that lets you start experimenting with the shift operators.

Listing 2-12. Experimenting with the Shift Operators

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      System.out.println(2 << 3);
      System.out.println(16 >> 3);
      System.out.println(-4 >> 1);
      System.out.println(-4 >>> 1);
   }
}

Compile Listing 2-12 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

16
2
-2
2147483646

Tip  The shift operators are faster than multiplying or dividing by powers of 2.

Unary Minus/Plus Operators

Unary minus (-) and unary plus (+) are the simplest of all operators. Unary minus returns the negative of its operand (such as -5 returns -5 and --5 returns 5), whereas unary plus returns its operand verbatim (such as +5 returns 5 and +-5 returns -5). Unary plus is not commonly used, but it is presented for completeness.

Precedence and Associativity

When evaluating a compound expression, Java takes each operator’s precedence (level of importance) into account to ensure that the expression evaluates as expected. For example, when presented with the expression 60 + 3 * 6, you expect multiplication to be performed before addition (multiplication has higher precedence than addition) and the final result to be 78. You don’t expect addition to occur first, yielding a result of 378.

Note  Table 2-3’s rightmost column presents a value that indicates an operator’s precedence: the higher the number, the higher the precedence. For example, addition’s precedence level is 10 and multiplication’s precedence level is 11, which means that multiplication is performed before addition.

Precedence can be circumvented by introducing open and close parentheses, ( and ), into the expression, where the innermost pair of nested parentheses is evaluated first. For example, evaluating 2 * ((60 + 3) * 6) results in (60 + 3) being evaluated first, (60 + 3) * 6 being evaluated next, and the overall expression being evaluated last. Similarly, in the expression 60 / (3 - 6), subtraction is performed before division.

During evaluation, operators with the same precedence level (such as addition and subtraction, which both have level 10) are processed according to their associativity (a property that determines how operators having the same precedence are grouped when parentheses are missing).

For example, expression 9 * 4 / 3 is evaluated as if it was (9 * 4) / 3 because * and / are left-to-right associative operators. In contrast, expression x = y = z = 100 is evaluated as if it was x = (y = (z = 100)), where 100 is assigned to z, z’s new value (100) is assigned to y, and y’s new value (100) is assigned to x because = is a right-to-left associative operator.

Most of Java’s operators are left-to-right associative. Right-to-left associative operators include assignment, bitwise complement, cast, compound assignment, conditional, logical complement, object creation, predecrement, preincrement, unary minus, and unary plus.

Listing 2-13 presents a CompoundExpressions application that lets you start experimenting with precedence and associativity.

Listing 2-13. Experimenting with Precedence and Associativity

public class CompoundExpressions
{
   public static void main(String[] args)
   {
      System.out.println(60 + 3 * 6);
      System.out.println(2 * ((60 + 3) * 6));
      System.out.println(9 * 4 / 3);
      int x, y, z;
      x = y = z = 100;
      System.out.println(x);
      System.out.println(y);
      System.out.println(z);
 
      int i = 0x12345678;
      byte b = (byte) (i & 255);
      System.out.println(b);
      System.out.println("b == 0x78: " + (b == 0x78));
      b = (byte) ((i >> 8) & 255);
      System.out.println(b);
      System.out.println("b == 0x56: " + (b == 0x56));
      b = (byte) ((i >> 16) & 255);
      System.out.println(b);
      System.out.println("b == 0x34: " + (b == 0x34));
      b = (byte) ((i >> 24) & 255);
      System.out.println(b);
      System.out.println("b == 0x12: " + (b == 0x12));
  }
}

You’ll often find yourself needing to use open and close parentheses to change an expression’s evaluation order. For example, consider the second part of the main() method, which extracts each of the 4 bytes in the 32-bit value assigned to integer variable i and outputs this byte.

After processing the declaration and initialization of 32-bit integer variable i (int i = 0x12345678;), the compiler encounters byte b = (byte) (i & 255);. It generates bytecode that first evaluates expression i & 255, which returns a 32-bit result, passes this result to the (byte) cast operator to convert it to an 8-bit result, and assigns the 8-bit result to 8-bit byte integer variable b.

Suppose the parentheses were absent, resulting in byte b = (byte) i & 255;. The compiler would then report an error about loss of precision because it interprets this expression as follows.

  1. Cast variable i to an 8-bit byte integer. The cast operator is a unary operator that takes only one operand. Also, cast has higher precedence (12) than bitwise AND (6) so cast is evaluated first.
  2. Widen i to a 32-bit integer as the left operand of bitwise AND (&). Operand 255 is already a 32-bit integer.
  3. Apply bitwise AND to these operands. The result is a 32-bit integer.
  4. Attempt to assign the 32-bit integer result to 8-bit byte integer variable b.

The 32-bit integer result can vary from 0 through 255. However, the largest positive integer that b can store is 127. If the result ranges from 128 through 255, it will be converted to -1 through -128. This is a loss of precision and so the compiler reports an error.

Another example where main() uses open and close parentheses to change evaluation order is System.out.println("b == 0x78: " + (b == 0x78));. When the parentheses are missing, the compiler reports an “incomparable types: String and int” error. It does so because string concatenation has higher precedence (10) than equality (7). As a result, the compiler interprets the expression (without parentheses) as follows.

  1. Convert b’s value to a string.
  2. Concatenate this string to "b == 0x78: ".
  3. Compare the resulting string with 32-bit integer 0x78 for equality, which is illegal.

Compile Listing 2-13 (javac CompoundExpressions.java) and run this application (java CompoundExpressions). You should observe the following output:

78
756
12
100
100
100
120
b == 0x78: true
86
b == 0x56: true
52
b == 0x34: true
18
b == 0x12: true

Note  Unlike languages such as C++, Java doesn’t let you overload operators. However, Java overloads the + (addition and string concatenation), ++ (preincrement and postincrement), and -- (predecrement and postdecrement) operator symbols.

Learning Statements

Statements are the workhorses of a program. They assign values to variables, control a program’s flow by making decisions and/or repeatedly executing other statements, and perform other tasks. A statement can be expressed as a simple statement or as a compound statement.

  • A simplestatement is a single standalone source code instruction for performing some task; it’s terminated with a semicolon.
  • A compoundstatement is a (possibly empty) sequence of simple and other compound statements sandwiched between open and close brace delimiters; a delimiter is a character that marks the beginning or end of some section. A method body (such as the main() method’s body) is an example. Compound statements can appear wherever simple statements appear and are alternatively referred to as blocks.

In this section I introduce you to many of Java’s statements. Additional statements are covered in later chapters. For example, in Chapter 3 I discuss the return statement.

Assignment Statements

The assignment statement assigns a value to a variable. This statement begins with a variable name, continues with the assignment operator (=) or a compound assignment operator (such as +=), and concludes with an assignment-compatible expression and a semicolon. The following are three examples:

x = 10;
ages[0] = 25;
counter += 10;

The first example assigns integer 10 to variable x, which is presumably of type integer as well. The second example assigns integer 25 to the first element of the ages array. The third example adds 10 to the value stored in counter and stores the sum in counter.

Note  Initializing a variable in the variable’s declaration (such as int counter = 1;) can be thought of as a special form of the assignment statement.

Decision Statements

The previously described conditional operator (?:) is useful for choosing between two expressions to evaluate and cannot be used to choose between two statements. For this purpose, Java supplies three decision statements: if, if-else, and switch.

If Statement

The if statement evaluates a Boolean expression and executes another statement if this expression evaluates to true. It has the following syntax:

if (Boolean expression)
   statement

This statement consists of reserved word if, followed by a Boolean expression in parentheses, followed by a statement to execute when Boolean expression evaluates to true.

The following example demonstrates the if statement:

if (numMonthlySales > 100)
   wage += bonus;

If the number of monthly sales exceeds 100, numMonthlySales > 100 evaluates to true and the wage += bonus; assignment statement executes. Otherwise, this assignment statement doesn’t execute.

Note  Many people prefer to wrap a single statement in brace characters in order to prevent the possibility of error. As a result, they would typically write the previous example as follows:

if (numMonthlySales > 100) {

   wage += bonus;

}

I don’t do this for single statements because I view the extra braces as unnecessary clutter. However, you might feel differently. Use whatever approach makes you the most comfortable.

If-Else Statement

The if-else statement evaluates a Boolean expression and executes one of two statements depending on whether this expression evaluates to true or false. It has the following syntax:

if (Boolean expression)
   statement1
else
   statement2

This statement consists of reserved word if, followed by a Boolean expression in parentheses, followed by a statement1 to execute when Boolean expression evaluates to true, followed by a statement2 to execute when Boolean expression evaluates to false.

The following example demonstrates the if-else statement:

if ((n & 1) == 1)
   System.out.println("odd");
else
   System.out.println("even");

This example assumes the existence of an int variable named n that’s been initialized to an integer. It then proceeds to determine if the integer is odd (not divisible by 2) or even (divisible by 2).

The Boolean expression first evaluates n & 1, which bitwise ANDs n’s value with 1. It then compares the result to 1. If they’re equal, a message stating that n’s value is odd outputs; otherwise, a message stating that n’s value is even outputs.

The parentheses are required because == has higher precedence than &. Without these parentheses, the expression’s evaluation order would change to first evaluating 1 == 1 and then trying to bitwise AND the Boolean result with n’s integer value. This order results in a compiler error message because of a type mismatch: you cannot bitwise AND an integer with a Boolean value.

You could rewrite this if-else statement example to use the conditional operator, as follows:

System.out.println((n & 1) == 1 ? "odd" : "even");

However, you cannot do so with the following example:

if ((n & 1) == 1)
   odd();
else
   even();

This example assumes the existence of odd() and even() methods that don’t return anything. Because the conditional operator requires that each of its second and third operands evaluates to a value, the compiler reports an error when attempting to compile (n & 1) == 1 ? odd() : even().

You can chain multiple if-else statements together, resulting in the following syntax:

if (Boolean expression1)
   statement1
else
if (Boolean expression2)
   statement2
else
   ...
else
   statementN

If Boolean expression1 evaluates to true, statement1 executes. Otherwise, if Boolean expression2 evaluates to true, statement2 executes. This pattern continues until one of these expressions evaluates to true and its corresponding statement executes, or the final else is reached and statementN (the default statement) executes.

Listing 2-14 presents a GradeLetters application that demonstrates chaining together multiple if-else statements.

Listing 2-14. Experimenting with If-Else Chaining

public class GradeLetters
{
   public static void main(String[] args)
   {
      int testMark = 69;
      char gradeLetter;
 
      if (testMark >= 90)
      {
         gradeLetter = 'A';
         System.out.println("You aced the test.");
      }
      else
      if (testMark >= 80)
      {
         gradeLetter = 'B';
         System.out.println("You did very well on this test.");
      }
      else
      if (testMark >= 70)
      {
         gradeLetter = 'C';
         System.out.println("You'll need to study more for future tests.");
      }
      else
      if (testMark >= 60)
      {
         gradeLetter = 'D';
         System.out.println("Your test result suggests that you need a tutor.");
      }
      else
      {
         gradeLetter = 'F';
         System.out.println("Your fail and need to attend summer school.");
      }
 
      System.out.println("Your grade is " + gradeLetter + ".");
   }
}

Compile Listing 2-14 as follows:

javac GradeLetters.java

Execute the resulting application as follows:

java GradeLetters

You should observe the following output:

Your test result suggests that you need a tutor.
Your grade is D.

DANGLING-ELSE PROBLEM

When if and if-else are used together and the source code isn’t properly indented, it can be difficult to determine which if associates with the else. See the following, for example:

if (car.door.isOpen())

   if (car.key.isPresent())

      car.start();

else car.door.open();

Did the developer intend for the else to match the inner if, but improperly formatted the code to make it appear otherwise? This reformatted possibility appears below:

if (car.door.isOpen())

   if (car.key.isPresent())

      car.start();

   else

      car.door.open();

If car.door.isOpen() and car.key.isPresent() each return true, car.start() executes. If car.door.isOpen() returns true and car.key.isPresent() returns false, car.door.open(); executes. Attempting to open an open door makes no sense.

The developer must have wanted the else to match the outer if but forgot that else matches the nearest if. This problem can be fixed by surrounding the inner if with braces, as follows:

if (car.door.isOpen())

{

   if (car.key.isPresent())

      car.start();

}

else

   car.door.open();

When car.door.isOpen() returns true, the compound statement executes. When this method returns false, car.door.open(); executes, which makes sense.

Forgetting that else matches the nearest if and using poor indentation to obscure this fact is known as the dangling-else problem.

Switch Statement

The switch statement lets you choose from among several execution paths in a more efficient manner than with equivalent chained if-else statements. It has the following syntax:

switch (selector expression)
{
   casevalue1:statement1[break;]
   casevalue2:statement2[break;]
   ...
   casevalueN:statementN[break;]
   [default:statement]
}

This statement consists of reserved word switch, followed by a selector expression in parentheses, followed by a body of cases. The selector expression is any expression that evaluates to an integer or character value. For example, it might evaluate to a 32-bit integer or to a 16-bit character.

Each case begins with reserved word case, continues with a literal value and a colon character (:), continues with a statement to execute; and optionally concludes with a break statement, which causes execution to continue after the switch statement.

After evaluating the selector expression, switch compares this value with each case’s value until it finds a match. When there is a match, the case’s statement is executed. For example, when the selector expression’s value matches value1, statement1 executes.

The optional break statement (anything placed in square brackets is optional), which consists of reserved word break followed by a semicolon, prevents the flow of execution from continuing with the next case’s statement. Instead, execution continues with the first statement following switch.

Note  You’ll usually place a break statement after a case’s statement. Forgetting to include break can lead to a hard-to-find bug. However, there are situations where you want to group several cases together and have them execute common code. In this situation, you would omit the break statement from the participating cases.

If none of the cases’ values match the selector expression’s value, and if a default case (signified by the default reserved word followed by a colon) is present, the default case’s statement is executed.

The following example demonstrates this statement:

switch (direction)
{
   case  0: System.out.println("You are travelling north."); break;
   case  1: System.out.println("You are travelling east."); break;
   case  2: System.out.println("You are travelling south."); break;
   case  3: System.out.println("You are travelling west."); break;
   default: System.out.println("You are lost.");
}

This example assumes that direction stores an integer value. When this value is in the range 0-3, an appropriate direction message is output; otherwise, a message about being lost is output.

Note  This example hardcodes values 0, 1, 2, and 3, which isn’t a good idea in practice. Instead, constants should be used. Chapter 3 introduces you to constants.

Loop Statements

It’s often necessary to repeatedly execute a statement; this repeated execution is called a loop. Java provides three kinds of loop statements: for, while, and do-while. In this section, I first discuss these statements. I then examine the topic of looping over the empty statement. Finally, I discuss the break, labeled break, continue, and labeled continue statements for prematurely ending all or part of a loop.

For Statement

The for statement lets you loop over a statement a specific number of times or even indefinitely. It has the following syntax:

for ([initialize]; [test]; [update])
   statement

This statement consists of reserved word for, followed by a header in parentheses, followed by a statement to execute. The header consists of an optional initialize section, followed by an optional test section, followed by an optional update section. A nonoptional semicolon separates each of the first two sections from the next section.

The initialize section consists of a comma-separated list of variable declarations or variable assignments. Some or all of these variables are typically used to control the loop’s duration and are known as loop-control variables.

The test section consists of a Boolean expression that determines how long the loop executes. Execution continues as long as this expression evaluates to true.

Finally, the update section consists of a comma-separated list of expressions that typically modify the loop-control variables.

The for statement is perfect for iterating (looping) over an array. Each iteration (loop execution) accesses one of the array’s elements via an array[index] expression, where array is the array whose element is being accessed and index is the zero-based location of the element being accessed.

The following example uses for to iterate over the array of command-line arguments passed to main():

public static void main(String[] args)
{
   for (int i = 0; i < args.length; i++)
      System.out.println(args[i]);
}

The initialization section declares variable i for controlling the loop, the test section compares i’s current value to the length of the args array to ensure that this value is less than the array’s length, and the update section increments i by 1. The for-based loop continues until i’s value equals the array’s length.

Each array element is accessed via the args[i] expression, which returns this array’s ith element’s value (which happens to be a String object in this example). The first value is stored in args[0].

Note  Although I’ve named the array containing command-line arguments args, this name isn’t mandatory. I could as easily have named it arguments (or even some_other_name).

Listing 2-15 presents a DumpMatrix application that uses a for-based loop to output the contents of a two-dimensional matrix array.

Listing 2-15. Iterating over a Two-Dimensional Array’s Rows and Columns

public class DumpMatrix
{
   public static void main(String[] args)
   {
      float[][] matrix = { { 1.0F, 2.0F, 3.0F }, { 4.0F, 5.0F, 6.0F }};
      for (int row = 0; row < matrix.length; row++)
      {
         for (int col = 0; col < matrix[row].length; col++)
            System.out.print(matrix[row][col] + " ");
         System.out.print(" ");
      }
   }
}

Expression matrix.length returns the number of rows in this tabular array. For each row, expression matrix[row].length returns the number of columns for that row. This latter expression suggests that each row can have a different number of columns, although each row has the same number of columns in the example.

System.out.print() is closely related to System.out.println(). Unlike the latter method, System.out.print() outputs its argument without a trailing newline.

Compile Listing 2-15 as follows:

javac DumpMatrix.java

Execute the resulting application as follows:

java DumpMatrix

You should observe the following output:

1.0 2.0 3.0
4.0 5.0 6.0

While Statement

The while statement repeatedly executes another statement while its Boolean expression evaluates to true. It has the following syntax:

while (Boolean expression)
   statement

This statement consists of reserved word while, followed by a parenthesized Boolean expression, followed by a statement to execute repeatedly. The while statement first evaluates the Boolean expression. If it’s true, while executes the other statement. Once again, the Boolean expression is evaluated. If it’s still true, while re-executes the statement. This cyclic pattern continues.

Prompting the user to enter a specific character is one situation where while is useful. For example, suppose that you want to prompt the user to enter a specific uppercase letter or its lowercase equivalent. The following example provides a demonstration:

int ch = 0;
while (ch != 'C' && ch != 'c')
{
   System.out.println("Press C or c to continue.");
   ch = System.in.read();
}

This example first initializes variable ch. This variable must be initialized; otherwise, the compiler will report an uninitialized variable when it tries to read ch’s value in the while statement’s Boolean expression.

This expression uses the conditional AND operator (&&) to test ch’s value. This operator first evaluates its left operand, which happens to be expression ch != 'C'. (The != operator converts 'C' from 16-bit unsigned char type to 32-bit signed int type before the comparison.)

If ch doesn’t contain C (it doesn’t at this point; 0 was just assigned to ch), this expression evaluates to true.

The && operator next evaluates its right operand, which happens to be expression ch != 'c'. Because this expression also evaluates to true, conditional AND returns true and while executes the compound statement.

The compound statement first outputs, via the System.out.println() method call, a message that prompts the user to press the C key with or without the Shift key. It next reads the entered keystroke via System.in.read(), saving its integer value in ch.

From left to write, System identifies a standard class of system utilities, in identifies an object located in System that provides methods for inputting one or more bytes from the standard input device, and read() returns the next byte (or -1 when there are no more bytes).

After this assignment, the compound statement ends and while re-evaluates its Boolean expression.

Suppose ch contains C’s integer value. Conditional AND evaluates ch != 'C', which evaluates to false. Detecting that the expression is already false, conditional AND short-circuits its evaluation by not evaluating its right operand and returns false. The while statement subsequently detects this value and terminates.

Suppose ch contains c’s integer value. Conditional AND evaluates ch != 'C', which evaluates to true. Detecting that the expression is true, conditional AND evaluates ch != 'c', which evaluates to false. Once again, the while statement terminates.

Note  A for statement can be coded as a while statement. For example,

for (int i = 0; i < 10; i++)

   System.out.println(i);

is equivalent to

int i = 0;

while (i < 10)

{

   System.out.println(i);

   i++;

}

Do-While Statement

The do-while statement repeatedly executes a statement while its Boolean expression evaluates to true. Unlike while, which evaluates the Boolean expression at the top of the loop, do-while evaluates the Boolean expression at the bottom of the loop. It has the following syntax:

do
   statement
while (Boolean expression);

This statement consists of the do reserved word, followed by a statement to execute repeatedly, followed by the while reserved word, followed by a parenthesized Boolean expression, followed by a semicolon.

The do-while statement first executes the other statement. It then evaluates the Boolean expression. If it’s true, do-while executes the other statement. Once again, the Boolean expression is evaluated. If it’s still true, do-while re-executes the statement. This cyclic pattern continues.

The following example demonstrates do-while prompting the user to enter a specific uppercase letter or its lowercase equivalent:

int ch;
do
{
   System.out.println("Press C or c to continue.");
   ch = System.in.read();
}
while (ch != 'C' && ch != 'c'),

This example is similar to its predecessor. Because the compound statement is no longer executed before the test, it’s no longer necessary to initialize chch is assigned System.in.read()’s return value before the Boolean expression’s evaluation.

Looping Over the Empty Statement

Java refers to a semicolon character appearing by itself as the empty statement. It’s sometimes convenient for a loop statement to execute the empty statement repeatedly. The actual work performed by the loop statement takes place in the statement header.

Consider the following example:

for (String line; (line = readLine()) != null; System.out.println(line));

This example uses for to present a programming idiom for copying lines of text that are read from some source, via the fictitious readLine() method in this example, to some destination, via System.out.println() in this example. Copying continues until readLine() returns null. Note the semicolon (empty statement) at the end of the line.

Caution  Be careful with the empty statement because it can introduce subtle bugs into your code. For example, the following loop is supposed to output the string Hello on 10 lines. Instead, only one instance of this string is output, because it’s the empty statement and not System.out.println() that’s executed 10 times:

for (int i = 0; i < 10; i++); // this ; represents the empty statement
       System.out.println("Hello");

Break and Labeled Break Statements

What do for (;;);, while (true); and do;while (true); have in common? Each of these loop statements presents an extreme example of an infinite loop (a loop that never ends). An infinite loop is something that you should avoid because its unending execution causes your application to hang, which isn’t desirable from the point of view of your application’s users.

Caution  An infinite loop can also arise from a loop’s Boolean expression comparing a floating-point value with a nonzero value via the equality or inequality operator, because many floating-point values have inexact internal representations. For example, the following example never ends because 0.1 doesn’t have an exact internal representation:

for (double d = 0.0; d != 1.0; d += 0.1)
       System.out.println(d);

However, there are times when it’s handy to code a loop as if it were infinite by using one of the aforementioned programming idioms. For example, you might code a while (true) loop that repeatedly prompts for a specific keystroke until the correct key is pressed. When the correct key is pressed, the loop must end. Java provides the break statement for this purpose.

The break statement transfers execution to the first statement following a switch statement (as discussed earlier) or a loop. In either scenario, this statement consists of reserved word break followed by a semicolon.

The following example uses break with an if decision statement to exit a while (true)-based infinite loop when the user presses the C or c key:

int ch;
while (true)
{
   System.out.println("Press C or c to continue.");
   ch = System.in.read();
   if (ch == 'C' || ch == 'c')
      break;
}

The break statement is also useful in the context of a finite loop. For example, consider a scenario where an array of values is searched for a specific value, and you want to exit the loop when this value is found. Listing 2-16 presents an EmployeeSearch application that demonstrates this scenario.

Listing 2-16. Searching for a Specific Employee ID

public class EmployeeSearch
{
   public static void main(String[] args)
   {
      int[] employeeIDs = { 123, 854, 567, 912, 224 };
      int employeeSearchID = 912;
      boolean found = false;
      for (int i = 0; i < employeeIDs.length; i++)
         if (employeeSearchID == employeeIDs[i])
         {
            found = true;
            break;
         }
      System.out.println((found) ? "employee " + employeeSearchID + " exists"
                                 : "no employee ID matches " + employeeSearchID);
   }
}

Listing 2-16 uses for and if statements to search an array of employee IDs to determine if a specific employee ID exists. If this ID is found, if’s compound statement assigns true to found. Because there’s no point in continuing the search, it then uses break to quit the loop.

Compile Listing 2-16 as follows:

javac EmployeeSearch.java

Run this application as follows:

java EmployeeSearch

You should observe the following output:

employee 912 exists

The labeled break statement transfers execution to the first statement following the loop that’s prefixed by a label (an identifier followed by a colon). It consists of reserved word break, followed by an identifier for which the matching label must exist. Furthermore, the label must immediately precede a loop statement.

The labeled break is useful for breaking out of nested loops (loops within loops). The following example reveals the labeled break statement transferring execution to the first statement that follows the outer for loop:

outer:
for (int i = 0; i < 3; i++)
   for (int j = 0; j < 3; j++)
      if (i == 1 && j == 1)
         break outer;
      else
         System.out.println("i=" + i + ", j=" + j);
System.out.println("Both loops terminated.");

When i’s value is 1 and j’s value is 1, break outer; is executed to terminate both for loops. This statement transfers execution to the first statement after the outer for loop, which happens to be System.out.println("Both loops terminated.");.

The following output is generated:

i=0, j=0
i=0, j=1
i=0, j=2
i=1, j=0
Both loops terminated.

Continue and Labeled Continue Statements

The continue statement skips the remainder of the current loop iteration, re-evaluates the loop’s Boolean expression, and performs another iteration (if true) or terminates the loop (if false). Continue consists of reserved word continue followed by a semicolon.

Consider a while loop that reads lines from a source and processes nonblank lines in some manner. Because it shouldn’t process blank lines, while skips the current iteration when a blank line is detected, as demonstrated in the following example:

String line;
while ((line = readLine()) != null)
{
   if (isBlank(line))
      continue;
   processLine(line);
}

This example employs a fictitious isBlank() method to determine if the currently read line is blank. If this method returns true, if executes the continue statement to skip the rest of the current iteration and read the next line whenever a blank line is detected. Otherwise, the fictitious processLine() method is called to process the line’s contents.

Look carefully at this example, and you should realize that the continue statement isn’t needed. Instead, this listing can be shortened via refactoring (rewriting source code to improve its readability, organization, or reusability), as demonstrated in the following example:

String line;
while ((line = readLine()) != null)
{
   if (!isBlank(line))
      processLine(line);
}

This example’s refactoring modifies if’s Boolean expression to use the logical complement operator (!). Whenever isBlank() returns false, this operator flips this value to true and if executes processLine(). Although continue isn’t necessary in this example, you’ll find it convenient to use this statement in more complex code where refactoring isn’t as easy to perform.

The labeled continue statement skips the remaining iterations of one or more nested loops and transfers execution to the labeled loop. It consists of reserved word continue, followed by an identifier for which a matching label must exist. Furthermore, the label must immediately precede a loop statement.

Labeled continue is useful for breaking out of nested loops while still continuing to execute the labeled loop. The following example reveals the labeled continue statement terminating the inner for loop’s iterations:

outer:
for (int i = 0; i < 3; i++)
   for (int j = 0; j < 3; j++)
      if (i == 1 && j == 1)
         continue outer;
      else
         System.out.println("i=" + i + ", j=" + j);
System.out.println("Both loops terminated.");

When i’s value is 1 and j’s value is 1, continue outer; is executed to terminate the inner for loop and continue with the outer for loop at its next value of i. Both loops continue until they finish.

The following output is generated:

i=0, j=0
i=0, j=1
i=0, j=2
i=1, j=0
i=2, j=0
i=2, j=1
i=2, j=2
Both loops terminated.

EXERCISES

The following exercises are designed to test your understanding of Chapter 2’s content.

  1. What is Unicode?
  2. What is a comment?
  3. Identify the three kinds of comments that Java supports.
  4. What is an identifer?
  5. True or false: Java is a case-insensitive language.
  6. What is a type?
  7. Define primitive type.
  8. Identify all of Java’s primitive types.
  9. Define user-defined type.
  10. Define array type.
  11. What is a variable?
  12. What is an expression?
  13. Identify the two expression categories.
  14. What is a literal?
  15. Is string literal "The quick brown fox jumps over the lazy dog." legal or illegal? Why?
  16. What is an operator?
  17. Identify the difference between a prefix operator and a postfix operator.
  18. What is the purpose of the cast operator?
  19. What is precedence?
  20. True or false: Most of Java’s operators are left-to-right associative.
  21. What is a statement?
  22. What is the difference between the while and do-while statements?
  23. What is the difference between the break and continue statements?
  24. Write a Compass application (the class is named Compass) whose main() method encapsulates the direction-oriented switch statement example presented earlier in this chapter. You’ll need to declare a direction variable with the appropriate type and initialize this variable.
  25. Create a Triangle application whose Triangle class’s main() method uses a pair of nested for statements along with System.out.print() to output a 10-row triangle of asterisks, where each row contains an odd number of asterisks (1, 3, 5, 7, and so on), as shown below:

                       *

                      ***

                     *****

                    *******

                   *********

                  ***********

                 *************

                ***************

               *****************

              *******************

    Compile and run this application.

  26. Write a pair of PromptForC applications whose main() methods encapsulate the while and do-while examples that prompt for input of letter c or letter C. Because each method uses System.in.read() to obtain input, append throws java.io.Exception to the main() method header.

Summary

Before developing Java applications, you need to understand the structure of a Java application. Essentially, every application drills down to a single class that declares a public static void main(String[] args) method.

Source code needs to be documented so that you (and any others who have to maintain it) can understand it, now and later. Java provides the comment feature for embedding documentation in source code. Single-line, multiline, and documentation comments are supported.

A single-line comment occupies all or part of a single line of source code. This comment begins with the // character sequence and continues with explanatory text. The compiler ignores everything from // to the end of the line in which // appears.

A multiline comment occupies one or more lines of source code. This comment begins with the /* character sequence, continues with explanatory text, and ends with the */ character sequence. Everything from /* through */ is ignored by the compiler.

A Javadoc comment occupies one or more lines of source code. This comment begins with the /** character sequence, continues with explanatory text, and ends with the */ character sequence. Everything from /** through */ is ignored by the compiler.

Identifiers are used to name classes, methods, and other source code entities. An identifier consists of letters (A-Z, a-z, or equivalent uppercase/lowercase letters in other human alphabets), digits (0-9 or equivalent digits in other human alphabets), connecting punctuation characters (such as the underscore), and currency symbols (such as the dollar sign, $). This name must begin with a letter, a currency symbol, or a connecting punctuation character; and its length cannot exceed the line in which it appears. Some identifiers are reserved by Java. Examples include abstract and case.

Applications process different types of values such as integers, floating-point values, characters, and strings. A type identifies a set of values (and their representation in memory) and a set of operations that transform these values into other values of that set.

A primitive type is a type that’s defined by the language and whose values are not objects. Java supports the Boolean, character, byte integer, short integer, integer, long integer, floating-point, and double precision floating-point primitive types.

A user-defined type is a type that’s defined by the developer using a class, an interface, an enum, or an annotation type and whose values are objects. User-defined types are also known as reference types.

An array type is a reference type that signifies an array, a region of memory that stores values in equal-size and contiguous slots, which are commonly referred to as elements. This type consists of the element type and one or more pairs of square brackets that indicate the number of dimensions.

Applications manipulate values that are stored in memory, which is symbolically represented in source code through the use of the variables feature. A variable is a named memory location that stores some type of value.

Java provides the expressions feature for initializing variables and for other purposes. An expression combines some arrangement of literals, variable names, method calls, and operators. At runtime, it evaluates to a value whose type is referred to as the expression’s type.

A simple expression is a literal, a variable name (containing a value), or a method call (returning a value). Java supports several kinds of literals: string, Boolean true and false, character, integer, floating-point, and null.

A compound expression is a sequence of simple expressions and operators, where an operator (a sequence of instructions symbolically represented in source code) transforms its operand expression value(s) into another value.

Java supplies many operators, which are classified by the number of operands that they take. A unary operator takes only one operand, a binary operator takes two operands, and Java’s single ternary operator takes three operands.

Operators are also classified as prefix, postfix, and infix. A prefix operator is a unary operator that precedes its operand, a postfix operator is a unary operator that trails its operand, and an infix operator is a binary or ternary operator that’s sandwiched between its operands.

Statements are the workhorses of a program. They assign values to variables, control a program’s flow by making decisions and/or repeatedly executing other statements, and perform other tasks. A statement can be expressed as a simple statement or as a compound statement.

In Chapter 3, I continue to explore the Java language by examining its support for classes and objects. You also learn more about arrays.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.140.206