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.
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.
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);
}
}
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 myappSee Section 13, System Properties and Applet Parameters, for more information on system properties.
david.games.tetris.SoundEffects.play()
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.
-------------------------------------------------------------- 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 ---------------+----------------------------------------------
setenv CLASSPATH .:~/classes:/usr/local/classesThis tells Java to search in and beneath the specified directories for non-system classes.
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.
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.
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.
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.
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.
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.
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";
--------+-------------------+---------+---------+----------------------------
--------+-------------------+---------+---------+----------------------------
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
--------+-------------------+---------+---------+----------------------------
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 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.
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.*
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.
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.
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.
There are two reasons for these restrictions:
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).
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.)
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();
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.
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.
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]; // illegalMultidimensional 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];
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 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.
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.
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().
| | | |
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 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);
}
}
int i;
String s;
for(i=0, s = "testing"; // initialize variables
(i < 10) && (s.length() >= 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);
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.
synchronized (expression) statementexpression 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.)
package games.tetris; import java.applet.*; import java.awt.*;
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.
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.
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.
}
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.
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.
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!
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;
}
}
}
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.
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.
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.
Java in a Nutshell | O'Reilly Java Page