Java in a Nutshell: Chapter 2 | WebReference

Java in a Nutshell: Chapter 2

Java in a Nutshell

Chapter 2: How Java Differs from C

Java is a lot like C, which makes it relatively easy for C programmers to learn. But there are a number of important differences between C and Java, such as the lack of a preprocessor, the use of 16-bit Unicode characters, and the exception handling mechanism. This chapter explains those differences, so that programmers who already know C can start programming in Java right away!

This chapter also points out similarities and differences between Java and C++. C++ programmers should beware, though: While Java borrows a lot of terminology and even syntax from C++, the analogies between Java and C++ are not nearly as strong as those between Java and C. C++ programmers should be careful not to be lulled into a false sense of familiarity with Java just because the languages share a number of keywords!

One of the main areas in which Java differs from C, of course, is that Java is an object-oriented language and has mechanisms to define classes and create objects that are instances of those classes. Java's object-oriented features are a topic for a chapter of their own, and they'll be explained in detail in Section 3, Classes and Objects in Java.


In this chapter:


Program Structure and Environment

A program in Java consists of one or more class definitions, each of which has been compiled into its own .class file of Java Virtual Machine object code. One of these classes must define a method* main(), which is where the program starts running.

To invoke a Java program, you run the Java interpreter, java, and specify the name of the class that contains the main() method. You should omit the .class extension when doing this. Note that a Java applet is not an application--it is a Java class that is loaded and run by already running Java application such as a Web browser or applet viewer.

The main() method that the Java interpreter invokes to start a Java program must have the following prototype:

public static void main(String argv[]) 
The Java interpreter runs until the main() method returns, or until the interpreter reaches the end of main(). If no threads have been created by the program, the interpreter exits. Otherwise, the interpreter continues running until the last thread terminates.

Command-Line Arguments

The single argument to main() is an array of strings, conventionally named argv[]. The length of this array (which would be passed as the argc argument in C) is available as argv.length, as is the case with any Java array. The elements of the array are the arguments, if any, that appeared on the interpreter command line after the class name. Note that the first element of the array is not the name of the class, as a C programmer might expect it to be. .Ref e shows how you could write a UNIX-style echo command (a program that simply prints out its arguments) in Java.
public class echo {
    public static void main(String argv[]) {
        for(int i=0; i < argv.length; i++) 
            System.out.print(argv[i] + " ");
        System.out.print("\n");
        System.exit(0);
    }
}

Program Exit Value

Note that main() must be declared to return void. Thus you cannot return a value from your Java program with a return statement in main(). If you need to return a value, call System.exit() with the desired integer value, as we've done in .Ref e . p 1 Note that the handling and interpretation of this exit value are, of course, operating-system dependent. System.exit() causes the Java interpreter to exit immediately, whether or not other threads are running.

Environment

The Java API does not allow a Java program to read operating system environment variables because they are platform-dependent. However, Java defines a similar, platform-independent mechanism, known as the system properties list, for associating textual values with names. A Java program can look up the value of a named property with the System.getProperty() method:
String homedir = System.getProperty("user.home");
String debug = System.getProperty("myapp.debug");
The Java interpreter automatically defines a number of standard system properties when it starts up. You can insert additional property definitions into the list by specifying the -D option to the interpreter:
%java -Dmyapp.debug=true myapp
See Section 13, System Properties and Applet Parameters, for more information on system properties.

The Name Space: Packages, Classes, and Fields

As a language that is designed to support dynamic loading of modules over the entire Internet, Java takes special care to avoid name space conflicts. Global variables are simply not part of the language. Neither are "global" functions or procedures, for that matter.

No Global Variables

In Java, every variable and method is declared within a class and forms part of that class. Also, every class is part of a package. Thus every Java variable or method may be referred to by its fully qualified name, which consists of the package name, the class name, and the field name (i.e., the variable or the method name), all separated by periods. Package names are themselves usually composed of multiple period-separated components. Thus, the fully qualified name for a method might be:
david.games.tetris.SoundEffects.play()

Packages, Classes, and Directory Structure

In Java, every compiled class is stored in a separate file. The name of this file must be the same as the name of the class, with the extension .class added. Thus the class SoundEffects would be stored in the file SoundEffects.class. This class file must be stored in a directory that has the same components as the package name. For example, if the fully qualified name of a class is david.games.tetris.SoundEffects, the full path of the class file must be david/games/tetris/SoundEffects.class.*
*We'll use UNIX-style directory specifications in this book. If you are a Windows programmer, simply change all the forward slashes in filenames to backward slashes. Similarly, in path specifications, change colons to semicolons.

A file of Java source code should have the extension .java. It consists of one or more class definitions. If more than one class is defined in a .java file, only one of the classes may be declared public (i.e., available outside of the package), and that class must have the same name as the source file (minus the .java extension, of course). If a source file contains more than one class definition, those classes are compiled into multiple .class files.

Packages of the Java API

The Java API consists of the classes defined in the eight packages listed below.

The Packages of the Java API

--------------------------------------------------------------
Package name   | Contents
---------------+----------------------------------------------
java.applet    | Classes for implementing applets
java.awt       | Classes for graphics, text, windows, and GUIs
java.awt.image | Classes for image processing
java.awt.peer  | Interfaces for a platform-independent GUI toolkit
java.io        | Classes for all kinds of input and output
java.lang      | Classes for the core language
java.net       | Classes for networking
java.util      | Classes for useful data types
---------------+----------------------------------------------

The Java Class Path

The Java interpreter looks up its system classes in a platform-dependent default location, or relative to the directories specified by the -classpath argument. It looks up user-defined classes in the current directory and relative to the directories specified by the CLASSPATH environment variable. The directories in a class path specification should be colon-separated on a UNIX system, and semicolon-separated on a Windows system. For example, on a UNIX system, you might use:
setenv CLASSPATH .:~/classes:/usr/local/classes
This tells Java to search in and beneath the specified directories for non-system classes.

Globally Unique Package Names

The Java designers have proposed an Internet-wide unique package naming scheme that is based on the domain name of the organization at which the package is developed.

The package Statement

The package statement must appear as the first statement (i.e., the first text other than comments and whitespace) in a file of Java source code, if it appears at all. It specifies which package the code in the file is part of. Java code that is part of a particular package has access to all classes (public and non-public) in the package, and to all non-private methods and fields in all those classes. When Java code is part of a named package, the compiled class file must be placed at the appropriate position in the CLASSPATH directory hierarchy before it can be accessed by the Java interpreter or other utilities.

If the package statement is omitted from a file, the code in that file is part of an unnamed default package. This is convenient for small test programs, or during development, because it means that the code can be interpreted from the current directory.

The import Statement

The import statement makes Java classes available to the current class under an abbreviated name. Public Java classes are always available by their fully qualified names, assuming that the appropriate class file can be found (and is readable) relative to the CLASSPATH environment variable. import doesn't actually make the class available or "read it in"; it simply saves you typing and makes your code more legible.

Any number of import statements may appear in a Java program. They must appear, however, after the optional package statement at the top of the file, and before the first class or interface definition in the file.

There are three forms of the import statement:

