AutoCompile is a short example of Java which automatically recompiles any source files in a directory that have changed. You might like to look at this example to provide a bit of context for what follows. As an exercise, you might like to consider how the example would be extended to cope with multiple directories or new and deleted files.
Java is a simple class-structured language which uses a C-style syntax but does not carry with it the immense baggage of C++. A class is simply an encapsulation of data and procedure. Classes are the only structured type mechanism available in Java. So expect to see typical Java source as consisting of a series of class declarations containing the methods for those classes - and methods look very similar to Ansi-C procedures (i.e., with typed arguments).
Java source is written in Unicode, but this subtlety can usually be overlooked as Java compilers accept an ASCII encoding.
Java provides three types of comment: C-style, C++-style and its own document comment:
/* C-style comment */ // C++-style comment up to the line end /** Documentation comment. HTML can be embedded here along ** with a few other tags that the javadoc tool uses. **/The javadoc tool generates a set of documentation pages for your source in HTML. It is well worth investigating.
As in C, lexical tokens in Java are case-sensitive and separated by white-space. Also as in C, the longest compound token is formed regardless of context (i.e., ++ is always the increment operator not a dyadic and monadic plus next to each other).
Java provides a familiar set of primitive types:
+ - * / % ! == != < <= > >= & | && || << >> >>>
class <ClassName> { <Body> }and a declaration of a class which extends another is of the form:
class <ClassName> extends <ParentName> { <Body> }where Body may contain:
static <type> <name> [ = <initial-value>];where type is the type of the value held by the variable and the optional initial-value is the initial value of the variable when the class is loaded. Several variables may be declared with the same type by separating their definitions with commas.
static <type> <name> (<arguments>) { <body> }where type is the type of the value returned by the method and may be void; arguments are zero, one or many (comma-separated) declarations of each argument as type followed by name.
Hint: If you write one class containing all your methods and make them static, then you are close to writing a traditional C source file full of procedures. But you would be missing the point of Java!
static { <body> }
<type> <name>;and several variables of the same type can be declared by separating the names with commas.
<type> <name> (<arguments>) { <body> }in the same fashion as class methods. Instance methods have no analogy with procedural programming constructs.
<ClassName> (<arguments>) { <body> }Note that they are distinguished from instance methods by not specifying the return type and by having the same name as the class. One can be invoked as follows:
new <ClassName> (<arguments>)where arguments are the actual values to be bound to the constructor argument names. As it suggests, this delivers a new object of the class.
abstract [static] <type> <name> (<arguments>) {}A class containing abstract method declarations must itself be declared as abstract and may not be instantiated - only subclasses of it may be instantiated. The utility is that by manipulating the class itself, you can invoke the abstract method and get the behaviour defined by the subclass for the object as determined at runtime.
<ClassName>.<memberName>e.g., Math.sqrt, and an instance member access is of the form:
<object-expression>.<memberName>where object-expression is an expression which delivers an object of a class that contains memberName - most often this will be a variable name or the result of a method invocation.
Note that a declaration can be used before it has been seen (Java does not require warnings of declarations to come) and remember that not all members of a class are always visible - see earlier.
Members of a class are accessible within its declaration without the class or object dot prefix. Similarly declarations of any ancestor classes are also visible without the prefix. Sometimes member declarations may be overriden by argument or local declarations, in which case the full prefix notation can be used for discrimination and the pseudo-variables this and super are available for selecting against the object or its superclass. This is most often useful in a constructor, e.g.,
class Point { int x, y; Point (int x, y) { this.x = x; this.y = y; } }
Method invocations have an extra twist: overloading. A class may contain many method declarations with the same name as long as their arguments differ in type. Java uses the types of the actual arguments in the invocation to determine which version of the method to invoke. This also applies to constructors. Overloading is a powerful concept for hiding tedious detail that C programmers will need to get some familiarity with. Programmers in Algol 68, Fortran 90, Ada, C++ and so forth will find Java's overloading straight-forward.
interface <InterfaceName> { <abstract-method-declarations> }where abstract-method-declarations are method declarations with no bodies (but no abstract keyword either).
class <ClassName> [extends <ParentName>] implements <InterfaceNames> { <Body> }where InterfaceNames are a comma-separated list of one or more interface names. Such a class must contain declarations for all methods and with the same argument types as declared in the interfaces named.
Array types are described by appending square brackets to the element type,
e.g., int[ ] or int[ ][ ].
(Aside: In a declaration, you can also use C-style syntax
where the brackets go after the identifier.)
Arrays are created by an extension of the normal constructor:
new int [100](but, annoyingly, this cannot be elided with a declaration, so int [100] x; is illegal).
The number of elements in any array can be found with the instance variable length as in array.length.
Arrays can be indexed to access an element (either to read it or write to it, but you can't hold onto the reference):
x [n] = x [n+1]All arrays have an origin or lower bound of 0, and hence an upper bound of .length-1.
Arrays can be copied with the clone() method which delivers a new independent reference holding the same element values. e.g.,
int [ ] y = x.clone();y can then be modified in isolation to x.
An array of chars is not a String although there exist methods to convert between the two.
Multi-dimensional arrays are in fact constructed as arrays of arrays of...
A rectangular array can be constructed in one operation:
double [ ][ ] square = new double [3][3]or an arbitrary shape array can be constructed in several operations:
double [ ][ ] triang = new double [2][]; triang [0] = new double [1]; triang [1] = new double [2];
<type> <name> [ = <initial-value>];where, in contrast to C, initial-value is not restricted to a compile-time value. Java also allows variables to be referenced textually before its declaration: Java insists that local variables are assigned to before being referenced and this rule is used to prevent cyclic declarations such as int x = y, y = x; . Java also allows declarations and statements to be mixed within a block (Algol 68 programmers will like this!).
{ int x = f (1); push (x); int y = f (x); push (y); return x+y; }
<variable> = <value>where variable can be a class, instance, or local variable or an array element and value may be any expression of the same type as the variable. (Aside: Java provides automatic 'widening' conversions between the value and variable, but not 'narrowing' conversions in contrast to C and Fortran.)
if (<expression>) <statement> else <statement>where expression must be a boolean expression and where the else and following statement are optional. Java, like C, suffers from the dangling else problem and both resolve the problem by deciding that an else part belongs to the closest if statement.
Java, like C, has a separate form for a conditional expression:
<boolean-expr> ? <then-expr> : <else-expr>which delivers the value of then-expr if boolean-expr evaluates to true and delivers the value of else-expr.
switch (<expression>) { case <labelexpr>: <statements> break; default: <statements> break; }The switch expression must be of type int, short, byte or char. The labelexpr must be a compile-time constant. The statements in a case or default part are optional as is the break statement itself. As in C, a case part that doesn't end in a break statement 'falls through' to the next case part - this can be a source of error for the unaware - and the best stylistic use of this facility is to allow one group of statements to be entered via several case labels.
for (<init> ; <expression> ; <update>) <statement>init is a list of statements executed before the loop is properly underway. Java allows declarations here which have scope of just the loop. expression (which must be of type boolean) is a test made on each cycle of the loop (including the first) for whether to continue or not. update is a list of statements executed on each round trip of the loop and statement is the body of the loop. Any or all of init, expression and update are optional and statement can be an empty statement (i.e., just the semicolon).
for (int n = 0; n < array.length; n++) System.out.println (array [n]);A while statement and do statement are of the form
while (<expression>) <statement> do <statement> while (<expression>);both of which repeatedly execute statement while expression (which must be of boolean type) is true. The difference is that the while statement performs the test before each iteration (and hence statement might not be executed at all) and the do statement performs the test after each iteration (and hence statement is always executed at least once).
The break statement allows control to jump out of a loop and the continue statement allows control to jump back to the start of the loop. They are written:
break; continue;Java also allows a label to be specified after a break or continue in which case control transfers out to the statement with that label. We will leave this to the reader to discover uses for.
An exception is simply an instance of the class Exception or one of its descendants.
Note that an exception object must be created before an exception can be thrown. It is common
practice to define your own exception class to extend Exception and this provides
an easy opportunity to package up additional information about why the exception was
raised.
e.g.,
class ParseException extends Exception { String offendor; ... }and when we come to raise a ParseException we create a new object of this class and can specify a String offendor which might be the misunderstood text. This can then be read by the exception handler and used to direct the error recovery.
The throw statement takes an instance of an Exception and then control transfers outwards and upwards until an enclosing try statement is encountered.
throw <exception>;You can think of it as an unusual return statement.
The try statement is of the form:
try <statement> catch (<exception> <X>) <handler-statement> finally <final-statement>statement (usually a block) is the statement to be tried. There may be several catch parts, each of which defines a parameter X which is of type exception and which holds the actual exception object during processing of the handler-statement. The finally is a statement that will always be executed regardless of whether any exception was raised. It is optional.
Java makes the nice distinction between checked and unchecked exceptions: methods that can have a checked exception thrown out of them must declare this fact (Java beginners will find failure to do this a common compiler error, but easy to fix). Broadly speaking unchecked exceptions are things like trying to dereference a null pointer, while all user-defined exceptions are checked exceptions. So a method that can throw checked exceptions declares this after the argument list with the keyword throws followed by a list of exceptions, e.g.,
void DoSomethingRisky () throws MyException, OhMyGodExceptionHint: to catch any exception simply declare a catch part with the superclass Exception as the exception. You should investigate some of the standard methods provided with Exception - a useful one when debugging is printStackTrace().
package <my.package.name>;Package names exist in a (world-wide) global namespace and it is conventional to include your Internet hostname as part of the full package name (in reverse order though), so I might name a package as uk.co.demon.occl-cam.fruits.
Packages and classes within them are named using the 'dot' notation, e.g., the package java.lang contains the class java.lang.String. You can always reference classes this way, but it is obviously clumsy and Java lets you import the classes for use without any further formality. The import statements should be at the head of the source file after the package name if present. You can either import a specific class:
import java.io.DataStream;or you can import all classes in a package 'on-demand':
import java.io.*;(java.io is a standard package of I/O classes).
Packages are principally used for distributing libraries of classes or for partitioning large projects. You don't have to use them as there is a default package if no name is supplied into which all class declarations go providing a simple global namespace local to your working directory.
A simple structure can be expressed in Java as a class with no
methods and no inheritors.
For example, the C version might be:
typedef struct point { int x, y; } point;The Java version would be:
class point {int x, y; }
Unions have to be constructed `inside-out' in Java - start with a
superclass for the union as a whole and extend that class with the
constituent members of the union.
For example, the C version might be:
typedef struct { int x, y; } point; typedef struct { float x, y; } vector; typedef union { point p; vector v; } coord;The Java version would be:
abstract class coord { } class point extends coord { int x, y; } class vector extends coord { float x, y; }Java is strong-typed and will not let you write a point and read a vector. You must either query what type of a coord that you have or include methods within the classes to handle the specific cases.
Note however that this approach does not provide for types which are constituents of more than one distinct union.
Procedures need to be encapsulated in a class and it is often useful
to define an interface (just as in C you might typedef your
function for convenience of reference): a class can then
implement that interface.
For example, the C version might be:
typedef float *f (float) Function; float integrate (float lw, float up, int steps, Function f) { ... } float (float x) sin2x { return sin (2*x); } .... integrate (0.0, 0.4, 10, sin2x);The Java version would be:
interface Function { float (float) f; } class Integrator { public static float integrate (float lw, float up, int steps, Function f) { ... } } class sin2x implements Function { float (float x) f { return sin (2*x); } } .... Integrator.integrate (0.0, 0.4, 10, new sin2x);Compare this to the Algol 68 version:
PROC integrate = (REAL lw, up, INT steps, PROC (REAL) REAL f) REAL: ... .... integrate (0.0, 0.4, 10, (REAL x) REAL: sin (2*x))But note how easily the Java version can be extended to provide a Function which has hidden state (e.g., the standard algorithm for computing normal variates).
The development environment as provided by the JDK is still a little crude, but balance this against a good set of portable APIs to handle modern programming tasks (which no other major programming language that I am aware of can claim), integration with the new wave of Internet and intranet computing and it has to be a commercial winner.
How long this will last, who can tell - if the C++ crowd get their grubby paws on it, I may have something very different to say about Java 2.0!