|
|
The Java programming language |
|
Java is a trademark of Sun Microsystems, Inc. This tutorial introduction to Java is intended to provide some familiarity with the language syntax, its capabilities, and the history of its development. In conjunction with our pages on Smalltalk and C++, it will help in comparing various object-oriented programming languages (OOPLs). (Also see our OOPL comparison chart.) At the end of this page, there are links to more information about Java, including how to download the Java development kit and several free Java development environments. This tutorial assumes no previous knowledge of Java or C, but does assume that the reader has done programming in some computer language. It also assumes familiarity with object-oriented concepts.
During the late 1980s and early 1990s, several candidates competed to fill the role of "a better C" (to use the term applied by Bjarne Stroustrup to C++). "A better C" was generally taken to mean a programming language that was comfortable to programmers trained in the C language, eliminated some of the known problems with C, was capable of high performance, and incorporated modern object-oriented features. C++, for a while the leading contender, emphasized compatibility and performance. Objective-C sacrificed some C compatibility for the sake of a "purer" approach to object-orientation based on Smalltalk. The apparent winner, however, is a relative latecomer, Java. (Though a recent entrant into the field, Microsoft's C#, may contest the title.) Java didn't set out to be a better C for every programmer, and in fact had an identity crisis early in its life. It started out in 1991 as a language called "Oak", part of a small project called the "Green Team" initiated by Patrick Naughton, Mike Sheridan, and James Gosling, who is primarily credited with the design of the language that became Java. (Bryan Youmans has a page on the history of Java, with some interesting thoughts on the language design. There's also an official version of the history from Sun.)
In 1995, the sense was, as Wired put it, that "Java holds the promise of caffeinating the Web, supercharging it with interactive games and animation and thousands of application programs nobody's even thought of". Nearly a decade later, with many other technologies available for delivering active content, Java applets play a minor role. Java has prospered, though, running on middleware servers and even mainframes. It may even be coming full circle with Sun's EmbeddedJava and Wireless Java, which promise to make Java the kind of ubiquitous platform the Green Team originally envisioned. The technology that allows Java applets to be downloaded and run on many different client machines is called the Java virtual machine (JVM), and is similar to the virtual machine that is the runtime engine for Smalltalk systems. Most computer languages are compiled from source code into machine language specific to the computer on which they will run. On the web, it is not feasible to dynamically compile applets when they run. So Java is normally compiled into low-level, machine-independent bytecodes, which form the module that is downloaded to a client machine and executed by the JVM resident on the machine. A full discussion is beyond the scope of this tutorial, but virtual machine architectures have proved to be a relatively simple way to provide language support across a broad range of machine architectures. The downside of virtual machines is that they cannot provide performance equivalent to fully compiled code. However, experience with Smalltalk and Java has shown that performance is adequate for most applications, and that certain techniques allow VMs to come very close to the performance of native compilation. VMs also have the advantage that they can enforce dynamic runtime constraints, such as security rules that are essential for running active content in web browsers. Since we are primarily presenting the Java language here, the JVM and other non-language aspects of the Java platform will not be discussed further. The Java language is based on C, so it is first necessary to know a little about the C Language. If you are already familiar with C, you can skip the next section. If not, please read it, since many of the operators and logic control constructs of C are used almost identically in Java. A little CThe following, from the famous Kernighan and Ritchie book, is a simple C program: #include <stdio.h>
The #include embeds definitions from the standard I/O library needed for the printf statement. Every C program has a main() function, which is called when the program is executed. Braces ( { } ) delineate blocks of statements, in this case the single statement making up the body of main(). This statement prints "hello, world" when the program is executed ("\n" is the code for line feed, i.e., it indicates a line termination). The semicolon terminates a statement. The double quote is the character string delimiter in C; the comment delimiter is a double slash (//). Comments may also be delimited by bracketing them with "/*" and "*/". Variables in C may be primitive types, such as characters and numbers: char c = 'c'; // Character
Each of the statements above does two things: it declares the variable to be a certain type, e.g., int (integer), and it also sets the value of the variable. We could just as well do these two things in separate statements: int i;
Every variable must be declared, however, before it can be used. Variables may also be arrays of primitive types: char c[] = {'c', 'h', 'a', 'r'}; // Array of characters (string)
Arrays are referenced by the index of their elements, starting at zero for the first element; e.g., c[3] returns 'r'. (Some other languages, such as Smalltalk, use 1 instead of 0 as the index of the first element in an array). C variables may also be structures, containing other variables of different types. For example, the data associated with a bank savings account might be represented as:
struct savingsAccount {
The individual variables, or members, of the structure are referenced by qualifying the structure name with the member name:
struct savingsAccount myAccount;
In addition to main(), a program can have any number of other functions:
#include <stdio.h>
This example adds the function cube(int x), which returns x3 and is called from main(), plus a few other constructs. The expression i++ is common in C; it means "use the value of i, then increment i by one." The entire for expression means "start with i set to 1; use i in the statement (or block of statements) after the for; increment i by 1; keep doing this until i gets to 10; then continue the program after the for." The string "%i %s %i %s", first argument to the printf, is a format definition, which is required when printing multiple fields; it indicates that the following arguments are to be printed as integer, string, integer, string. The resulting output is
1 cubed is 1
The C if and while control structures resemble the for. Here is an example of if: int i, j;
Notice that the equality test in C is ==, and = is used for assignment. Most other operators are like those in other languages; != means "not equal." You will frequently encounter in C programs the rather cryptic variant int i;
instead of int i;
This works because C does not have a boolean type; instead, it uses 0 for false, and any integer greater than zero for true. Thus !i means "i is false," which is precisely equivalent to i == 0. (This code would not be legal in Java, which has a boolean primitive type, and does not allow conversion between integers and booleans.) C, unlike Java or Smalltalk, explicitly differentiates accessing an object directly (by value) from accessing it through a pointer (by reference). This is illustrated in the following example: int sum1(int x, int y) {
The variables i and j are declared as integers and set to 10. The variables pi and pj are declared as pointers to integers, and set to the addresses of i and j (int *pi means "the object pointed to by i is an integer"; &i means "the address of i"). Pointer references are useful when objects are large, because passing a pointer is faster than copying the object. They are needed when objects are dynamically allocated, since their locations are not known at the time the program is compiled. Java still has the distinction between value and reference, but it is handled automatically: "primitive" types (such as integers) are always accessed by value, complex types (objects and arrays) are always accessed by reference. Creation and dereferencing of pointers is handled "under the covers," so the programmer is not explicitly aware of it. This results in a significant simplification, which undoubtedly contributes to Java's popularity. Pointers allow the programmer to statically compile only a pointer, then dynamically allocate storage for the creation of data structures or objects at runtime. For example:
struct savingsAccount *myAccount;
malloc is a system function for memory allocation. Storage gotten with malloc must be explicitly released using free. Otherwise, it will be released only when the program ends, and the program may exhaust all the available storage if large numbers of arrays and structs are created. It is also important that the storage not be freed while it is still in use. Notice that whereas a direct structure reference uses dot notation (e.g., myAccount.balance), structure reference via a pointer uses an arrow (e.g., myAccount->balance). The operation in the fifth line of the above example, (struct savingsAccount *), is a cast, explained below. It tells the C compiler that what is returned by malloc, which is a pointer to a block of storage, is now to be treated as a pointer to a savingsAccount structure. Storage management is a major source of errors in C and C++ programs. Java, like Smalltalk, uses garbage collection and does not permit programmers to manage storage usage explicitly. The garbage collector runs periodically, and determines whether any references exist to objects. If not, the objects are deleted and their storage is freed. The designers of Java argued that the small additional overhead of garbage collection is a reasonable price to pay in return for not being exposed to storage allocation and deallocation errors made by programmers. One more feature of C that is relevant to Java is the notion of a cast. Casting, or type conversion, changes the type of an object. As an example of why this might be necessary, the standard C library function sqrt, which returns the square root of its argument, takes a floating-point double as its argument. Suppose we want the square root of an int - the cast operator (double) performs the appropriate conversion before calling sqrt:
int i;
Casting can be dangerous, particularly in combination with other error-prone features such as pointers; what will be the result of this code? #include <stdio.h>
The compiler says everything is fine, but the program prints out Square root of 4 is 1115.818982 In line 9, the programmer casts pi to int; pi, defined as "pointer to int", is a machine memory address, essentially a random number, thus the result. This error looks implausible in a short program; but in programs containing thousands of lines of code, perhaps written by several programmers, errors like this are not uncommon, and notoriously difficult to find. Java also permits C-type casting. Java compilers are stricter than C compilers about issuing errors or warnings about dangerous casts, but it is still possible to create bugs, particularly with primitive types. Back to JavaJava, like C++, Objective-C, and C#, adds objects, classes, and inheritance to C. Before delving too deeply into these object-oriented features, let's look at the syntax of a couple of simple Java programs. Simple Java programs: Here is the Java equivalent to the Kernighan and Ritchie "hello world" program:
// A very simple class
There are a few superficial differences, such as the specification of the argv parameter for the main() function, which passes the command-line arguments, if any, that were entered by the user when the program was started. (argv is actually used in C too, but need not be specified if there are no arguments expected. Java is stricter about function signatures.) A significant difference is that main() is no longer a free-floating function, it is a method in the class HelloWorld. Unlike in C (which does not have classes), or even in C++, functions in Java can only exist as methods in a class. Another example of this is how the program prints "hello, world". In C, printf is a function to print to an output stream; the default output stream is stdout, which is normally assigned to the console or command window from which a program is executed. In Java, the standard output goes to an instance of the PrintStream class. This instance is stored as a static variable in the System class. So the fifth line in the program gets a PrintStream instance from the variable out in System and invokes its print method with the string "hello, world" as argument. Notice that there is no #include statement. The concept in Java that is roughly analogous to #include is import, which names a package and causes the classes from that package to be accessible. No import is needed to access System here, but only because System is part of the java.lang package, which is automatically imported for all Java programs. We could have added import java.lang.*; as the first line of the program, but in this special case it is not necessary. The "*" implies that all classes in a package are to be imported; it is also possible to import individual classes: import java.lang.System; Note that the dot notation used to reference fields and methods in an object is descended from the dot notation used in a C-language struct. In fact, one way to think of a class in Java is as a struct that has functions (methods) as well as data fields or members. (In C++, struct is synonymous with class; Java does not use the struct keyword.) Methods and variables defined with the keyword static are called class methods and class fields. They are associated with the class, and do not require creation of an instance. Standalone programs like this are compiled from source code containing a single public class, which produces a Java class file (named, in this case, HelloWorld.class). The class file is executed from a command like this: java HelloWorld Which tells the Java runtime module to look for class file HelloWorld.class, and start executing its main method. (Other ways of executing Java progams, such as the applet we talked about earlier, are discussed below.) Here is a more complicated program, similar to the C program above that prints the cubes of the integers 1-10. This progam adds the functionality of accepting a command-line argument specifying how many cubes are to be printed:
public class PrintCubes {
Notice that the function cube is private, meaning it can only be invoked from within the HelloWorld class. The main method must be public, since it is invoked from outside the class. Java also has a protected modifier, designating a method that can be invoked from within the class or from any of its subclasses, but not from unrelated classes. Line 8 says "if there is a command line argument, assume it is an integer specifying the number of cubes to be printed". Before unpacking this, a brief digression: Java, for efficiency and compatibility, retains all the primitive data types of C: int,double, etc., and even adds some new ones, such as boolean. These are not objects, i.e., they are not instances of classes. (Thus Java is not a "pure" object-oriented language like Smalltalk.) Corresponding to each primitive type, however, is a class with a similar name: Integer corresponding to int, Boolean corresponding to boolean, etc. Each of these classes just "wraps" a primitive value, and adds behavior to it. The most common use of these wrapper classes is to store primitive objects in collections. Though the Java primitive array can hold primitive values, just as in C, instances of the more complex collection classes (such as ArrayList, which is used in the banking example below) cannot. These collections must hold reference, or object, types rather than primitives, thus the need for wrapper classes. Another example of added behavior is used in line 8: argv[0], the first command line argument, is a string (just as in C). The Integer class has a static method parseInt() that takes a string like "20" as argument and answers the corresponding int value. Line 8 of the program uses this to set iterations to the command-line argument entered by the user. Class Integer also has a constructor function that takes a string as argument, and returns an Integer instance that wraps the corresponding int value. E.g., new Integer("20") instantiates an instance of Integer wrapping the integer value 20. (The new operator must always be used when invoking a constructor.) The method intValue(), invoked on the instance just constructed, returns the int 20. Thus instead of using parseInt(), line 8 of the program could have been coded as if (argv.length != 0) iterations = (new Integer(argv[0])).intValue(); Note that all the standard "primitive" computational operators such as + require that their operands be int values, and will not work with Integer instances. As mentioned above, there are other contexts (such as holding numbers in collections) where we need objects, not primitive types; in those contexts we use objects like Integer and Double instances rather than int or double primitives. This complicatation is not present in C, or in OO languages such as Smalltalk, which do not have primitive types at all. One last point on this program: The + in line 11 is not doing addition, it is a string concatenation operator. If one of the arguments to + is a String, and the type of the result (in this case, the type of the argument expected by println) is String, Java assumes string concatenation and converts the other argument to a String. (All objects and primitives in Java can be converted to String.) String concatenation is the only case where the operator + can be overloaded in this way. In the example application below, we use instances of another number class, BigDecimal; in calculating with BigDecimal, we must use methods like add() and multiply(), rather than primitive operators like + and *. This inability to overload is a purely syntactic feature of Java; other OO languages, such as C++ and Smalltalk, do allow overloading of infix arithmetic operators. Object-oriented programming in Java: The two programs above, though they are implemented as Java classes, are not object-oriented in spirit. HelloWorld and PrintCubes are really just procedural programs with a veneer of objects. For programmers coming to Java from C or another procedural language, writing thinly-disguised procedural code is an easy trap to fall into. As a consequence, the programmer loses the the clarity and structuring capabilities that constitute the power of object-oriented programming. A "real" Java progam contains the following elements:
The big picture, which is the same for Java or any other OOPL, is this: In procedural programs, work is done by passing data to functions; the overall work of a program is done by a collection of functions derived by "top-down functional decomposition". In object programs, work is done by message-passing among autonomous objects, derived by analysis of the entities in the real-world problem domain. Each object encapsulates both data and functionality, and other objects know only its interface, not the details of its implementation. Before looking at an example application (which sends promotional letters to all the accountholders at a bank), let's examine the structure of the source code for a Java class, since classes (and their instances) are the building blocks of all Java programs. Structure of a Java class: Here's the outline of a class definition. We'll discuss only the highlights here:
<modifiers> class <class_name>
The most common modifier for a class is public, indicating that the class is accessible to any other class in the program. Another common modifier is abstract, indicating that the class cannot be instantiated. This is done when a superclass is meant only to provide common behavior to concrete subclasses (the BankAccount class below is an example). The final modifier indicates that a class cannot be subclassed; this is typically done for security reasons. (The final modifier can also be used for variables, in which case it means that the initial value set for the variable cannot be changed. i.e., it is a constant.) A class extends its superclass. The classes in the example above, such as HelloWorld, did not use this keyword. This does not mean they have no superclass; if the superclass is not specified, it defaults to Object, so we could have written public class HelloWorld extends Object { Passing over (for the moment) the meaning of implements, we come to class and instance field (also called variable) definitions, which look like this: <modifiers> <variable type> <variable name>; These are comparable to data definitions in C, but with added modifiers like public, private, and protected, which have the same meaning as described above for methods (i.e., they control the scope of visibility of the variable). A static initializer block is an unnamed block of code inside brackets ( { and } ) that is executed once when the class is loaded. It is used, if necessary, to perform complex initializations such as setting up tables, etc. Next we find definitions of constructors, which are like methods with the same name as the class, used to initialize new instances. Constructors are defined with no explicit return type, since they always return an instance of the class in which they are defined. (An example of a class with more than one constructor is shown below.) Constructors are always invoked with the new operator, e.g., for the class Point, which can take as constructor arguments the x and y coordinates of the point: Point originPoint = new Point(0, 0); Finally, there are method definitions; those defined as static are class methods, the remainder are instance methods. These look a lot like C function definitions, except for the modifiers and the fact that functions in Java are always defined as methods within the body of a class definition: <modifiers> <return type> <method name>(<argument definitions>) {
There is one extra "hidden" parameter passed to any instance method, which is the particular object that is the "message receiver". This object can be explicitly identified within the method body as this, though it is usually not necessary; any method invocation that does not explicitly identify the receiving object automatically goes against this. So these two lines of code within a method produce identical results: public int fooMethod {
Java interfaces and dynamic binding: The keyword implements in a class definition refers to a Java interface. An interface is a specification of methods that can be implemented by any class. Interfaces can be used to document the "contract" that must be fulfilled by certain objects. They are also essential for the general use of dynamic binding in Java. Recall that dynamic or "late" binding is deferring the decision on which class a method comes from until runtime. (The resulting behavior is called polymorphism.) An example is the method toString(), which renders an object as a string, usually for printing. This method is defined in class Object, and overridden in subclasses. Note the two invocations of toString() in this code fragment: Object printable;
Though printable is defined as Object, the first occurrence invokes toString() in Integer (which prints 5), the second invokes it in TreeSet (which prints [1, 2, 3]. The choice of method is based on the class of the actual object assigned to printable. The example just given depends only on inheritance; the compiler knows that a valid toString() method will exist at runtime for the class of the printable object, because there is a default implementation of toString() in class Object. Now let's look at an example that requires interfaces. The TreeSet class used above is a set (collection with no duplicate members) whose elements can be iterated in order. To maintain its elements in sorted order, TreeSet requires that they support a method that allows two elements to be compared to see which one goes first (this is essential for any sort algorithm). The particular method that's used is compareTo(). How can a TreeSet guarantee that any object added to it supports compareTo()? It is not reasonable to just implement compareTo() in class Object, because some objects have no natural ordering. It's not even reasonable to make all objects with a natural ordering inherit from something like ComparableObject, because comparability is just one small property among many similar ones that might demand similar treatment; since Java only supports single inheritance, a class could only inherit one of these properties. Java's solution is interfaces. Though a class can inherit from only one superclass, it can implement many interfaces. So a TreeSet does not specify any particular class for its elements, but requires that they implement the Comparable interface. The method that actually does the comparison (somewhat simplified) looks like this: // Compare two keys using the correct comparison method
If the object being added does not implement Comparable, the cast (Comparable)k1 will fail. Similarly, in the following code line 2 is OK because class Integer implements Comparable, but line 3 will cause an error because class Object does not: Comparable c, d;
Thus in terms of type-checking, interfaces work just like classes, but avoid the complexity of multiple inheritance. Other examples of commonly-used interfaces in Java include Collection, which specifies the required interface for objects that hold collections of other objects, and Serializable, for objects that can render themselves as streams of bytes (e.g., for transmission on a network or storage in a file). Two example classes: Here are two complete source files for Java classes used in the sample application below: BankAccount and its subclass SavingsAccount. They illustrate many of the points discussed above. Both classes import java.math.* in order to use the BigDecimal class, which models fixed-point decimal numbers such as account balances. Because BankAccount implements the Comparable interface, it must have a compareTo() method, which in this case sorts by account number. The constructor insures that the variable active is always set in a new BankAccount. import java.math.*;
Notice that BankAccount is abstract, and it has two methods declared abstract. These methods must be implemented by any concrete subclass of BankAccount, and they are indeed implemented in the SavingsAccount subclass:
import java.math.*;
Another thing to notice about the relationship between BankAccount and SavingsAccount is that all the instance variables in BankAccount are declared as private. This means that they cannot be accessed from outside the class, not even by its subclasses. Thus if the getWithdrawalLimit() method in SavingsAccount were coded like this: return balance; it would cause a compiler error. BankAccount supplies get...() and set...() methods for all its instance variables to help insure that they are used correctly. There are sometimes good reasons for classes to expose their instance variables to subclasses, or even to expose them to the world by declaring them as public; one occasionally sees a Java class that looks like a C struct, with public instance variables and no methods. But generally, such a lack of encapsulation reflects poor design. The static class variable ACCOUNT_TYPE is declared final, so it cannt be changed. interestRate is also static, since it is common to all instances of SavingsAccount, but it can be changed by using the static method setInterestRate(). More information on these classes and the others from the sample application can be found in the javadoc for the application. An example application: Let's take a simple example. A bank occasionally sends informational or promotional letters to all its accountholders, or to all holders of a particular kind of account, such as savings. We want a program that will produce printed letters and addressed envelopes based on the following inputs:
Classes involved in this problem (representing "real-world" entities) include BankAccount, subclasses such as SavingsAccount, Name and Address (sub-objects of BankAccount), Letter, Envelope, and various "technical" classes representing files, databases, etc. The example code has been radically simplified to make a compact example, but it is a fully working application. Some of the things that are missing, such as exception handling, are discussed below. The design has also been simplified; for example, it would be preferable to use XML as a template for the letter text, making it easy to plug in names, salutations, etc. Lots of code for validity-checking inputs is also missing. But the example as given is sufficient for illustrative purposes.
Here is a diagram of the classes and relationships involved in this application,
using the conventions of the industry-standard
UML (Unified Modeling Language):
LetterGenerator contains the sequencing logic for the program. It uses the standard Java classes FileInputStream and FileOutputStream for reading in the "boilerplate" letter text, and printing the content of the envelopes and letters. (In a more complete application, a subsequent process would take the output file and physically print the envelopes and letters.) BankAccountFile represents access to the database in which bank accounts are stored. In this sample application, it merely returns a small collection of test accounts. Note the use of a static initializer block to initialize the test accounts. BankAccount, described above, is the abstract superclass for SavingsAccount and CheckingAccount. Bank accounts have instances of Name and Address to store information on the account owner. Notice the three different constructors in Name: One taking no arguments, one taking a string that is parsed into its components, and one taking three strings for title, first name, and last name. You might wonder why Name and Address are classes, and not just strings stored in BankAccount. Either implementation is possible, but making them separate classes simplifies the coding of BankAccount, and also makes the Name and Address classes available for use in other contexts. (Click on the links to see the Java class definition files.) Here is a UML sequence diagram showing the flow of messages between objects
in the process implemented by
LetterGenerator:
The objects involved are along the top axis of the chart. (Iterator is a Java interface defining iteration over a collection. You can think of it as a pointer, or cursor, ranging over the elements in the collection.) Messages (method invocations) from one object to another are indicated by lines with an arrow; the object returned is shown by an arrow with a small circle at the origin. The sequence (time flow) is from top to bottom. An arrow from an object to itself indicates method invocation or iteration within the same object; in that case, the lines of the arrow are drawn to bracket the code that is being executed. In some cases, pseudocode such as "while more accounts" is used to summarize what's going on. The "actor" icon in the upper left indicates that the process is kicked off by a user invoking the program with parameters specifying the boilerplate text, and which accountholders are to be sent the letter. For example, java LetterGenerator letter.txt S > lettersOut.txt invokes the program to send the text of letter.txt to the holders of savings accounts, and print the result to letter.txt. These UML charts use a somewhat simplified form of the full notation. Purists may object, but we feel that it is a waste of time and paper to produce diagrams at an unnecessary level of detail. For Java in particular, a small number of high-level diagrams in UML or similar format, plus conscientious use of javadoc, seems to be optimal in terms of value for time expended in documentation. Exception handling in Java: The PrintCubes program above expected one command-line argument, an integer specifying the number of cubes to print. The parameter is entered as a string, passed to the program, and converted to an integer: iterations = Integer.parseInt(argv[0]); What if the user makes an error and enters, say "t" instead of "5"? The result will be an output like this: Exception in thread "main" java.lang.NumberFormatException: t
This is not a very obvious way of telling the user that "t" is not a valid parameter. What has happened is a Java NumberFormatException in the process of trying to convert "t" into an integer. Since we can readily anticipate this kind of error, we'd like to catch it and present a friendlier message to the user. Java exception handling supports that, as shown in the following revised initialization of iterations:
if (argv.length != 0) {
The try block is the code in which we anticipate that an exception may occur; the immediately following catch block specifies which kind of exception is anticipated, and contains the code to be executed if it occurs: In this case, print an error message and exit. The error message presented to the user is now:
Usage: PrintCubes <number of iterations>
If a method in a Java class anticipates an exception, but wants the method caller to handle it, it may announce that fact by using the throws keyword. As an example, many methods in BufferedReader and FileInputStream throw IOException, for instance if a file encounters a physical I/O error. The caller must decide what to do if this happens, so methods are coded in FileInputStream like this:
public int read(byte b[]) throws IOException {
If a method throws an exception, any method that invokes it must either have a catch block identifying the exception, or announce that it throws the exception so that its caller can handle it. If not, a compiler error will result. You may have noticed an example in the code for LetterGenerator: The run() method invokes methods in FileInputStream but does not catch IOException, so it throws it to its caller. The main() method, which invokes run(), also does not catch IOException, so it must throw it. Ultimately, if there is no catcher, the exception causes the program to terminate and print several error lines like what we saw above. In real applications, the IOException should be caught somewhere, at least for the purpose of printing a more meaningful error message. Documenting Java code with javadoc: The javadoc program, supplied with the Sun JDK, is, in our opinion, the best documentation tool ever developed for programs. Most documentation schemes fail because they are too complex, require too much work on the part of programmers (who really just want to get code working), or maintain the documentation in separate files which must be (but rarely are) updated when the code is changed. javadoc generates documentation directly from the code, and requires only a modest investment on the part of the programmer to produce a useful result. There are three kinds of comments that can be used in Java code: // This is a single-line comment.
The third comment looks to the compiler like an ordinary multiline comment, but the extra * flags it as meaningful to javadoc. These comments, along with the actual structure of the Java code, are used to generate HTML documentation. The best way to see how this works is to look at the actual javadoc documentation, along with the comments placed in the class source code files ( LetterGenerator, BankAccountFile, BankAccount, SavingsAccount, CheckingAccount, Name, and Address). Java Programs, applets, Java server pages, and servlets: In addition to standalone Java programs, there are two types of programs associated with the World Wide Web: Applets, encountered above, which are downloaded and executed under control of the client web browser; and servlets, which are invoked by HTML pages and run on the web server. Whereas standalone programs are always entered via the main() method, other methods must be coded for applets and servlets. An applet is invoked from an HTML page by code like this: <applet code=MyApplet.class>
MyApplet.class is the name of the class file output by the Java compiler, and is downloaded to the client. The applet code must be a subclass of Applet. Methods that applets typically override from Applet include init(), called by the browser to tell the applet that it has been loaded into the system; start(), called by the browser to tell the applet that it should start executing; and stop(), called by the browser to tell the applet that it should stop executing. Applet, its superclasses, and other Java classes provide a wealth of functionality to build applets that provide graphics, sound, image display, animation, and other capabilities. A servlet is also invoked by HTML code, typically resulting from some user action such as submitting a form. The servlet runs on the server, though, not on the client. Here is an example of servlet incovation: <form action="/servlets/MyServlet" method="post"> This is similar to the invocation of a CGI (Common Gateway Interface) script. MyServlet.class must be the name of a class file containing a class named MyServlet, which must be a subclass of HttpServlet. The server invokes the servlet through its doGet() or doPost() method, depending on whether the action specified in the HTML is get or post. The servlet then performs whatever action is appropriate (e.g., searching a database), builds an HTML response, and sends it back to the client browser. Java code can also be imbedded directly in HTML, using Java server pages (JSPs), much like a scripting language. Here is a very simple example; <% and %> bracket the Java code:
<html>
This outputs a line like Hello! It is now Fri Jul 05 23:43:56 MDT 2002 Unlike client-side scripting, an HTML page with embedded JSP code is converted and compiled on the server into a servlet class and executed. (If you're interested, here is the servlet Java code generated for this example by the Apache Tomcat server.) There is some skepticism about JSP. Much of this boils down to the same thing that can be said of ASP, client-side JavaScript, and other scripting technologies used on the web: Web pages are necessarily dedicated to presenting a user interface, and mixing application logic with UI code is bad practice. To this can be added the facts that HTML pages in general, and JSP in particular, have very little structuring capability, and that web page coding is often done by people with very little programming experience. All in all, JSP needs to be used carefully if you expect to build a reliable and maintainable web site. * * * * * * * * * We have now briefly covered the high points of the Java language. There are many aspects of programming in Java, ranging from Java Beans to inner classes to user interface programming, that are beyond the scope of this short tutorial. For these, we refer you to the links below. For programming practice, the basic tools, including good graphical IDEs (Integrated Development Environments) are free or reasonably priced. Enjoy! Links
General information and tutorials
Downloads
Coding style and standards
Performance
|