import      package ;
import      package.class ;
import      package.* ;
The first form allows the specified package to be known by the name of its last component. For example, the following import statement allows java.awt.image.ImageFilter to be called image.ImageFilter:
import java.awt.image;
The second form allows the specified class in the specified package to be known by its class name alone. Thus, this import statement allows you to type Hashtable instead of java.util.Hashtable:
import java.util.Hashtable;
Finally, the third form of the import statement makes all classes in a package available by their class name. For example, the following import statement is implicit (you need not specify it yourself) in every Java program:
import java.lang.*;
It makes the core classes of the language available by their unqualified class names. If two packages imported with this form of the statement contain classes with the same name, it is an error to use either of those ambiguous classes without using its fully qualified name.

Access to Packages, Classes, and Fields

Java has the following rules about access to packages, classes, and fields within classes. Note that the public, private, and protected keywords used in these rules will be explained in more detail in the next chapter.

Local Variables

The name space rules we've been describing apply to packages, classes, and the fields within classes. Java also supports local variables, declared within method definitions. These local variables behave just like local variables in C--they do not have globally unique hierarchical names, nor do they have access modifiers like public and private.

Comments

Java supports three types of comments:

Since C-style comments do not nest, it is a good idea to use C++-style // comments for most of your short comments within method bodies. This allows you to use /* */ comments to comment out large blocks of code when you need to do that during development. This is especially important because, as you will see, Java does not support a preprocessor that allows you to use #if 0 to comment out a block.

No Preprocessor

Java does not include any kind of preprocessor like the C cpp preprocessor. It may seem hard to imagine programming without #define, #include, and #ifdef, but in fact, Java really does not require these constructs.

Defining Constants

Any variable declared final in Java is a constant--its value must be specified with an initializer when it is declared, and that value may never be changed. The Java equivalent of a C #define'ed constant is a static final variable declared within a class definition. If the compiler can compute the value of such a static final variable at compile-time, it uses the computed value to pre-compute other compile-time constants that refer to the value. The variable java.lang.Math.PI is an example of such a constant. It is declared like this:
public final class Math {
    ...
    public static final double PI = 3.14159.....;
    ...
}
Note two things about this example. First, the C convention of using CAPITAL letters for constants is also a Java convention. Second, note the advantage Java constants have over C preprocessor constants: Java constants have globally unique hierarchial names, while constants defined with the C preprocessor always run the risk of a name collision. Also, Java constants are strongly typed and allow better type-checking by the compiler than C preprocessor constants.

Defining Macros

The C preprocessor allows you to define macros--a construct that looks like a function invocation but that is actually replaced directly with C code, saving the overhead of a function call. Java has no equivalent to this sort of macro, but compiler technology has advanced to a point where macros are rarely necessary any more. A good Java compiler should automatically be able to "inline" short Java methods where appropriate.

Including Files

Java does not have a #include directive, but it does not need one. Java defines a mapping of fully qualified class names (like java.lang.Math) to a directory and file structure (like java/lang/Math.class). This means that when the Java compiler needs to read in a specified class file, it knows exactly where to find it and does not need a special directive to tell it where to look.

Furthermore, Java does not make the distinction between declaring a variable or procedure and defining it that C does. This means that there is no need for C-style header files or function prototypes--a single Java object file serves as the interface definition and implementation for a class.

Java does have an import statement, which is superficially similar to the C preprocessor #include directive. What this statement does, however, is tell the compiler that the current file is using the specified classes, or classes from the specified package, and allows us to refer to those classes with abbreviated names. For example, since the compiler implicitly imports all the classes of the java.lang package, we can refer to the constant java.lang.Math.PI by the shorter name Math.PI.

Conditional Compilation

Java does not have any form of the C #ifdef or #if directives to perform conditional compilation. In theory, conditional compilation is not necessary in Java since it is a platform-independent language, and thus there are no platform dependencies that require the technique. In practice, however, conditional compilation is still often useful in Java--to provide slightly different user interfaces on different platforms, for example, or to support optional inclusion of debugging code in programs.

While Java does not define explicit constructs for conditional compilation, a good Java compiler (such as Sun's javac) performs conditional compilation implicitly--that is, it does not compile code if it can prove that the code will never be executed. Generally, this means that code within an if statement testing an expression that is always false is not included. Thus, placing code within an if (false) block is equivalent to surrounding it with #if 0 and #endif in C.

Conditional compilation also works with constants, which, as we saw above, are static final variables. A class might define the constant like this:

private static final boolean DEBUG = false;
With such a constant defined, any code within an if (DEBUG) block is not actually compiled into the class file. To activate debugging for the class, it is only necessary to change the value of the constant to true and recompile the class.

Unicode and Character Escapes

Java characters, strings, and identifiers (e.g., variable, method, and class names) are composed of 16-bit Unicode characters. This makes Java programs relatively easy to internationalize for non-English-speaking users. It also makes the language easier to work with for non-English-speaking programmers--a Thai programmer could use the Thai alphabet for class and method names in her Java code.

If two-byte characters seem confusing or intimidating to you, fear not. The Unicode character set is compatible with ASCII and the first 256 characters (0x0000 to 0x00FF) are identical to the ISO8859-1 (Latin-1) characters 0x00 to 0xFF. Furthermore, the Java language design and the Java String API make the character representation entirely transparent to you. If you are using only Latin-1 characters, there is no way that you can even distinguish a Java 16-bit character from the 8-bit characters you are familiar with. For more information on Unicode, see Section 16, The Unicode Standard.

Most platforms cannot display all 34,000 currently defined Unicode characters, so Java programs may be written (and Java output may appear) with special Unicode escape sequences. Anywhere within a Java program (not only within character and string literals), a Unicode character may be represented with the Unicode escape sequence \uxxxx, where xxxx is a sequence of one to four hexadecimal digits.

Java also supports all of the standard C character escape sequences, such as \n, \t, and \xxx (where xxx is three octal digits). Note, however, that Java does not support line continuation with \ at the end of a line. Long strings must either be specified on a single long line, or they must be created from shorter strings using the string concatenation (+) operator. (Note that the concatenation of two constant strings is done at compile-time rather than at run-time, so using the + operator in this way is not inefficient.)

There are two important differences between Unicode escapes and C-style escape characters. First, as we've noted, Unicode escapes can appear anywhere within a Java program, while the other escape characters can appear only in character and string constants.

The second, and more subtle, difference is that Unicode \u escape sequences are processed before the other escape characters, and thus the two types of escape sequences can have very different semantics. A Unicode escape is simply an alternative way to represent a character that may not be displayable on certain (non-Unicode) systems. Some of the character escapes, however, represent special characters in a way that prevents the usual interpretation of those characters by the compiler. The following examples make this difference clear. Note that \u0022 and \u005c are the Unicode escapes for the double-quote character and the backslash character.

// \" represents a " character, and prevents the normal
// interpretation of that character by the compiler.
// This is a string consisting of a double-quote character.
String quote = "\"";
// We can't represent the same string with a single Unicode escape.
// \u0022 has exactly the same meaning to the compiler as ".
// The string below turns into """: an empty string followed
// by an unterminated string, which yields a compilation error.
String quote = "\u0022";
// Here we represent both characters of an \" escape as
// Unicode escapes. This turns into "\"", and is the same
// string as in our first example.
String quote = "\u005c\u0022";

Primitive Data Types

Java adds byte and boolean primitive types to the standard set of C types. In addition, it strictly defines the size and signedness of its types. In C, an int may be 16, 32, or 64 bits, and a char may act signed or unsigned depending on the platform. Not so in Java. In C, an uninitialized local variable usually has garbage as its value. In Java, all variables have guaranteed default values, though the compiler may warn you in places where you rely, accidentally or not, on these default values. .Ref t lists Java's primitive data types. The subsections below provide details about these types.

Java Primitive Data Types

--------+-------------------+---------+---------+----------------------------
--------+-------------------+---------+---------+----------------------------
Type    | Contains          | Default | Size    |Min Value       Max Value
--------+-------------------+---------+---------+----------------------------
--------+-------------------+---------+---------+----------------------------
boolean | true or false     | false   |   1 bit | N.A.           N.A.
--------+-------------------+---------+---------+----------------------------
char    | Unicode character | \u0000  | 16 bits | \u0000        \uFFFF
--------+-------------------+---------+---------+----------------------------
byte    | signed integer    | 0       |  8 bits | -128            127
--------+-------------------+---------+---------+----------------------------
short   | signed integer    | 0       | 16 bits | -32768        32767
--------+-------------------+---------+---------+----------------------------
int     | signed integer    | 0       | 32 bits | -2147483648     
        |                   |         |         |           2147483647
--------+-------------------+---------+---------+----------------------------
long    | signed integer    | 0       | 64 bits | -9223372036854775808
        |                   |         |         |        9223372036854775807
--------+-------------------+---------+---------+----------------------------
float   | IEEE 754          | 0.0     | 32 bits | +-3.40282347E+38
        | floating-point    |         |         |         +-1.40239846E-45
--------+-------------------+---------+---------+----------------------------
double  | IEEE 754          | 0.0     | 64 bits | +-1.79769313486231570E+308
        | floating-point    |         |         | +-4.94065645841246544E-324
--------+-------------------+---------+---------+----------------------------

The boolean Type

boolean values are not integers, may not be treated as integers, and may never be cast to or from any other type. To perform C-style conversions between a boolean value b and an int i, use the following code:
b = (i != 0);	// integer-to-boolean: non-0 -> true; 0 -> false; 
i = (b)?1:0;	// boolean-to-integer: true -> 1; false -> 0;

The char Type

Values of type char do not have a sign. If a char is cast to a byte or a short, a negative value may result.

The char type in Java holds a two-byte Unicode character. While this may seem intimidating to those not familiar with Unicode and the techniques of program internationalization, it is in fact totally transparent. Java does not provide a way to compute the size of a variable, nor does it allow any sort of pointer arithmetic. What this means is that if you are only using ASCII or Latin-1 characters, there is no way to distinguish a Java char from a C char.

Integral Types

All integral types, other than char, are signed. There is no unsigned keyword as there is in C.

It is not legal to write long int or short int as it is in C.

A long constant may be distinguished from other integral constants by appending the character l or L to it.

Integer division by zero or modulo zero causes an ArithmeticException to be thrown.*

*Exceptions signal errors in Java. Exception handling is described later in this chapter.

Floating-Point Types

Floating-point literals may be specified to be of type float by appending an f or F to the value; they may be specified to be of type double by appending a d or D.

float and double types have special values that may be the result of certain floating-point operations: positive infinity, negative infinity, negative zero and not-a-number. The java.lang.Float and java.lang.Double classes define some of these values as constants: POSITIVE_INFINITY, NEGATIVE_INFINITY, and NaN.

NaN is unordered--comparing it to any other number, including itself, yields false. Use Float.isNaN() or Double.isNaN() to test for NaN.

Negative zero compares equal to regular zero (positive zero), but the two zeros may be distinguished by division: one divided by negative zero yields negative infinity; one divided by positive zero yields positive infinity.

Floating-point arithmetic never causes exceptions, even in the case of division by zero.

Reference Data Types

The non-primitive data types in Java are objects and arrays. These non-primitive types are often called "reference types" because they are handled "by reference"--in other words, the address of the object or array is stored in a variable, passed to methods, and so on. By comparison, primitive types are handled "by value"--the actual primitive values are stored in variables and passed to methods.

In C, you can manipulate a value by reference by taking its address with the & operator, and you can "dereference" an address with the * and -> operators. These operators do not exist in Java. Primitive types are always passed by value; arrays and objects are always passed by reference.

Because objects are passed by reference, two different variables may refer to the same object:

Button a, b;
p = new Button();			// p refers to a Button object
q = p;				// q refers to the same Button.
p.setLabel("Ok");			// A change to the object through p...
String s = q.getLabel();		// ...is also visible through q.  
			// s now contains "Ok".

This is not true of primitive types, however:

int i = 3;				// i contains the value 3.
int j = i;				// j contains a copy of the value in i.
i = 2;				// Changing i doesn't change j.
			// Now, i == 2 and j == 3.

Copying Objects

Because reference types are not passed by value, assigning one object to another in Java does not copy the value of the object. It merely assigns a reference to the object. Consider the following code:
Button a = new Button("Okay");
Button b = new Button("Cancel");
a = b;
After these lines are executed, the variable a contains a reference to the object that b refers to. The object that a used to refer to is lost.

To copy the data of one object into another object, use the clone() method:

Vector b = new Vector;
c = b.clone();
After these lines run, the variable c refers to an object that is a duplicate of the object referred to by b. Note that not all types support the clone() method. Only classes that implement the Cloneable interface may be cloned. Look up java.lang.Cloneable and java.lang.Object.clone() in Section 23, The java.lang Package, for more information on cloning objects.

Arrays are also reference types, and assigning an array simply copies a reference to the array. To actually copy the values stored in an array, you must assign each of the values individually or use the System.arraycopy() method.

Checking Objects for Equality

Another implication of passing objects by reference is that the == operator tests whether two variables refer to the same object, not whether two objects contain the same values. To actually test whether two separate objects are the same, you must use a specially written method for that object type (just as you might use strcmp() to compare C strings for equality). In Java, a number of classes define an equals() method that you can use to perform this test.

Java Has No Pointers

The referencing and dereferencing of objects is handled for you automatically by Java. Java does not allow you to manipulate pointers or memory addresses of any kind:

There are two reasons for these restrictions:

To a C programmer, the lack of pointers and pointer arithmetic may seem an odious restriction in Java. But once you get used to the Java object-oriented programming model, it no longer seems like a serious restriction at all. The lack of pointers does mean that you probably can't do things like write UNIX device drivers in Java (at least not without using native methods written in C). But big deal--most of us never have to do this kind of low-level programming anyway.

null

The default value for variables of all reference types is null. null is a reserved value that indicates "an absence of reference"--i.e., that a variable does not refer to any object or array.

In Java, null is a reserved keyword, unlike NULL in C, where it is just a constant defined to be 0. null is an exception to the strong typing rules of Java--it may be assigned to any variable of reference type (i.e., any variable which has a class, interface, or array as its type).

null cannot be cast to any primitive type, including integral types and boolean. It should not be considered equal to zero (although it may well be implemented this way).

Reference Type Summary

The distinction between primitive types passed by value, and objects and arrays passed by reference is a crucial one in Java. Be sure you understand the following:

Objects

Now that you know objects are passed by reference, we should discuss how they are created, used, and destroyed. The following subsections provide a very brief overview of objects. Section 3, Classes and Objects in Java, explains classes and objects in much greater detail.

Creating Objects

Declaring a variable to hold an object does not create the object itself; the variable only holds the reference to the object. To actually create an object, you must use the new keyword. This is followed by the object's class (i.e., its type) and an optional argument list in parentheses. These arguments are passed to the constructor method for the class, which serves to initialize internal fields in the new object. For example:
java.awt.Button b = new java.awt.Button();
ComplexNumber c = new ComplexNumber(1.0, 1.414);
There are actually two other ways to create an object. First, you can create a String object simply by enclosing characters in double quotes:
String s = "This is a test";
Because strings are used so frequently, the Java compiler provides this technique as a shortcut. The second alternative way to create objects is by calling the newInstance() method of a Class object. This technique is generally used only when dynamically loading classes, so we won't discuss it here.

The memory for newly created objects is dynamically allocated. Creating an object with new in Java is like calling malloc() in C to allocate memory for an instance of a struct. It is also, of course, a lot like using the new operator in C++. (Below, though, we'll see where this analogy to malloc() in C and new in C++ breaks down.)

Accessing Objects

As you've probably noticed in various example code fragments by now, the way you access the fields of an object is with a dot:
ComplexNumber c = new ComplexNumber();
c.x = 1.0;
c.y = -1.414;
This syntax is reminiscent of accessing the fields of a struct in C. Recall, though, that Java objects are always accessed by reference, and that Java performs any necessary dereferencing for you. Thus, the dot in Java is more like -> in C. Java hides the fact that there is a reference here in an attempt to make your programming easier.

The other difference between C and Java when accessing objects is that in Java you refer to an object's methods as if they were fields in the object itself:

ComplexNumber c = new ComplexNumber(1.0, -1.414);
double magnitude = c.magnitude();

Garbage Collection

Objects in Java are created with the new keyword, but there is no corresponding old or delete keyword or free() method to get rid of them when they are no longer needed. If creating an object with new is like calling malloc() in C or using new in C++, then it would seem that Java is full of memory leaks, because we never call free() or use the delete operator.

In fact, this isn't the case. Java uses a technique called garbage collection to automatically detect objects that are no longer being used (an object is no longer in use when there are no more references to it) and to free them. This means that in our programs, we never need to worry about freeing memory or destroying objects--the garbage collector takes care of that.

If you are a C or C++ programmer, it may take some getting used to to just let allocated objects go without worrying about reclaiming their memory. Once you get used to it, however, you'll begin to appreciate what a nice feature this is. We'll discuss garbage collection in more detail in the next chapter.

Arrays

Most of what we learned in the previous sections about reference types and objects applies equally well to arrays in Java: The following subsections explain these and other details.

Creating and Destroying Arrays

There are two ways to create arrays in Java. The first uses new, and specifies how large the array should be:
byte octet_buffer[] = new byte[1024];
Button buttons[] = new Button[10];
Since creating an array does not create the objects that are stored in the array, there is no constructor to call, and the argument list is omitted with this form of the new keyword. The elements of an array created in this way are initialized to the default value for their type. The elements of an array of int are initialized to 0, for example, and the elements of an array of objects are initialized to null.

The second way to create an array is with a static initializer, which looks just like it does in C:

int lookup_table[] = {1, 2, 4, 8, 16, 32, 64, 128};
This syntax dynamically creates an array and initializes its elements to the specified values. The elements specified in an array initializer may be arbitrary expressions. This is different than in C, where they must be constant expressions.

Arrays are automatically garbage collected, just like objects are.

Multidimensional Arrays

Java also supports multidimensional arrays. These are implemented as arrays-of-arrays, as they are in C. You specify a variable as a multidimensional array type simply by appending the appropriate number of [] pairs after it. You allocate a multidimensional array with new by specifying the appropriate number of elements (between square brackets) for each dimension. For example:
byte TwoDimArray[][] = new byte[256][16];
This statement does three things:

When allocating a multidimensional array, you do not have to specify the number of elements that are contained in each dimension. For example:

int threeD[][][] = new int[10][][];
This expression allocates an array that contains ten elements, each of type int[][]. It is a single-dimensional allocation, although when the array elements are properly initialized to meaningful values, the array will be multidimensional. The rule for this sort of array allocation is that the first n dimensions (where n is at least one) must have the number of elements specified, and these dimensions may be followed by m additional dimensions with no dimension size specified. The following is legal:
String lots_of_strings[][][][] = new String[5][3][][];
This is not:
double temperature_data[][][] = new double[100][][10];	// illegal
Multidimensional arrays can also be allocated and initialized with nested initializers. For example, you might declare the following multidimensional array of strings for use by the getParameterInfo() method of an applet:
String param_info[][] = {
{"foreground",		"Color",		"foreground color"},
{"background",	"Color",		"background color"},
{"message",		"String",		"the banner to display"}
};
Note that since Java implements multidimensional arrays as arrays-of-arrays, multidimensional arrays need not be "rectangular." For example, this is how you could create and initialize a "triangular array":
short triangle[][] = new short[10][];	// a single-dimensional array
for(int i = 0; i < triangle.length; i++) {	// for each element of that array
triangle[i] = new short[i+1];		// allocate a new array
for(int j=0; j < i+1; j++)			// for each element of the new array
triangle[i][j] = i + j;			// initialize it to a value.
}
You can also declare and initialize non-rectangular arrays with nested initializers:
static int[][] twodim = {{1, 2}, (3, 4, 5}, {5, 6, 7, 8}};
To simulate multiple dimensions within a single-dimensional array, you'd use code just as you would in C:
final int rows = 600;
final int columns = 800;
byte pixels[] = new byte[rows*columns];
// access element [i,j] like this:
byte b = pixels[i + j*columns];

Accessing Array Elements

Array access in Java is just like array access in C--you access an element of an array by putting an integer-valued expression between square brackets after the name of the array:
int a[] = new int[100];
a[0] = 0;
for(int i = 1; i < a.length; i++) a[i] = i + a[i-1];
Notice how we computed the number of elements of the array in this example--by accessing the length field of the array. This is the only field that arrays support. Note that it is a read-only field--any attempt to store a value into the length field of an array will fail.

In all Java array references, the index is checked to make sure it is not too small (less than zero) or too big (greater than or equal to the array length). If the index is out of bounds, an ArrayIndexOutOfBoundsException is thrown.*

*The discussion of exceptions and exception handling is still to come.
This is another way that Java works to prevent bugs (and security problems).

Are Arrays Objects?

It is useful to consider arrays to be a separate kind of reference type from objects. In some ways, though, arrays behave just like objects. As we saw, arrays use the object syntax .length to refer to their length. Arrays may also be assigned to variables of type Object, and the methods of the Object class may be invoked for arrays. (Object is the root class in Java, which means that all objects can be assigned to a variable of type Object and all objects can invoke the methods of Object.)

The evidence suggests that arrays are, in fact, objects. Java defines enough special syntax for arrays, however, that it is still most useful to consider them a different kind of reference type than objects.

Declaring Array Variables and Arguments

In C, you declare an array variable or array function argument by placing square brackets next to the variable name:
void reverse(char strbuf[], int buffer_size)  {
char buffer[500];
...
}
In Java, you would have to declare buffer as an array variable, and then allocate the array itself with new, but otherwise you could use the same syntax, with the array brackets after the variable or argument name.

However, Java also allows you to put the array brackets after the type name instead. So you could rewrite this code fragment to look something like this:

void reverse(char[] strbuf, int buffer_size) {
char[] buffer = new char[500];
...
}

In a lot of ways, this new array syntax is easier to read and easier to understand. (It doesn't work in C, by the way, because pointers make C's type declaration syntax a real mess.) The only problem with this new syntax is that if you get in the habit of using it, it will make it harder for you when you (hopefully only occasionally!) have to switch back and program in C.

Java even allows you to mix the declaration styles, which is something you may find occasionally useful (or frequently confusing!) for certain data structures or algorithms. For example:

// row and column are arrays of byte.
// matrix is an array of an array of bytes.
byte[] row, column, matrix[];
// This method takes an array of bytes and an
// array of arrays of bytes
public void dot_product(byte[] column, byte[] matrix[]) { ... }

A final point to note about array declarations is that (as we've seen throughout this section) the size of an array is not part of its type as it is in C. Thus, you can declare a variable to be of type String[], for example, and assign any array of String objects to it, regardless of the length of the array:

String[] strings;			// this variable can refer to any String array
strings = new String[10];		// one that contains 10 Strings
strings = new String[20];		// or one that contains 20.

Strings

Strings in Java are not null-terminated arrays of characters as they are in C. Instead, they are instances of the java.lang.String class. Java strings are unusual, in that the compiler treats them almost as if they were primitive types--for example, it automatically creates a String object when it encounters a double-quoted constant in the program. And, the language defines an operator that operates on String objects--the + operator for string concatenation.

An important feature of String objects is that they are immutable--i.e., there are no methods defined that allow you to change the contents of a String. If you need to modify the contents of a String, you have to create a StringBuffer object from the String object, modify the contents of the StringBuffer, and then create a new String from the contents of the StringBuffer.

Note that it is moot to ask whether Java strings are terminated with a NUL character (\u0000) or not. Java performs run-time bounds checking on all array and string accesses, so there is no way to examine the value of any internal terminator character that appears after the last character of the string.

Both the String and StringBuffer classes are documented in Section 23, The java.lang Package, and you'll find a complete set of methods for string handling and manipulation there. Some of the more important String methods are: length(), charAt(), equals(), compareTo(), indexOf(), lastIndexOf(), and substring().

Operators

Java supports almost all of the standard C operators. These standard operators have the same precedence and associativity in Java as they do in C. They are listed below and also in quick reference form in Section 10, Java Syntax.

Java Operators

      |            |                   |        |
Prec. | Operator   | Operand Type(s)   | Assoc. | Operation Performed
------+------------+-------------------+--------+--------------------------------
1     | ++         | arithmetic        | R      | pre-or-post increment
      |            |                   |        | (unary)
      | --         | arithmetic        | R      | pre-or-post decrement
      |            |                   |        | (unary)
      | +, -       | arithmetic        | R      | unary plus, unary minus
      | ~          | integral          | R      | bitwise complement (unary)
      | !          | boolean           | R      | logical complement (unary)
      | (type)     | any               | R      | cast
2     | *, /, %    | arithmetic        | L      | multiplication, division,
      |            |                   |        | remainder
3     | +, -       | arithmetic        | L      | addition, subtraction
      | +          | String            | L      | string concatenation
4     | <<         | integral          | L      | left shift
      | >>         | integral          | L      | right shift with sign
      |            |                   |        | extension
      | >>>        | integral          | L      | right shift with zero
      |            |                   |        | extension
5     | <, <=      | arithmetic        | L      | less than, less than or equal
      | >, >=      | arithmetic        | L      | greater than, greater than
      |            |                   |        | or equal
      | instanceof | object, type      | L      | type comparison
6     | ==         | primitive         | L      | equal (have identical
      |            |                   |        | values)
      | !=         | primitive         | L      | not equal (have different
      |            |                   |        | values)
      | ==         | object            | L      | equal (refer to same object)
      | !=         | object            | L      | not equal (refer to different
      |            |                   |        | objects)
7     | &          | integral          | L      | bitwise AND
      | &          | boolean           | L      | boolean AND
8     | ^          | integral          | L      | bitwise XOR
      | ^          | boolean           | L      | boolean XOR
9     | |          | integral          | L      | bitwise OR
      | |          | boolean           | L      | boolean OR
10    | &&         | boolean           | L      | conditional AND
11    | ||         | boolean           | L      | conditional OR
12    | ?:         | boolean, any, any | R      | conditional (ternary)
      |            |                   |        | operator
13    | =          | variable, any     | R      | assignment
      | *=,    /=, | variable, any     | R      | assignment with operation
      | %=,        |                   |        |
      | +=, -=,    |                   |        |
      | <<=, >>=,  |                   |        |
      | >>>=,      |                   |        |
      | &=,    ^=, |                   |        |
      | |=,        |                   |        |
------+------------+-------------------+--------+--------------------------------
Note the following Java operator differences from C. Java does not support the comma operator for combining two expressions into one (although the for statement simulates this operator in a useful way). Since Java does not allow you to manipulate pointers directly, it does not support the reference and dereference operators * and &, nor the sizeof operator. Further, Java doesn't consider [] (array access) and . (field access) to be operators, as C does.

Java also adds some new operators:

+
The + operator applied to String values concatenates them.*
*To C++ programmers, this looks like operator overloading. In fact, Java does not support operator overloading--the language designers decided (after much debate) that overloaded operators were a neat idea, but that code that relied on them became hard to read and understand.
If only one operand of + is a String, the other one is converted to a string. The conversion is done automatically for primitive types, and by calling the toString() method of non-primitive types. This String + operator has the same precedence as the arithmetic + operator. The += operator works as you would expect for String values.

instanceof
The instanceof operator returns true if the object on its left-hand side is an instance of the class (or implements the interface) specified on its right-hand side. instanceof returns false if the object is not an instance of the specified class or does not implement the specified interface. It also returns false if the specified object is null. The instanceof operator has the same precedence as the <, <=, >, and >= operators.

>>>
Because all integral types in Java are signed values, the Java >> operator is defined to do a right shift with sign extension. The >>> operator treats the value to be shifted as an unsigned number and shifts the bits right with zero extension. The >>>= operator works as you would expect.

& and |
When & and | are applied to integral types in Java, they perform the expected bitwise AND and OR operations. Java makes a strong distinction between integral types and the boolean type, however. Thus, if these operators are applied to boolean types, they perform logical AND and logical OR operations. These logical AND and logical OR operators always evaluate both of their operands, even when the result of the operation is determined after evaluating only the left operand. This is useful when the operands are expressions with side effects (such as method calls) and you always want the side effects to occur. However, when you do not want the right operand evaluated if it is not necessary, you can use the &&amp; and || operators, which perform "short-circuited" logical AND and logical OR operations just as in C. The &= and |= operators perform a bitwise or logical operation depending on the type of the operators, as you would expect.

Statements

Many of Java's control statements are similar or identical to C statements. This section lists and, where necessary, explains Java's statements. Note that the topic of exceptions and the try/catch/finally statement is substantial enough that it is covered later in a section of its own.

The if/else, while, and do/while Statements

The if, else, do, and while statements are exactly the same in Java as they are in C. The only substantial difference arises because the Java boolean type cannot be cast to other types. In Java, the values 0 and null are not the same as false, and non-zero and non-null values are not the same as true.

The conditional expression that is expected by the if, the while, and the do/while statements must be of boolean type in Java. Specifying an integer type or a reference type won't do. Thus, the following C code is not legal in Java:

int i = 10;
while(i--) {
  Object o = get_object();
  if (o) {
    do { ... } while(j);
  }
}
In Java, you must make the condition you are testing for clear by explictly testing your value against 0 or null. Use code like the following:
int i = 10;
while(i-- > 0) {
  Object o = get_object();
  if (o != null) {
    do { ... } while(j != 0);
  }
}

The switch Statement

The switch statement is the same in Java as it is in C. You may use byte, char, short, int, or long types as the values of the case labels, and you may also specify a default label just as you do in C.

The for Loop

The for statement is perhaps the most useful looping construct available in Java. There are only two differences between the Java for loop and the C for loop. The first difference is that although Java does not support the C comma operator (which allows multiple expressions to be joined into a single expression), the Java for loop simulates it by allowing multiple comma-separated expressions to appear in the initialization and increment sections, but not the test section, of the loop. For example:
int i;
String s;
for(i=0, s = "testing";			// initialize variables
    (i = 1);		// test for continuation
    i++, s = s.substring(1))			// increment variables
{    
    System.out.println(s);			// loop body
}
As you can see, this "difference" between the Java and C for loops is really a similarity.

The second difference is the addition of the C++ ability to declare local loop variables in the initialization section of the loop:

for(int i = 0; i < my_array.length; i++) 
    System.out.println("a[" + i + "] = " + my_array[i]);
Variables declared in this way have the for loop as their scope. In other words, they are only valid within the body of the for loop and within the initialization, test, and increment expressions of the loop. Variables with the same name outside of the loop are not changed.

Note that because variable declaration syntax also uses the comma, the Java syntax allows you to either specify multiple comma-separated initialization expressions or to declare and initialize multiple comma-separated variables of the same type. You may not mix variable declarations with other expressions. For example, the following for loop declares and initializes two variables that are valid only within the for loop. Variables by the same name outside of the loop are not changed.

int j = -3;	// this j remains unchanged.
for(int i=0, j=10; i < j; i++, j--) System.out.println("k = " + i*j);

Labelled break and continue Statements

The break and continue statements, used alone, behave the same in Java as they do in C. However, in Java, they may optionally be followed by a label that specifies an enclosing loop (for continue) or any enclosing statement (for break). The labelled forms of these statements allow you to "break" and "continue" any specified statement or loop within a method definition, not only the nearest enclosing statements or loop.

The break statement, without a label, transfers control out of ("breaks out of" or terminates) the nearest enclosing for, while, do or switch statement, exactly as in C. If the break keyword is followed by an identifier that is the label of an arbitrary enclosing statement, execution transfers out of that enclosing statement. After the break statement is executed, any required finally clauses are executed, and control resumes at the statement following the terminated statement. (The finally clause and the try statement it is associated with are exception handling constructs and are explained in the next section.) For example:

test: if (check(i)) {
    try {
        for(int j=0; j < 10; j++) {
            if (j > i) break;		// terminate just this loop
            if (a[i][j] == null) 
                break test;		// do the finally clause and
        }				// terminate the if statement.
    }
    finally { cleanup(a, i, j); }
}

Without a label, the continue statement works exactly as in C: It stops the iteration in progress and causes execution to resume after the last statement in the while, do, or for loop, just before the loop iteration is to begin again. If the continue keyword is followed by an identifier that is the label of an enclosing loop, execution skips to the end of that loop instead. If there are any finally clauses between the continue statement and the end of the appropriate loop, these clauses are executed before control is transferred to the end of the loop.

The following code fragment illustrates how the continue statement works in its labelled and unlabelled forms.

big_loop: while(!done) {
    if (test(a,b) == 0) continue;	// control goes to point 2.
    try {
        for(int i=0; i < 10; i++) {
            if (a[i] == null) 
                continue;		// control goes to point 1.
            else if (b[i] == null)
                continue big_loop;	// control goes to point 2,
				// after doing the finally block.
            doit(a[i],b[i]);
				// point 1.  Increment and start loop again with the test.
        }
    }
    finally { cleanup(a,b); }
				// point 2.  Start loop again with the (!done) test.
}
Note the non-intuitive feature of the labelled continue statement: The loop label must appear at the top of the loop, but continue causes execution to transfer to the very bottom of the loop.

No goto Statement

goto is a reserved word in Java, but the goto statement is not currently part of the language. Labelled break and continue statements replace some important and legitimate uses of goto, and the try/catch/finally statement replaces the others.

The synchronized Statement

Since Java is a multithreaded system, care must often be taken to prevent multiple threads from modifying objects simultaneously in a way that might leave the object's state corrupted. Sections of code that must not be executed simultaneously are known as "critical sections." Java provides the synchronized statement to protect these critical sections. The syntax is:
synchronized (expression) statement
expression is an expression that must resolve to an object or an array. The statement is the code of the critical section, which is usually a block of statements (within { and }). The synchronized statement attempts to acquire an exclusive lock for the object or array specified by expression. It does not execute the critical section of code until it can obtain this lock, and in this way, ensures that no other threads can be executing the section at the same time.

Note that you do not have to use the synchronized statement unless your program creates multiple threads that share data. If only one thread ever accesses a data structure, there is no need to protect it with synchronized. When you do have to use it, it might be in code like the following:

public static void SortIntArray(int[] a) {
	// Sort the array a. This is synchronized so that some other
	// thread can't change elements of the array while we're sorting it.
	// At least not other threads that protect their changes to the
	// array with synchronized.
  synchronized (a) {
	// do the array sort here. 
  }
}

The synchronized keyword is more often used as a method modifier in Java. When applied to a method, it indicates that the entire method is a critical section. For a synchronized class method (a static method), Java obtains an exclusive lock on the class before executing the method. For a synchronized instance method, Java obtains an exclusive lock on the class instance. (Class methods and instance methods are discussed in the next chapter.)

The package and import Statements

The package statement, as we saw earlier in the chapter, specifies the package that the classes in a file of Java source code are part of. If it appears, it must be the first statement of a Java file. The import statement, which we also saw earlier, allows us to refer to classes by abbreviated names. import statements must appear after the package statement, if any, and before any other statements in a Java file. For example:
package games.tetris;
import java.applet.*;
import java.awt.*;

Exceptions and Exception Handling

Exception handing is a significant new feature of Java.*
*It is similar to, but not quite the same as, exception handling in C++.
There are a number of new terms associated with exception handling. First, an exception is a signal that indicates that some sort of exceptional condition (such as an error) has occurred. To throw an exception is to signal an exceptional condition. To catch an exception is to handle it--to take whatever actions are necessary to recover from it.

Exceptions propagate up through the lexical block structure of a Java method, and then up the method call stack. If an exception is not caught by the block of code that throws it, it propagates to the next higher enclosing block of code. If it is not caught there, it propagates up again. If it is not caught anywhere in the method, it propagates to the invoking method, where it again propagates through the block structure. If an exception is never caught, it propagates all the way to the main() method from which the program started, and causes the Java interpreter to print an error message and a stack trace and exit.

As we'll see in the subsections below, exceptions make error handling (and "exceptional condition" handling) more regular and logical by allowing you to group all your exception handling code into one place. Instead of worrying about all of the things that can go wrong with each line of your code, you can concentrate on the algorithm at hand and place all your error handling code (that is, your exception catching code) in a single place.

Exception Objects

An exception in Java is an object that is an instance of some subclass of java.lang.Throwable. Throwable has two standard subclasses: java.lang.Error and java.lang.Exception.*
*We'll use the term "exception" to refer to any subclass of Throwable, whether it is actually an Exception or an Error.
Exceptions that are subclasses of Error generally indicate linkage problems related to dynamic loading, or virtual machine problems such as running out of memory. They should almost always be considered unrecoverable, and should not be caught. While the distinction is not always clear, exceptions that are subclasses of Exception indicate conditions that may be caught and recovered from. They include such exceptions as java.io.EOFException, which signals the end of a file and java.lang.ArrayAccessOutOfBounds, which indicates that a program has tried to read past the end of an array.

Since exceptions are objects, they can contain data and define methods. The Throwable object, at the top of the exception class hierarchy, includes a String message in its definition and this field is inherited by all exception classes. This field is used to store a human-readable error message that describes the exceptional condition. It is set when the exception object is created by passing an argument to the constructor method. The message can be read from the exception with the Throwable.getMessage() method. Most exceptions contain only this single message, but a few add other data. The java.io.InterruptedIOException, for example, adds the following field:

public int bytesTransferred;
This field specifies how much of the I/O was complete before the exceptional condition occurred.

Exception Handling

The try/catch/finally statement is Java's exception handling mechanism. try establishes a block of code that is to have its exceptions and abnormal exits (through break, continue, return, or exception propagation) handled. The try block is followed by zero or more catch clauses that catch and handle specified types of exceptions. The catch clauses are optionally followed by a finally block that contains "clean-up" code. The statements of a finally block are guaranteed to be executed, regardless of how the code in the try block exits. A detailed example of the try/catch/finally syntax is shown in .Ref e .
 
try {
	// Normally this code runs from the top of the block to the bottom 
	// without problems.  But it sometimes may raise exceptions or
	// exit the block via a break, continue, or return statement.
}
catch (SomeException e1) {
	// Handle an exception object e1 of type SomeException
	// or of a subclass of that type. 
}
catch (AnotherException e2) {
	// Handle an exception object e2 of type AnotherException
	// or of a subclass of that type.
}
finally {
	// Always execute this code, after we leave the try clause,
	// regardless of whether we leave it:
	//    1) Normally, after reaching the bottom of the block.
	//    2) With an exception that is handled by a catch.
	//    3) With an exception that is not handled.
	//    4) Because of a break, continue, or return statement.
}

try

The try clause simply establishes a block of code that is to have its exceptions and abnormal exits (through break, continue, return, or exception propagation) handled. The try clause by itself doesn't do anything interesting; it is the catch and finally clauses that do the exception handling and clean-up operations.

catch

A try block may be followed by zero or more catch clauses that specify code to handle various types of exceptions. catch clauses have an unusual syntax: each is declared with an argument, much like a method argument. This argument must be of type Throwable or a subclass. When an exception occurs, the first catch clause that has an argument of the appropriate type is invoked. The type of the argument must match the type of the exception object, or it must be a superclass of the exception. This catch argument is valid only within the catch block, and refers to the actual exception object that was thrown.

The code within a catch block should take whatever action is necessary to cope with the exceptional condition. If the exception was a java.io.FileNotFoundException exception, for example, you might handle it by asking the user to check his or her spelling and try again. Note that it is not required to have a catch clause for every possible exception--in some cases the correct response is to allow the exception to propagate up and be caught by the invoking method. In other cases, such as a programming error signaled by NullPointerException, the correct response is to not catch the exception at all, but to allow it to propagate and to have the Java interpreter exit with a stack trace and an error message.

finally

The finally clause is generally used to clean up (close files, release resources, etc.) after the try clause. What is useful about the finally clause is that the code in a finally block is guaranteed to be executed, if any portion of the try block is executed, regardless of how the code in the try block completes. In the normal case, control reaches the end of the try block and then proceeds to the finally block, which performs any necessary cleanup.

If control leaves the try block because of a return, continue, or break statement, the contents of the finally block are executed before control transfers to its new destination.

If an exception occurs in the try block and there is a local catch block to handle the exception, control transfers first to the catch block, and then to the finally block. If there is not a local catch block to handle the exception, control transfers first to the finally block, and then propagates up to the nearest catch clause that can handle the exception.

Note that if a finally block itself transfers control with a return, continue, or break statement, or by raising an exception, the pending control transfer is abandoned, and this new transfer is processed.

Also note that try and finally can be used together without exceptions or any catch clauses. In this case, the finally block is simply cleanup code that is guaranteed to be executed regardless of any break, continue, or return statements within the try clause.

Declaring Exceptions

Java requires that any method that can cause a "normal exception" to occur must either catch the exception or specify the type of the exception with a throws clause in the method declaration.*
*C++ programmers note that Java uses throws where C++ uses throw.
Such a throws clause might look like these:
public void open_file() throws IOException {
	// Statements here that might generate an uncaught java.io.IOException
}
public void myfunc(int arg) throws MyException1, MyException2 {
    ...
}

Note that the exception class specified in a throws clause may be a superclass of the exception type that is actually thrown. Thus if a method throws exceptions a, b, and c, all of which are subclasses of d, the throws clause may specify all of a, b, and c, or it may simply specify d.

We said above that the throws clause must be used to declare any "normal exceptions." This oxymoronic phrase refers to any subclass of Throwable that is not a subclass of Error or a subclass of RuntimeException. Java does not require these types of exceptions to be declared because practically any method can conceivably generate them, and it would quickly become tedious to properly declare them all. For example, every method running on a buggy Java interpreter can throw an InternalError exception (a subclass of Error) and it doesn't make sense to have to declare this in a throws clause for every method. Similarly, as far as the Java compiler is concerned, any method that accesses an array can generate an ArrayIndexOutOfBoundsException exception (a subclass of RuntimeException).

The standard exceptions that you often have to declare are java.io.IOException and a number of its more specific subclasses. java.lang.InterruptedException and several other less commonly used exceptions must also be declared. How do you know when you have to declare a throws clause? One way is to pay close attention to the documentation for the methods you call--if any "normal exceptions" can be thrown, either catch them or declare them. Another way to know what exceptions you've got to declare is to declare none and wait for the compilation errors--the compiler will tell you what to put in your throws clause!

Defining and Generating Exceptions

You can signal your own exceptions with the throw statement. The throw keyword must be followed by an object that is Throwable or a subclass. Often, exception objects are allocated in the same statement that they are thrown in:
throw new MyException("my exceptional condition occurred.");

When an exception is thrown, normal program execution stops and the interpreter looks for a catch clause that can handle the exception. Execution propagates up through enclosing statements and through invoking functions until such a handler is found. Any finally blocks that are passed during this propagation are executed.

Using exceptions is a good way to signal and handle errors in your own code. By grouping all your error handling and recover code together within the try/catch/finally structure, you will end up with cleaner code that is easier to understand. Sometimes, when you are throwing an exception, you can use one of the exception classes already defined by Java API. Often, though, you will want to define and throw your own exception types.

.Ref e shows how you can define your own exception types, throw them, and handle them. It also helps clarify how exceptions propagate. It is a long example, but worth studying in some detail. You'll know you understand exception handling if you can answer the following: What happens when this program is invoked with no argument; with a string argument; and with integer arguments 0, 1, 2, and 99?

 0
// Here we define some exception types of our own.
// Exception classes generally have constructors but no data or 
// other methods.  All these do is call their superclass constructors.
class MyException extends Exception {
    public MyException() { super(); }
    public MyException(String s) { super(s); }
}
class MyOtherException extends Exception {
    public MyOtherException() { super(); }
    public MyOtherException(String s) { super(s); }
}
class MySubException extends MyException {
    public MySubException() { super(); }
    public MySubException(String s) { super(s); }
}
public class throwtest {
	// This is the main() method.  Note that it uses two
	// catch clauses to handle two standard Java exceptions.
    public static void main(String argv[]) {
        int i;
	// First, convert our argument to an integer
	// Make sure we have an argument and that it is convertible.
        try {
            i = Integer.parseInt(argv[0]);
        }
        catch (ArrayIndexOutOfBoundsException e) { // argv is empty
            System.out.println("Must specify an argument");
            return;
        }
        catch (NumberFormatException e) { // argv[0] isn't an integer
            System.out.println("Must specify an integer argument.");
            return;
        }
	// Now, pass that integer to method a().
        a(i);
    }
	// This method invokes b(), which is declared to throw
	// one type of exception.  We handle that one exception.
    public static void a(int i) {
        try {
            b(i);
        }
        catch (MyException e) {                              // Point 1.
		// Here we handle MyException and 
		// its subclass MyOtherException
            if (e instanceof MySubException)
                System.out.print("MySubException: ");
            else
                System.out.print("MyException: ");
            System.out.println(e.getMessage());
            System.out.println("Handled at point 1");
        }
    }
	// This method invokes c(), and handles one of the
	// two exception types that that method can throw.  The other 
	// exception type is not handled, and is propagated up
	// and declared in this method's throws clause.
	// This method also has a finally clause to finish up 
	// the work of its try clause.  Note that the finally clause
	// is executed after a local catch clause, but before
	// a containing catch clause or one in an invoking procedure.
    public static void b(int i) throws MyException {
        int result;
        try {
            System.out.print("i = " + i);
            result = c(i);
            System.out.print(" c(i) = " + result);
        }
        catch (MyOtherException e) {                          // Point 2
		// Handle MyOtherException exceptions:
            System.out.println("MyOtherException: " + e.getMessage());
            System.out.println("Handled at point 2");
        }
        finally {
		// Terminate the output we printed above with a newline.
            System.out.print("\n");
        }
    }
	// This method computes a value or throws an exception.
	// The throws clause only lists two exceptions, because
	// one of the exceptions thrown is a subclass of another.
    public static int c(int i) throws MyException, MyOtherException {
        switch (i) {
            case 0: // processing resumes at point 1 above
                throw new MyException("input too low");
            case 1: // processing resumes at point 1 above
                throw new MySubException("input still too low");
            case 99:// processing resumes at point 2 above
                throw new MyOtherException("input too high");
            default:
                return i*i;
        }
    }
}

Miscellaneous Differences

A number of miscellaneous differences between Java and C are described in the sections that follow. Miscellaneous differences that were mentioned elsewhere, such as the lack of the goto statement and the sizeof operator, are not repeated here.

Local Variable Declarations

A feature that Java has borrowed from C++ is the ability to declare and initialize local variables anywhere in a method body or other block of code. Declarations and their initializers no longer have to be the first statements in any block--you can declare them where it is convenient and fits well with the structure of your code.

Don't let this freedom make you sloppy, however! For someone reading your program, it is nice to have variable declarations grouped together in one place. As a rule of thumb, put your declarations at the top of the block, unless you have some good organizational reason for putting them elsewhere.

Forward References

For compiler efficiency, C requires that variables and functions must be defined, or at least declared, before they can be used or called. That is, forward references are now allowed. Java does not make this restriction, and by lifting it, it also does away with the whole concept of a variable or function declaration that is separate from the definition.

Java allows very flexible forward references. A method may refer to a variable or another method of its class, regardless of where in the current class the variable or method are defined. Similarly, it may refer to any class, regardless of where in the current file (or outside of the file) that class is defined. The only place that forward references are not allowed is in variable initialization. A variable initializer (for local variables, class variables, or instance variables) may not refer to other variables that have not yet been declared and initialized.

Method Overloading

A technique that Java borrows from C++ is called method overloading. Overloaded methods are methods that have the same name, but have different signatures. In other words, they return different values or they take different types of arguments, a different number of arguments, or the same type of arguments in different positions in the argument list. Method overloading is commonly used in Java to define a number of related functions with the same name, but different arguments. Overloaded methods usually perform the same basic operation, but allow the programmer to specify arguments in different ways depending on what is convenient in a given situation. Method overloading is discussed in more detail in the next chapter.

The void Keyword

The void keyword is used in Java, as in C, to indicate that a function returns no value. (As we will see in the next section, constructor methods are an exception to this rule.)

Java differs from C (and is similar to C++) in that methods that take no arguments are declared with empty parentheses, not with the void keyword. Also unlike C, Java does not have any void * type, nor does it require a (void) cast in order to correctly ignore the result returned by a call to a non-void method.

Modifiers

Java defines a number of modifier keywords that may be applied to variable and/or method declarations to provide additional information or place restrictions on the variable or method:
final
The final keyword is a modifier that may be applied to classes, methods, and variables. It has a similar, but not identical meaning in each case. A final class may never be subclassed. A final method may never be overridden. A final variable may never have its value set. This modifier is discussed in more detail in the next chapter.

native
native is a modifier that may be applied to method declarations. It indicates that the method is implemented elsewhere in C, or in some other platform-dependent fashion. A native method should have a semicolon in place of its body.

synchronized
We saw the synchronized keyword in a previous section where it was a statement that marked a critical section of code. The same keyword can also be used as a modifier for class or instance methods. It indicates that the method modifies the internal state of the class or the internal state of an instance of the class in a way that is not thread-safe. Before running a synchronized class method, Java obtains a lock on the class, to ensure that no other threads can be modifiying the class concurrently. Before running a synchronized instance method, Java obtains a lock on the instance that invoked the method, ensuring that no other thread can be modifying the object at the same time.

transient
The transient keyword is a modifier that may be applied to instance variables in a class. It specifies that the variable is not part of the persistent state of the object. This modifier is not currently used by any part of Java. Eventually it will be used to indicate things like scratch variables that are not part of an object's state, and thus never need to be saved to disk.

volatile
The volatile keyword is a modifier that may be applied to variables. It specifies that the variable changes asynchronously (e.g., it may be a memory-mapped hardware register on a peripheral device), and that the compiler should not attempt to perform optimizations with it. For example, it should read the variable's value from memory every time and not attempt to save a copy of it in a register.

No Structures or Unions

Java does not support C struct or union types. Note, however that a class is essentially the same thing as a struct, but with more features. And you can simulate the important features of a union by subclassing.

No Enumerated Types

Java does not support the C enum keyword for defining types that consist of one of a specified number of named values. This is somewhat surprising for a strongly-typed language like Java. Enumerated types can be partially simulated with the use of static final constant values.

No Bitfields

Java does not support the C ability to define variables that occupy particular bits within struct and union types. This feature of C is usually only used to interface directly to hardware devices, which is never necessary with Java's platform-independent programming model.

No typedef

Java does not support the C typedef keyword to define aliases for type names. Java has a much simpler type naming scheme than C does, however, and so there is no need for something like typedef.

No Variable-Length Argument Lists

Java does not allow you to define methods that take a variable number of arguments, as C does. This is because Java is a strongly typed language and there is no way to do appropriate type checking for a method with variable arguments. Method overloading allows you to simulate C "varargs" functions for simple cases, but there is no general replacement for this C feature.


Comments are welcome

Copyright © 1997 O'Reilly & Associates and