JAVA OVERVIEW
CONTENTS
- Introduction
- A Brief History of Java
- Applets
- Java's Features
- The Fundamentals of the Java Language
- Java as an Object-Oriented Language
- Basic Coding Elements
- Flow of Execution
- Using Classes, Objects, and Interfaces
- How Java Differs from C++
- Obtaining the Java SDK
- The Java Development Environment
- Compiling with Java
- Creating Objects from Java Classes
- Creating Your Own Classes
- Creating Interfaces
- Using the Garbage Collector
- Packages in Java
- java.lang
- java.awt
- java.applet
- java.awt.image
- java.awt.peer
- java.io
- java.net
- java.util
- The Sample Applet: The Order Entry System
- The Java AWT
- Applets
- Applet Limitations
- The Applet Life Cycle
- Adding Applets to Web Pages
- Netscape Navigator and Applets
- Applets and HotJava
- Linked Lists, Queues, Search Trees, and Other Dynamic Data Structures
- Using the Utilities Package
- Classes
- Why the Java Database Connectivity (JDBC) Specification?
- Storing Data for the Web
- Providing Access to Data
- The JDBC API
- Simple Database Access Using the JDBC Interfaces
- Result Sets and the Meta-data Interfaces
- Other JDBC Functionality
- Building a JDBC Implementation
- Extending JDBC
- Designing a Database Application
- What Is a Thread?
- Problems with Multithreading
- Synchronizing Threads
- The Dining Philosophers Problem
- The Java Language
- The Java Compiler
- The Java Interpreter
- The Execution of Code
- Java Virtual Machine
- Limitations
- Known Bugs
- Future Java Security
Introduction
If you have purchased this book, you are probably planning to program with Java. There are many reasons for using Java as your programming language. You might want to learn an object-oriented programming language, and maybe you've heard that Java is simple to use. You might have come across some interesting applets while browsing the Internet (if you're using a Java-capable browser), and perhaps you want to learn to write applets of your own and add them to your Internet or intranet HTML documents. You might want to learn how to add Internet functionality to your C applications or how to use Java to write full-fledged applications that are portable. This book helps you accomplish any and all of these goals.
This overview introduces the many features of Java and explains why they are important to you, the Java programmer. It is worthwhile for you to understand the richness and usefulness of Java before getting into the specifics of the Java environment and language.
Java is an interpreted language that is similar, superficially, to C++, but different in many important ways. Java was originally intended to be an extension of the C compiler but has been completely rebuilt from the ground up to be a smaller, portable, purely object-oriented language that eliminates many of the sources of bugs and complexities common in C++.
A Brief History of Java
Java has been around since 1991, developed by a small team of Sun Microsystems developers in a project originally called the Green project. The intent of the project was to develop a platform-independent software technology that would be used in the consumer electronics industry. The language that the team created was originally called Oak.
The first implementation of Oak was in a PDA-type device called Star Seven (*7) that consisted of the Oak language, an operating system called GreenOS, a user interface, and hardware. The name *7 was derived from the telephone sequence that was used in the team's office and that was dialed in order to answer any ringing telephone from any other phone in the office. This PDA-type device was intended to be sold to consumer electronics manufacturers who would distribute the boxes under their company name. In 1993, the team, then incorporated as FirstPerson, Inc., decided to gear their technology toward a new implementation for which demand was building in the entertainment industry-interactive television. They proposed their technology to Time Warner as an operating system for set-top boxes and video-on-demand technology that would decode the data stream that Time Warner would be sending to television sets around the country. In June of 1993, Time Warner selected Silicon Graphics' technology over Sun's. A later deal fell apart and FirstPerson decided to disband. Half of the members of the original FirstPerson team continued to work with the Oak technology, however, applying it to multimedia and network computing.
Around the time the FirstPerson project was floundering in consumer electronics, a new craze was gaining momentum in America; the craze was called "Web surfing." The World Wide Web, a name applied to the Internet's millions of linked HTML documents was suddenly becoming popular for use by the masses. The reason for this was the introduction of a graphical Web browser called Mosaic, developed by ncSA. The browser simplified Web browsing by combining text and graphics into a single interface to eliminate the need for users to learn many confusing UNIX and DOS commands. Navigating around the Web was much easier using Mosaic.
It has only been since 1994 that Oak technology has been applied to the Web. In 1994, two Sun developers created the first version of HotJava, then called WebRunner, which is a graphical browser for the Web that exists today. The browser was coded entirely in the Oak language, by this time called Java. Soon after, the Java compiler was rewritten in the Java language from its original C code, thus proving that Java could be used effectively as an application language. Sun introduced Java in May 1995 at the SunWorld 95 convention.
Web surfing has become an enormously popular practice among millions of computer users. Until Java, however, the content of information on the Internet has been a bland series of HTML documents. Web users are hungry for applications that are interactive, that users can execute no matter what hardware or software platform they are using, and that travel across heterogeneous networks and do not spread viruses to their computers. Java can create such applications.
Applets
On the Internet, Java programs are called applets. Applets are Java applications that are embedded inside HTML files and can be downloaded into a Java-capable browser with the click of a mouse. Applets are different from regular Java applications. A Java application simply has a single main() method that indicates to the Java runtime system that it is an application. A Java applet is an application that includes several additional methods that the runtime system uses that tell it how to handle the applet, such as what to do when a user clicks an applet icon and how it looks on a page.
Before your browser's runtime Java interpreter downloads and executes the applet's code, the Java interpreter verifies the code's integrity. Java is more than a tool to help you write applets, however. It is a new, powerful programming environment.
Java's Features
Sun describes Java as a "simple, object-oriented, interpreted, robust, secure, architecture-neutral, portable, high-performance, multithreaded, and dynamic language."
Each of the features mentioned in this quotation from Sun's Web page is an important part of the Java development environment as well as a critical requirement for Web programming. The combination of these features makes Java a powerful and useful programming language that empowers you, the programmer, with the tools you need to easily create powerful programs for today's distributed environments.
Simple
Java is simple to use for three main reasons: First, Java is familiar to you if you know C. Second, Java eliminates components of C that cause bugs and memory leaks and replaces their functionality with more efficient solutions and automated tasks, so you have a lot less debugging to worry about than you would using C or C++. Third, Java provides a powerful set of pre-tested class libraries that give you the ability to use their advanced features with just a few additional lines of code.
Object-Oriented
Java is an object-oriented programming language that uses software objects called classes and is based upon reusable, extensible code. This means that you can use Java's classes, which are sets of variables and methods, as templates to create other classes with added functionality without rewriting the code from the parent classes or superclasses. If you plan your application's class hierarchy well, your application will be small and easy to develop. The hierarchy of classes is explained later in this chapter.
Robust
Java is robust because the language removes the use of pointers and the Java runtime system manages memory for you. The problems with pointers in C and C++ was that pointers directly addressed memory space. In a distributed environment like the Internet, when code is downloaded to diverse systems, there is no way of knowing for sure that memory space addressed by pointers is not occupied by the system. Overwriting this memory space could crash a system. Java also gives you automatic bounds checking for arrays, so they cannot index address space not allocated to the array. Automatic memory management is done using the Garbage Collector, which is explained in detail in Chapter 4, "Creating Your Own Objects."
Interpreted
Java is interpreted, so your development cycle is much faster. As you learn later when the Java interpreter is discussed, you need only to compile for a single, virtual machine and your code can run on any hardware platform that has the Java interpreter ported to it.
Secure
Java is secure, so you can download Java programs from anywhere with confidence that they will not damage your system. Java provides extensive compile-time checking, followed by a second, multilayered level of runtime checking. Java's security structure is described in detail in Chapter 19, "Security Issues."
Architecture Neutral
Java is architecture neutral, so your applications are portable across multiple platforms. Java's applications are written and compiled into bytecode for Java's virtual machine, which emulates an actual hardware chip. Bytecode is converted to binary machine code by the Java interpreter installed at the client, so applications need not be written for individual platforms and then ported from platform to platform. Java additionally ensures that your applications are the same on every platform by strictly defining the sizes of its basic data types and the behavior of its arithmetic operators. Operator overloading, the process of modifying the behavior of operators, is prohibited by Java.
High Performance
Java is "high performance" because its bytecode is efficient and has multithreading built in for applications that need to perform multiple concurrent activities. Although threads still require the use of classes, Java balances the addition of thread synchronization between the language and class levels. Java's bytecode is efficient because it is compiled to an intermediate level that is near enough to native machine code that performance is not significantly sacrificed when the Java bytecode is run by the interpreter.
Dynamic
Java is dynamic, so your applications are adaptable to changing environments because Java's architecture allows you to dynamically load classes at runtime from anywhere on the network, which means that you can add functionality to existing applications by simply linking in new classes. For example, if your applet is being run by a browser that doesn't have one of the classes included in your applet's bytecode, the browser can download the appropriate class from the server that is storing your applet, check the bytecode, and execute it. This is assuming your browser has not been configured with strict security. Chapter 19 covers browser security.
The Fundamentals of the Java Language
This chapter introduces you to the basic concepts and functions of the Java programming language and gives you brief examples to illustrate important points. The later, more advanced chapters guide you through extensive samples of code, and so the examples in this chapter are short. By the end of this chapter, you should have a basic understanding of the Java language and be ready to explore in detail Java's class libraries in Chapter 3, "An Introduction to Java Classes."
Java programming will be fairly easy for you if you have had experience with C++ because Java is designed to look and feel like C++. If you are an experienced C programmer, you will have to let go of some old habits to program Java, however, as you will discover later in this chapter. Please do not be concerned if you have little or no experience in programming-this book assumes no prior knowledge of C++. You may need to read through the chapters more slowly and carefully than an experienced programmer.
Java as an Object-Oriented Language
Java's class structure is made up of the following major components: classes, hierarchy, variables, methods, and inheritance.
Classes
The key to understanding Java's object-oriented design is understanding what classes are and what you can do with them. Classes are templates that you use to create actual objects. The instructions contained in a class are used to create one or more objects, which can be called instances of classes in Java. When you create an object from a class, you instantiate the object, which means you create an instance of the class. The words instances and objects are used interchangeably throughout the discussions of classes in this book, depending on the context of the sentence in which they're used.
In object-oriented programming, you can think of an object as you would any real-world object, for example, a rectangle. The actual rectangle would be an instance of the class Rectangle.
A very rudimentary declaration of a class is as follows:
class classname {
//class instructions
}
The instructions in a class are made of two basic components: variables that hold data, and methods that manipulate the data.
Before you begin creating the classes in your application, you must design the class structure. Java's class structure is organized into a hierarchy.
Hierarchy
Classes are organized into a hierarchy to allow you to easily reuse code. When you write a Java program, first determine which objects you'll need to use in your code. Then determine what variables and methods the object's class must store. When you know what instructions your classes contain, plan your hierarchy.
Without planning your hierarchy first, you would begin creating a class for each object individually and undoubtedly would find that you are repeating instructions from class to class. In a hierarchy, instructions common to groups of objects are separated out in parent classes, or superclasses, and can be used by all of their subclasses. Therefore, when you plan your hierarchy, you would group objects by the instructions that they have in common and organize them into a hierarchy.
Superclasses are used as templates to create subclasses with variables and methods that make each subclass unique. Each superclass can be a parent to one or many other subclasses. Unlike in C++, a subclass can have only one superclass. Therefore, the Java class hierarchy looks something like the one shown in Figure 1.1.
Figure 1.1: Java class hierarchy.
If a superclass is not defined when a class is declared, the class is automatically made a subclass of Java's Object class. Every class in a Java program is a descendant of Object. Object itself has no superclass.
Variables
Classes store information that describe objects in instance variables. When objects are created from a class, they contain new instances of the class' variables. These instance variables can have values that are different from one object to the next. Values of instance variables are called data. When an instance variable's data is changed, it affects only the individual object. There is a way in Java to assign a variable to a class that, if changed, is changed in all instances of the class. Such a variable is called a static variable, which you learn about later in this chapter in the section called "Static Methods and Variables."
The basic declaration statement of a variable is this:
datatype variablename;
Two variables that might be declared for a rectangle are length and width. They could be declared in one line because they have the same data type. (You explore data types in Table 1.1 of this chapter.) Their declaration statement might be this:
int length, width;
If you created an object from a class with only these two variables declared, the object would simply hold this data. It would not know to draw lines with these values to form a rectangle. Methods must be declared to use the data in variables.
Methods
Methods are functions that must be associated with individual classes. In C and C++ and other procedural languages, functions can be placed anywhere in the code. In Java, they must be stored within classes. Instances of methods are created when instances of classes are created. Unlike variables, methods are not duplicated in objects-their code is stored only in the class.
When an object's method is invoked from its class, it uses the data of variables in the object.
Every method returns a value if it is not declared as void. To declare a method, you use the following statement:
returntype methodname (parameter list) {
//method code
}
At this point in the chapter, you won't get into coding the method that draws the rectangle. After you've learned the basic coding elements of Java later in this chapter, exercises in coding methods will be more useful.
Inheritance
Variables and methods that are stored in classes in the class hierarchy are inherited by subclasses, so you do not need to re-create them. Objects created from a subclass will contain not only the instances of the variables and methods of the subclass, but also its superclass' variables and methods, as well as those of the parent of its superclass, and so on. When a variable or method is referenced in an object, it is retrieved in a specific order: Java first searches for it in the current class, then, if it is not found, it searches the parent class, and so on.
In summary, objects in your Java program are created from classes that contain variables and methods to describe the object. Classes are organized into a hierarchy in which classes inherit functionality from parent classes, which allows for reusable code. These basic concepts of the structure of Java's object-oriented programming language will become clearer to you as you read through the rest of the sections.
Basic Coding Elements
Your Java code is used to create classes, objects, interfaces, and packages. You'll learn how to create each of these in Chapter 4. This section outlines the elements of the code you need to use to create them.
Your code is written in a series of statements, which can be organized into blocks. These statements contain data and operators, which are components of expressions. You can annotate your code using comments, which makes the code more understandable.
Statements
Any line of code before a semicolon is known as a statement and is executed by the Java interpreter when it hits the semicolon; after executing that statement, the interpreter moves to the next statement. Each statement contains instructions for using data and operators.
Expressions
An expression is a part of a statement that uses data and operators to return a value.
Blocks
A block is a collection of statements enclosed in curly braces. Any variables that you declare and that assign values within a block are erased when the flow of execution leaves the block. The block in which the variable's value exists is called the scope of the variable.
Comments
Comments are used to annotate the code so that a reader can understand the purpose of certain lines and blocks of code. Comments are ignored by the Java compiler. Multiline comments are preceded by /* and are ended with */. Single line comments are preceded by //. The double slash "comments out" text only to the end of a line. A comment would appear as follows:
/* This declares the length variable for the Rectangle class */
int length;
or
int length; //Declares the length variable for the Rectangle class
Data Types
To represent data values in your code you use literals. Literals are described by types, named by identifiers, and stored in variables, which were outlined earlier in the chapter and are explored further in this section.
When you use literals in your code, they appear in their raw form rather than as a result of an expression. Several types of literals are commonly used: numbers, integers, floating points, characters, Booleans, and strings.
Table 1.1 outlines Java's strict definitions of these data types.
Table 1.1. Rules for Java literals.
Literal Type | Typename | Rule |
Number | Num | Can_ be integer, floating point, or character. |
Integer | Can be decimal, hexadecimal, or octal. | |
Byte | 8-bit integers between -128 and 127. | |
Short | 16-bit integers between -32768 and 32767. | |
Int | 32-bit integers between -2147483648 and 2147483647. | |
Long | 64-bit integers between -9223372036854775808 and 9223372036854775807 or have L or l appended to them. | |
Hex | Preceded by 0x or 0X. | |
Oct | Preceded by 0. | |
Floating point | Any number with a decimal point. Can be made exponential by appending an e or E, followed by the exponent. | |
Float | 32-bit. | |
Double | 64-bit. | |
Character | Char | 16-bit integers represented by a single character and enclosed in single quotes. |
In Java, the Unicode character map is used. The following special characters must be represented by escape sequences: | ||
backspace \b | ||
backslash \\ | ||
carriage return \r | ||
double quote \" | ||
formfeed \f | ||
hex number \xhh | ||
horizontal tab \t | ||
newline \n | ||
octal number \000 | ||
question mark \q | ||
single quote \' | ||
vertical tab \v | ||
Boolean | Boolean | Can only be true or false. Are not represented by 0 or 1. |
String | String | Zero or more characters enclosed in double quotes. |
Literals are described by identifiers. Identifiers are sequences of letters and digits, and can also be used to describe variables, methods, and classes. Identifiers can consist of any letter from a to z, underscore, dollar sign, digits from 0 to 9 (except as the first character); identifiers are case-sensitive.
Java has several reserved keywords that are its own identifiers,
which cannot be used as identifiers in any way other than that
defined by Java, as listed in Table 1.2. Though these words are
reserved, not all are used in the most recent release.
Table 1.2. Reserved keywords.
Abstract | else | int | short |
boolean | extends | interface | static |
break | final | long | super |
byte | finally | native | switch |
case | float | new | synchronized |
cast | for | null | this |
catch | future | operator | throw |
char | generic | outer | throws |
class | goto | package | transient |
const | if | private | try |
continue | implements | protected | var |
default | import | public | void |
do | inner | rest | volatile |
double | instanceof | return | while |
Operators
Operators are used to compare values. Java has strict definitions of operators. It doesn't allow for overloading, which is a C developer's common practice of changing the behavior of operators.
Java provides two types of operators: binary and unary. Binary operators are used to compare two values. Unary operators use a single value, for example:
a >= b
a++
The first example uses a binary operator, >=, which compares variables a and b. The second is a unary operator, ++, which increments the value of a by one.
All of Java's binary and unary operators can be found in Table
1.3. They are organized according to the precedence with which
they are performed.
Table 1.3. Binary and unary operators.
Operator | Description |
., (), [] | Precedence overriding decimal, parentheses, brackets |
!, ~, ++, -- | Boolean negation, bitwise complement, increment, decrement |
*, /, % | Multiplication, division, modulus |
+, - | Addition, subtraction or unary negation |
<<, >>, >>> | Left shift, right shift, zero-fill right shift |
<, <=, >, >= | Less than, less than or equal to, greater than, greater than or equal to |
==, != | Equals, is not equal to |
& | Bitwise or Boolean AND |
^ | Bitwise or Boolean XOR |
| | Bitwise or Boolean OR |
&& | Evaluation AND, Logical AND |
|| | Evaluation OR, Logical OR |
?: | If then else |
=, +=, -=, *=, /=, %=, &=, ^=, |=, <<=, >>= | Assignment operators |
, | Comma |
Declaring Variables
There are four types of statements to use for variables: declarations, assignments, initializers, and casts.
You must always declare variables before you can use them in your Java program. Variable declarations assign data types to variables. A declaration statement in Java consists of a data type followed by an identifier. Any of the data types listed in the previous table can be used to declare variables, for example:
Boolean IsReady;
float miles;
int x, y, z;
short pages;
Assignments are statements that assign values to variables. These, like declarations, are required before variables can be used. They are called by setting an identifier equal to a value. This value, of course, must be compatible with the data type assigned to the variable identifier. Initializers are assignment statements that are combined with the declaration statement, for example:
Boolean IsReady = false
float miles = 3.62
short pages = 240
If you want certain variable values to remain constant in your code, you can use the final keyword, which ensures that the variable cannot be changed by the code. Its form is this:
final int pages = 500
Casts are statements you use if you need to place a value of one type into a variable of another type. In C++, automatic coercion allows you to do this without declaring that you were aware of this change. In Java, you must explicitly call such an instruction with a cast statement. Cast statements are generally called as follows:
datatype identifier = (datatype) identifier
Java allows casts only between compatible data types.
In this section, you have learned about the data and operators that are used in expressions that are parts of statements. You now understand that statements are organized with blocks and annotated with comments. You have also examined some basic statements that deal with variable declarations. These are the fundamental elements of Java coding.
Flow of Execution
Now that you understand the basic structure and elements of the Java language, you'll see how statements flow through a Java program in this section. By default, the Java interpreter executes statements in sequential order. This section introduces you to some more complex types of statements that alter this flow of execution.
Conditionals
if, if...else, ?:, and switch are four conditional statements that are used often in code. They evaluate an expression and, based on the value of the expression, they control which statements are executed.
The if statements consist of if followed by a test expression. If the test expression returns a true value, the statement or block of statements after the expression is executed. They are structured as follows:
if (test expression) statement;
if...else statements are similar to if statements, but include a false statement that is executed if the test expression returns a false value. They are structured as follows:
if (test expression) true-statement;
else false-statement;
The ?: is a ternary operator that allows you to assign one of two values based on the evaluation of an expression as true or as false. Its form is as follows:
(test expression) ? true-value : false-value
The switch statement evaluates an expression's value and jumps to a statement identified by the literal value of the expression. It saves coding time if there are several values in your statement. Its form is as follows:
switch (expression) {
case value: statements
case value: statements
default: statements
}
Loops
Loops provide you with the ability to repeat certain statements until a condition is met. Java provides three types of loop statements: while, do...while, and for. Three other statements commonly used with loops are break, continue, and labels, but these can be used with other types of Java statements (such as conditionals) as necessary.
The while loop evaluates a condition to see if it is true, and then executes statements and checks the condition again. If the condition is still true, while executes the statements and checks again. This process continues until the condition is evaluated as false. The form is this:
while (expression) true-statements
The do...while loop is similar to while, but do...while executes statements before the condition is evaluated. When you use do...while, loop statements are always executed at least once. The form is this:
do statements while (expression)
The for loop allows you to test a range of values in your expression. Its form is generally this:
for (initializer; expression; increment)
statements
The break statement is used to exit loop and conditional statements before meeting a test condition. The continue statement directs execution back to the beginning of a loop without completing all of the loop statements. A label is used to identify a statement so execution can be directed to it with a break or continue statement. A colon (:) must be appended to a label name.
An example of a label is as follows:
variable declarations;
Label:
while (test expression) {
statement;
if(test expression) {
continue Label;
}
statement;
}
Arrays
Arrays create slots that allow you to store a list of variables. Arrays are allocated using the new operator to create the array and assigning it to a variable with =. Arrays are declared as follows:
datatype variable[] = new datatype[number-of-slots];
The first half of the statement declares the variable that holds the array. Your array declarations should end in square brackets. The second half creates the array and assigns it to the variable.
The new modifier automatically initializes your array to false for Boolean arrays, 0 for numeric arrays, \0 for character arrays, and null for all other types of arrays. You can choose to initialize the array on the same line as the declaration, as shown here:
datatype [] = {element1, element2, element3, etc.}
Using Classes, Objects, and Interfaces
Every Java application is essentially a class that contains the main() method. This section discusses briefly how to create and use classes, objects, and interfaces in Java applications. You'll learn about many additional elements of the Java programming language that are important to know when using classes and objects.
Creating Classes
The first step in creating an object in your application is to create its class. The following form is used to define a class:
class classname [extends classname] [implements interface] {
[variable declaration;]
[method declaration;]
}
In this form, the classname is the name of the new class; extends classname is where the parent class of a subclass would be named, after the word extends. The implements interface defines an interface used by the class (interfaces are covered later in the chapter). If you do not want the compiler to allow your class to be subclassed, you can precede your class statement with the word final. If you wish to create a class that must be subclassed, you would precede the class with the word abstract.
You explored variable declaration in detail in the first section. Keep in mind that variables must be initialized before being used in methods. Now you'll learn how methods are declared. Methods generally use the following form:
[Modifiers] return-type method-name (parameter-list) {
[statements;]
}
The return type specifies the type of data or class that is returned when the method is run. The void return type is used when no value is returned. When naming the method, you must follow the same rules for naming classes and variables described earlier in the chapter. You must always enclose the parameter list in parentheses even if the parentheses are empty. The parameter list contains the types and variable names of all variables that you want to pass to the method. The statements are the code of your method.
Overloaded Methods
You use overloaded methods in your code when you need to call a method with different sets of parameter lists. Methods that allow flexibility in the parameters they use can be called from different parts of your program with different variables. Java's API classes, discussed in Chapter 3, use overloaded methods. To allow a different type of parameter information to be passed to your method, repeat the method in your code with the alternate parameter list included, as follows:
returnvalue methodname (parameter list 1)
returnvalue methodname (parameter list 2)
When the method is called and parameters are passed to it, Java determines which version of the method has a parameter list that most closely matches the parameters passed and executes it.
Static Methods and Variables
Static methods and variables are similar to the methods and variables already discussed, but they are a part of the class and do not require you to create an object to use them. Static variables are common to all instances created from the class they are stored in. When the value of a static variable in a class is changed, it is likewise changed for all instances of the class.
You might want to use static methods to provide utilities for the rest of your application. Statics can also be used with initializers to create constant data that is used by all instances of the class. All static statements-whether they are variables, methods, or initializers-are preceded by the word static.
Constructors
Constructors are methods that you use when you create objects. They take the function of initializers a step further. They have the same name as the class and return no value. A default constructor is automatically created by the Java compiler if you don't specify one in your code. It is called when an object is created without parameters. Constructors, like methods, can be overloaded.
Creating Objects
After you have a class that has variables and methods stored, you can create objects from it. You can think of an object as an area of memory that is allocated for an object's instance variables. To create the object and automatically allocate memory for it, always use the operator new. This new operator creates the object of the type specified and calls the constructor that is appropriate for the parameters passed in the parameter list. A reference is then made by Java to the new object. The form of an object creation is generally as follows:
classname reference-variable;
reference-variable = new classname (parameter-list)
The first line declares a variable that holds the reference to the object (references are explained in the next section). The second line creates a new object and assigns the variable to it. If parameters are passed, the appropriate constructor is called. If no parameters are passed, the object's default constructor is called.
References
References, which are created when objects are created, can be used to access variables and methods in other objects. References are used by other methods from other objects in the following form:
reference-variable.instance-variable
reference-variable.instance-method
Variables and methods stored in other objects are called by using the reference variable followed by a dot and the name of the foreign variable or method.
You use the reference value in comparisons. The operators used in references are ==, !=, and instanceof. The == and != operators can be used to tell whether two references refer to the same object or whether a reference does not refer to any object (such a case would return a null value). The instanceof operator is used to determine whether an object was created from a certain class or one of its subclasses. If a statement comparing an object to a class using instanceof returns true, that object is in fact an instance of the specified class.
If you are a C programmer, note that pointers are replaced by references in Java. This is explained in greater detail later in the book.
Class Inheritance
When a subclass is defined, it must indicate the superclass it is being created from. This is done with the extends modifier. The form of a subclass declaration is this:
class subclassname extends superclassname {
//new instructions
}
If a subclass uses a method from a superclass but adds functionality to it, it overrides the method. In Java, you do not need to duplicate the code in the other class. Just refer to the original method in the superclass with super.
null, this, and super Variables
Every class has the following three special variables: null, this, and super.
The null variable has a reference that points to nothing. You may use this reference to assign no value to a variable's identifier. null variables are empty containers that represent the absence of an object. To create an empty container, the following form is used:
datatype variablename = null;
The this variable has a reference to the actual object. A class can refer to itself using this. To refer to itself, a class uses the following form:
methodname(this);
The super variable has a reference to the superclass type of the class. You might wish to reference the superclass type when creating objects. Just precede your variables with the word super:
super (x,y)
Encapsulation
Encapsulation is used to hide methods and variables in your classes from being accessed from foreign methods. To encapsulate variables and methods in your classes, precede their declarations with the keyword private. Private variables and methods can be seen only within a class. Subclasses or external methods cannot see them.
Access Modifiers
The private modifier is one of three types of access modifiers that control the way foreign methods see your variables, methods, and classes. The others are public and protected.
Any foreign methods are able to access variables and methods preceded by the word public. There can be only one public class per source file. This class must have the same name as the source file. Applets, which are explained in detail in a later chapter, must contain at least one public class that is a subclass of the Java.Applet package.
Protected variables and methods can be used only within a class or its subclasses. Foreign methods cannot access protected variables and methods.
Creating Interfaces
Interfaces in Java are unlike interfaces in any other language. They provide a second form of inheritance. Because Java does not allow you to inherit from more than one superclass, Java lets you implement interfaces, which provides you with the functionality that inheritance from multiple superclasses would. Using interfaces, your object can use methods and variables from classes that are outside of its class hierarchy.
Interfaces are structured as entirely abstract classes with variables that are static and final and contain methods without code to implement them. Interfaces can inherit from an unlimited number of other interfaces. They are structured the same way in which classes are structured, except with the keyword interface instead of class:
interface interfacename [extends interfacename] {
[variable declaration;]
[method declaration;]
}
Interfaces are implemented by classes and can extend other interfaces. Classes that implement interfaces include the code for the methods of interfaces. These classes can implement more than one interface.
Interfaces can be used to conjoin several unrelated classes and have them respond to the same methods. For example, using interfaces, several shape classes such as rectangle, triangle, and circle can respond to a method called MoveLeft(), which would repaint the shape a certain distance to the left of the original location. In such a case, MoveLeft() would be declared in the interface, but its code would be stored in the class that implements the interface.
Packages
Java provides you with several libraries of classes which are called packages. You can use any of the classes in these packages by importing them into your application. Additionally, you may wish to package related classes you create so they can be reused in other applications. Packages are presented in detail in Chapter 3.
How Java Differs from C++
Now that you have a basic understanding of creating and working with classes, objects, and interfaces, you should be ready for Chapter 2, "Getting Started," that introduces you to the Java development environment. Before proceeding to Chapter 2, read through this section; it explains the differences between Java and C++. It is important that you understand what Java has changed and the reasons for these changes.
Java functionality differs from that of C and C++ in many ways. As I discussed earlier in this chapter, these changes are intended to create an object-oriented language that eliminates many of the opportunities for bugs and memory leaks that are common in C and C++. If you have experience in C or C++ programming, some of Java's changes may take some getting used to. The following list touches on the most important of these changes:
- Java is an interpreted language, not a compiled language as is C++. This means that compiling is done by an interpreter before execution.
- Java uses classes or interfaces to build composite data types instead of structures and unions, as in C++. This ensures portability.
- There are no #defines in Java because the development team felt that using #defines advocates coding that is hard to read.
- Command-line arguments are different in Java. They are arrays of strings that contain the arguments. Through a mechanism known as varargs, C++ allows you to provide a variable number of arguments to a function. This mechanism is not supported by the Java language.
- Java has no header files. Instead, Java uses interfaces that show only the methods and final, or constant, variables instead of the entire structure.
- Pointers, one of the primary features that introduce bugs and memory leaks into programs, are removed in Java. By getting rid of structures and encapsulating arrays through references, Java has attempted to get rid of the original reasoning behind pointers. Java does not allow you to construct a reference to anonymous memory, so it produces robust, efficient code much less prone to bugs, memory leaks, and corruption.
- Java has replaced multiple inheritance by interfaces to avoid problems with fragile superclasses.
- To ensure a purely object-oriented structure, there are no individual functions in Java. Functions must be encapsulated in a class.
- While Java retains goto as a reserved word, it is not implemented or supported by the Java language.
- Java has strict definition of operators. It doesn't allow for operator overloading.
- Automatic coercion, which is a common cause of inaccuracy in C++, would allow you to place an incompatible variable into another without declaring that you were aware of this change. In Java, in order to store a variable of one type in a variable of another type, you must explicitly call it with a cast statement.
- Java programs crash reliably and obviously, whereas crashes in C and C++ programs are not as apparent.
- Java implements a new function called automatic garbage collection. The Java runtime system keeps track of all references to an object until the object is no longer needed. When there are no more references to an object, it makes it available for garbage collection.
- Java also implements automatic memory management and thread controls. Although threads still require the use of classes, Java balances the addition of thread synchronization between the language and class levels. For example, garbage collection is run as a background process (or low-priority thread). It remains quiet until there is either a sufficient pause in the execution of foreground threads for it to run, or the system explicitly requires the use of memory which is taken up by unreferenced classes.
- The Java language provides a finally statement for use with Java exceptions. The finally statement delimits a block of code used to release system resources and perform various other cleanup operations after the try statement.
- Java strings are first-class objects. They are a class provided in the java.lang package. This provides consistency and predictability in string functions.
Obtaining the Java SDK
This book helps you accomplish any and all of the goals outlined in the last chapter with the help of the Java Developer's Kit (JDK) and a Java-capable Web browser, which you will need to install on your computer. The JDK includes all of the primary tools of the Java development environment: the compiler, interpreter, debugger, and AppletViewer. You'll need the browser to run applets from pages on the Internet. Consequently, you must have a TCP/IP connection to the Internet for your browser to take advantage of Java's networking capabilities. You can install the JDK from the internet.
If you do not have a CD-ROM drive, you can download the JDK from the Java Web site or the Java FTP site. To download from the Sun Java home page, connect to the following URL:
The Java home page of Sun Microsystems lists several links. Two of the links are Developer's Corner and Downloads. You should be able to navigate through either of these links to get to the JDK page. This page provides you with detailed instructions for downloading and installing the JDK on your platform.
ftp://java.sun.com/pub/
or
ftp://www.blackdown.org/pub/Java/pub/
To download from the FTP site, FTP to the following:
Download the JDK for your platform using binary transfer mode and follow the detailed instructions for installing the JDK on your platform.
You need to configure your particular system after downloading the JDK. The most important step after downloading and extracting the JDK is setting the class path. If you are a Windows 95 user, you need to insert into the following line into your autoexec.bat file:
set classpath=.;c:\java;c:\java\lib\classes.zip
This method is also assuming that the JDK is installed on your C drive. On NT, you have to go into the Control Panel, then choose System, and then in User Environment Variables, under the dialog box, enter the name of the new variable in the Variable text area, as follows:
Path=c:\java
If you have any trouble downloading from Sun, try one of the Java mirror sites that are listed on the Java home page.
Note: |
Java's home page is one of your most valuable sources of information on Java. Because Java is still evolving as a network programming language, you should regularly check that home page for news on releases, bugs, fixes, and information
on third-party Java product development. You might also find the Frequently Asked Questions (FAQs) sections useful. Here are a couple of other Web sites among a rapidly growing number of Java sites that are worth exploring:
|
Currently, three 32-bit Web browsers support Java applets: Netscape Navigator 2.0 or greater, Microsoft Internet Explorer 3.0, and Sun HotJava. At the time of this writing, Netscape Navigator 2.0 (or later) and Microsoft's Internet Explorer 3.0 are the only Web browsers capable of working with applets created with the latest JDK tools. Sun's Web browser, HotJava version 1.0 preBeta1, is compatible with the FCS applet API. HotJava is worth downloading because it is currently one of the largest applications written in Java, and it includes source code. Be sure to check the Java home page for updates on the release of HotJava and the JDK.
The Java Development Environment
The Java Developer's Kit provides you with four basic tools that help you write, compile, debug, test, and run Java code. These tools are the Java compiler, the Java interpreter, the Java debugger, and the Java AppletViewer. Java's other tools, such as the Java API documentation generator (javadoc), the Java disassembler (javap), and the Java header and stub file creator (javah) are included in the JDK, but they are not necessary for the purposes of this book. The Java tools, combined with Java libraries of utility classes and methods, form the complete Java system.
The following section describes the basic Java tools that you will use and explains how they are useful.
Your Text Editor
You can write your Java source code using any standard text editor, such as Notepad, Write, or Edit for Windows NT/95 users and TextEdit for Solaris users. A variety of text editors more suitable for development can be found on the Internet. You might want to use a text editor that comes with a development application, such as Visual C++. Java source code is generally saved with the extension .java.
The Java Compiler
Your Java source code can be compiled using javac, the Java compiler. It compiles source code into bytecode for the interpreter to execute. Compiled Java code is automatically given the extension .class by the compiler.
One important change that the Java team made from C was in compiling. C is a compiled language. It outputs binary machine code, which can be run only on the machine for which it is compiled. Compiled C code executes quickly, but it is architecture-dependent. As stated before, one of the important features of Java is that it is architecture-neutral. Java accomplishes architecture independence by splitting the compiling function across two tools: the Java compiler and the Java interpreter. The Java compiler outputs bytecode, similar to machine code but written for the Java virtual machine, which doesn't exist. The interpreter verifies this bytecode, converts it into machine code of the hardware platform it is installed on, and executes it. Source code must only be written for one machine: the virtual machine. The interpreter takes care of the rest. Therefore, the Java language is both compiled and interpreted.
The Java Interpreter
Java's interpreter is called java. It converts the bytecode output from the javac compiler to machine code and executes it.
Java is unlike purely interpreted languages, which generally interpret source code before execution, sacrificing performance. Another important feature of Java that Sun boasts is high performance. Execution by Java's interpreter is near to the speed of binary executables produced by compiled languages. The reason for this is that Java code is compiled to an intermediate stage where the file is still architecture-neutral, but close enough to machine code that it can run efficiently. In addition, Java's multithreading feature can improve performance by moving interpreter operation to the background. Multithreading is discussed in Chapter 15, "Interfacing with a Database: Catalog Applet."
In addition to architecture-independence, the other advantage of using the Java interpreter is security. The interpreter can evaluate classes to ensure that the bytecodes being interpreted do not violate any language constraints or perform illegal activities on the system or memory. This can prevent many viruses from spreading.
The interpreter runs outside the browsing environment. It provides the programmer with the ability to run stand-alone applications that have nothing to do with the Internet but that are portable and platform-independent.
The Java Debugger
You can debug your code using the Java debugger, called jdb. It helps you find and fix bugs in Java code.
The Java debugger provides a command-line debugging environment for Java programs. Debugging can be done on a local or remote Java interpreter.
The Java AppletViewer
You can test your applets using the Java applet viewer, called AppletViewer. It provides a programmer with a way of testing applets outside of a full-blown Web browser.
Although Netscape Navigator has Java functionality and can be used to view applets, its security features prevent it from loading applets from the local drive. It also doesn't have the networking capabilities of the Java AppletViewer. Therefore, the AppletViewer is the best tool for full applet capability.
Compiling with Java
When using the JDK, the process of compiling is currently performed in a command line (shell environment). This section describes in detail how you use the Java tools; it steps you through creating and compiling your first Java program.
The first step is to run the text editor of choice and create the classic HelloWorld program. Type the following lines of source code exactly as written here:
// HelloWorld.java
class HelloWorld {
public static void main (String args[]) {
System.out.println("Hello World!");
}
}
Save the file as HelloWorld.java in the classes directory. Don't be concerned at this point with what this code means. The next chapter explains the fundamentals of the Java language, and this program's syntax becomes clear to you.
javac/javac_g
After saving your source code with the extension .java, you can compile it using the Java compiler, javac. To run the javac compiler, execute it with the following syntax. (Note that javac expects an extension after the file name.)
javac [ options ] filename.java
javac_g [ options ] filename.java
For your example, run the following command:
javac HelloWorld.java
If the code compiles correctly, you will see two files in your classes directory: HelloWorld.java and HelloWorld.class. The .class file has now been converted to bytecode.
The following error message might result if you mistyped the javac commands. Retype the command carefully if you receive this error:
For Windows : bad command or file name
For Solaris: /bin/sh: javac: not found
The next error means that an expression is mistyped in your source code. Check your source code for errors if you receive this message:
Invalid expression statement
The following error means that either the javac command or your Java file cannot be found. If you receive this error, make sure that your path includes the directory containing the Java tools so you are able to run the tools from any directory without an explicit mapping to them. Also, make sure you are running the command from the same directory as your HelloWorld.java file.
Error message for Windows NT/95:
Bad command or file name
Error message for Solaris:
/bin/sh: javac: not found
When you run the compiler, you can feed it certain options that
change its behavior. Table 2.1 provides a list of all of the command-line
options that you can feed javac
and a description of each option.
Table 2.1. Command-line options for javac.
Option | Function |
-classpath path | Sets path where javac looks for classes it needs. Directory list is colon-delimited. |
-d directory | Specifies the root directory for creating a directory tree for a hierarchy of packages. |
-g | Turns on debugging tables in code generation for later debugging of bytecodes. |
-nowarn | Suppresses warnings that the compiler produces. |
-O | Optimizes code produced by inlining static, final, and private methods. |
-verbose | Prints messages about the source file and classes. |
javac_g is a non-optimized version of javac that is suitable for use with debuggers such as jdb.
java/java_g
You can use the Java interpreter to verify and execute your code. To run the Java interpreter, enter the executable name, options, class name (without the file name extension, unlike javac), and arguments as outlined here:
java [ options ] classname args
java_g [ options ] classname args
For our example, run the following command:
java HelloWorld
This should produce the following output:
Hello World!
Ordinarily, you compile source code with javac and then execute it using Java. However, Java can be used to compile and run bytecode when the -cs option is used. As each class is loaded, its modification date is compared to the modification date of the class source file. If the source has been modified more recently, it is recompiled and the new bytecode file is loaded. Java repeats this procedure until all the classes are correctly compiled and loaded.
java_g is a non-optimized version of Java that is suitable for use with debuggers such as jdb.
There are several options that you can feed the interpreter that
change its behavior. Table 2.2 lists all of the command-line options
for the Java interpreter.
Table 2.2. Command-line options for Java.
Command | Function |
-cs, -checksource | Recompiles any class whose .java source file is later than its .class file. |
-classpath path | Overrides the CLASSPATH environment variable. |
-mx x | Sets maximum size of memory allocation pool to x. Pool must be larger than 1,000 bytes and a k or m must be appended to the number to indicate size. The default is 16MB. |
-ms x | Sets the size of the memory allocation pool to x. Pool must be larger than 1,000 bytes and a k or m must be appended to the number to indicate size. The default is 1MB. |
-noasyncgc | Turns off asynchronous garbage collection. The only time garbage collection occurs is when the program calls for it or runs out of memory. |
-ss x | Sets the maximum stack size for C threads to x. Must be greater than 1,000 bytes and a k or m must be appended to the number to indicate size. |
-oss x | Sets the maximum stack size for Java threads to x. |
-v, -verbose | Prints a message to stdout when a class is loaded. |
-verify | Uses verifier on all code. |
-verifyremote | Uses verifier only on classes loaded with classloader. |
-noverify | Disables verifier. |
-verbosegc | Prints a message when garbage collector frees memory. |
-t | Prints trace of an instruction being executed. Only available with javag. |
-debug | Allows jdb connection to current session of Java interpreter. Displays password when debugging session is started. |
-DpropName=newVal | Enables user to change values at runtime. Requires the full packaging extension to the class variable. |
jdb
If the compiler returns errors related to your code, you can use the Java debugger to debug your code. The most common way to start jdb on local classes is using the following syntax:
jdb classname [parameters]
To run the debugger, you are substituting the command for Java with jdb. This starts the Java interpreter with the class to be debugged and any specified parameters, and stops before executing the class's first process.
If you need to run jdb with Java interpreter already running, you can connect to the interpreter using the -host and -password options. In order to be able to retrieve the password from the Java interpreter session, it must have been invoked using the -debug option. When you start Java using the -debug option, it provides a password with which the jdb can be started.
You can feed the Java debugger several command-line parameters
that change its behavior. These parameters can be listed using
jdb's help parameter. Table
2.3 lists all of these commands.
Table 2.3. Command-line parameters for the jdb.
Option | Function |
catch classID | Breaks for the specified exception. |
Classes | Lists currently known classes. |
clear classID:line | Clears a breakpoint. |
Cont | Continues execution from breakpoint. |
down [n frames] | Moves down a thread's stack. |
dump ID [ID..] | Prints all object information. |
exit (or quit) | Exits debugger. |
help (or ?) | Lists commands. |
ignore classID | Ignores the specified exception. |
list [line number] | Prints source code. |
load classname | Loads class. |
locals | Prints all local variables in current stack frame. |
memory | Reports memory usage. |
methods | Lists methods in a class. |
print ID [ID..] | Prints an object or field. |
resume [threadID..] | Resumes threads. Default is all. |
run class [args] | Starts execution of a loaded class. |
step | Executes current line. |
stop in classID.method | Sets a breakpoint in a method. |
stop at classID:line | Sets a breakpoint at a line. |
suspend [threadID..] | Suspends threads. Default is all. |
threads threadgroup | Lists threads. |
thread threadID | Sets default thread. |
threadgroups | Lists threadgroups. |
threadgroup name | Sets current threadgroup. |
up [n frames] | Moves up a thread's stack. |
use [path] | Displays or changes source path. |
where [threadID] or all | Dumps a thread's stack. |
!! | Repeats last command. |
Table 2.4 lists the command-line options for jdb that are used when accessing a running interpreter.
Table 2.4. Command-line options for the jdb.
Command | Function |
-host <hostname> | Sets the name of the host machine on which the interpreter session to attach to is running. |
-password <password> | Logs in to the active interpreter session. This is the password printed by the Java interpreter. The password prints when invoked by the -debug option. |
AppletViewer
You can use the AppletViewer to test applets in a runtime environment. The AppletViewer takes HTML files that refer to the applet and displays them. The only option for AppletViewer is -debug. This starts the AppletViewer in the jdb.
To invoke the AppletViewer, change to the directory of the HTML file in which the applet is embedded and type the following command:
appletviewer filename.html
The AppletViewer program has a few menu options that you can use
while it is running. Its menu also allows you to set network and
security properties for appletviewer.
Table 2.5 lists the AppletViewer menu options and their descriptions.
Table 2.5. Applet menu options.
Option | Function |
Restart | Runs the loaded applet again. |
Reload | Reloads the applet from disk. Useful if the class has changed since it was loaded. |
Clone | Creates a new window based upon command-line arguments for the first. |
Tag | Shows the applet tag used in the HTML document to start the applet. |
Info | Provides any information about the applet that is available. |
Properties | Allows the different configurations to be set for AppletViewer. |
AppletViewer's properties are outlined in Table 2.6. They provide
the network and security options of AppletViewer.
Table 2.6. AppletViewer properties.
Option | Property |
Http proxy server | required |
Http proxy port | required |
Firewall proxy server | required |
Firewall proxy port | required |
Network access | Several levels-no access, only access to applet's host, unrestricted access |
Class access | restricted or unrestricted |
Creating Objects from Java Classes
The next section teaches you how to create your own class. This section uses the classes from Java's built-in packages to concentrate on creating objects. As you learned in Chapter 3, Java provides several basic libraries of classes that have been tested and are thread-safe. You will want to use many of these utilities provided by Java's packages in your code in order to save time.
The operator you will use in your code to create new objects is called, appropriately, new. When you call the new operator in your code, you follow it by the name of the class from which you want to instantiate the object. Java automatically allocates a portion of memory to store instances of the variables declared in the class. This portion of memory is the object. After allocating an object, you will use methods in the object's class to send messages to it. You also can send messages to the object from methods in other classes.
You can use several pieces of code to create an object in Java in addition to the new operator. When new creates the object from the specified class, it automatically calls a constructor to build it. The constructors you create enable parameters to be passed to the object. You can create your own constructors for objects, but it is not a requirement. Java calls a default constructor if new is called without parameters. You can create one or many constructors for the object, each with different parameters, or you can let Java assign a default constructor. When new is called with parameters, Java selects the constructor you created that has the matching parameters.
When you create an object, you typically declare a reference variable that will hold the object's reference. Java creates a reference automatically whenever new instantiates an object in order to locate the object in memory when necessary. You will need a reference variable that stores the reference in order to refer to the object in your code. The reference variable is a name you assign to the object.
The very rudimentary form of an object creation follows:
classname reference-variable = new classname (parameter list);
Here, classname represents the class you use to create the object. reference-variable is the name you use to refer to the reference Java creates so that Java's runtime system can locate it. The new operator followed by classname actually instantiates the object from the class. The parameter list is the part of the object creation that specifies which of the constructors stored in the class are used to create the object.
The following example creates a new Rectangle object from the Rectangle class in the java.awt package:
Rectangle ThisRect = new Rectangle();
This example of the creation of a new Rectangle object accomplishes four tasks in one line of code: it declares the reference variable ThisRect, creates a new Rectangle object, assigns the Rectangle object to the reference variable ThisRect, and initializes the object.
These tasks can be separated into two lines in the following form:
classname reference-variable; //variable declaration
reference-variable = new classname (parameter list); //creation, assignment,
Âinitialization
In the Rectangle example, you could create the Rectangle object in two lines, as this code shows:
Rectangle ThisRect;
ThisRect = new Rectangle();
Declaring the Reference Variable
Rectangle ThisRect is a simple variable declaration, much like the object-variable declarations covered in Chapter 1. This declaration tells the compiler that the reference variable ThisRect refers to an object for which the class is Rectangle. When you think about it, the class in the declaration of an object-reference variable is very similar to the data type in the declaration of a variable in a class. Recall that the data type explained in Chapter 1 was declared as the following:
data-type variable-name;
The data types used to declare object variables basically are predefined Java classes with states and behaviors just like any other class, except that data types are classes that cannot be subclassed. Therefore, you can think of the declaration of a reference variable to hold an object just as you would the declaration of a variable to hold a value of a data type.
Creating the Object
Declarations do not instantiate objects. Rectangle ThisRect does not create a new Rectangle object; it just creates a variable named ThisRect to hold a Rectangle object. To instantiate the Rectangle object, you assign the reference variable to the object-creation sequence, which consists of the new operator followed by the class name and its constructor parameters.
The new operator returns a reference to the newly created Rectangle object, which is stored in the ThisRect reference variable.
Initializing the Object
Constructors are special methods provided by each Java class to initialize new objects from a class. The new operator creates the object, and the constructor initializes it.
Here's an example of using the new operator with parameters for a constructor to build a Rectangle object with a width of 4 and a height of 2:
new Rectangle(4, 2);
Java.awt.Rectangle provides several constructors. In the example, Rectangle(4, 2) calls the constructor that exists in Java.awt.Rectangle that has arguments that match the number and types of parameters specified in the initialization statement. The 4 and 2 parameters match the number and type of the width and height arguments of the following Java.awt.Rectangle constructor:
public Rectangle(int width, int height);
A class may provide multiple constructors to perform different kinds of initializations on new objects. When looking at the implementation for a class, you can recognize the constructors because they have the same name as the class and have no return type. In a class with multiple constructors, they all have the same name but different arguments. Each constructor initializes the new object in a different way. In addition to the default constructor used to initialize a new Rectangle object and the Rectangle constructor used earlier for ThisRect, Rectangle can use a different constructor, as shown in this code:
Rectangle ThisRect = new Rectangle(3, 3, 4, 2);
This creates a Rectangle object at point 3,3 of width 4 and height 2, using the following constructor from Java.awt.Rectangle:
public Rectangle(int x, int y, int width, int height);
Using the Object
After your object is instantiated, you can change its behavior by using methods to change the values of its variables or by directly assigning new values to the variables. Using methods to change variables is a more consistent way to manipulate objects. This section examines both these procedures.
You can access an object's variables directly from another object by adding a period (.) to the end of the reference-variable name and appending the name of the object variable, as shown in this example:
reference-variable.variable;
To access the width variable in one of the Rectangle objects created in the preceding section, you can use the following reference:
ThisRect.width;
To change the value of the width and height variables in the ThisRect object, you simply set them equal to new values in the following statements:
ThisRect.width = 5;
ThisRect.height = 3;
To get the width variable from the ThisRect object, you can refer to it as the following:
Width = ThisRect.width;
You can call an object's methods by adding a period (.) to the end of the reference-variable name and appending the name of the object method, followed by parameters to the method enclosed in parentheses:
Reference-variable.methodName(parameters);
To invoke the Java.awt.Rectangle.reshape method on the ThisRect object, you use this statement:
ThisRect.reshape(5, 3);
This statement reshapes the object by modifying its height and width variables. It has the same effect as the direct variable assignments used earlier in this section:
ThisRect.width = 5;
ThisRect.height = 3;
The reshape() method in the Java.awt.Rectangle package is declared void, so it doesn't return a value. All methods that are not declared as void do evaluate to some value. You can use the value returned by a method in expressions or as variable values.
Creating Your Own Classes
Although you can create functional applications by creating and using objects with Java's built-in classes, you undoubtedly will want to know how to create your own classes, constructors, methods, and variables to add additional functionality to your applications. This section explains how you can accomplish these tasks.
Writing the Class
When you create your own class, you usually will want it to be a subclass of a built-in Java class. Most basic functionality is provided by the classes in Java's packages. By restricting your class creations to subclasses of Java classes, you ensure the portability of your application. You know for sure that every user of your application will have Java's built-in classes available in his runtime system.
Remember that the primary advantage of subclassing is that it enables you to reuse code. You create subclasses as extensions of existing classes to create new objects with properties that are enhancements of existing objects. These subclasses use the existing methods and variables of the superclass and add methods and variables that make each subclass unique.
You learned the basic form of a class structure in Chapter 1. An example of how you can create a subclass called Square from the Rectangle class follows:
public class Square extends Rectangle {
//new variable and method declarations
}
Square is the name of your subclass. The name of your class must be a legal Java identifier and should begin with a capital letter. The extends Rectangle part is where the Rectangle class is identified as the superclass. This allows the Square class to use any variables or methods defined in Rectangle. If a superclass is not specified, Java assumes that the Java.lang.Object class is the superclass. The Square class' unique variables and methods are declared next.
The class Square statement uses the public access modifier to allow all other classes and subclasses to access it. You can precede a class name or method name with the word final if you do not want the compiler to allow it to be subclassed or overridden, or by the word abstract if you want to require that it be subclassed.
Your subclass inherits variables and methods from its superclass that are declared public or protected by the superclass. If variables and methods are declared private, they are not inherited. If no access modifier is specified, only classes within the same package can inherit methods and variables.
Within your subclass, you can hide the superclass' variables by using the superclass' variable names for subclass variables, and you can override methods inherited from the superclass. Although the subclass does not inherit the superclass' hidden variables and may override the superclass' methods, the subclass always can access these variables and methods as they appear in the superclass by using the keyword super, as this example shows:
ThisHeight = super.height;
This statement refers to the value of height as it is stored in the superclass.
You create all the methods and variables of the class, enclosing them in curly braces ({}). The collection of methods and variable declarations within the braces are called the body of the class. The variables typically are declared first.
Declaring the Member Variables
The class member variable declaration is much like the reference variable declaration in an object creation:
type variable-name;
The difference is that member variables exist in the body of the class, but are declared outside of methods, object creations, and constructors. In the following example of the Square class, the area variable is declared:
class Square extends Rectangle {
int area;
// methods
}
Typically, a member variable is not capitalized. It must be a legal Java identifier. No two member variables within a class can have the same name.
The member variable declaration offers several optional modifiers, as shown in this code:
[access-modifier] [static] [final] [transient] [volatile] type variable-name;
The access modifier public, private, or protected restricts access in the same way it does for methods and classes. static defines the variable as a class variable rather than an instance variable. This means that when the variable value is changed, it is changed in all instances. An instance variable is specific to the instance only. final indicates that the variable is a constant. Constant variables typically are written in all uppercase letters. They cannot be changed. transient indicates that the variable is not part of the persistent state of the object and will not be saved when the object is archived. volatile indicates that the variable is modified asynchronously by concurrently running threads.
Creating the Methods
As you learned in Chapter 1, a method returns a value unless it is declared as void. The method name is preceded by a return type to inform Java of how to interpret the value returned. It is followed by an optional list of arguments enclosed in parentheses. Like classes and member variables, the method declaration can be preceded by an access modifier. The following code shows how the method declaration is structured:
[access-modifier] returnType methodName([arguments]) {
//statements
}
If you include arguments when you write a method, you can call it with matching parameters. Parameters pass information to the method.
Using the fundamentals you learned in the previous chapters of Part I about statements, expressions, operators, and variables, you can create methods to manipulate your objects.
The following sections explain several types of methods available in Java: Class and Instance methods, constructors, and finalize() methods.
Using Instance Members versus Class Members
Members-a word for the variables and methods in a class-can be specific to the class or to the instance of the class, depending on how you write them and where you place them in your code. This is an important concept to learn, because variable values may differ when you define them as class variables rather than instance variables, and vice versa. Different rules apply to instance and class members as well, so you will be prone to compile errors if you are not mindful of these concepts.
Member variables can be class variables or instance variables. Class variables are declared using the static modifier. When Java's runtime system loads the class, the class variables are allocated in memory only once. When instances of the class are created, the class variables are not copied but instead are shared by all the instances. No additional memory is allocated for these variables because only one copy exists. Class variables can be accessed from any instance or from the class. Because class variables are shared by instances, the values assigned to them are the same for all instances of the class. When class variables are changed, they are changed universally for all instances that refer to them.
If no modifier is specified, variables are instance variables by default. Unlike class variables, all instance variables are copied and allocated memory by the Java runtime system each time an instance is created. An object's instance variables can be accessed only from an object-not from the class. If you want to access the instance variables of object A from your class, for example, you cannot refer to it directly. You must create a new object B that refers to it. The value of an instance variable is specific to the object. Other objects created from the same class can have different values assigned to their instance variables.
Member methods can be class methods or instance methods. Class methods also are declared using the static modifier. They have no access to the instance variables of the objects. They are invoked on the class and do not require any instance to be created in order to be called.
Instance methods, on the other hand, have access to the instance variables of the object, other objects, and the class variables of their class. They can be run only when an object is created.
You will want to use class variables in your code when you need only one copy of an item that must be accessible by all objects created from the class. Using class variables saves memory. You will want to use class methods for security reasons-to restrict access to the objects' instance variables.
Creating the Constructors and the Finalize() Methods
Constructors and finalize() methods are special methods you can use in your class. As you have learned, constructors are used to build objects when they are instantiated. You can use the finalize() method to destroy objects.
As you know, classes can store multiple constructors (all with the same name) with different arguments that are called when the new operator instantiates an object with parameters. If no parameters are passed, Java assigns a default constructor. The default constructor for the Rectangle class, for example, is Rectangle(). Constructors use the same access modifiers as methods and classes.
Many of Java's built-in classes provide multiple constructors. The Java.awt.Rectangle class that you have been using in this chapter, for example, provides the following constructors:
public Rectangle();
public Rectangle(int x, int y, int width, int height);
public Rectangle(int width, int height);
public Rectangle(Point p, Dimension d);
public Rectangle(Point p);
public Rectangle(Dimension d);
In your class, you may want to create multiple constructors if you will be passing different parameters when creating new objects. The Java compiler decides which constructor to use based on the number and type of parameters passed. If you create a new rectangle with the following statement, for example, Java selects the second constructor:
new Rectangle(3, 3, 4, 2);
When you write your constructors, keep in mind that each name must be the same as the name of its class, and each constructor must have arguments that are different in number or in type. Unlike regular method declarations, you do not define return types.
Constructors are not limited to single-line declarations. They can declare variables and methods like a regular method does. Although they can look much like regular methods, you will be able to spot constructors when you read through Java code, because they do not specify a return type and have the same name as the class.
If you need to access the constructors in your Square class' superclass, for example, you can do so by using the keyword super before the declaration:
super.Rectangle()
This line invokes a constructor provided by Rectangle, which is the superclass of Square. Typically, the superclass constructor is invoked first in the subclass' constructor.
When objects no longer are needed in an application, they must be cleaned out of memory. Java provides an automatic Garbage Collector to find unused objects and reclaim their memory. The Garbage Collector runs a finalize() method just before clearing an object from memory. You can override Java's finalize() method in your code. This finalize() method, provided by the Java.lang.Object class, releases system resources, such as open files or open sockets, before the object is collected. The last section of this chapter describes in detail how garbage collection works. For now, it is enough to know that the Garbage Collector is responsible for automatically clearing unused objects from memory.
You have the option of using the Object class' finalize() method or overriding it by creating your own finalize() method for your class. The structure of a finalize() method declaration follows:
protected void finalize() throws throwable{
//statements
}
In the body of this special method, you will close files and sockets after determining that they are no longer in use.
To call a finalize() method specified in your superclass, precede the name finalize() with the keyword super and a period (.). It is a good idea to call the superclass' finalize() method after your class' finalize() method, in case the object has obtained resources through methods that it inherited. Such a finalize() method looks like this:
protected void finalize() throws Throwable {
// clean up statements
super.finalize();
}
Creating Interfaces
At this point, you know how to create classes, to define their member variables and methods, and to instantiate objects from them. You will be able to create some small applications with this knowledge. Java's strict class hierarchy rules limit a subclass you create, however, to inherit only from the classes in its hierarchy. To create more complex applications, you undoubtedly will need to inherit from classes outside your class' hierarchy. In Java, you access methods and variables from classes outside your class' hierarchy by implementing multiple interfaces in your class. These interfaces must be defined in the foreign classes that you are accessing. You also can make portions of your class available to other classes that cannot inherit from it by defining interfaces in your class.
Interfaces always are abstract. Their variables can be used only as constants; they always are static and final. Their methods are abstract and public. An interface declares a set of methods and constants without specifying the implementation for any of the methods. When a class implements an interface, it provides implementations for all the methods declared in the interface.
As you learned in Chapter 1, interfaces are implemented by a class in the class declaration:
class classname implements [interface-list] {
}
The form of an interface declaration for a class follows:
[public access-modifier] interface Interface-name extends [interface-list]{
//methods and constants
}
The public access modifier is the only modifier supported in the latest release of Java for interfaces. It can be used before an interface declaration to allow all other classes and packages to use it. If public is not declared, only the classes within its package can use the interface. The keyword interface is followed by the name of the interface, which typically is capitalized. The extends interface-list part is similar to a class extending a superclass, but it can list multiple interfaces that it extends. This list is comma-delimited.
Like inheritance in a subclass, an interface inherits all constants and methods from the interfaces it extends unless the interface hides an inherited constant by declaring a variable of the same name or overrides a method with a new method declaration.
There is no need to use any of the following modifiers in an interface, because they are invalid:
private
protected
synchronized
transient
volatile
Here is an example of an interface declaration:
interface Movable {
void moveLeft(int x, int y);
void moveRight(int x, int y);
}
This is an example of the implementation of the Movable() interface in a class:
class Rectangle implements Movable {
public void moveLeft(3, 1) {
//code for moving object
}
}
Using the Garbage Collector
Now that you have learned to subclass objects, you might wonder how the memory they are allocated is managed. If you're a C++ developer, you might think that I missed some memory-management steps in my subclassing explanation. In Java, memory management is performed automatically with a utility called a Garbage Collector. When an object that has been instantiated by your application finishes performing its task, it is destroyed and Java's automatic Garbage Collector reclaims its memory.
The Java team made its most important improvement over C++ in automatic memory management and thread controls. Through the use of an automatic, threaded garbage-collection utility, Java removes the burden of memory management from the shoulders of the programmer yet retains high performance standards. Additionally, it eliminates the many bugs commonly caused by the use of pointers in C++ applications without sacrificing performance. This section describes what garbage collection is and how it uses multithreading to maintain the performance of your application.
Understanding Garbage Collection
Garbage collection is Java's answer to automatic memory management. This section on garbage collection begins by explaining why the management of memory in a multithreaded application should be automated, and how Java automates explicit memory management tasks of C-type languages, and how garbage collection works.
In C and C++, creating multithreaded applications is possible through explicit memory management. Programmers manage memory by using memory-management libraries to allocate memory, free memory, and keep track of which memory is available to be freed and when. Explicit memory management has proved to be a common source of bugs, crashes, memory leaks, and performance degradation in C++ applications. The need to free programmers from the encumbrance of memory management is evidenced by the fact that most bugs in C++-type code are caused by misuse of pointers and freeing of objects that are allocated in memory. If memory management is automated, programmers can spend most of their time worrying about the functionality of their applications instead of wasting it by debugging memory problems.
Pointers, pointer arithmetic, malloc, and free, which are used for memory management in C++, automatically are incorporated into Java's environment. Pointers are replaced by references. As you discovered earlier in this chapter, Java has a new operator, which is used to allocate memory for objects. There is no free function, however, that a programmer can invoke in code to clean up the memory space when the object no longer is needed. There is, in fact, no need to deallocate or free memory explicitly in Java.
Java generally automates memory management by tracking the use of objects that are created as your application runs. The interpreter automatically marks objects to be freed from memory when they no longer are in use. Such automatic memory management is performed by Java's Garbage Collector.
The purpose of Java's Garbage Collector is to ensure that memory is available when it is needed. When the Garbage Collector executes, it searches, discovers, marks, clears, and compacts unused memory, increasing the likelihood that adequate memory resources are available when required by the user.
More specifically, when an object is instantiated from a class, it is given a unique reference, which is used by the Garbage Collector. The Garbage Collector sets the reference counter to 1 when the object is allocated. It keeps track of all the references to objects instantiated in an application by incrementing the counter each time an object is referenced and decrementing it when the reference is gone. The Garbage Collector searches for reference counters that are equal to 0, meaning that there are no more references to the object. After discovering that a counter is set to 0, the Garbage Collector marks the unused object for removal, making it a candidate for garbage collection.
Java ensures that certain objects integral to the system will never be freed, such as the Object class.
After an object is cleared from memory, it leaves a hole the size of the object that can be reused. The Java Garbage Collector searches memory for fragments and reorganizes it by compacting it. When the Garbage Collector compacts memory, it consolidates the objects that have references into a contiguous group, making one large area of unallocated memory available for use by new objects as necessary.
Java provides for situations in which a long chain of object references comes full circle, back to the originating reference, leaving the counts for unused objects at 1 and the memory uncleared. Java avoids this problem by marking root objects, searching all references to objects, marking them, searching those objects' references, and so on until no other references exist. The Garbage Collector then removes all unmarked objects and compacts memory.
The effect of compacting objects is that they are moved to different areas of memory. In Java, references to objects that have moved are not lost, because these references are not pointers to specific areas of memory but instead are handles that are maintained in an object index, which maps them to actual objects. When an object is moved, only its references in the object index must be repaired.
You might wonder how such activities possibly can run throughout the execution of an application without degrading performance. The following section explores that question.
Looking At the Garbage Collector's Effect on Performance
The Garbage Collector's processes, described in the last section, would normally be very CPU-intensive-incrementing and decrementing counters for vast quantities of references to objects, searching them for 0 values, compacting memory, and remapping references in the object index. Java's Garbage Collector is not CPU-intensive, partly because it provides some additional tricks, such as designating objects that do not need to be referenced and saving work for the Garbage Collector, but mostly due to the fact that it runs as a separate, low-priority thread.
The Java Garbage Collector takes advantage of the user's behavior when interacting with Java applications. When a user pauses while using an application, when the system pauses, or when the system requires the use of memory taken up by defunct classes, the Java runtime system runs the Garbage Collector in a low-priority background thread and frees unused objects from memory. Because the Garbage Collector utility is effective only because it runs in a multithreaded environment, this section briefly explains what multithreading means.
Multithreading is an innovation that makes applications more interactive and faster. Single-threaded applications only have the capability to execute one process at a time. While a process is executing, all other processes are stalled. Because every application is a separate process, you have the option of switching between applications in such an environment, pushing one application's processing to the foreground; this pauses all other applications, however. When a process polls the operating system for events and requests an event, the operating system checks to see whether any other processes are performing any event processing and gives them time to complete. The operating system then passes the event to the process and allocates time for it to execute. With several application processes given time to execute, the user perceives that they are running simultaneously. They are not executing their tasks simultaneously, however. If one of these processes is lengthy, it monopolizes the system's resources and other processes do not run.
Such an application would be fine for small tasks but would not be practical for Internet applications, which may need to run several applets at once or perform tasks while the user is doing something else.
Multithreaded applications have the capability to maintain multiple concurrent paths of execution. While the user is performing some action in an application, other applications can perform other tasks. These paths of execution are called threads.
Java is a multithreaded application. It balances thread synchronization between the class level (performed at runtime) and the language level (performed when code is written). The runtime Java interpreter runs the Garbage Collector as a low-priority background thread while executing an application's code without disturbing the performance of the application. You will see how to create multithreaded applications with the Java language in Chapter 16, "Multithreading with Java."
Java's automatic garbage collection makes programming in Java easier, eliminating many potential bugs that would arise if you managed memory explicitly. It generally provides better performance than you will find in most applications created with explicit memory management.
http://java.sun.com/JDK-1.0/api/packages.html
Programmers in most other languages have to re-invent the wheel repeatedly to accomplish basic tasks. In contrast, Java comes with several standard packages of ready-made classes to handle basic functionality. These carefully designed packages are a joy to use: you'll find that quite often a few lines of Java can accomplish what it takes a C programmer dozens of lines of mostly tedious code to do.
If you're a C or C++ programmer, keep in mind as you learn Java that every variable of class type is actually a reference to the class object. In C a variable can be an entire struct, which is different from being a pointer to struct. This isn't the case in Java. For example, comparing variables of class type in Java (using the relational operators == or !=) is actually a comparison of references, not values (but see the equals() method of java.lang.Object). Internally this is like comparing memory addresses, but references aren't exactly pointers: you cannot access those addresses. Assigning and copying in method arguments is always the copying of a single reference value for each object, never a field-by-field copy as in C. This is no limitation, and it actually saves you grief.
If you're an Eiffel programmer, then of course you're right at home!
The standard Java classes are organized into packages, and also by inheritance. Packages are a way of grouping related classes to avoid potential naming conflicts, while inheritance provides the economy of expression that makes object-oriented programming powerful. The standard Java packages are
java.lang
java.awt
java.applet
java.awt.image
java.awt.peer
java.io
java.net
java.util
The standard Java classes are often quite minimal implementations, designed to be subclassed by you, the programmer. In this way, you get the best compromise between having to write the same code over and over, and having to live with libraries that are cluttered and too "fat." Perhaps best of all, you can be certain that the code you share with others has the same core of basic classes to draw upon. This makes your applets smaller and hence more "internet friendly." These classes are intended to be reliable and efficient, and some may even use platform-dependent native code behind the scenes. So it is often impossible to write a more efficient routine in Java alone. Java is a relatively "small" language, and most of these packages are not strictly required by the language definition, with the exception of the java.lang package.
Packages in Java
In Java, a package is a collection of classes that are in some way related, and are to be compiled in their own name space. Typically, all the classes in a package are designed together by the same person or team. Thus, name conflicts between classes within the package can be avoided by design. To avoid name conflicts across different packages, the package name of a class is prepended to the class to distinguish it from classes of the same name that may exist in other packages.
For example, the fully qualified name of the Java Button class is java.awt.Button, since this class is defined in the java.awt package. If you like being confused, you can even write your own class called Button that does something completely different from the Java Button class. Then you can still access the predefined class by using its long name, java.awt.Button.
If no name conflicts exist, you may import the name java.awt.Button, or indeed the entire package if you choose. Imported names may be used in their short form (i.e. String rather than java.lang.String). For example, to import the entire java.awt package, as well as the single class java.util.Vector, put these two lines at the top of your source file:
import java.awt.*;
import java.util.Vector;
The java.lang package is automatically imported into every Java compilation.
http://java.sun.com/JDK-1.0/knownbugs.html#Compiler
Caution: |
|
Packages also provide new levels of access control. The default rule for access control (when no access control specifier is present) is not the same as public: it makes the class or field visible to any class inside the package and invisible outside the package. This is sometimes called "friendly" access control. Packages must explicitly declare classes and fields to be public if they are to be visible from other packages. The special access specifier private protected makes a field accessible to subclasses in the same package only. Subclasses from other packages do not see a private protected field.
java.lang
The java.lang package contains the various classes that are essential to the definition of the Java language or that, by their nature, need to access the internals of Java in a way that most classes cannot do. For example, the string class java.lang.String is part of the language definition, and java.lang.Thread is used to control concurrent threads of execution in Java. The java.lang package contains the primordial class Object, of which every Java class is a subclass. It also contains wrapper classes for the various primitive types, which allow you to treat an int, for example, as a bona fide Java object (instance of a subclass of Object), by instantiating a java.lang.Integer. You need to do this in order to store integers in a vector array (java.util.Vector), for example. Objects belonging to these wrapper classes are freely convertible into the corresponding primitive type.
Note: |
|
Object
One of the most important classes in Java, the Object class implements the basic methods that every class must have. Every Java class inherits from this class; Object is the only Java class with no superclass. Every method in the Object class is available to every object in Java (though another, intermediate base class may override some methods). The Object class includes utility methods to generate hash codes and to create clones, and the method equals(), which tests two objects for equality. There are also some thread synchronization primitives (notify(), notifyAll(), wait()), a way to interrogate objects about their run-time class (getClass()), and the finalize() method, which you can override to specify some code that is performed before the object is garbage-collected. For example, here you may close any open files that are created by the object.
A reference to Object can reference any Java object at runtime. For example, an array of Objects can hold references to many different types of object; all these types are subclasses of Object. This is a completely type-safe way to get beyond the "casting void pointers" trick, which every C programmer is familiar with.
Caution: |
|
The equals() method
The equals()Object method is used this way:
public class Thing extends Object
{
static Thing Fred;
...
public boolean sameAsFred( Object o )
{
return o.equals( Fred );
}
}
Note that equals() is supposed to be a different kind of test than comparing references. Equals is a comparison of the two given objects by value, which means they can be equal even if they are different objects so long as they have the same type and are functionally equivalent objects. That is, every instance variable that represents a part of the state of the external object being modeled is equal in the two objects. This is a recursive definition since the instance variables may themselves be class objects. So this kind of test is relatively more expensive than comparing references. The predefined library classes each override the equals() method to mean something sensible. However if you want this method call to be meaningful, you generally override it in your own objects. This is especially true if you are inheriting directly from the Object class since the equals() method in the Object class does nothing except compare references. And remember that even if your class has no explicit superclass (empty extends clause), your class has Object as its implicit superclass. The following segment is a typical example of overriding the equals() method:
public class MyClass
{
String name;
int serialno;
/* other methods ... */
public boolean equals( MyClass o )
{
return name.equals(o.name) && serialno==o.serialno;
}
}
If you are strict about using inheritance to express is-a relations, rather than has-a relations, then your equals() method is like the above, albeit with different instance variables. A Manager is-an Employee; any further instance variables a Manager may have are not part of its identity as an Employee, so Manager need not override Employee's equals() method at all.
Now suppose you need to express the idea of a workgroup. If you assume that a workgroup always has one manager, it is certainly tempting to make the WorkGroup class extend the Manager class. However, you run into problems: a workgroup just isn't a special kind of manager. When you try to write the equals() method, you are compelled to write ugly code such as the following:
public class WorkGroup extends Manager
{
/* This is an example of how NOT to do this! */
String groupname;
/* other methods ... */
public boolean equals( WorkGroup o )
{
return super.equals( o ) && groupname.equals(o.groupname);
}
}
This code is Object ugly because you must explicitly call on the superclass to find whether the two workgroups have the same manager. This is nonsense! Moreover, this class is not easy to modify. Next week, the boss may tell you that a workgroup can have two managers. A much better way to express the idea of a workgroup is to make a WorkGroup class (extending Object) having instance variables to specify the Manager and the other Employees (a workgroup has a manager, and so forth).
Generally speaking, if your class inherits directly from Object, you override the equals() method, making it compare corresponding instance variables. If your class extends a subclass of Object, then you must decide what semantics are appropriate, although by being consistent you can often avoid this decision.
The toString() method
The Object class defines the toString() method, which returns a String that represents the value of the object. The Object class itself cannot do more than a rudimentary job at this. You should therefore override this in your own classes whenever such a conversion is meaningful. The string representation may be incomplete; there is no requirement that the object be recoverable from the string.
The clone() method
The clone()Object method creates a "clone" of the object, and returns it. By default, this is a "shallow" clone: instance variables that are references of class type are not cloned recursively. Rather, the reference value is copied. This means that the clone may reference some of the same objects as the original. If you prefer different behavior, you must override this method in your own objects.
Objects that specifically do not want to be cloned may throw a CloneNotSupportedException from their clone() method to complain about it.
Wrappers for Basic Types
The classes Boolean, Character, Double, Float, Integer, and Long, defined in the package java.lang, are full-fledged Java objects whose purpose is to represent the values of primitive types. They all work in about the same way, so let's look at Boolean as an example.
You can use a boolean value to construct a Boolean object:
Boolean b = new Boolean( true );
To get boolean values back out of a Boolean object, use the booleanValue() method:
if ( b.booleanValue() ) System.out.println( "yes" );
The Boolean class also provides both constant Boolean values as class variables:
if ( b.equals( Boolean.TRUE ) )
flag = Boolean.FALSE;
Similarly, each of the various wrapper classes provides class variables to delimit its range of legal values. The abstract class java.lang.Number is also provided as a superclass for the numerical classes Double, Float, Integer, and Long. The Number class merely specifies the four abstract methods intValue(), longValue(), floatValue(), and doubleValue(); this guarantees that any instance of a subclass of Number (say a Double) can be converted into any of the four representations (possibly with rounding).
Strings in Java
Java provides a smart implementation of character strings. A String object holds a fixed character string. Since these objects are read only, the implementation of String can be clever and return shared references into a pool of unique strings if it chooses to (you can use the intern() method to guarantee this). An object provides an array in which to manipulate string data; it grows as required when new data is appended or inserted.
A String is typically constructed using a double-quoted string literal:
String s = "My String";
However, Strings can also be constructed from arrays of char or byte:
char s_data[] = { 'M', 'y', ' ', 'S', 't', 'r', 'i', 'n', 'g' };
String s = new String( s_data );
Or from a StringBuffer:
String s = new String( my_strbuffer );
There are many useful methods for scanning strings and for extracting substrings:
String ring = "My String".substring(5, 9); // extract substring "ring"
Notice how the indices start at 0, and notice that the lower bound is inclusive, the upper exclusive. Then the length of the substring is easily calculated as 9-5 = 4.
The length() method gives the length of a string.
The String class also has several static valueOf() methods that know how to convert various types into string representations. The notation is quite mnemonic:
String five = String.valueOf(5);
and so on. For more general objects, the method call String.valueOf(Object) uses the object's toString() method to perform the conversion. StringBuffer objects hold string data of variable length. You can append() any Object on the end, and the result is to append the string representation of that object to the StringBuffer (typically, you append a String anyway). You can insert() an Object at any index, and the string representation is inserted at that point, moving the rest of the buffer to make room. Although StringBuffer objects are handy for working with string data, all of the useful scanning methods are in the String class, so usually StringBuffer objects are an intermediate step to constructing a String. You can convert a StringBuffer to a string using the toString() method, or the constructor String(StringBuffer). Conversion of StringBuffers into Strings is smart: the array of character data is not copied unless and until a subsequent operation via a StringBuffer reference tries to alter the data, and then a copy is made transparently.
You can concatenate a String with another String or StringBuffer by using the + operator. Indeed, so long as one operand of the + operator is a String, then the other is converted into a String and the result is the concatenation of the two Strings:
String myaddress = 1234 + ' ' + "Birch St.," + ' ' + "Birchville";
Math
The class java.lang.Math is a class with only static methods and no instance variables. There are no instances of this class, so it requires no constructors. It is a collection of mathematically useful functions, together with the two constants Math.PI (an approximation of p, the circumference of a circle divided by its diameter) and Math.E, which is approximately Euler's number e, the base for natural logarithms.
// how many times does this angle wrap around
double q = Math.floor( angle / (2*Math.PI) );
// bring the angle into the range 0..2*PI
angle -= q * 2 * Math.PI;
The trigonometric functions always measure angles in radians (180 degrees equals [pi] radians; equivalently, a radian is the angle subtended by a circular arc of length 1 on a circle of radius 1), and the exponential and log functions use Math.E as the base. Some Math functions throw an ArithmeticException if their argument is absurd. Try Math.sqrt(-1.0) to see this happen. Interestingly, although the tangent of a right angle is undefined, Math.tan(Math.PI/2) does not result in an exception; rather, a very large number is returned. Due to roundoff, Math.PI/2 is never exactly a right angle.
Cloning Objects: the Cloneable Interface
Besides overriding the clone() method in java.lang.Object, a class may implement the interface Cloneable to indicate that it makes sense to clone this type of object. The interface is an empty one. You don't need to supply any methods to conform to Cloneable, although you may want to override the default clone() method. Other classes can tell whether a class implements Cloneable by examining its class descriptor (an instance of the class Class).
Threads of Execution
In Java, a program or applet can be busy with several things at once. This means your classes must be able to create and control threads of execution. The java.lang classes Thread, ThreadGroup, and the interface Runnable provide this control. A Thread represents a single thread: a context of sequential execution. What gets executed is the run() method of the Thread, or of a Runnable that is designated as the Thread's target. A ThreadGroup can hold several Threads and ThreadGroups, which is handy in case you want to organize your Threads into a tree structure and operate on whole subtrees of Threads at once. Moreover, Threads are prohibited from accessing the parent of their ThreadGroup. So by using ThreadGroups, you can be sure a rogue thread isn't going to suddenly suspend or kill a thread you don't want it to. Chapter 16, "Multithreading with Java," explains multithreaded Java programming.
Exceptions and Errors
Java contains an elegant exception-handling mechanism. When a method cannot complete normally, there are three choices. You can return a nonsensical value, never ever return, or throw an exception. The first choice is not usually acceptable, and the second is downright antisocial. Applets want to take particular care that this doesn't happen. What's left? Throwing an exception transfers control non-locally to a block of "rescue" code defined in some currently executing method that called on your method, perhaps indirectly (for example, to a context perhaps several frames up the execution stack, but in the same thread). This rescue code is called a "catch block." The objects that get "thrown and caught" are of class Exception, Error, or any class that implements the interface Throwable. These thrown objects describe the exceptional condition and the context in which it occurs. An Exception indicates that a method can't complete its stated mission because of bad arguments or unavailable resources. An Error is more serious and indicates a condition that is abnormal and unexpected. In the Java API these are heavily subclassed to provide more and less specific "flavors" of exception. Chapter 10, "The Order Entry System: Exception Handling and Browser Interaction," explains exception handling in more detail.
The Runtime Environment
The java.lang package provides access to the external system environment by way of the related classes Runtime and System. External processes are manipulated by way of class java.lang.Process, and security policy is set by an object of type java.lang.SecurityManager.
Compiler
The java.lang.Compiler class provides a way to access an embedded Java compiler, which is loaded at startup if the java.compiler system property is defined. The value of the property should be the name of a dynamically linked library implementing the compiler. There is no predefined compiler; you must provide one.
Runtime
You can't construct a Runtime instance yourself. A Runtime object is obtained by calling the static method Runtime.getRuntime(). By using a Runtime instance, you can
- Execute a subprocess.
- Exit the program.
- Load a dynamically linked library.
- Run the garbage collector or finalize objects.
- Estimate free memory.
- Control program tracing.
- Localize streams (make them translate from Unicode to the local character set).
A Runtime object can be used to execute another system process by way of the exec() method (in four flavors) that returns a java.lang.Process object. The Process instance is useful for attaching to the standard input, output, and error streams of the new process. You can also kill the subprocess, wait for it to terminate, and retrieve its exit code, all by way of the Process object. The process exists outside the Java virtual machine. It is not a Thread but a separate system process, and some aspects of its behavior may be system-dependent.
You can use a Runtime object to load a dynamically linked library. This is necessary in order to use native methods in Java. Procedures for loading dynamic libraries are covered in Chapter 14, "Extending Java."
The methods traceInstructions() and traceMethodCalls() request that the Java virtual machine print trace information about each instruction or each method call that gets executed, respectively. Where the output ends up or whether tracing is supported at all is implementation-dependent.
While you can use a Runtime object to run the garbage collector or finalize any outstanding objects (gc() and runFinalization() methods), this should not normally be necessary, since the Java environment runs a separate thread whose purpose is to finalize and garbage collect when necessary (see Chapter 4). Furthermore, although the exit() method can be used to exit the program, it should normally be avoided, except to specify an exit code upon normal termination of a stand-alone program. Low-level methods and applets generally throw exceptions instead.
System
While the Runtime functionality is accessed through an actual instance, the System class provides some similar functions by way of static methods and class variables. There are no instances of the System class. The System class allows you to
- Access the standard input, output, and error streams.
- Exit the program.
- Load a dynamically linked library.
- Run the garbage collector or finalize objects.
- Access system Properties.
- Access the SecurityManager.
- Perform system-dependent array copy and time-check operations.
The standard input, output, and error streams of your Java application
or applet are accessed as System.in,
System.out, and
System.err.
These class variables are PrintStream
objects (see java.io, below),
allowing your application to perform the usual UNIX-style I/O.
That's not much use in a finished applet, since an applet embedded
in a web page is typically disallowed from doing
anything useful with these streams. They are handy for debugging
in appletviewer, and also
in stand-alone Java applications.
Java also maintains some system properties, accessible through the System class. These take the place of environment variables and anything else in the system that is relevant to the Java environment. The static method getProperties() returns a java.util.Properties object describing the system properties. For example, the properties can be listed by a little program such as the following:
public class Props
{
public static void main( String args[] )
{
System.getProperties().list(System.err);
}
}
This program results in a list of system properties:
-- listing properties --
java.home=/mnt2/java
java.version=1.0
file.separator=/
line.separator=
java.vendor=Sun Microsystems Inc.
user.name=korpen
os.arch=sparc
os.name=Solaris
java.vendor.url=http://www.sun.com/
user.dir=/nfs/grad/korpen/www/java
java.class.path=.:/home/grad/korpen/www/java:/mnt2/ja...
java.class.version=45.3
os.version=2.x
path.separator=:
user.home=/homes/staff/korpen
Use the static method getProperty() to get individual properties by name.
SecurityManager
By extending the abstract class java.lang.SecurityManager, you can specify a security policy for the current Java program. Any code loaded over the Internet by your program is then subject to that policy, for example. A Java program has only one SecurityManager. You can look up the current SecurityManager by calling System.getSecurityManager(). This method returns null to indicate that the default security policy is being used. The default policy is rather lax. However, you can install a custom security manager. This allows you to do the following, among other things:
- Prevent Java code from deleting, writing, or reading certain files.
- Monitor or disallow certain socket connections.
- Control which Threads may access which other Threads or ThreadGroups.
- Control access to packages, and to system properties.
For example, the method call that checks whether the calling code is allowed to delete a certain file is declared:
public void checkDelete( String file );
The method must either return quietly, or throw a SecurityException. This is typical of the public methods in class SecurityManager.
To provide a custom security manager, write a subclass of SecurityManager and override some of its check methods. Although the SecurityManager class is abstract, none of its methods are abstract. You still want to override a fair number of them, though, since the check methods inherited from SecurityManager always throw a SecurityException. You don't have to call on these methods yourself for the security manager to be effective. Once the security manager is installed, various library methods call on it to check for security clearance. To install your SecurityManager, create an instance of it, and call System.setSecurityManager(). Here is a little program (SMDemo.java) that demonstrates how to use a custom security manager. You should create files named DELETEME and KEEPME before running the program:
import java.io.File;
class MySecurityManager extends SecurityManager
{
public void checkDelete( String file )
{
// Only allow the file "DELETEME" to be deleted.
if ( !file.equals( "DELETEME" ) )
throw new SecurityException( "cannot delete: " + file );
}
// Override many more checkXXX() methods here...
}
public class SMDemo
{
public static void main( String argv[] )
{
MySecurityManager m = new MySecurityManager();
File deleteme = new File( "DELETEME" );
File keepme = new File( "KEEPME" );
System.setSecurityManager( m );
deleteme.delete(); // Should be OK.
keepme.delete(); // Should get a SecurityException.
System.exit(0);
}
}
After you execute the program, you should see that the file DELETEME is gone and the KEEPME file is still there, the program having triggered a SecurityException upon trying to delete it.
The security manager can only be set once in a program. So by setting it yourself, you know that untrusted code isn't busy installing its own super-lenient policy. See Chapter 19, "Security Issues," for more details on security issues.
Applets are not usually allowed to set the security manager.
Classes at Runtime
Even at runtime, it is possible to access certain features of a class. This is done by way of the class Class, which implements a class descriptor object for a Java class. You can get a class descriptor from an existing class either by using the getClass() method of java.lang.Object or by calling the static method Class.forName():
Class stringClass = Class.forName("String");
Using a class descriptor, you can find out:
- The class name
- The superclass
- Whether the class is actually an interface
- Which interfaces the class implements
- Which ClassLoader originated this class
There is also a way to instantiate new objects from the class descriptor: the newInstance() method. This has the limitation that no arguments can be passed to the constructor, so it fails unless the class has an accessible constructor which takes no arguments. There also doesn't seem to be any way to use the class descriptor to produce a valid operand for the right-hand side of instanceof.
Class java.lang.ClassLoader is meant to provide a way to load classes at runtime from a user-defined source. It is an abstract class. A subclass must implement the loadClass() method to load an array of bytes from somewhere and then convert it into a class descriptor by calling resolveClass() and defineClass().
java.awt
The java.awt package is a uniform interface to various windowing environments (AWT stands for Abstract Window Toolkit). The various classes in this package make it easy to create graphical user interface (GUI) elements such as scrollbars, text fields, buttons, checkboxes, and so on. Internally, these classes bind to a native windows toolkit in the local implementation. Your applet does not have to know which toolkit is actually being used since the same method calls have functionally equivalent results in any implementation, be it X Window, MacOS, OS/2, or Windows NT/95. You'll find that programming in java.awt is easier and more elegant than native windows programming anyhow. Future versions of Java reportedly will incorporate still more powerful coordinate-based drawing functions, making Java unbeatable for writing portable GUI, driven programs.
The interface between java.awt and the native windows toolkit is provided by the java.awt.Toolkit class and the package java.awt.peer, which is discussed later.
The framework for any GUI application is provided by the java.awt.Component class and its subclasses. Every GUI element (except menus) corresponds to a subclass of Component. Menus have slightly different requirements and they subclass the java.awt.MenuComponent class.
Many of the components are demonstrated in the included Java program AWTDemo.java, together with some nifty event-handling tricks. Figure 3.1 shows some Checkboxes, a Choice, three Labels, a TextField (subclassed to accept only numbers), a Scrollbar and a Button. These classes are all explained in the following sections.
Figure 3.1: The sample program AWTDemo.java: first screen
Component
The Component class represents a GUI component that has size, font, and color attributes, can redraw itself, and handle events (such as a mouse click) which occur within the component's display area, as well as perform various other functions. An example of a simple component is java.awt.Button, which displays a rectangular button with a string label and responds to click events. Components size themselves using java.awt.Dimension objects. These are simple objects with two instance variables: width and length.
Some of the handiest methods from java.awt.Component include the following:
hide()-Hide the component.
show()-Show the component. Most components are visible by default, but Windows are initially invisible, and must be shown. Showing a Window also brings it to the front.
disable()-Disable the component so a button has its text grayed out, for example.
enable()-Enable a disabled component again.
paint()-Here the component paints itself. When inheriting from another component, you typically override this. You shouldn't call this method directly. Call repaint().
repaint()-Asks the runtime system to paint the component as soon as possible.
handleEvent()-You can override this method to provide specific responses to user events.
createImage()-Create images from an ImageProducer. This is described later in this chapter as part of the package java.awt.image.
validate()-Verify that the component is valid, which means that the peer has been created and displays properly (see java.awt.peer, below), and that the component is properly laid out.
A word about validation: components are validated automatically before they are drawn for the first time. But if you add() new components to a container after it has already been shown, you need to call the validate() method of the container explicitly (anyway, it never hurts to do so). The validate() method is recursive. It validates all components contained in the validated component, as well as the component itself.
Container Components
Container components contain other components. All containers belong to a subclass of the java.awt.Container class which knows how to add() and remove() components to or from the container and how to layout() the various components using a layout manager. Every component that is not a top-level window (instance of Window or a subclass) must be added to a container to be visible on screen.
Of course containers can contain containers, so a GUI is nothing but a hierarchy of components organized by containers. This can be several levels deep. Every component is either a top-level window or else has a parent container (accessible by way of the getParent() method in java.awt.Component).
Containers are either Panels or Windows. A Panel (java.awt.Panel) is a general purpose container that sits inside a parent container on your screen. An applet is a Panel, for example. A Window (java.awt.Window) occupies its own top-level window on your screen. By default, a Window is very plain without borders, title, or pulldown menus. These added features are provided by a special class of window, java.awt.Frame. The AWTDemo.java program uses a Frame as its top-level window (see Figure 3.1). A handy method that is special to Window subclasses is the pack() method. This method resizes the Window so that every component in it can be laid out at its preferred size.
Tip: |
You should call a Window's resize() or pack() method before showing it for the first time, to give it a definite size. |
The remaining container classes are Dialog and FileDialog, which are each a kind of Window. A Dialog object implements a window that is optionally modal (a modal dialog grabs the input focus so the user is forced to provide input before proceeding) and that vanishes when the parent Frame is iconified. These are properties that a dialog box should have. A FileDialog provides a standard type of modal dialog box that allows the user to select a file on the local filesystem for saving or loading. The sample program AWTDemo.java shows a FileDialog when you select Load... from the File menu. Use a java.io.FilenameFilter object to filter the filenames that are displayed in the dialog.
Buttons and Other Components
Let's take a look at some of the standard components that comprise a user interface. For the details of how to handle the various events, see the next section, "Event Handling."
Button
A Button component is a simple pushbutton that displays a string label and responds to mouse presses from the user. Pressing a button triggers an action event. See Figure 3.1 for a picture of a typical Button.
Canvas
A Canvas is a blank area suitable for drawing in. You can use a Graphics object to put polygons or images on the Canvas. Because a Canvas has no predefined responses to events and because its appearance is completely arbitrary, a Canvas is a good place to start when designing custom components that look unlike the standard components.
Checkbox
A Checkbox is a small box with two states: either it's checked or unchecked. Clicking the mouse over an enabled Checkbox toggles its state. A Checkbox can have a string label. Figures 3.1, 3.2, and 3.3 all have Checkboxes. Notice how they look different when in a CheckboxGroup.
CheckboxGroup
You put Checkboxes in a CheckboxGroup in order to make them exhibit "radio button" behavior: when one is checked, the others become unchecked. Only one Checkbox from the group can be checked at any time. The CheckboxGroup does not act as a container for the Checkbox component; it only tells some of the Checkboxes to uncheck themselves when necessary. Checkboxes can be placed in a group on creation or by calling their setCheckboxGroup() method.
Choice
A Choice component allows the user to specify one of a short list of choices, which appear on a little popup menu next to the current choice. The choices on the list are identified by a string name. Figure 3.1 shows a Choice component.
Label
A Label component displays a line of text. The text can be aligned to the left, right, or center of the Label. The user isn't allowed to edit the text in a Label. Use a TextField for that. Figure 3.1 shows several Labels.
List
A List presents a scrollable list of items, identified by string names. Use this instead of a Choice when multiple selections are meaningful or when there may be too many items to conveniently display on a single popup menu. Figure 3.2 shows a typical list. Lists can allow or disallow multiple selections.
Figure 3.2: The sample program AWTDemo.java: second screen
Scrollbar
Most Scrollbar components are automatically generated when required by List or TextArea components. If you want to create your own scrollbars, you can do so. The orientation is specified by the constants Scrollbar.HORIZONTAL and Scrollbar.VERTICAL. Take a look at the horizontal scrollbar in Figure 3.1.
The Scrollbar reports its current position via the getValue() method. To make the values meaningful, set the minimum and maximum values, together with the line and page increment values, using either the full five-argument constructor or the setValues() method.
There are five basic operations on a Scrollbar: line up, line down, page up, page down, and absolute positioning. Corresponding to these, there are five scrollbar event types. You don't have to discriminate between these very often. The event argument is always the integer value reflecting the new scrollbar position. Unless you want real-time response to Scrollbar actions, it is not necessary to handle Scrollbar events at all.
TextField
A TextField component holds a single line of text in a little window. The text is allowed to be longer than the window, in which case only part of it shows. By default, the user is allowed to edit the text. You can also set a TextField to be read only using setEditable(false). This method is from class TextComponent, the abstract superclass of both TextField and TextArea. The NumberField shown in Figure 3.1 is a customized form of TextField.
TextArea
A TextArea is a pane containing lines of text. Like a TextField, it can be editable or not. Figure 3.3 shows a TextArea in action.
Figure 3.3 : The sample program AWTDemo.java : third screen
Event Handling
Programs that use a graphical user interface are inherently event-driven, at least in part. An event refers to something the user causes to happen using the mouse, keyboard, or other input device. An event-driven program typically just sits in an infinite loop that goes something like "wait for event, handle event, repeat." In Java, you don't have to code this behavior; it is taken care of by the AWT. You override the event handling methods in the Component class in order to perform specific actions in response to user events.
When an event occurs within a GUI component, it is the native windows toolkit that first receives the event. The event is then passed to the AWT class that represents the component (the native widget corresponding to the AWT component is said to be the peer of the component). For example, when a Button is pressed, what really happens is that the peer of the button receives the event, creates a java.awt.Event object to describe it, and sends it to the java.awt.Button object corresponding to that button. The handleEvent() method of the Button object is invoked. By default, this method is inherited from the Component class and it decodes the event and calls on the various short form event-handler methods of the Button.
The short form event-handler methods are largely self-explanatory:
mouseDown(), mouseDrag(), mouseUp(), mouseMove(), mouseEnter(), mouseExit()
keyDown(), keyUp()
action()
The action()method means that an "action" has occurred. This depends on which type of component received the event. For example, for a Button, this represents a press. The default versions of all these methods do nothing and then return false. The boolean return value of all these methods (and of handleEvent()) indicates whether the event is fully handled. Return true only when you wish no further action on this event.
Among its instance variables, each Event object has an event type, a target, and an arbitrary argument. The event type is one of the predefined constants describing roughly what has happened: Event.ACTION_EVENT, Event.KEY_PRESS, and so on. The target is the component in which this event has occurred, and the argument is an arbitrary Object that further specifies the event. For example, for Scrollbars, it is an Integer which is the new slider value.
Until the Event is fully
handled by some event handler, it continues to propagate up the
container hierarchy, passing from each component to its parent
container. If the event emerges from a
top-level container such as Window
or Frame and is not fully
handled, then it is passed back to the native windows toolkit
to be handled in the usual way.
http://java.sun.com/tutorial/ui/components/peer.html
Caution: |
|
There are a few ways you can intercede in this process to provide custom behavior (using Button as an example):
- Override a short form event-handler function in a Container which contains the Button.
- Override the handleEvent() method of a Container which contains the button.
- Subclass Button and override an event handler.
For example, a TextField object generates keyboard events. Usually, you want the default behavior: the characters typed appear normally in the TextField. By subclassing TextField, you can intercept the keyboard events and provide alternative behavior such as putting all input characters in lowercase. You achieve this by altering the event as it passes up the hierarchy but is still returning false so that the changed event is returned to the native text field widget. On the other hand, returning true in response to some keyboard events ensures that those events are ignored.
See the example program AWTDemo.java for an idea of how to do this. The TextField shown in Figure 3.2 is actually an instance of class NumberField (defined in AWTDemo.java), which extends TextField and overrides the handleEvent() method. You can only type numbers in this component.
If you override both handleEvent() and one of the short-form event handlers in the same component, be aware that the short-form handler is never called, unless your handleEvent() explicitly does so, or else calls on the superclass's handleEvent() method to do so. The easiest way to do this is to make your handleEvent() method like this:
public boolean handleEvent( Event e )
{
if ( e.id == Event.ACTION_EVENT ) {
// do some stuff ...
return true; // this event was fully handled
}
// perhaps more stuff...
return super.handleEvent( e ); // refer this event to a higher authority
}
If you are consistent with this practice, you can be sure that the superclass knows how to decode the event and call on your short-form event handlers.
Getting Painted
To do any special drawing operations that change the look of your component, you need to override the paint() method and put the drawing code there. The single argument to paint() is a Graphics object through which you can draw images, lines, text, and so forth. The AWT calls on your paint() method when the component needs to be redrawn.
All paint requests occur in a single high-priority thread. This guarantees that they happen in the proper order and quickly. This also means that you never call the paint() method of a Component directly. Call the repaint() method instead when you wish a Component to be redisplayed. Many built-in methods automatically result in a repaint(), but you may need to call repaint() yourself in certain situations.
The repaint()method actually results in a call to the component's update() method. The default update() method clears the display area of the component to the current background color and then calls paint(). This can create excessive flicker for some applications so you may want to override the update() method to prevent the background from getting cleared. See Chapter 17, "Advanced Graphics: Multimedia," and Chapter 18, "Serious Play: Game Applets," for examples of advanced painting techniques including animation and double-buffered graphics.
Events propagate up the component hierarchy but paint requests propagate down. The top-level windows are drawn first, and then their immediate children, and so on. This ensures that the children show up on top of their parent container.
Menus
The AWT includes support for pulldown menus. The various components that implement the menus are not of class Component, but rather of class java.awt.MenuComponent. This reflects the "popup" nature of menus: they don't occupy space in a parent container, but pop up on top of other windows when required.
A menu is represented by a java.awt.Menu object. It can only be displayed on a menu bar (java.awt.MenuBar). In turn, a menu bar must be associated to a Frame in order to be useful. So a Frame can have a MenuBar, a MenuBar contains Menus, and a Menu contains MenuItems.
A MenuItem is a choice on a menu which is labeled with a string. CheckboxMenuItem extends MenuItem and has a string label with a checkbox gadget beside it. Selecting the item toggles the checkbox. The Menu class itself extends MenuItem, so a menu can be an item on another menu, that is to say, a submenu.
AWT menus support separators and tear-off functionality. A tear-off menu can be dragged onto the desktop where it occupies a new top-level window. A separator is a menu item with the special name "-". A tear-off menu is created by calling the constructor with a second, boolean argument set to true:
Menu my_menu = new Menu( "My Menu", true ); // create a tear-off menu
A menu bar can have a designated help menu which is distinguished from the other menus in some way. For example, it is often placed at the extreme right end of the menu bar. See the sample program AWTDemo.java for examples of menus on a menu bar including a help menu, separators, and a submenu.
Layout Managers
When you add components into a container, it is the layout manager of the container which determines the actual size and location of each component. The two argument forms of add() allow you to specify a placement argument which is interpreted by the layout manager. The java.awt classes implementing layout manager policies are described here. They all implement the interface java.awt.LayoutManager.
All containers have a default layout manager, but you can designate whichever layout manager has your favorite policy by passing a new instance of the LayoutManager to the container's setLayout() method. You can also provide custom layout managers by implementing the LayoutManager interface yourself. The sample program AWTDemo.java demonstrates many of the layout managers in action.
Tip: |
|
BorderLayout
The possible placements are "North," "South," "East," "West," and "Center." They are identified by the string names. The components around the edges are laid out first and the center component gets the leftover room. This can make some components larger than necessary since they are stretched out to meet the edges of the container. Put the components inside a Panel to avoid this.
Caution: |
|
CardLayout
This layout manager lets several components occupy the same space, with only one visible at a time. Think of the components as lying on "cards," which are shown one at a time. You need a way for the user to flip through the cards. Typically this is a Choice or a series of Checkboxes in a CheckboxGroup. You can also have next/back Buttons.
In the sample program AWTDemo.java, there are three "cards" in a CardLayout, and the user switches between them using Checkboxes. The three cards are shown in Figures 3.1, 3.2, and 3.3.
FlowLayout
This is one of the simpler layout managers. Components are arranged left to right in a row until no more fit. Then a new row is begun. Each row is centered in the parent component by default. This is the default layout for Panels.
GridLayout
A GridLayout arranges components in a grid of rectangular cells, all the same size. The contents of each cell are resized to fill the cell so you may want to put them on a panel in some of the cells and let them take their natural size. The Checkboxes in Figure 3.2 are laid out using a GridLayout.
As you add components to a GridLayout, the cells are populated in reading order: from left to right in each row and then down to the next row.
GridBagLayout
The most flexible layout manager provided by java.awt is the GridBagLayout. Like a GridLayout, it is based on a rectangular array of cells. However, each component may occupy a rectangular area covering several cells. There is no requirement that the child components have the same size. Each child component has an associated GridBagConstraints object to give hints to the layout manager about its minimum size and preferred position in the container.
Graphics and Images
The java.awt package includes some classes to help you draw custom graphics and images. You typically want to draw either in a Canvas, or an off-screen Image (for double-buffered graphics see Chapter 17). Drawing operations are usually performed in the paint() or update() methods of the component you want to draw in.
Graphics
An instance of java.awt.Graphics is the single argument to paint() and update(). This object provides access to a drawable area as well as a graphics "context": a current drawing color, font, and drawing mode. Here are just a few of the methods in the class Graphics:
drawLine()
drawPolygon()
drawRect()
drawOval()-A misnomer because it really draws an ellipse.
drawImage()-Draw a bitmap Image, perhaps scaling it first.
drawString()-Draw a String in the current font.
fillPolygon()
setColor()
setFont()
getFontMetrics()
To assist in these coordinate-based drawing operations, there are the java.awt classes Point, Polygon, and Rectangle.
Image
An Image object references a bitmapped image. Applets can load images from a URL. If you have an ImageProducer handy, you can call createImage() in either class Component or Toolkit. It's also possible to load an image using the getImage() method of a Toolkit object which can load from a URL or a file. The Java AWT has built-in support for the GIF and JPEG formats. See java.applet and java.awt.image for more on producing Images.
An Image is not displayed on-screen automatically. You must paint it in a component using the drawImage() method in java.awt.Graphics. You can also perform arbitrary drawing operations in an Image by getting a Graphics object for the Image from the getGraphics() method.
When loading images, the java.awt.MediaTracker class can come in handy. A MediaTracker provides a way to wait for one or several related images to finish loading before doing anything further.
Color
You can mix a color from red, green, and blue light. This is the RGB Color Model. It's important in Java since most of the time you draw onto a computer monitor which represents colors this way. RGB is the default color model for java.awt.Color objects which represent colors in Java.
A Color object is constructed from three intensity values in the range 0-255, one for each primary color: red, green, and blue. Alternatively, a single int can hold the 24 bits that serve to define any RGB color. Colors can also be converted between RGB and another color model, the HSB color model (Hue, Saturation, Brightness). The saturation of a color is a measure of how vibrant or intense the hue appears. Color with zero saturation is just a shade of gray while a color with saturation equal to 1 is as vibrant as the color can be. Brightness controls whether green looks more like forest green or lime green, for example. The HSB color model is useful for some operations such as desaturating a color image or changing the brightness only.
There are many convenient class variables for common colors. Thus, Color.red denotes red, Color.white denotes white, and so forth.
Fonts
If you get sick of looking at your system's default font or if you want extra large titles and such, you can create a java.awt.Font instance to represent a font which is available on your system. For example, to specify italic, 18-point Helvetica:
Font helv18i = new Font( "Helvetica", Font.ITALIC, 18 );
You then use this as an argument to setFont() in class Graphics or Component. You probably want to stick to well-known font names because each font you load must be available on the user's system. Use the java.awt.Toolkit method getFontList() to read off the locally available font names:
import java.awt.*;
public class Fonts
{
public static void main(String args[])
{
Toolkit t = Toolkit.getDefaultToolkit();
String fonts[] = t.getFontList();
for (int i = 0; i < fonts.length; i++)
System.err.println( fonts[i] );
System.exit(0);
}
}
To provide the measurements necessary for basic typesetting, a FontMetrics object can be retrieved for each font. There are methods in both classes Graphics and Toolkit which provide a FontMetrics instance, but you can construct one from the font directly:
FontMetrics helv18i_m = new FontMetrics( helv18i );
The FontMetrics object provides you with such arcane knowledge as the ascent, leading (rhymes with bedding!), descent, and character widths of the font. Unless you already love typesetting, try your best to use a standard text component and avoid accessing font metrics directly. If you love typesetting, then be aware that you can't do any high-powered typesetting with these objects in any case. The Font and FontMetrics classes don't seem to have any idea about kerning pairs and ligatures, for example. A FontMetrics object provides enough information to display screen fonts readably.
java.applet
Java applets are one of the main attractions of programming in Java. The java.applet package provides some methods that are very useful for programming applets. Some of these methods are shortcuts. They can be done using the other standard Java classes but only with a fair amount of work. Would you rather load an image over the Web by opening your own socket connection, speaking http to a Web server and parsing the header and image data, or by doing this?
public class MyApplet extends java.applet.Applet
{
Image my_image;
public void init()
{
try {
my_image = getImage( new URL( getCodeBase(), "myimage.jpg" ) );
} catch (MalformedURLException e) {}
}
// More stuff ...
}
Similar support exists for loading audio and even for directing the host browser to load a new Web page.
Applet
Every applet is defined by a public class extending java.applet.Applet which extends java.awt.Panel. The Applet class provides the four basic methods which embody the life cycle of an applet:
init()-Called to initialize the applet after loading.
start()-Called when the applet is displayed.
stop()-Called when the applet is no longer being displayed.
destroy()-Called when the applet is about to be unloaded.
Don't call these methods yourself. The host environment calls them automatically in response to user actions.
Each of these four methods should be fast. In particular, don't make the mistake of putting a lengthy or infinite loop into the start() method. If you have a lot of work to do (such as I/O or animation), use the start() method to start a new Thread which does the real work. You can even give your applet a run() method and declare that it implements Runnable; then pass in the applet itself as the target of the new thread:
public class BusyApplet extends Applet implements Runnable
{
Thread t;
public void run()
{
// Get busy ...
}
public void start()
{
t = new Thread(this); // Thread t executes my run() method
t.start();
}
public void stop()
{
t.stop();
t = null;
}
// etc ...
}
Use the init() method to initialize any local variables and load external resources such as images and audio clips. You may think that these operations are time-consuming and should therefore have a separate thread but the routines described below for images and audio already function asynchronously. They return immediately after starting separate threads, as required.
Some of the handy multimedia-related methods in class Applet include the following:
getAppletContext()-Get a handle to the current context (host browser or applet viewer).
getAudioClip()-Get an audio clip from a URL.
getImage()-Get an image from a URL.
play()-Play an audio clip directly from a URL.
showStatus()-Show a message on the status line of the host browser.
The interfaces AppletContext and AudioClip are provided as a system-independent way of accessing the objects returned by getAppletContext() and getAudioClip(). You can use an AppletContext instance to find other applets on the same page or to direct the browser to visit a new URL.
When an applet is included in a Web page using the APPLET tag, the page designer can specify certain applet parameters which are string names with associated string values. You can access these values using the getParameter() method and use them to modify the behavior of your applet.
java.awt.image
The java.awt.image package allows device-independent loading and filtering of bitmapped images.
Color Models
A color model is a way of representing colors numerically. The abstract class java.awt.image.ColorModel provides a uniform superclass for various color models. The subclasses have to know how to convert their representation into the default RGB values together with transparency information (an alpha value). An alpha of 0 is transparent; 255 is opaque.
Two predefined types of color model in java.awt.image are DirectColorModel and IndexColorModel. A DirectColorModel encodes red, green, blue, and alpha channel values but possibly with less than eight bits per channel and the channel masks can be arbitrarily ordered within a 32-bit integer. An IndexColorModel works by looking up colors on a color table of red, green, blue, and alpha values. The maximum number of bits in each value is arbitrary up to a point; you never need more than eight per channel. The color table can have any length.
Producing Images
The java.awt.image package provides a black-box type protocol for loading image data. The interfaces ImageConsumer, ImageProducer, and ImageObserver allow objects to declare that they are interested in the following:
- Receiving image data (ImageConsumer)
- Producing image data (ImageProducer)
- Being notified of progress in loading or preparing images (ImageObserver)
An ImageConsumer registers itself with the ImageProducer. The producer loads the image data from "somewhere" which depends on the exact implementation of the interface, and sends the data to the consumer by calling the consumer's setPixels() methods. What the consumer receives is the raw pixel data in an array. For example, the java.awt.image class PixelGrabber is an ImageConsumer which grabs a rectangular sub-image of a given Image.
Any Component or Toolkit instance can use its createImage() method to create an Image object when given an ImageProducer. You've seen how to load images in an applet and from a java.awt.Toolkit object. Further sources for Images include:
MemoryImageSource-Produces an Image from an array of RGB values.
FilteredImageSource-Produces an Image from an existing ImageProducer and an ImageFilter (see the section "Image Filters," later in this chapter).
Both of these classes implement the ImageProducer interface. Also, every Image can supply an ImageProducer (via the getSource() method) which reproduces the image itself.
The interface java.awt.image.ImageObserver requires the single method imageUpdate(). This method is called if the ImageObserver is supplied as an argument to an Image method such as getWidth() and the status of the image suddenly changes (the unknown width becomes known, for example). The java.awt.Component class uses this mechanism to redraw components automatically as their images load.
Image Filters
As image data passes from producer to consumer it can be filtered. The java.awt.image class ImageFilter implements an image filter which does nothing. It is the superclass for all image filters. The RGBImageFilter class extends ImageFilter and provides a shortcut for writing filters which only want to manipulate RGB color data. To write a custom image filter, you subclass one of these.
Using an ImageFilter is a matter of instantiating a FilteredImageSource with it and the original image. Then you can create the new image from the FilteredImageSource by the usual createImage() method.
Possible uses for custom image filters include rotating existing images, adjusting their brightness and contrast, blurring images, or doing other special effects. The predefined CropImageFilter is an ImageFilter which extracts a specific rectangular sub-image.
java.awt.peer
Every java.awt.Component object has a peer. Essentially a peer is an object in the native windows toolkit together with an interface to it. A Toolkit instance knows how to create peer objects. In Java programming it is very seldom necessary to do anything relating directly to the peer objects; but it's nice to know they are there. The java.awt.peer package is nothing but a collection of interfaces to these peer objects, one for each AWT component. This includes ButtonPeer, CanvasPeer, CheckboxPeer, and so on. Each interface provides the basic methods which the AWT uses to manipulate the peer in a toolkit-independent way. You should not attempt to call these methods directly. In fact, unless you are writing your own toolkit, you don't need to know this package at all.
java.io
The Java model for I/O is entirely based around streams. A stream is a one-way flow of bytes from one place to another. Files, pipes, and sockets are places to attach streams to. The many flavors of stream classes defined in the java.io package are organized by inheritance to avoid duplication of methods. A stream class throws an IOException when things go awry.
Of course, java.io also provides methods for accessing files on a local file system. As far as possible this is made system-independent.
The predefined PrintStreams, System.out, and System.err are useful for printing diagnostics when debugging Java programs and applets. The standard input stream System.in is also available; it is of class InputStream.
Basic Streams
The most basic stream classes don't provide buffering, and they don't structure their data at all. They provide nothing but a pathway for bytes. The differences between them lie in the mechanical question of where the bytes are to be found: in a file, in memory, or on a pipe. You generally use one of these classes only as a step toward instantiating a more useful form of stream.
InputStream, OutputStream
These are the base classes for all the other stream classes. They allow you to read() and write() arrays of byte. InputStream objects can declare that they support mark and reset behavior. This means that the input stream can be marked at some point and repositioned there subsequently. This is handy if you try to parse an input stream. The OutputStream class has a flush() method which writes any bytes that may be saved in a buffer (however, an instance of OutputStream is not required to buffer bytes).
FileInputStream, FileOutputStream
These classes attach streams to File and FileDescriptor objects which correspond to files on a local file system. They extend InputStream and OutputStream and provide the same basic functionality. You can get a FileDescriptor object referencing the stream by calling the getFD() method.
ByteArrayInputStream, ByteArrayOutputStream
The class ByteArrayInputStream extends InputStream and reads out of an array of bytes rather than from a file or socket. This is useful when you have the data already in memory but you need to pass it to a method which expects a stream.
Likewise, ByteArrayOutputStream writes into a buffer of bytes which grows as required. You can access the written data as an array of bytes by calling the toByteArray() method or as a String, using toString().
StringBufferInputStream
A StringBufferInputStream is an InputStream which reads from a StringBuffer.
PipedInputStream, PipedOutputStream
These streams correspond in pairs: every PipedOutputStream needs to write to a PipedInputStream, and vice versa. This arrangement can be thought of as a pipe between the thread writing the PipedOutputStream and the thread reading the PipedInputStream. In this way, you can create Runnable objects which act as stream filters, for example (but also see FilterInputStream and FilterOutputStream, below).
These streams provide the same basic read/write functionality as InputStream and OutputStream, above.
SequenceInputStream
This class extends InputStream and allows the transparent concatenation of several InputStreams into a single stream. When one stream hits end-of-file, the SequenceInputStream automatically begins reading from the next one in sequence.
The constructor takes either two InputStreams or else a java.util.Enumeration of InputStreams.
Filtered Streams
Just moving bytes is not enough. Various forms of improved functionality are needed so often that embedding them into the stream object itself makes sense. All these improved or filtered stream classes extend FilterInputStream or FilterOutputStream. You can think of them as filters because they have an input and an output, and the output is the input but transformed in a useful way.
FilterInputStream, FilterOutputStream
These are the base classes for streams which extend the basic I/O operations of InputStream and OutputStream. They are intended to be subclassed. You can't instantiate them because the constructor is protected.
A FilterInputStream is constructed from a single InputStream which then becomes an instance variable. Now FilterInputStream extends InputStream so it implements all the methods of InputStream but only as trivial wrappers which access its actual, protected InputStream. Additional methods providing the extended functionality are to be supplied in subclasses.
The same setup applies to FilterOutputStream.
BufferedInputStream, BufferedOutputStream
The class BufferedInputStream extends FilterInputStream and you use it like an InputStream. The difference is that a BufferedInputStream is more efficient. It saves up bytes in a large buffer until you need them. This means that most read() requests don't actually cause an I/O operation.
Class BufferedOutputStream does the same only for output streams.
LineNumberInputStream
This class acts like an InputStream with the added methods getLineNumber() and setLineNumber() which let you keep track of line numbers.
PrintStream
A PrintStream is a special kind of OutputStream with the added methods print() and println(). These methods print the string representation of any object (as per String.valueOf() or toString()) onto the output stream. The println() method appends a newline character while print() does not. Even though a char is 16 bits wide, only the lower eight bits of each character are written.
Upon creation, you can specify whether the stream should flush itself every time a new line is written.
PushbackInputStream
This is an InputStream which allows you to unread() a single byte into a pushback buffer. You may not unread() another byte until the pushback byte is read again. This is useful for parsing strings since you often need to peek at the next byte without necessarily accepting it as input.
Data I/O
Streams read and write bytes but quite often you want to send
other primitive Java types. The interfaces DataInput
and DataOutput specify that
a class knows how to read and write the various primitive Java
types in a machine-independent way. The most useful methods in
DataInput and
DataOutput
are shown in Tables 3.1 and 3.2. All of the DataOutput
methods in Table 3.2 return void.
Table 3.1. Useful DataInput methods.
Method | Description |
boolean readBoolean() | Read a single boolean. |
byte readByte() | Read a single byte (8 bits). |
char readChar() | Read a single char (16 bits). |
float readFloat() | Read a single float (32 bits). |
double readDouble() | Read a single double (64 bits). |
int readInt() | Read a single int (32 bits). |
long readLong() | Read a single long (64 bits). |
short readShort() | Read a single short (16 bits). |
int readUnsignedByte() | Read a byte and interpret as an unsigned integer. |
int readUnsignedShort() | Read a short and interpret as an unsigned integer. |
void readFully(byte b[]) | Read bytes into an array until it is full. |
void readFully(byte b[],int,int) | Read bytes into a sub-array until full. |
String readUTF() | Read a UTF-encoded string. |
String readLine() | Read a sequence of bytes terminated by new line. |
All but one of these input methods throws a
java.io.EOFException
if the end of the input stream is reached before all the bytes
in the specified object can be read. The lone exception is
readLine().
It returns null to indicate
that EOF occurred before a new line was seen.
Table 3.2. Useful DataOutput methods (all return void).
Method | Description |
writeBoolean(boolean) | Write a single boolean. |
write(int) | Write a single byte (8 bits). |
writeByte(int) | Write a single byte (8 bits). |
write(byte[]) | Write a sequence of bytes. |
write(byte[],int,int) | Write a sequence of bytes from a sub-array. |
writeChar(int) | Write a single char (16 bits). |
writeFloat(float) | Write a single float (32 bits). |
writeDouble(double) | Write a single double (64 bits). |
writeInt(int) | Write a single int (32 bits). |
writeLong(long) | Write a single long (64 bits). |
writeShort(int) | Write a single short (16 bits). |
writeUTF(String) | Write a String in UTF-encoded format. |
writeBytes(String) | Write a String as a sequence of bytes. |
writeChars(String) | Write a String as a sequence of chars. |
Using these methods, you can read and write primitive Java types with ease. Your classes can use these methods to write themselves onto a stream by writing each instance variable.
Caution: |
|
If you are willing to let the new line character delimit the end of all your strings, you can read them using readLine(). A convenient way to do this is to make the corresponding output stream be a PrintStream and use the println() method to write the String. However, this method creates problems if your strings contain embedded new line characters.
To read and write more general strings, there are at least two options. Either the receiving end knows the length of the string in advance, in which case you may use writeBytes(), or else you need a scheme for terminating strings reliably. The easiest way to achieve this is also the most flexible way to read and write strings: by using readUTF() and writeUTF(). These methods read and write strings in a modified UTF-8 format. UTF is an ISO standard format which translates Unicode characters into streams of bytes in such a way that the normal ASCII bytes in the stream (encoded as 0x00-0x7f) always correspond to actual ASCII characters, while the non-ASCII bytes encode non-ASCII characters. A big advantage to using readUTF() and writeUTF() is that there is no need for you to send the length of the string or to add a terminator character. The UTF formatting takes care of that.
DataInputStream, DataOutputStream
These stream classes extend FilterInputStream and FilterOutputStream and provide a concrete implementation of the interfaces DataInput and DataOutput. They are two of the most useful stream classes in java.io, particularly for network applications.
Using Files
Although applets are seldom allowed to access the local file system, stand-alone Java programs can do so. The java.io package provides some classes to try and make this as system-independent as possible. Conventions about path separator characters and such are loaded from the system properties (see java.lang.System).
File
A java.io.File object represents a file name which may correspond to a file on an external file system. Using a File object, you can test whether such a file exists (exists() method). You can check permissions with canRead() and canWrite(). If the file is a directory (isDirectory()), you can list its contents by calling list(). You can use a java.io.FilenameFilter to restrict which files get listed. The File class includes several handy methods to create directories, rename files, check modification times, and so forth.
To open the file for reading or writing, instantiate a FileInputStream or a FileOutputStream.
FileDescriptor
A FileDescriptor is an opaque handle to an open file on the local system. You can use it to instantiate FileInputStream and FileOutputStream objects. File descriptors for the standard input, output, and error streams are accessible as static class variables in, out, and err (for example, FileDescriptor.out is the standard output).
RandomAccessFile
Class RandomAccessFile is the most flexible way to access a local file. This class implements both DataInput and DataOutput and provides a seekable file pointer. It is a suitable base class for classes which want to store fixed-length data records in a file. A RandomAccessFile may be opened in read-only or in read-write mode.
Interface FilenameFilter
The FilenameFilter interface has a single method, accept(), which decides which files to include in a listing of a given directory. FilenameFilters are used in the list() method of java.io.File and in java.awt.FileDialog.
StreamTokenizer
This is a base class for writing lexical analyzers. It scans an InputStream and breaks it into a sequence of tokens of predefined types.
java.net
The java.net package handles network-related functions: URLs, World Wide Web connections, and sockets for more general network interaction. Chapter 12, "Network Programming with Java," explores some possible applications of these classes in client/server applets.
Addressing the Web: URLs
A URL is a Uniform Resource Locator, an address which references a "resource" on the World Wide Web. While a resource is often just a web page sitting in an HTML file, it can be more, such as a search engine query or a CGI script, for example. A URL contains more information than just the internet address of the WWW server. The protocol for connecting to the server and the location of the resource on the server are all embedded in the URL. A typical URL reads:
http://www.myserver.com/~me/myFile.html
Here, the protocol is http (HyperText Transfer Protocol), the server is www.myserver.com, and the virtual path is /~me/myFile.html. Schematically, a URL has a format like:
protocol://server:portNumber/virtualPath#referenceInfo
This is a fairly general form of URL. Most of the elements are optional. Missing elements are interpreted in the context where the URL is defined. For a web page or applet to refer to a URL with the same protocol on the same server and port number, it is enough to have only the virtual path.
An absolute URL is one whose virtual path begins with / or ~user (the WWW home directory of a particular user). This includes any URL which specifies a server. A URL which isn't absolute is relative. A URL may also have reference information after a hash sign (#). The interpretation of this reference depends on which protocol is being used. For http it is the name of a hypertext anchor in the named HTML file. The browser is requested to jump directly to that position in the file.
The default port number for http is 80. Other typical protocols you may see include gopher, telnet, nntp, file, and ftp.
URL
Java provides the java.net.URL class which encodes a URL in a convenient and uniform way. After you create a URL, you can't alter its value. Here are some ways to construct URLs:
URL u1 = new URL( "http", "www.myserver.com", 80, "/myFile.html" );
URL u2 = new URL( "http", "www.myserver.com", "/myFile.html" );
URL u3 = new URL( "http://www.myserver.com" );
URL u4 = new URL( u3, "myFile.html" );
The first three forms expect you to know an absolute virtual path to the resource (a virtual path beginning with / or ~). The last example is different. A URL is created by resolving the second argument, a String, as a URL in the context of the first URL. If the String is a relative URL, the return value is the complete absolute URL to the same resource, where the string is interpreted as being relative to the first URL. If the second argument already represents an absolute URL then it is returned (unchanged).
Applets often need to create URLs relative to their code base (the URL of the directory containing their class file). For this, use the getCodeBase() method of java.applet.Applet:
URL my_image_url = new URL( getCodeBase(), "myImage.gif" );
In creating URLs, you usually have to catch the exception java.net.MalformedURLException.
The URL class has the methods getProtocol(), getHost(), getPort(), getFile(), and getRef() to save you the trouble of parsing the URL. getRef() gets the reference information after the hash sign. You can also use openConnection() to open a URLConnection to the object at that URL or getContent() to return a Java object representing the content of the URL. If you wish to read the contents as raw data, use the openStream() method to get an InputStream to the URL.
For applets, URLs are perhaps most useful for calling the getImage() and getAudioClip() methods.
URLConnection
There may be times when you want to have the possibility of more flexible interaction with a Web server. For example, you may want to use the POST method to send information to a CGI script. A URLConnection represents a connection to a given URL and allows a richer interaction than just the URL object. You can open an OutputStream to a URL, get various http header fields, and so on. It is possible to set certain properties of the connection before connecting so as to restrict the types of interaction allowed. Class URLConnection also has the methods getContent() and getInputStream() which do the same thing as getContent() and openStream() in the class URL.
URLEncoder
To pass string arguments to a CGI script (such as a search engine), you have to put a query on the end of the URL by appending a question mark and a series of argument definitions:
http://www.myserver.com/cgi-bin/myscript.cgi?name=JoeBlow
Here, the value of the name argument is "JoeBlow." In general, to cope with whitespace and other weird characters, the argument values must be translated into a format corresponding to a special MIME type. The class java.net.URLEncoder exists just to provide this translation, by way of its lone static method, encode(). For example:
String s = URLEncoder.encode( "William Thornhump \003" );
System.out.println( s );
(notice that the string contains a Control+C character with value 3). This code results in the output:
William+Thornhump+%03
URLStreamHandler
A URLStreamHandler knows how to handle a particular type of protocol over a stream connected to a URL. Unless you are interested in embedding new protocols into your Java program, you don't have to bother with these low-level objects. Use the methods in the classes URL and URLConnection instead. A URLStreamHandler for a given protocol is instantiated once when the protocol name is first encountered in creating a URL instance. This is an abstract class.
The java.net package includes the interface URLStreamHandlerFactory. A class implementing this interface knows how to create a URLStreamHandler from a given protocol name, by way of the createURLStreamHandler() method. To provide a custom URLStreamHandler for a new protocol, you first have to subclass the abstract class URLStreamHandler, overriding methods as appropriate (at the very least, you must implement the openConnection() method). Next, write a class implementing URLStreamHandlerFactory, whose createURLStreamHandler() method understands the new protocol name, and set it as the URLStreamHandlerFactory for your application:
URL.setURLStreamHandlerFactory( myFactory ); // a static method in class URL
Your new URLStreamHandlerFactory doesn't have to worry about decoding the standard protocols. If the createURLStreamHandler() method returns null, the standard factory is consulted. This also allows you to override the standard protocol definitions, if you wish. A Java program can only set the URLStreamHandlerFactory once.
Content Handlers
As URLStreamHandlers are
to protocol types, so ContentHandlers
are to content types of a URL. An object in the abstract
class java.net.ContentHandler
knows how to read a given URLConnection
and turn the input into an Object.
Again, unless you are interested in extending the different content
types understood by your Java program, you don't need to bother
with this class. The different ways of encoding the content of
a URL are known as MIME types, so you need a ContentHandler
for each MIME type you wish to be able to access. To make a custom
ContentHandler which
corresponds to a new MIME type, you need to implement the single
method getContent(). To make
it work with the existing routines in classes URL
and URLConnection, you must
also install a new ContentHandlerFactory,
using the static method setContentHandlerFactory()
in class URLConnection. A
ContentHandlerFactory is
an object which implements the interface java.net.ContentHandlerFactory
by providing the method createContentHandler()
which turns the string name of a MIME type into a
ContentHandler.
Sockets and Internet Addresses
A socket is one endpoint of a two-way network connection. In Java, sockets come in three flavors: Socket, ServerSocket, and DatagramSocket. The actual implementation of all three of these kinds of socket is accomplished by the class SocketImpl which provides a fairly standard set of socket calls. To work with fancy setups like firewalls and proxies, you may have to provide your own socket implementation by subclassing SocketImpl and setting your own SocketImplFactory.
InetAddress
An InetAddress object holds an internet address and is essential for using sockets. You can get an InetAddress by calling the static method InetAddress.getByName() with a host name or an IP address in standard string format:
InetAddress a = InetAddress.getByName( "www.myhost.com" );
InetAddress b = InetAddress.getByName( "123.45.67.89" );
To refer to the local host, you can pass in null.
ServerSocket
A ServerSocket is created on a local port and then it listens for incoming socket connections. The accept() method blocks until there is a connection and then returns a corresponding Socket object. The new connection is a stream connection, as described below under Socket. The ServerSocket continues to listen until closed.
Caution: |
|
Socket
A Socket object represents a standard stream socket. This means that data passes along the connection in sequence and is reliably delivered. The stream socket is a layer of socket functionality which uses a more primitive packet interface behind the scenes, transparently resending bad packets as required (as long as the connection remains open). This type of socket can have an InputStream or an OutputStream connected to it, or both. These streams are accessed by way of the getInputStream() and getOutputStream() methods. (In fact, you can specify with an optional constructor argument that a Socket should be a datagram socket-not a stream socket-but it's not clear why this is a good idea, since there is a datagram socket class already.)
A Socket is created using an address and port number to which a connection is attempted immediately. If there is a socket listening at that port (Java ServerSocket or otherwise) and it is willing to accept the connection, then the Socket is constructed and may be used immediately. Otherwise, the constructor throws an exception.
Datagram Sockets
Datagram sockets bind to a local port where they send and receive datagram packets. A datagram packet is a short sequence of bytes, addressed to a particular host and port. Unlike a stream socket, a datagram socket does not maintain a connection with a remote socket. Packets may be sent to any host and port at which a datagram socket is waiting to receive one. It is the packet, not the socket, which knows to whom it is addressed.
In Java, datagram sockets are implemented by the class java.net.DatagramSocket. A DatagramSocket can send() and receive() datagram packets which are represented by class java.net.DatagramPacket. The packets are not guaranteed to arrive in sequence or even to arrive at all.
java.util
The java.util package is explored in Chapter 13, "General Purpose Classes." It contains some utility classes and useful data structures, described briefly here.
Dictionaries, Hashtables, and Properties
A Dictionary lets you create an association between keys and values. These can be any Java objects. In a real dictionary, the key is a word, and the associated value is the definition of that word. The key-value pairs are stored in the Dictionary using the put() method. The values are retrieved by the get() method, given the corresponding key. The keys() and elements() methods return an Enumeration of the keys or the elements of the Dictionary.
A Hashtable is an efficient form of Dictionary which relies on a hashing function, which generates an integer hash code for each key and uses standard hashing techniques to ensure speedy access. This relies on proper implementations of hashCode() and equals() in the objects used as keys. In particular, the hash codes should not be too "predictable."
Class java.util.Properties extends Hashtable, and adds the load(), save(), and list() methods for writing and reading the key-value pairs to and from streams. The convenience method getProperty() performs an explicit type cast operation on the returned element for you, from Object to String. The system properties are stored in a Properties object.
Stacks and Vectors
Arrays in Java are already far nicer objects than in many programming languages, however, they are most appropriate for storing items up to a fixed maximum number. A java.util.Vector object is like an array which automatically allocates more storage as required. The elements of the Vector are stored in sequence, indexed by integers starting from 0. The size() method returns the current number of elements. Any Java object can be stored in a Vector. You can insert an element into a position in the Vector and the existing elements roll over to make room. When you remove an element, the objects to the right roll back and fill the space. So you normally can't rely on an object staying at a fixed index in the Vector.
The Vector class is flexible enough to serve as a base class for various stack and queue type data structures. The class java.util.Stack extends Vector and provides a basic pushdown stack of objects (Last In, First Out or LIFO). You can push() an element onto the top of the stack and you can peek() at the top element or pop() it off the stack.
Counting Things: Enumerations
There are many situations where you have a collection of elements and you want to iterate over the collection, visiting each element once. If they are already in some particular ordered structure such as an array or Vector, you can do this with a simple for loop. A more flexible way is provided by the interface java.util.Enumeration. For example, an Enumeration can iterate over the elements in a hash table using the hasMoreElements() and nextElement() methods. To get the Enumeration, call the elements() method of the Hashtable (or Vector, Stack, and so on).
Observers and Observables
Sometimes an object needs to become an observer which monitors the condition of a second, observable object. For example, a spreadsheet must monitor changes in its cells and respond by recalculating the spreadsheet values. This is particularly important in a multithreaded environment. Java offers a uniform approach to the observer model by way of the base class java.util.Observable and the interface java.util.Observer. An Observable keeps a list of Observers which have registered their interest in watching changes in the Observable. When an observable change occurs, the Observable object calls its notifyObservers() method. This results in calls to the update() method of each Observer.
Other Utility Classes
You may think that Java has everything except the kitchen sink, but here it is: the kitchen sink department. Java provides additional classes to handle those tricky functions which everybody needs at some point but which are a real headache to code. The java.util classes BitSet, Date, Random, and StringTokenizer offer bit-string logical operations, time and date functions, random number generation, and string-splitting capability, respectively.
The Sample Applet: The Order Entry System
The World Wide Web (WWW) is evolving from being a means to communicate static scientific documents in its early days to becoming an interactive and alive medium. Currently, both individuals and businesses use it as a medium for their messages to reach a worldwide audience. Java is changing the Web from being a static means of communicating. The future of the WWW, as many people predict, is to be a unique medium involving active interaction, motion, and multimedia expression.
The Order Entry System functions in this interactive capacity. It includes graphical user interface (GUI) components such as buttons, scrolling lists, and text entry fields for use by someone viewing your Web site. This chapter covers the techniques of password entry and validation in Java Applets in order to limit access to your applet. It also checks the data to make sure that the data is appropriate for entry.
The Order Entry System functions as a stand-alone window, complete with a menu bar, and has all of the functions of a working window common to operating systems, such as Windows 95, X Window, and others. This functionality is accomplished through the use of Java frames and the layout managers provided in Java; the functionality is covered in Chapter 8, "The Order Entry System: Managing the Applet Layout." The layout of the GUI components is managed using the GridBagLayout Managers to ensure a clean interface. You will be aiming to produce a professional-looking and clean-running applet in both appearance and function. In keeping with this goal, you will also add graphics-based text and figures, and a logo to the applet. Figure 5.1 shows the Order Entry System GUI.
Figure 5.1 : The Order Entry System CUI.
The Order Entry System takes user input from the keyboard and mouse. This is handled using code based on the concept of events, which are happenings that require some kind of reaction from a Java program using the AWT. For example, one such event requires the browser to command the Web browser to change its current focus to another HTML document. The concept of events in Java will be covered in the upcoming chapters. This enables the user to view, in the case of the Order Entry System, different Web documents containing descriptions of the different products available. Finally, this chapter covers the means to get the order back to you, either through e-mail or through use of CGI scripting.
You will build the Order Entry System block by block, adding more function and capability as you progress through Java concepts and their implementations. Each chapter covers an additional group of Java's capabilities and applies those to the System. But first, I need to cover some necessary concepts.
The Java AWT
Essential to Java is platform independence. As even those with just a little experience in programming know, porting programs between platforms is often a daunting task. Take, for instance, the question of how many bytes are required to store an integer type. Many systems, such as UNIX-based machines, use four bytes to store an integer. Intel machines use two. This inconsistency in changing platforms suggest what kinds of problems are posed to developers wishing to create machine-independent code. The designers of Java have attempted to circumvent these problems through careful planning.
The Java Abstract Windowing Toolkit (AWT) is the means by which GUIs are implemented in the Java language. It provides the programmer with the means to create windows utilizing buttons, text entry fields, and all of the typical elements you expect in windows systems. It allows the Java programmer to design windowing programs that can be used on any machine in which a Java bytecode interpreter and an AWT have been implemented. Above all, the AWT is important because it allows you to easily produce high-quality interactive and efficient Java applications and applets.
Note: |
|
An important characteristic of the Java AWT is that it has been designed to be event-driven. By this, I mean that actions are delivered to your program based on the AWT, and your program handles them from there. The AWT has standardized those actions to make handling them very easy.
Also important to the portability of the AWT across different platforms and operating systems is how it handles the layout of different components. In many window systems, such as Microsoft Windows, the programmer must specify exact coordinates to specify the position of each different component of the window. This is, obviously, a problem when you are trying to design with platform-independence as a goal. What happens when you design an applet for a screen size of 1024¥768 and someone tries to view it on a screen of much lower resolution? An applet that once filled the entire screen cleanly is now a mess. The AWT is designed to circumvent this problem by not specifying exact layouts for a window; instead, it uses various different lay out managers in the class library, managers that function based on general rules to lay out the components in a window. This is covered in depth in Chapter 8.
Note: |
|
The Organization of the Java AWT
The Java AWT is designed based on the concepts of components and containers. Components are the building blocks of AWT-based applets and applications. A diagram of the inheritance hierarchy of some of the AWT is shown in Figure 5.2.
Figure 5.2: The partial inheritance hierarchy of the AWT.
You should notice that the vast majority of the other classes of the AWT are subclasses of the class Component.
One subclass of the component class is containers. Containers are AWT components that function to contain other components. Containers allow you, the programmer, to break down your GUI into smaller and smaller sections until each is laid out exactly the way you want it. This is accomplished easily because containers themselves are derived from components. Because each container can apply a specific layout to the components that it is housing, the programmer has much more control.
The type Component can be conceptually divided into three major categories: containers, user interface components, and windowing components.
Containers
Containers hold other components, including containers themselves. The container type you see most often is the Panel class. Panels are the general container class that function just to hold other components. They don't do much else, but they are indispensable in the overall design goals for the AWT.
User Interface Components
User interface components are the buttons, scrolling lists, and text entry fields that you should be familiar with in dealing with any windows-based interface. Here is a list of the major interface components provided by the AWT:
Labels
Buttons
Radio buttons
Checkboxes
Lists
Choices
Text fields
Text areas
Scrollbars
Menus
Canvases
Note: |
|
These different types of interface components, their form and their functions, are detailed in depth in the chapters to come as you build the Order Entry System.
Windowing Components
Windowing components are the components that function to produce stand-alone windows and menus in your Java applets and applications. Although these are technically a type of container, they are more easily understood if they are placed in a separate grouping aside from general containers. The major types of windowing components are frames and dialogs. Frames are the means by which you can create stand-alone windows outside of your browser; you also use frames when creating your own windowed application. Frames can include menu bars. Dialogs are limited windows. The most common type of dialog is the "OK" window, which presents a message to a user and then gets an OK to continue. Chapter 8 explores two types of dialogs: modal and non-modal. The difference between the two types is that modal dialogs allow input from the user only to its window, and no others, while it is active.
The Order Entry System contains almost all of the different components available. The System GUI is shown in Figure 5.3.
Figure 5.3: The components in the Order Entry System interface.
supersimple AWT-Based Applet Example
This simple applet isn't going to do much except incorporate a button and a comment field into an applet. This example is useful for you to get a feel for the basic Java applet constructors before you move on to more complex examples. In any case, here it is:
1. import java.awt.*;
2.
3. public class supersimple extends java.applet.Applet
4. {
5. public void init()
6. {
7. add(new Button("Order"));
8. TextArea CommentArea = new TextArea(5,25);
9. CommentArea.insertText("Hal Bialeck",0);
10. add(CommentArea);
11. }
12.}
Note: |
|
Caution: |
|
Line 1 imports all of the classes in the Java.awt package. Earlier chapters covered the process of importing other classes into your classes. If you are not familiar with this process, go back and review that section. You can view applets simply as classes you write to extend and modify the framework of the java.applet.Applet class. This process is specified in the class declaration in line 3.
As for the rest of the code, here is an explanation in brief (don't worry much about understanding it all now). Line 5 declares the new function init(), which overrides the method imported in the Java.applet.Applet class. Line 7 creates and inserts a button with the text Order on it. Lines 9 and 10 create a text area and insert the text Hal Bialeck in the beginning of the area. Finally, line 11 inserts the text area.
This example gives you a feel for the flow of Java applets.
Figure 5.4 shows this applet when displayed in the AppletViewer utility from the JDK.
Figure 5.4 : The supersimple AWT example applet.
Note: |
|
This chapter has covered what makes up the framework of classes to produce Java-based GUIs. Next is the concept of applets, which are Java programs specially designed to be downloaded across the Internet and included in an HTML document displayed by a Java enhanced browser.
Applets
Applets are designed to bring the Web alive. They function to add animation, sound, and eventually complete multimedia into HTML documents. Java is also part of the future of interfacing with virtual-reality environments implemented via VRML. At present, Java is limited only by the capabilities of the Internet itself.
For more information on VRML, the Virtual Reality Markup Language for the Web, visit VRML's home at http://www.vrml.org.
The most important feature of applets on the Web, however, is the fact that applets change the Web from being a static medium to one based on interactivity with users. At present, Java is limited only by the capabilities of the Internet itself. As the Internet grows in data transmission, Java is ready to expand with it.
Applets are capable of commanding and interacting with the Web browser executing them.
Tip: |
|
Technically, applets are subclasses of the panel container of the AWT. They derive much of their function and form from panels. But just as the process of imbedding container upon container makes panels so powerful, the same is true about the versatility of applets. The diagram of the inheritance path of the class implementing applets, java.applet.Applet, is shown in Figure 5.5.
Figure 5.5 : The inheritance path of applets.
Java Applets versus Java Applications
Java programs are divided into two types: applets and applications. As I have discussed previously, applets are Java programs that are specialized for use over the Web. Applications are stand-alone Java programs that can be run via a Java interpreter, and when run in that manner they appear just as any compiled C++ or Basic program would. Since this book focuses on Java programming for the WWW, I will concentrate on applets when examining the Java language.
As you will find out in Chapter 12, "Network Programming with Java," networking with Java is powerful and easy. As Java was designed to be a networking language, many of the problems with network programming in other languages have been removed. Both Java applications and applets have access to these networking capabilities, but applets are limited in scope to where they can connect and to where they can perform input/output tasks.
Applet Limitations
As of the 1.0.2 release of Java from June of 1996, applets are limited in order to ensure the security of the user. When you are designing applets, you should be aware of limitations in the areas of read/write access, connectivity, and native language library access.
Read/Write Limitations
Applets cannot read or write to the local file system. If applets were able to access the local file system, there would be little to stop an evil applet from searching the local files for valuable information and then sending that information back to its originating server. Nothing would stop a programmer from writing an applet to reformat a hard drive or spread a virus. Obviously, allowing Java applets to have read/write privileges on local machines would be disastrous.
Connectivity Limitations
Applets cannot make connections and transfer data except from the machine from which they were downloaded. For example, say an applet was written to post form data or send e-mail messages. Although allowing this kind of access would be advantageous, it is obviously dangerous. Suppose someone wrote an applet that would send a threatening e-mail message to the President ([email protected]) each time someone viewed a page on the Web. Allowing this kind of access would be disastrous in that no one browsing the Web would be able to trust applets they are downloading across the Internet. By their nature, applets are untrusted by Web browsers. In fact, if you run the Order Entry System you are constructing over the next six chapters in Netscape Navigator 2.0, you will be presented with a large label informing you that you are viewing an "Untrusted Applet Window." This is to keep an unscrupulous programmer from writing an applet that would disguise itself as a trusted application and prompt the user for a password that it would return to its server. This distrust of Java applets is becoming less common as more people and developers accept Java.
Native Library Access
Java has the capability to access native libraries from other languages such as C++. Applets are restricted from this feature. If this was allowed, there would be nothing to keep applets from calling native language methods that would perform some evil action. By limiting the applet's library access, the ability for a programmer to write some evil methods in another language and circumvent Java's security measures by using them in an applet is removed.
Process Limitations
Java applets are also restricted from executing any code on a local machine. This includes forking processes on UNIX systems. These limitations are entirely necessary. Say, for instance, an evil applet spawned a process to search (grep'ed in UNIX) your file system for the word address or password, and then sent an e-mail message containing the results back its server. This would be disastrous.
Hopefully, as the Internet and Java develop, the tight leash around applets will loosen. For the present day, however, you have to live with these constraints in order to take advantage of the capabilities of Java.
Note: |
|
The Applet Life Cycle
Applets follow a set life cycle during their execution. They are initialized, started, stopped, and destroyed. Initially, the Java bytecodes are run through a security check by an object running in the browser. Then, to start execution, the Java runtime calls the init() method of your applet.
The init() Method
The init() method is where your applet does much of its setup, such as defining its layout, parsing parameters, or setting the background colors. As with all of these methods, if you do not override the default methods provided in the java.applet.Applet class, they are called and do their normal duty. In the case of the init() method, if it isn't overridden, nothing goes on. In any case, it is still called.
If you noticed in the supersimple example discussed earlier, you utilized the init() method to set the initial layout for the applet. For the other three methods standard in the life cycle of Java applets, you simply used the default methods in the Applet class.
The start() Method
The start() method is used mainly when implementing threads in Java. You will learn more about threading in Java in Chapter 16, "Multithreading with Java," which covers threading and multithreading. If you have no clue as to what threads are, it might be helpful to glance at the beginning of that chapter.
In Java, threading is most helpful when performing audio playing or animation. In these cases, or if you want your program to be able to stop and restart, it is helpful to override the start() method and implement your own. But if this isn't the case, you can just utilize the start() method in the Applet class.
The stop() Method
The stop() method is used to do what its name suggests: stop what is going on. In it, you usually stop() the threads that you initiated in the start() method. As is the case with the start() method, if you aren't doing anything that is threaded, you do not have to worry about implementing this method in your applet.
Tip: |
|
The destroy() Method
As with the previous three methods, the destroy() method is as simple as its name. When it is called, the applet is told to free up system resources. In most general cases, you probably won't need to override this method, although there are some very special cases in which you might want more control on applet shutdowns.
Figure 5.6 is a diagram of the call path of these methods used to control Java applets.
Figure 5.6 : The life cycle of applets.
It is important to note that applet design should include multiple calls to start() and stop(); init() and destroy(), however, are never called more than once.
Note: |
|
Ideally, I would have liked to include an example demonstrating the life cycle of a Java applet. However, to do so would mean incorporating threading, and that would complicate things more than helping. For the present time, keep these applet characteristics in mind as you develop the Order Entry System in the chapters to come.
Adding Applets to Web Pages
This section covers the HTML codes specific to Java applets and how to include them in your Web pages. You should have a small amount of familiarity with the way HTML works before going through this section. If you don't, it would be best if you looked at one of the many books or Web documents available to teach HTML. A good source of information is Laura Lemay's Teach Yourself HTML in a Week. Many good Web documents teach HTML. Many of them are available on-line.
This section familiarizes you with how two browsers utilize Java: Netscape Navigator version 2.0 and above, and Sun's HotJava. This section covers the tags for declaring an applet in a Web page, parameters, and other features of HTML code for implementing applets. You will be, if nothing else, entirely comfortable with the process of putting not only your own applets into your pages, but also those written by programmers (those who give you permission, of course).
Netscape Navigator and Applets
Netscape has pushed long and hard to continue developments above and beyond its competition in the Web browser arena. By releasing its software for free, Netscape has attracted millions of new users and continues doing so through technical advances. The 1.0.2 version of Java is included in the JDK on the CD-ROM with this book. Most surveys report that the Navigator is far and away the dominant browser on the market today.
You can assume that most of the other browsers on the market today will follow the Netscape format of applet inclusion in HTML documents. This is the same for Microsoft Internet Explorer and IBM's releases of its Web Explorer.
Note: |
|
HTML Coding for Applets in Netscape
The initiating tag for inserting an applet in an HTML document is the <APPLET> tag. It is followed later by the </APPLET> tag, which signifies that all HTML code between the two tags is focused on the applet.
Note: |
|
The <APPLET> tag follows this format:
<APPLET CODE = "yourclass.class" WIDTH = 200 HEIGHT = 100>
This tells the browser the following:
- That it should place an applet on the page
- The name of the applet
- The size it should allocate for display of the applet
By default, the browser looks in the directory in which it finds the HTML document to find the applet. However, there is another parameter available, the CODEBASE parameter, that allows you to specify where the browser should look to find your applet code. Here is an example of this feature:
<APPLET CODEBASE = "myapplets" CODE = "king.class" WIDTH = 10 HEIGHT = 0>
</APPLET>
You can also specify your code to be loaded from another site in the following manner:
<APPLET CODEBASE = "http://coolapplets.com/java/"
CODE = "theirapplet.class"
WIDTH = 100 HEIGHT = 50>
</APPLET>
This code says to the browser: "I want you to insert an applet named theirapplet.class and you can find it at http://coolapplets.com/java. Make it a width of 100 pixels and a height of 50 pixels." To sum it up, the CODE parameter tells the browser what applet to get, and the CODEBASE parameter specifies where to look for it.
Parameters and Applets
Java programs can be written to accept parameters specified in between the <APPLET> and </APPLET> tags. The following HTML code demonstrates this feature.
<APPLET CODEBASE = "http://discus.com/fish/and/Java"
CODE = "angelfish.class"
WIDTH = 100
HEIGHT = 50>
<PARAM NAME = betta value=1>
<PARAM NAME = cichlid value = "Jack Dempsey">
</APPLET>
This code does the same as the previous example except it also provides some variables for the applet to access. Chapter 10 covers how to handle these parameters in our applets.
Aligning the Applets
Just as with images, you are able to specify in your HTML code how you want to align your applets relative to other items on your page. Here is an example of the HTML declaration of an applet that included alignment of the applet:
<APPLET CODEBASE = "http://phil.com/gd/java/"
CODE = "lesh.class"
WIDTH = 100
HEIGHT = 500
ALIGN = ABSMIDDLE>
</APPLET>
If the terms ABSMIDDLE and BASELINE aren't familiar to you, I would strongly suggest reviewing alignment in HTML documents. Wise and proper alignment is the key to producing professional quality HTML pages, with or without Java.
Note: |
|
Feature Focus |
|
Displaying the supersimple Example Applet
As promised earlier in this chapter, here is the full HTML code for placing the supersimple applet:
1. <HTML>
2. <HEAD>
3. <TITLE>The supersimple Applet</TITLE>
4. </HEAD>
5. <BODY BGCOLOR = #FFFFFF>
6. This is the supersimple Example Applet:
7. <APPLET CODE = "supersimple.class" WIDTH = 100 HEIGHT = 50 ALIGN = ABSMIDDLE>
8. <hr>
9. Your browser does not support Java.
10. Go get a real one.
11. </APPLET>
12. </BODY>
13. </HTML>
This HTML document, viewed through the AppletViewer utility, is shown in Figure 5.7.
Figure 5.7: The MTML code when displayed by the Appletviewer.
This code simply inserts the supersimple applet onto an HTML document with a white background. On a Java-capable browser and through the AppletViewer utility, this all comes out fine. However, on a browser not able to display applets, the text inside is displayed. Figure 5.8 shows what happens when viewed from a non-Java enhanced browser.
Figure 5.8 : The above HTML viewed through Netscape 2.0 for Windows 3.1.
Tip: |
|
Applets and HotJava
The HotJava browser released by Sun was actually written with the Java language. HotJava, being written in Java itself, demonstrates the capabilities of the Java language. However, there is a big catch in using it: HotJava currently supports only an earlier alpha release of the Java language. Sadly, browsers capable of the beta release and later releases, such as Netscape Navigator, are not compatible with these alpha release applets. For now, the vast majority of applets are being written and compiled in Java 1.0 beta and later. As of the time of this writing, Java is in the 1.0.2 release.
Note: |
|
As of the time of this writing, HotJava is in pre-beta release. By the time you read this, it should be available in full release to handle the Java 1.02 release.
For a comprehensive listing of applets on the Web, a good place
to start is Gamelan at
http://www.gamelan.com/.
However, numerous alpha 3 release applets are available. If you are looking to see everything that's out there, it would be wise to fire up HotJava at least once and go looking for alpha-based applets. A good place to start is Gamelan at http://www.gamelan.com/.
Here is the form for including alpha 3 release Java applets in your Web pages:
<APP CODE = yourapplet NAME1 = "a value" NAME2 = 456 . . . >
In this example, NAME1 and NAME2 are replaced by the names of your parameters to your applet. There is no limit to the number of parameters. Also, note that the CODE parameter does not require you to place .class after your applet name. Finally, there is no </APP> tag, so including alternate text is impossible in the alpha 3 release.
Get the latest version of Java, HotJava, and related information at http://java.sun.com/.
If you want to go all out, you can write your applet both in the alpha release and in the later releases. To include both, simply insert the <APP> tag in between the <APPLET> tag and the </APPLET> tag. This displays your alpha 3 release applet for HotJava and the Java beta applet for browsers that support that release. Remember, you can always get the latest version of Java, HotJava, and related information on the Web at http://java.sun.com/.
The java.io Package
The java.io package is a group of library classes that enable you to implement and use data streams in your Java programs. These classes derive from the abstract classes java.io.InputStream and java.io.OutputStream. Different subclasses of these abstract classes enable you to use different types of streams in multiple situations. FileInputStream, for example, is a subclass of the InputStream abstract class.
All classes in the java.io package generally throw IOExceptions. These exceptions deal with input and output errors. One such class that is a descendant of the IOException class is the EOFException, which is thrown when a read encounters the end of a file. You learned about exceptions in the preceding chapter. When implementing streams in your Java programs, you need to be sure to deal with these exceptions when necessary.
The Two Big Daddies
As you learned earlier, there are two major abstract classes in the java.io package from which all the stream classes descend: InputStream and OutputStream. The most important fact about these two classes is that they are all abstractly implemented. For this reason, to actually implement a stream, you will not use one of these classes, but instead will use their subclasses. InputStream and OutputStream are simply templates for the process of stream handling.
Each of these two classes is designed to cause a thread under which they are running to wait until all the input requested is available to be read or written. This comes back to the concept of multithreading. The thread in which your stream is implemented can be blocked by the read and write method until its task is done.
InputStream
The InputStream class abstractly implements a number of methods that allow for the consuming of bytes of data. These methods follow:
read
skip
available
close
mark
reset
read
The read method simply reads
a byte. The InputStream class
contains several read methods:
abstract int read() | Returns the next byte in the stream. |
int read(byte bytearray[]) | Returns an array of bytes read in from the stream. The integer returned is the number of bytes in the array. |
int read(byte bytearray[], | This returns the number of bytes read. |
int offset, int length) | The bytes read are stored in the bytearray returned, which is of size length. The offset is the offset in the bytearray where the bytes are placed. |
Note: |
|
The read function returns a value of -1 to signify the end of a stream.
skip
The skip method is used to move past a number of bytes in a stream. It takes the following form:
long skip (long NumBytes)
Here, NumBytes is the number of bytes you want to skip, and the number returned is the actual number of bytes that were skipped. This number can be less than or equal to the value in NumBytes if the end of the stream is reached, for example.
available
The available function returns the amount of bytes that can be read without waiting. In other words, it returns the number of bytes that you can have right now, without your process having to wait around for more to be generated. The available function has the following declaration in the InputStream class:
abstract int available() throws IOException {
close
The close method closes the input stream after you are done with it. This method frees the resources that a stream is using and allows them to be used in other areas. This usually is included in the finally section of your try/catch block performing I/O tasks (see the preceding chapter).
mark
The mark void is implemented in only some of the stream classes. It places a marker at the current position in the stream. This marking procedure is meant to be used in situations in which you must read a little ahead to figure out what a stream contains by using some kind of general parser. Look in the library source code for the InputStream class for a better idea of how Java's designers think you would implement this kind of parser.
You can check to see whether the stream you are using allows the mark method by using the markSupported function:
boolean markSupported()
This function returns true if you can use markers; otherwise, it returns false.
The mark void accepts an integer parameter that sets the maximum number of bytes you will read before resetting back to the mark with the reset method. If you read past that number of bytes, the mark is forgotten.
reset
The reset void returns your read position back to the place where you just marked.
OutputStream
The OutputStream class abstractly defines a number of methods that enable you to produce bytes for output. These methods are write, flush, and close.
write
The write method in the OutputStream class does what you would expect: It places bytes into an output stream. There are three major forms of the write method:
abstract void write(int b) | Writes a byte b. It blocks your process until the byte actually is written. |
void write(byte b[]) | Writes an array of bytes. It blocks your process until the bytes actually are written. |
void write(byte b[], | Writes a subarray of the byte array. |
int off, int length) | off is the offset in the array, and the length is the number of bytes written. This method also blocks your process until the bytes actually are written. |
Why is only one of the methods abstract for both the read and write methods? Well, if you look at the source of the OutputStream and InputStream classes, you'll see that the other methods that are not abstract simply do some manipulation and then call the original abstract method.
flush
The flush method flushes the stream. It pushes out any bytes that are buffered in the stream.
close
The close method closes the stream. It releases any of the resources associated with the stream.
So Many Streams in Java
The basic functions are used by the subclasses of the InputStream and OutputStream classes to allow the reading and writing of more complicated structures than bytes between a variety of sources. This variety of classes is designed to take much of the "grunt work" out of input and output for you, the programmer.
Table 11.1 lists the multiple streams available, which are described
in this chapter.
Table 11.1. Java streams.
Stream Type | Types Handled | Function |
BufferedInputStream | bytes | Allows the buffered input of a stream of bytes. |
BufferedOutputStream | bytes | Allows the buffered output of a stream of bytes. |
ByteArrayInputStream | bytes | A stream in which the source is a byte array. |
ByteArrayOutputStream | bytes | A stream for which the destination is a byte array. |
DataInputStream | all | Allows the input of a stream of binary data. |
DataOutputStream | all | Allows the output of a stream of binary data. |
FileInputStream | bytes | File-specific stream input. |
FileOutputStream | bytes | File-specific stream output. |
FilterInputStream | all | Parent class for implementing filtered input streams. |
FilterOutputStream | all | Parent class for implementing filtered output streams. |
InputStream | bytes | Generic input stream class. |
LineNumberInputStream | all | Implements a stream from which you can find out what line you are on at any time. |
OutputStream | bytes | Generic output stream class. |
PipedInputStream | all | Allows the creation of an input pipe between one thread and a producer thread. |
PipedOutputStream | all | Allows the creation of an output pipe between one thread and a consumer thread. |
PrintStream | all | Allows the typical text printing of data. |
PushBackInputStream | bytes | Implements an input stream with a 1-byte pushback buffer. |
StringBufferInputStream | strings | Allows the buffered input of a stream of strings. |
Tip: |
|
The FileInputStream and FileOutputStream Classes
The FileInputStream class enables you to load information from a file located in the file system. The FileOutputStream class does just the opposite; it writes bytes to a file in the local file system. Suppose that you want to create an input stream from a file and then load the bytes one at a time and print them. The following code shows how you can use the FileInputStream to accomplish this:
int x;
try {
// Declare the file input stream.
InputStream fis = new FileInputStream("c:\isnt\it\romantic\abc.txt");
// Read in x from the file. If not EOF then print x out.
while ((x = fis.read())!= -1) {
System.out.print(x);
}
} catch (Exception e) {
System.out.print(e.getMessage());
}
Note: |
|
And there you go. There is also another function you can use with the FileInputStream and FileOutputStream classes: the getFD function, which returns a file descriptor of the stream.
Note: |
|
The ByteArrayInputStream and ByteArrayOutputStream Classes
These two stream types enable you to create streams to and from arrays of bytes. The following code block reads bytes from a stream and prints them to System.out:
byte b = new byte[100];
// Code to place numbers in b here . .
try {
// Declare the new byte array input stream. .
InputStream BAIS = new ByteArrayInputStream(b);
while (BAIS.available > 0) {
System.out.print(BAIS.read());
}
} catch (Exception e) {
System.out.print("Something went wrong sucka.");
}
Tip: |
|
Caution: |
|
FilterInputStream, FilterOutputStream, and Their Children
The FilterInputStream and FilterOutputStream classes are subclasses of the InputStream and OutputStream classes, respectively. They function in the same way as their parents by making possible the existence of their children. You can implement your own filtered streams, although they will not do much good. The real difference is made by their children (this is discussed in the next section).
The BufferedInputStream and BufferedOutputStream Classes
The BufferedInputStream and BufferedOutputStream classes extend the idea of the stream to include the capability to buffer the input and output stream. In a buffered stream, the next chunk of read or written data first is placed into a buffer and then is made available. The next read or write, in other words, is not done to the other end of the stream but instead to a buffer in memory.
Why is using a buffered stream advantageous? Well, there is one major benefit: It reduces the overall number of reads and writes to the stream by increasing the chunks of data handled at one time. Therefore, fewer accesses and connections between the device generating the data and the consumer occur.
Two major constructors exist for each of these types:
BufferedInputStream(InputStream InS)
BufferedInputStream(InputStream InS, int Size)
BufferedOutputStream(OutputStream OutS)
BufferedOutputStream(OutputStream OutS, int Size)
In each case, the constructor takes another instance of a stream and wraps the new buffered stream around it. So, your old stream is now a buffered stream. The following code declares a buffered file output stream:
OutputStream FilOutStr = new FileOutputStream("/usr/bin/X11/dinky.dat");
OutputStream BufOutStr = new BufferedOutputStream(FilOutStr, 1024);
The second parameter in each of the cases specifies the number of bytes the buffer will contain.
Note: |
|
The PushBackInputStream Class
The PushBackInputStream class implements one more method: unread. This method enables you to implement a 1-byte pushback buffer. In other words, it enables you to push a byte back onto the stream. It implements the new unread method along with the other methods available in the InputStream class.
The unread method takes one parameter: a character (or byte) that you can push back onto the stream.
Why would you want to implement this? Suppose that you are using the first character of a stream to specify for what segment of your program this stream will be used. Each part of your program that might deal with the stream then checks the first character and, if it isn't what it is looking for, puts the character back on the stream and passes it to the next handler.
The PrintStream Class
Remember all those times you used the System.out.print statement? Well, when you did, you were using one of the methods of a class that extends the PrintStream class.
The PrintStream class enables you to easily handle the output of the normal Java language types, such as integer, strings, and so on. You use this class to output the normal print, println, and write methods you are accustomed to in other languages such as C++.
The print method of an instance of the PrintStream is overloaded to accept all the general Java language types. Suppose that you want to write a long integer. You can use this code:
PS.print(ALong);
The println method prints its parameter and then moves to the next line:
PS.println(AnInteger);
You also can use the write, flush, and close methods. The write method enables you to write bytes to the stream in the same format as the original abstract OutputStream class did.
You also can send an object to be printed. The value printed is what results from the object's toString function.
The LineNumberInputStream Class
The LineNumberInputStream class enables you to implement a stream that lets you know what line you currently are viewing. You can declare this stream type as a wrapper to another stream:
InputStream LnNoInpStr = new LineNumberInputStream(new FileInputStream("\usr2\1997\girdley\myfile.txt"));
Then, at any point, you can use this code:
LnNoInpStr.getLineNumber();
This function returns an integer value of what line you are at in the file. Also available is setLineNumber(int No), which enables you to specify the line number of your position. This is useful if you are looking at a file with a header and you want to start counting line numbers after it, for example.
Note: |
|
The DataInputStream and DataOutputStream Classes
These two classes allow you to read and write data in a binary format without having to worry about all the grunt work involved in implementing and managing that data. These two classes allow you to implement RandomAccessFileStreams, which is covered next. The files used by these two classes are by far the most efficient means of dealing with data in Java. You can use the methods of the two classes to read and write Java language types easily to and from a binary storage format.
The DataOutputStream class enables you to use a number of methods: writeInt, writeChar, and so on. There is one of these methods for each of the general Java language types. See the pattern?
The same is true for the DataInputStream class. A number of methods are available, including readLong, readChar, and so on. In both classes, you still can read and write individual bytes.
The following code loads the data in a file of integers stored in binary format and then outputs it to the standard output:
InputStream IS = new DataInputStream(
new FileInputStream("/usr2/sun/yidata.dat"));
try {
while (true) {
System.out.print(IS.readInt());
}
}
finally {
IS.close();
}
Easy enough.
The PipedInputStream and PipedOutputStream Classes
The PipedInputStream and PipedOutputStream classes are useful for creating pipes (a feature that should be familiar to UNIX system users) that are used to connect two parallel threads (see the next chapter for more information on multithreading). To use this class, simply declare a PipedInputStream in one process and a PipedOutputStream in the other, and then connect them like this:
PipedInputStream InStream = new PipedInputStream();
PipedOutputStream OutStream = new PipedOutputStream(InStream);
Now, when one process outputs, the other process can access the data.
Dealing with Files
The File and RandomAccessFile classes enable you to perform comprehensive management and to use files and the local file system.
The File Class
The File class enables you to construct an object that contains information about an entry in the file system. Three constructors are available:
File(String thePath)
File(String thePath, String theFileName)
File(File dir, String Name)
Table 11.2 summarizes the methods available to you.
Table 11.2. File class methods.
Method | Description |
public String getName() | Returns the name of the file. |
public String getPath() | Returns the path of the file. |
public String getAbsolutePath() | Returns the absolute path of the file. |
public String getParent() | Gets the name of the parent directory. |
public boolean exists() | Does the file exist? |
public boolean canWrite() | Can we write to the file? |
public boolean canRead() | Can we read the file? |
public boolean isFile() | Does a normal file exist? |
public boolean isDirectory() | Does this directory exist? |
public native boolean isAbsolute(); | Is the file name absolute? |
public long lastModified() | Returns the last modified date. Should only be used as a comparison to a previous change. |
public long length() | Returns the length of the file in bytes. |
Random Access Files
The DataInputStream and DataOutputStream classes allow the implementation of random access files, which are files that you can read from and write to at any point you specify. There are two constructors for the RandomAccessFile class:
RandomAccessFile(String FileName, String FileMode)
RandomAccessFile(File theFile, String FileMode)
The first parameter in each of these constructors specifies the file with which you are dealing. The second parameter is r, w, or rw, which sets the mode of the file to be read, write, or read-write, respectively.
Table 11.3 summarizes some of the methods available in the RandomAccessFile class.
Table 11.3. RandomAccessFile class methods.
Description | Method |
public final FileDescriptor getFD() | Returns the opaque file descriptor object. |
public final void readFully(byte b[], int off, int len) | Reads the remaining bytes in a file. |
public int skipBytes(int n) | Skips a number of bytes in a file. |
public native long getFilePointer() throws IOException; | Returns the current location of the file pointer. |
public native void seek(long pos) throws IOException; | Sets the file pointer to the specified absolute position. |
public native long length()throws IOException; | Returns the length of the file. |
public native void close() throws IOException; | Closes the file. |
The normal read and write methods also are implemented.
The following code block opens a file for reading and then prints a character at byte position 1000 in the file:
RandomAccessFile RAF = new RandomAccessFile("HalBialeck.dat", "r");
RAF.seek(1000);
System.out.print(RAF.read());
The Protocol Zoo
The Internet started out around 1969 as a kind of science fair project by the U.S. military, and it was known as the ARPANET. That network no longer exists, but various protocols that were invented for it are still in use. These protocols allow a collection of physical networks to link up over gateways and become a sort of internetwork, or Internet. These physical networks generally are Ethernet LANs, dial-up lines, and so on. The Internet is a logical network cobbled together out of various physical networks that cooperate so they appear to the user as one large network.
This vague definition leaves undefined the word cooperate, which is where things get slightly complicated. The various protocols that implement this cooperation have acronymic titles such as IP (Internet Protocol), TCP (Transfer Control Protocol), UDP (User Datagram Protocol), ICMP (Internet Control Message Protocol), ARP (Address Resolution Protocol), and so on. You certainly can tell it's a defense department project!
As a user of the Internet, you need only a basic understanding of what IP, TCP, and UDP can do for you, as well as a nodding acquaintance with DNS domain names. The other low-level protocols are of interest mainly to network administrators, hackers, and generally anybody with just too much time on their hands.
IP
The Internet is the Frankenstein monster of packet-switching networks. Packet switching means that data is sent down a wire (or other medium) in little packets (or datagrams) with a destination address and control fields slapped on the front. It's sort of like the postal system; you have to address your letters correctly. You've probably seen IP addresses hanging around on the Internet, looking like 123.45.67.89 or something similar (this is called dotted quad notation). These addresses are 32-bit numbers that identify a particular network device on the Internet (your Ethernet card, for example), via a mapping known as Address Resolution Protocol (ARP). The Internet Protocol (IP) defines the basics of how datagrams are addressed and routed, as well as who receives or forwards them.
Whatever you might think of the post office, it at least tries to get your letter delivered reliably. The IP protocol makes no such guarantee. Nor can it; the underlying hardware is intentionally made to be occasionally unreliable in the interests of speed and economy of design. Packets can be dropped (intentionally!) without notice; they may arrive out of sequence or with their data garbled.
Techno-geeks sum this up by saying that IP lives on the network layer. In a conceptual tower of ISO networking layers, this is the second floor; it assumes only that there is a way of sending packets down the wire to a specific destination. It's up to the higher layers to provide as little or as much error control and recovery as necessary.
UDP
The next level up from the network layer is called the transport layer. This layer defines end-to-end communications in two flavors: UDP and TCP. An endpoint for communication is defined by a socket, which is the conjunction of an IP address and a port number. A port number is a positive integer that identifies a logical "port" on your machine. These ports are not physical entities; they simply distinguish data arriving over a single physical connection so that it can be delivered to any of hundreds of different "sockets" owned by the various applications on the machine. This provides for a virtually limitless number of end-to-end connections through a single network interface.
The User Datagram Protocol (UDP) lets you send datagrams of your own to another host and port number. Essentially, you are just using IP, but with the addition of port numbers and with the various details of IP headers and such hidden from view. Just as with IP, there is no guarantee of delivery, error detection, or sequencing of datagrams.
If you want to use UDP from Java, the java.net.DatagramSocket and java.net.DatagramPacket classes are what you need. Examples of their use follow.
TCP
The Transfer Control Protocol (TCP) slaps a reliable and sequenced connection on top of the unreliable, unsequenced functionality of IP. This is why you often will see the whole setup referred to as TCP/IP. A TCP socket is again the conjunction of an IP address with a port number. The data you send over a TCP socket is divvied up into datagrams and stamped with sequence numbers and error-detection codes. At the receiving end, the packets are checked for errors and then lined up in sequence order. Protocol Packets are resent as necessary, so that the whole stream of bytes arrives intact at the receiving end. It's kind of like registered mail.
Because of the reliable and sequenced nature of TCP sockets, they often are called stream sockets; you can read and write data in continuous streams of bytes without worrying about packets, headers, and so on. Because streams figure so prominently in the java.io classes, it's logical to suppose that TCP sockets are a natural in Java-and they are! Stream socket functionality in Java is provided by the classes java.net.ServerSocket and java.net.Socket.
DNS
Not really a protocol, but more like an entire subsystem of the Internet, the Domain Name Service (DNS) makes it easier on the carbon unit peripherals (humans!) when it comes to remembering IP addresses. DNS is a collection of programs and protocols that allow a central authority to assign symbolic names to Internet hosts based on a hierarchy of domains and subdomains. It's a huge, distributed database of nicknames-one for every Internet host registered in a domain. Suppose that you work at Widgets Galore Company, and your machine has the nickname wallaby. Your DNS name will look like this:
wallaby.WidgetsGalore.com
The .com ending signifies a commercial institution. Reading from right to left, you move from the general to the specific in your DNS hierarchy: not just any commercial interest, but one called WidgetsGalore. Not any machine at Widgets Galore, but the one known as wallaby. The left-most word is always the local host name for the machine in question. The remaining words specify the domain of that machine. There may be other machines in the world named wallaby, but not in your domain.
The name servers and resolver libraries on the various hosts in your domain know how to answer queries about wallaby; they return the actual IP address of your machine-a 32-bit number. From the user's point of view, this all happens more or less automatically, which lets us poor humans forget about memorizing 32-bit IP addresses and get on with memorizing important stuff, like 20-bit phone numbers.
In Java, IP addresses are encapsulated in the class java.net.InetAddress, whether they are given as symbolic DNS names or 32-bit numbers in dotted quad form.
World Wide Web
For many people, the World Wide Web is the Internet. It certainly is the most painless and fun way to be a virtual tourist, zooming around looking at pictures of people you've never met and places you'd like to visit. It's also full of ways to search for information on any topic whatsoever. Most importantly for Java enthusiasts, the WWW is the natural habitat for Java applets.
Applets are embedded in Web pages using the APPLET tag. When you tell your Web browser to load a certain Web page, it opens a socket connection to a Web server specified in the URL of the page. (A URL is a Uniform Resource Locator-the address of a resource on the Web. For a general discussion of URLs, see java.net.URL in Chapter 3.) The Web server then writes the contents of the page over the socket connection. Your browser decodes the contents and then displays them according to their type. If your browser understands applets, it reads the APPLET tag to figure out what additional data it must request from the Web server. This data consists of the Java class files defining your applet, which your browser then must load and execute. A Java-compatible Web browser knows how to be a Java virtual machine.
The language of the Web is HyperText Transfer Protocol (HTTP). The browser sends HTTP requests, such as GET and POST, to the server, and the server responds with an HTTP header and body. The header defines (among other things) the content type and content encoding of the body. There are many standard types and encodings, which are known as MIME types. The MIME type for standard Web pages is text/html, which also is known as HyperText Markup Language (HTML). I'm not going to talk much about HTML because you know all about that anyway.
Java allows for flexible interaction with Web servers, including the GET and POST methods of HTTP. But the Web is more than hypertext alone; a URL can specify any one of a number of different protocols to be used for fetching a resource. These include FTP, Gopher, NNTP (Usenet news), and so on. Better yet, you can teach Java about these or any protocol because the way in which java.net.URL objects interpret their contents is fully extensible, via the classes java.net.URLStreamHandler and java.net.ContentHandler (and other related classes).
Sockets in Java
To open a socket in Java, you specify an IP address and a port number. IP addresses in Java are represented by the class java.net.InetAddress, which doesn't care whether you want to specify the dotted quad or the DNS name of a host. You can get an InetAddress for the fictional machine wallaby.WidgetsGalore.com, for example, by using the static method getByName():
InetAddress wally = InetAddress.getByName("wallaby.WidgetsGalore.com");
On the other hand, you could give the string in dotted quad form:
InetAddress wally = InetAddress.getByName("123.45.67.89");
It really doesn't matter. To get the address of the local host (the machine you're running on), you can pass the null reference to getByName(), or you can use InetAddress.getLocalHost(). These methods throw an UnknownHostException if (you guessed it!) the host is not known to DNS. An InetAddress object has a constant value; you can't change it, so just throw it away when you're done with it.
An InetAddress alone does not make a socket. You must have a port number. In a typically asymmetric client/server application, only the server side needs to worry about which port number it uses, because only the server side needs to be found at a well-known socket location. So, you can specify a port number or use the magic port number 0, which means, give me any available port. If you ask for a port that is already in use, you trigger an exception. Users generally should avoid using ports 1 through 1024 because these are reserved for system-based services.
Stream Socket Classes
The handiest sockets for use in Java are TCP stream sockets. For this reason, they receive prime consideration in Java's naming scheme, and they simply are called java.net.Socket. A Socket's whole raison d'etre is to be connected to another Socket, so think about how this happens. In order to get a connection happening between two machines, one of them has to ask first, or initiate the connection. This machine is playing the role of client, and the one who answers is a server (these distinctions are a bit arbitrary-after the connection is established, it is symmetrical).
ServerSocket
If the client asks for a connection, somebody had better be listening on the server end. This somebody is a java.net.ServerSocket-an object that creates a passive socket on your local host and then sits and listens to a specific port. Listing 12.1 is a fragment containing a very minimal server class, which accepts connections and then does nothing with them. The main() method calls the handle_connection() method to do any actual work with the socket.
Listing 12.1. A minimal server.
public class SillyServer
{
// A bare-bones example: exception handling omitted!
public static void main( String args[] )
{
ServerSocket serv;
serv = new ServerSocket( 8081 );
while ( true ) {
Socket s;
s = serv.accept(); // Wait for a connection
handle_connection( s ); // Got one, now do something!
s.close();
}
}
}
This code fragment just listens forever on port number 8081 until a connection is requested and then calls the method handle_connection() to deal with the new connection (the name handle_connection() is arbitrary here; it serves to describe a place to put your application-specific code). The accept() method blocks forever until a connection is received, at which point it returns a new Socket object representing the connection; you should close the Socket when you're done with it. After accepting a connection, the server socket returns to its listening state, and you can accept() further connections on the same ServerSocket as often as you want.
The infinite looping behavior in this server program is typical. After all, you want servers to hang around and service any number of requests. In case another request for a connection arrives before you have a chance to accept it, it is kept waiting in a queue of pending connections. You can specify the length of this queue by an optional second argument to the ServerSocket constructor. It typically defaults to five. When the queue is full of unserviced connections, further connections are refused. In practice, though, handling requests in a single thread is not a good idea unless each connection can be handled very quickly. Most real-life server programs should start a new thread for each connection accepted.
A connected Socket always knows the address and port of the remote socket it's connected to; these are made available by the Socket methods getInetAddress() and getPort(). This allows your server to reject certain connections out of hand; just close the Socket if you don't like the client's address.
Socket
On the client's end, you need to ask the server for a connection. This is done with a java.net.Socket object, which you construct yourself, as shown in Listing 12.2.
Listing 12.2. A minimal client.
import java.io.*;
import java.net.*;
public class SillyClient
{
Socket sock;
DataInputStream in;
DataOutputStream out;
public SillyClient( String server, int port ) throws IOException
{
// Create a socket connected to the server:
sock = new Socket( server, port );
// Okay, now attach some streams to it.
in = new DataInputStream( sock.getInputStream() );
out = new DataOutputStream( sock.getOutputStream() );
}
}
Notice how the client needs to know the exact port number of the server's socket as well as the IP address. You can specify the address as a String or as an InetAddress instance. If the Socket constructor succeeds (throws no exception), you're connected! Also, you never specify which local port is to be used. For the client end, it simply doesn't matter beforehand which port you are using. After connecting, you can find out the local port number by calling getLocalPort().
These are supposed to be stream sockets, so there had better be a way to get a stream attached to them. The methods getInputStream() and getOutputStream() do just that. They return an object of class InputStream or OutputStream, which allows you to read or write using the given socket. A socket can have a stream of either type or both attached to it; sockets provide a two-way flow of information. When you're done with the stream, it's good programming practice to close() it before you close the associated socket. If you use a form of BufferedOutputStream, you might want to explicitly flush() it on occasion; it certainly never hurts to do so.
For security reasons, applets usually are allowed to connect only to sockets that live at the same IP address as their own class files. This prevents various antisocial behaviors, but it also puts a real crimp in what you can do from a typical Web browser environment. An applet can find out the InetAddress of its home machine by examining its getCodeBase() URL. But keep your applets portable-don't hard code the IP address!
These restrictions don't apply to stand-alone programs (unless your SecurityManager is quite paranoid).
A Basic Client/Server Applet
You'll now do a simple client/server transaction. The server will be a stand-alone program, listening on a specific port on the machine wallaby.WidgetsGalore.com, and the client will be an applet. The client calls up the server, and the server prints a short sales report back to the client. Then the transaction is complete. For this, you just need to flesh out the SillyServer class used earlier-call it SimpleServer, as shown in Listing 12.3.
Listing 12.3. A simple server program.
import java.io.*;
import java.net.*;
public class SimpleServer
{
static final int DEFAULT_PORT = 8081;
static void handle_connection( Socket s ) throws IOException
{
PrintStream out = new PrintStream(s.getOutputStream());
DataInputStream in = new DataInputStream(new FileInputStream( "sales.txt" ));
String line;
System.err.println( "Connection from " + s.getInetAddress() );
while ( (line = in.readLine()) != null )
out.println( line );
in.close();
out.close();
}
public static void main( String args[] )
{
int port;
ServerSocket serv;
if (args.length == 1)
port = new Integer(args[0]).intValue();
else
port = DEFAULT_PORT;
try {
serv = new ServerSocket( port );
} catch ( IOException e ) {
System.err.println( "I/O exception: " + e );
return;
}
System.err.println( "listening on port " + port );
while ( true ) {
Socket s = null;
try {
s = serv.accept(); // Wait for a connection
handle_connection( s ); // Got one, now do something!
} catch ( IOException e ) {
System.err.println( "I/O exception: " + e );
} finally {
if (s!=null) try { s.close(); } catch (IOException e) {}
}
}
}
}
Notice that the server program still does everything sequentially in a single thread. You can get away with this only because the entire transaction lasts just a fraction of a second.
The corresponding applet, SimpleClient, has a button named load, which you click to open a socket and copy the data to a TextArea. Listing 12.4 shows the important parts of the applet code.
Listing 12.4. A simple client applet.
public class SimpleClient extends Applet
{
String server;
int port;
Label title = new Label( "A Simple Client Applet", Label.CENTER );
TextArea text = new TextArea();
Button load = new Button( "Load Sales Report" );
public void init()
{
// Initialize the applet ...
}
void loadSales()
{
Socket s = null;
try {
s = new Socket( server, port );
DataInputStream in = new DataInputStream(s.getInputStream());
String line;
while ( ( line = in.readLine() ) != null )
text.appendText( line + "\n" );
in.close();
} catch (IOException e) {
showStatus( "I/O Exception: " + e );
} finally {
if (s!=null) try { s.close(); } catch (IOException e) {}
}
}
public boolean action(Event e, Object o)
{
if ( e.target == load ) {
loadSales();
return true;
}
return false;
}
}
Now, on the server end, you must provide a file sales.txt, with any text you want in it. After the client-side user clicks the Load Sales Report button, the contents of that file are transferred over the socket and printed in the TextArea. Because applets often are disallowed from opening local files, this is a useful alternative.
In a more complicated application, you need to define a protocol for client/server interaction over the stream socket. The basics of how you connect and use the socket always remain the same, however. It is worth pointing out that for large client/server and distributed applications, packages are available to ease the task. One such package is called Remote Method Invocation (RMI), which provides a transparent way to call on the methods of a remote Java object. Later, I'll discuss another approach that is used in ChatApplet.java.
Datagram Sockets
Stream sockets provide a reliable connection to a fixed destination socket, but you pay for this convenience. A slight overhead is involved in checking for errors and correctly sequencing the packets. Moreover, for some applications, using stream sockets for every transmission would be prohibitively complex. Consider the example of a chat applet, which would be a member of a network of similar applets. These applets are to send messages typed by the user to all the members of the network. Using stream sockets, this requires each applet to open a socket for every other applet in the network, so in a network of N machines, there will be roughly N ¥ N sockets open, occupying just as many ports. This is quite wasteful and, in any case, the nature of a chat network is discrete; messages are dispatched in small quantities of text. For this purpose, datagrams are ideal. A datagram or UDP socket occupies one port but can send datagrams to any remote UDP socket. The datagram is sent immediately, and the sending thread does not block waiting for its delivery, regardless of whether anyone actually receives the packet. Short of using some kind of IP multicasting or broadcasting protocol, this is the most efficient way to reach a large number of peers with discrete update-type information.
Although user datagrams are not checked for data errors, you can choose to implement your own checksums. You also might want to have a mechanism for verifying that a datagram has arrived.
DatagramSocket
The java.net.DatagramSocket class represents a UDP socket on the local host. You can create it on a specific port:
DatagramSocket ds = new DatagramSocket( 9876 );
or on any available port:
DatagramSocket ds = new DatagramSocket();
After the socket is created, you can send and receive datagram packets using the send() and the receive() methods. The packets themselves are represented by java.net.DatagramPacket objects, which you can construct from an array of bytes and addressing information. The send() method requires the DatagramPacket to be fully constructed; it must be filled in with a correct data buffer, data length, remote InetAddress, and remote port. The receive() method requires only a data buffer and a maximum length to receive. The receive() method blocks until a datagram arrives and then fills in the buffer of the specified datagram. The actual length of the data received then is available via getLength(). For example, you can receive string data like this:
DatagramSocket sock = new DatagramSocket();
byte b[] = new byte[1000];
DatagramPacket p = new DatagramPacket( b, 1000 );
sock.receive( p ); // wait for a packet
byte data[] = p.getData();
int len = p.getLength();
String s = new String( data, 0, 0, len );
Similarly, data to be sent is constructed from an array of bytes. Suppose that you want to send a string. You can use this code:
String message;
InetAddress remoteaddr;
int remoteport;
DatagramSocket sock = new DatagramSocket();
// Set the message, remoteaddr and remoteport ...
byte b[] = new byte[ message.length() ];
message.getBytes( 0, m.length(), b, 0 );
DatagramPacket p = new DatagramPacket(b, m.length(), remoteaddr, remoteport);
try {
sock.send( p );
} catch (IOException e) {
// Uh-oh! Do something.
}
A Basic Datagram Application
This section looks at a very basic use of datagrams: the two-way chat program DatagramChat.java, as shown in Figure 12.1. The user is expected to enter the remote host name and port number, so this program is not really very user-friendly. However, this does avoid the complication of having a central hookup server, which otherwise would be necessary in order to let the chat windows find each other's sockets.
Figure 12.1 : A datagram chat applet.
After the user types a message in the input TextField and then clicks Send, the message is sent in a DatagramPacket, along with a leading byte with the value PRINT or EchO (in this case, PRINT). Listing 12.5 shows the code that the DatagramChat class uses to send a message datagram.
Listing 12.5. Sending a datagram in class DatagramChat.
void sendMessage()
{
if ( remoteaddr==null ) {
status.setText( "You must specify a remote host and port!" );
return;
}
String m = input.getText().trim();
if (m.length() < 1) return;
byte b[] = new byte[ m.length()+1 ];
b[0] = PRINT;
m.getBytes( 0, m.length(), b, 1 );
DatagramPacket p = new DatagramPacket(b, m.length()+1, remoteaddr, remoteport);
try {
sock.send( p );
status.setText( "Message sent to " + remoteaddr );
} catch (IOException e) {
status.setText( "Message send failed: " + e.getMessage() );
}
}
The same window must be able to receive messages from the remote peer. To accomplish this, it implements the Runnable interface and has its run() method execute in a separate Thread. All the run() method does is wait for messages and then respond-an infinite loop. The response to a message is to print it in a TextArea, and (if it is marked PRINT) send back the same message, now marked EchO. Messages marked EchO also are printed to the TextArea, but they are not returned to the sender. This means that the chat window supports remote echoing; if you don't see the message in the TextArea, chances are the remote user doesn't see it either. Listing 12.6 shows the run() method of the DatagramChat class.
Listing 12.6. The run() method of DatagramChat.
public void run()
{
byte b[] = new byte[MTU];
while ( sock != null ) {
try {
DatagramPacket p = new DatagramPacket( b, MTU );
sock.receive( p );
byte data[] = p.getData();
int len = p.getLength();
byte op = data[0];
String s = new String( data, 0, 1, len-1 );
print( s );
if ( op==PRINT ) {
// Echo the packet back to the sender
data[0] = EchO;
DatagramPacket q = new DatagramPacket(data, len, p.getAddress(),
Âp.getPort());
sock.send(q);
}
} catch (IOException e) {}
}
}
The troublesome thing about this program is that it is hard to run as an applet (it is provided as DatagramChatApplet.java). The Web browser usually will not permit you to receive datagrams in an applet (although my applet viewer lets me do this if I load the applet locally). Of course, it's possible to run this as a stand-alone application (java DatagramChat), but that defeats much of the purpose of having applets at all.
For this reason, chat applets in Java will have to use a stream socket connection to a server program running on their home server-the Web server where the applet's class files reside. This star-like configuration loads down the server and eats up several port numbers. Not only that-it requires a carefully designed protocol to make sure that client and server can accomplish essentially asynchronous tasks in a sequential conversation and still recover from unexpected conditions. Worse, it makes the response time unacceptably slow if chat groups of a few dozen applets or more exist. Nevertheless, any applet that wants to communicate with other applets using sockets has to live with these restrictions. The next section discusses one way to achieve this.
A Chat Applet
The program ChatApplet.java on the book's accompanying CD-ROM is a more realistic attempt at a chat applet (see Figure 12.2). It uses a stream socket to talk to a central server program (ChatServer.java), located on the same machine as the applet's code. The server program coordinates connections to several clients, grouping them into chat pools (different channels or rooms). The trans package provides a uniform way for client and server to communicate requests and commands to each other. The server's port number defaults to 8081, but any port number can be given as the single command-line argument. The applet itself is just a panel containing a ChatWindow, in which you can set the server name, port number, and initial screen name by parameters in the APPLET tag (server, port, and name). It won't be necessary to specify the server, because the applet figures out its home server by using the getCodeBase() method (don't forget to have the server running, though). The ChatApplet class contains a single instance of class ChatWindow, which does all the work. The ChatWindow class can also run as a stand-alone program.
Figure 12.2 : The ChatApplet.java applet.
The server program has most of the smarts about which chat pools are defined and who is in them. The client applets just act as a user interface to the server, which updates the pools at will. Ideally, the smarts would be distributed somewhat more democratically in a web of interconnected applets, but due to security restrictions (discussed earlier), you are forced into the star configuration. This limits the usefulness of the applet, because large numbers of clients will bring the server to a crawl, and because the system is not fault-tolerant.
A detailed discussion of the mechanics of these programs is really beyond the scope of this chapter, but this section will go over the basic idea of how the client/server transactions are performed.
The trans Package
The trans package used by ChatApplet.java and ChatServer.java provides an artificial kind of remote procedure call functionality. Transactions are sent back and forth over a stream socket in a semi-asynchronous way. The transactions are instances of a subclass of the abstract class Transaction. The socket is encapsulated in an object of type TChannel. This is a smart channel for initiating and responding to transactions. Either side can be the client (or initiator) of a transaction (depending only on the transaction type itself), so there is no essential asymmetry built in.
Each Transaction object exists only to be converted to and from its properties, which are pairs of name/value strings written in a TProps object, which is essentially a jazzed-up Hashtable with better read and write methods than the java.util.Properties class has. The exact class of the transaction determines its type, so the inheritance mechanism is used here basically as a way to discriminate among the different types of transactions while treating them on an equal footing as objects characterized by their properties. The properties are accessed via the getProperty() and setProperty() methods. On creation, the constructor sets the properties to reflect the transaction parameters. The transaction is sent by passing it to the initiate() method of an opened TChannel object. The ChatServer, for example, greets the client and asks for its screen name by initiating a transaction of class Hello, as Listing 12.7 shows.
Listing 12.7. How the ChatServer says hello.
class ClientManager extends TChannel
{
// ...
}
class HandleClient extends Thread
{
ClientManager cm;
// ...
try {
Hello h = new Hello(); // a Hello object has no initial props
cm.initiate(h); // start the transaction
cm.name = h.name; // this gets the result: a screen name
// ...
} catch ( IOException e ) {}
}
Initiating a transaction means that the following items are written to the stream socket:
- An opcode byte (indicating REQ (request) or ACK (acknowledge)
- A serial number (this serves to match replies to previous requests)
- The name of the transaction class (here, it is Hello)
- The properties of the transaction (a TProps object, written out)
The best way to write primitive Java types over a stream socket is to use a DataOutputStream attached to the socket (see Chapter 3 for a full discussion of data I/O). The opcode is written using writeByte(), the serial number with writeInt(), and the various strings with writeUTF(). The receiving socket then can read the data using a DataInputStream, in a parallel fashion.
The most important methods in a Transaction class are its build() and decode() methods. The build() method is run on the receiving end, and it accepts a single TProps object as an argument. These properties are the ones that were written on the socket at the initiating end, and the build() method's job is to take some action based on the values of these properties. The build() method can use setProperty() to set some return properties as well (on entry to build(), the receiver-side transaction's properties are guaranteed to be empty).
When the build() method finishes, the result properties are written back (even if they remain empty), with an opcode of ACK (acknowledge). It then is the job of the decode() method to make sense of these return properties on the initiating end. The result of a Hello transaction is a screen name, for example. This means that the build() and the decode() method each take half the responsibility for the encoding and decoding, whereas the constructor need not do anything at all. Listing 12.8 shows the complete code of the Hello class (here, ChatTransaction extends Transaction).
Listing 12.8. The Hello Transaction class.
import java.net.*;
import trans.TProps;
// A Hello transaction requests that the client identify itself.
public class Hello extends ChatTransaction
{
static String NAME = "name";
String name;
public Hello() {}
protected void build( TProps p )
{
setProperty( NAME, getWindow().screenname );
}
protected void decode()
{
name = getProperty( NAME );
}
}
Even though the constructor is empty here, you still must declare it, if only to make it public. The trans package requires that all Transactions be public classes with a public constructor that takes no arguments (a null constructor). This is because the receiving TChannel actually instantiates a new transaction of the correct type by using the class descriptor's newInstance() method:
// This code instantiates a Transaction of class named in the String tClass:
try {
Class c = Class.forName( tClass );
t = (Transaction)c.newInstance();
} catch (Exception ex) {
throw new TransactionException();
}
Transactions appear synchronous to the initiating thread; the initiate() method does not return until the transaction's return properties have been read and decoded. The same TChannel can act as both initiator and receiver simultaneously, however, and each received transaction has its build() method executed in a separate thread. If this build() method decides to initiate() another transaction back to the sender, you have a subtransaction, and that's okay, too. In this way, entire conversations of transactions and subtransactions can be carried out more or less transparently. The instigating thread doesn't see any of the subtransactions, because it is blocked, waiting on the outcome of the entire process. This multithreading requires that some care be taken in determining which methods should be synchronized and which should not, in order to avoid deadlock.
The downside of the approach is that there currently is no way to provide a time-out period for transactions to complete. Also, in the event of an error, there is no defined way to restore the TChannel to a sane state. It also would be nice to have a form of initiate() that returns immediately, executing the transaction in another thread and optionally notifying the caller by executing an agreed-upon method call when the transaction is completed (this is best done by defining an interface-for example, TransactionObserver). Performing these improvements would be fairly straightforward (after reading about multithreaded programs), so consider it an exercise!
The WWW in Java
The main reason behind the phenomenal rise of Java is its potential to become the lingua franca of interactive applications on the Internet. The exponential growth of the World Wide Web (WWW) has started a drive to redistribute the burden of computation, which has until now rested almost completely on the Web server. The CGI scripts so common today require no cleverness on the client end, but this simplicity comes at a price. The variety and semantics of CGI interaction are limited by the HTML form syntax and the strictures of HTTP, and a single server is forced to think on behalf of a potentially limitless number of clients. Java, on the other hand, is a real programming language that loads once and executes at the client end. This makes an enormous difference in terms of efficiency, both from the client's point of view (faster execution) and from the point of view of the network (lower bandwidth). So, although Java will soon be powering telephones and stereos, it is on the Web that Java finds its first natural home.
In this section, you'll see how Java programs can interact with Web servers and also with existing CGI programs. Java can convert URLs (Web addresses) into usable program objects. On a lower level, Java can read the raw contents of a URL. Java applets can also request certain actions of their host browser, such as displaying a status line or loading a new document.
The URL Class
A URL refers to a specific resource on the Web: an HTML file, image, animation, or whatever the case may be. URLs look like this:
http://www.WidgetsGalore.com/~MrWidget/sales.html
URLs specify a protocol (http), a server (www.WidgetsGalore.com), and a path to the resource in question. In Java, URLs are encapsulated in a constant object of class java.net.URL, which is described in Chapter 3.
An applet can use a URL to load a new browser page, as shown in this code:
class MyApplet extends Applet
{
// Other stuff ...
public void showNewPage( URL u )
{
getAppletContext().showDocument( u );
}
}
If you use framesets in your document, there is also an optional second argument to specify the target frame or window.
Java URL objects have a getContent() method for converting the contents of the URL into a Java object, assuming that such a conversion is defined. A URL can reference an image, and then getContent() returns an Object whose runtime class is some subclass of Image, for example. The content type of most Web pages is text/html, which has no defined conversion. You can use the URL object to open an InputStream to read the contents of the URL, however:
URL url = new URL("http://www.WidgetsGalore.com/~MrWidget/home.html");
DataInputStream in = new DataInputStream(url.openStream());
String line;
while ( ( line = in.readLine() ) != null )
System.out.println( line );
in.close();
When it is defined, the getContent() conversion is performed by a ContentHandler object. The following sections tell you how to define custom ContentHandlers for your own types of objects.
The URLConnection Class
The URL class may get a lot of credit, but the URLConnection class really does most of the work. A URLConnection object manages an interaction with a Web server at a given URL. When you call on a URL's getContent() method, the URL object creates a URLConnection to do the dirty work. You can retrieve the URLConnection associated with a URL by calling the openConnection() method of the URL. This can give you access to the values of the HTTP header fields, which tell you such things as the last modification date. It lets you specify details of the interaction, such as retrieval conditional on the last modification time (use setIfModifiedSince() or set the If-Modified-Since header field using setRequestProperty()).
A URLConnection object remains unconnected until its connect() method is called, which happens when some operation such as getContent() requires that a connection be made. Before it is connected, you are free to modify the fields that control the transaction. After it is connected, this is an error. URLConnections that use the HTTP protocol can perform the HTTP GET and POST methods because they are smart enough to wait for you to access their I/O streams before connecting to the Web server and issuing a GET or a POST request; basically, the POST method is used only if you actually write some output. You'll see how to do this soon.
The built-in functionality of URLConnections is centered on the HTTP protocol, but a URL object can use any protocol name that has an associated URLStreamHandler. A URLStreamHandler object must implement the abstract method openConnection(), which returns a URLConnection object given a URL. The URL knows where to get the stream handler for a given protocol by consulting the URLStreamHandlerFactory. You can define your own protocols by writing a class implementing the URLStreamHandlerFactory interface and then setting it to be the stream-handler factory using the static method URL.setStreamHandlerFactory(). In practice, though, you're almost always better off using HTTP and defining new content handlers corresponding to custom MIME types. The exception is if you're trying to write a reusable set of classes to perform FTP or some other such protocol that already exists.
Caution |
|
MIME Types and Content Handlers
How does a resource sitting on a Web server get converted into a usable Java object? First, when the Web server returns the contents of the corresponding URL, it includes header fields indicating the MIME type and encoding of the content to follow. For a GIF image, the MIME type is identified by the string image/gif. For HTML, it is text/html. When a URL or URLConnection performs its getContent() method to create a new object from a URL, it needs to find a content handler for whatever MIME type the server returns. The java.net package automatically parses the MIME type from the appropriate HTTP header field and then passes it to a ContentHandlerFactory, which returns the required handler or null (or throws a runtime exception). If you want to know the MIME type explicitly, the getContentType() method is available in the class URLConnection.
A content handler is an object of class java.net.ContentHandler, with a method getContent(), which takes a URLConnection and returns an Object. A different content-handler class exists for each recognized MIME type, because they each must do different things with the data from the URL.
The correspondence between MIME types and ContentHandlers is established by the ContentHandlerFactory. A Java program has one ContentHandlerFactory: an object implementing the interface java.net.ContentHandlerFactory, which has been installed as the ContentHandlerFactory, as shown in this code:
URLConnection.setContentHandlerFactory( chf );
The ContentHandlerFactory interface specifies one method; the createContentHandler() method accepts a MIME type name string and returns a ContentHandler object for that MIME type, or null if one is not known. You never call on a ContentHandler or ContentHandlerFactory directly; they are there to be used by the higher level classes URL and URLConnection, and to be subclassed by programmers who want to add new MIME-type capabilities to Java.
Now look at a functioning but simple example. Consider a Java class called Colleague:
class Colleague
{
String name;
String phone;
String fax;
Date birthday;
Date nextMeeting;
}
To prepare for loading Colleague objects from a URL using HTTP, you perform the following steps:
- Define a new MIME type to the HTTP server.
- Implement a ContentHandler class for the type.
- Install a custom ContentHandlerFactory that understands the new type.
Defining a New MIME Type
You can call the new MIME type anything, as long as it doesn't mess up other MIME types on the same server. Call it application/java-Colleague to avoid trouble. To tell the Web server about the new MIME type, you have to add a local configuration directive. For ncSA httpd, one of the most common servers, you put the following line in an .htaccess file in the same directories as your Colleague URLs:
AddType application/java-Colleague .coll
The server then recognizes any URLs with the .coll file name extension as belonging to the MIME type application/java-Colleague and reports them as such. This won't work if your site disallows per-directory access configuration for httpd. Talk to your system administrator if you're not sure; she may prefer to define the new type for you in a global configuration file. Also, other servers provide this functionality in different ways.
Writing a Custom ContentHandler
Listing 12.9 shows a very simple ContentHandler for the class Colleague.
Listing 12.9. A Custom ContentHandler class.
class ColleagueHandler extends ContentHandler
{
// Read from the URL and create a Colleague:
public Object getContent( URLConnection c ) throws IOException
{
DataInputStream in = new DataInputStream( c.getInputStream() );
Colleague coll = new Colleague();
String s;
coll.name = in.readLine();
coll.phone = in.readLine();
coll.fax = in.readLine();
try {
coll.birthday = new Date( in.readLine() );
coll.nextMeeting = new Date( in.readLine() );
} catch (Exception e) {
throw new IOException( e.getMessage() );
}
return coll;
}
}
This class knows how to read from a URLConnection and construct a new Colleague object. It defines the format; there are five lines in a .coll file, representing the name, phone, fax, birthday, and next meeting date of that given colleague. For a real application, you should use tagged fields (for example, Birthday: Jun 5 1972). This makes it easier to define new versions of your format without invalidating old URLs.
Using a Custom ContentHandlerFactory
Writing a content handler factory class is easy, but it's a good idea to make it flexible (because you only get to install it once!). You might want to make a class like the one shown in Listing 12.10, which lets your program add new MIME types at will.
Listing 12.10. A custom ContentHandlerFactory.
class MyContentHandlerFactory implements ContentHandlerFactory
{
Hashtable handlers = new Hashtable();
void addMimeType( String mimetype, ContentHandler handler )
{
handlers.put( mimetype, handler );
}
public ContentHandler createContentHandler( String mimetype )
{
return (ContentHandler)handlers.get( mimetype );
}
}
This class is flexible, because other classes from the same package can add new ContentHandlers on-the-fly by calling addMimeType(). Listing 12.11 shows a little demo program which does this (ContentHandlerDemo.java).
Listing 12.11. ContentHandlerDemo.java: using a custom ContentHandler.
public class ContentHandlerDemo
{
public static void main(String args[])
{
if ( args.length != 1 ) {
System.err.println( "please give a URL argument!" );
System.exit(1);
}
MyContentHandlerFactory chf = new MyContentHandlerFactory();
ColleagueHandler collHandler = new ColleagueHandler();
chf.addMimeType( "application/java-Colleague", collHandler );
URLConnection.setContentHandlerFactory( chf );
try {
URL url = new URL( args[0] );
Colleague coll = (Colleague)url.getContent();
System.out.println("Loaded colleague:");
System.out.println("name = " + coll.name);
System.out.println("phone = " + coll.phone);
System.out.println("fax = " + coll.fax);
System.out.println("birthday = " + coll.birthday);
System.out.println("next meeting = " + coll.nextMeeting);
} catch ( Exception e ) {
System.err.println( "Exception: " + e ) ;
System.exit(1);
}
}
}
To use this, define a colleague in a file. Fred.coll, for example, could contain this text:
Fred Fredrickson
800-8001
805-8123
Jan 12 1970
Jul 26 1996 3:15 PM
Then, by giving the URL to Fred.coll, the ContentHandlerDemo program should be able to load the information from the server, as this example shows (using the UNIX command prompt as an example):
wallaby$ java ContentHandlerDemo http://www.WidgetsGalore.com/~MrWidget/colleagues/ÂFred coll
Loaded colleague:
name = Fred Fredrickson
phone = 800-8001
fax = 805-8123
birthday = Mon Jan 12 00:00:00 PST 1970
next meeting = Fri Jul 26 15:15:00 PDT 1996
If you can't get this working, make sure that your file is in a location where your Web server expects to find it. Also make sure that you have defined a new MIME type to the server and provided the correct URL on the command line.
Tip |
|
The GET method
The GET method is the usual HTTP method for retrieving a URL, so you actually are using it every time you follow a hypertext link that isn't a Submit button on an HTML form. Some URLs expect to be fed additional information, however, which is appended onto the URL after a question mark. This so-called query information is useful for passing argument values to executable CGI scripts, such as WWW pages interfacing to databases or search engines. The query information is a sequence of attribute names and values, as this code shows:
http://www.myserver.edu/searchquery.cgi?text=My+Favorite+Foods&name=Fred
This query information encodes the name/value pairs "text=My Favorite Foods" and "name=Fred". Any CGI application that expects to receive query information must be able to decode this information. The encoding is a standard one, in which space characters are mapped to + and any troublesome characters (control codes, +, %, and so on) are mapped to %xx, where xx is the hexadecimal value of the character. Separating name/value pairs by ampersands (&) is the usual thing to do when there is more than one pair.
To interact with such a CGI program, you can use the java.net.URLEncoder class to encode the URL. Somewhat surprisingly, the URLEncoder class maps & and = to their hexadecimal code equivalents, so if you want to use the syntax described in the preceding paragraph, you need to encode each value separately and concatenate the results with ampersands and equal signs yourself. The CGI program must retrieve the information from the environment variable QUERY_STRING and reverse the encoding process described earlier. Look at some established CGI scripts if you're not sure how this works.
To use the information returned by the CGI script, you can read an InputStream furnished by URL.openStream() or URLConnection.getInputStream(). Alternatively, you can furnish a content handler for the MIME type of the object returned and just do a getContent() on the encoded URL. To avoid extensibility problems, you might not want to provide content handlers for common MIME types such as text/html (unless you are writing a Web browser in Java!).
The POST Method
The POST method is the method used when submitting an HTML form. Generally, it enables you to write to a URLConnection so that whatever you write is fed to the referenced object (usually a CGI program) on its standard input stream. Because of the way HTTP transactions work, you should write and close the output stream before you read the input or do anything else that would cause the URLConnection to connect. The server can require that the content length of the input be specified along with the request, and this can be done only before connecting. This also lets the URLConnection object realize what you're up to and then specify the POST method rather than GET.
Listing 12.12 shows a simple example of how to use the POST method to interface with a CGI program. The PostMethodDemo applet posts the contents of a TextArea to a little CGI program called wordcount.cgi and then shows the result. You have to call this applet by its full URL, because it uses its code base to construct a URL to the CGI program, which should be in the same directory. Depending on your server configuration, you might not be allowed to execute CGI programs in user directories. Then you should move wordcount.cgi to your cgi-bin directory on the Web server and modify the applet source to supply the correct URL.
Listing 12.12. PostMethodDemo.java: using the POST method.
import java.io.*;
import java.net.*;
import java.awt.*;
import java.applet.Applet;
public class PostMethodDemo extends Applet
{
Label title = new Label( "An Applet using the POST method", Label.CENTER );
TextArea text = new TextArea();
Button load = new Button( "Post!" );
public void init()
{
// Initialize the applet
setLayout( new BorderLayout() ); add( "North", title );
add( "Center", text );
Panel p = new Panel();
p.add( load );
add( "South", p );
validate();
}
void postText()
{
try {
String s;
URL u = new URL( getCodeBase(), "wordcount.cgi" );
URLConnection c = u.openConnection();
PrintStream out = new PrintStream(c.getOutputStream());
out.print( text.getText().trim() );
out.close();
DataInputStream in = new DataInputStream(c.getInputStream());
text.setText("");
while ( ( s = in.readLine() ) != null )
text.appendText( s + "\n" );
in.close();
} catch (IOException e) {
text.appendText( "Exception: " + e + "\n" ); return;
}
}
public boolean action(Event e, Object o)
{
if ( e.target == load ) {
postText();
return true;
}
return false;
}
}
The CGI program wordcount.cgi is just a simple Perl script that counts words and lines. You should make sure that it has world execute permissions and that it finds your local Perl compiler correctly (try running it by hand!). Notice that the first thing it prints is a header line indicating the content type, text/plain, followed by a blank line. The blank line is mandatory, and it is not part of the content. It separates the head information from the content body:
#!/usr/bin/perl
$words = 0;
$lines = 0;
print "Content-type: text/plain\n\n";
while (<>) {
$lines++;
$words += s/\S+//g;
}
print "You entered $words words on $lines lines.\n";
Using the POST method doesn't prevent you from also passing query information along with the URL if you choose, just as with the GET method (of course, this depends on the CGI program being smart enough to look for the query string).
Table 13.1 shows the classes that are part of the utilities package
that will be discussed.
Table 13.1. Utilities package classes.
Class | Description |
BitSet | Implements a collection of binary values |
Date | Date and time data storage and use |
Dictionary | Used to store a collection of key and value pairs |
Hashtable | Used to store a hash table |
Observable | Used to store observable data |
Properties | Storage and use of a properties list that can be saved |
Random | Used to generate a pseudo-random number |
Stack | A class to store and implement a stack |
StringTokenizer | Used to tokenize a string |
Vector | Used to store a vector data type |
Note: |
|
Linked Lists, Queues, Search Trees, and Other Dynamic Data Structures
One would expect that the Vector class, as described previously, would eliminate the necessity for creating your own data structures. But there may be times when you might want to conserve space to the maximum or access your data in a specialized way. In these cases, there is a technique to implement such data structures in Java.
As you learned before, Java has no pointers. Since dynamically linked lists and queues are implemented using pointers, is it then impossible to create these two data structures in Java? Not quite. Just as with many other tasks in Java, you need to do a little "funky stepping" to get it right, because the implementation of lists, queues, and other dynamic data structures is not intuitive.
To define your own dynamic data structures, you will want to make use of the fact that references to objects in Java are already dynamic. This is demonstrated and necessitated by the practices Java utilizes, such as interfaces and abstract implementations.
If you are accustomed to implementing dynamically linked lists or queues in C++, the format you will use to create your own version of these structures should seem very familiar to you. For example, the following creates a Node class for the list that contains a string:
class Node {
String Name;
Node Prev;
Node Next;
}
This would be a doubly linked list, which has links backward and forward to other nodes containing strings. You could just as easily convert this type to link objects in just about any way to exhibit just about any behavior you want: queues, stacks (remember, there is already a Stack object in the class library), doubly linked lists, circular lists, binary search trees, and the list goes on.
To implement such a list, you could create a DoubleList class that would contain one such Node object and links strung out from there. You can use the keyword null to represent an empty object. Here is an example of the DoubleList declaration:
class DoubleList {
// Declare the listhead to be of the Node type we created above.
// Also, set it to be an empty object.
Node ListHead = null;
.
.
}
Next you create methods to act upon the list, such as InsertNode or ClearMyListJerk-whatever you want.
You would also probably want to create a constructor method for the Node class that would accept parameters to set the previous and next nodes at construction time; or you could create a method such as SetNext or SetNextToNull. Either way would work just fine.
Note: |
|
Using the Utilities Package
The utilities package has two interfaces that can be used in classes
of your own design: Enumeration
and Observer. An interface
is a set of methods that must be written for any class that claims
to implement the interface. This provides a way to consistently
use all classes that implement the interface. The following list
summarizes the Enumeration
and Observer interfaces:
Enumeration | Interface for classes that can enumerate a vector |
Observer | Interface for classes that can observe observable objects |
The Enumeration interface is used for classes that can retrieve data from a list, element by element. For example, there is an Enumeration class in the utilities package that implements the Enumeration interface for use in conjunction with the Vector class. This frees you from hard-core traversal of the different classes of data structures.
The Observer interface is useful in designing classes that can watch for changes that occur in other classes.
Caution: |
|
Enumeration
This interface specifies a set of methods used to enumerate-that is, iterate through-a list. An object that implements this interface may be used to iterate through a list only once because the Enumeration object is consumed through its use.
For example, an Enumeration object can be used to print all the elements of a Vector object, v, as follows:
for (Enumeration e=v.elements();e.hasMoreElements();)
System.out.print(e.nextElement()+" ");
The Enumeration interface specifies only two methods: hasMoreElements() and nextElement(). The hasMoreElements() method must return True if there are elements remaining in the enumeration. The nextElement() method must return an object representing the next element within the object that is being enumerated. The details of how the Enumeration interface is implemented and how the data is represented internally are left up to the implementation of the specific class.
Observer
This interface, if implemented by a class, allows an object of the class to observe other objects of the class Observable. The Observer is notified whenever the Observable object that it is watching has been changed.
The interface only specifies one method, update(Observable, Object). This method is called by the observed object to notify the Observer of changes. A reference to the observed object is passed along with any additional object that the observed object wishes to pass to the Observer. The first argument enables the Observer to operate on the observed object, while the second argument is used to pass information from the observed to the Observer.
Classes
The utilities package supplies ten different classes that provide a wide variety of functionality. Although these classes don't generally have much in common, they all provide support for the most common data structures used by programmers. The techniques described next will enable you to create your own specialized classes to supplement those missing.
The classes supplied in the java.util package, however limited, do provide a great advantage over previous languages. The main advantage is that these classes simplify some things and eliminate a lot of the garbage that you were stuck with in the past, in terms of freeing memory and doing mundane programming tasks.
However, there are a number of limitations. For example, you have to "dance a little bit" to implement some of the more complicated data structures. Also, if you want speed, there are much faster languages to choose from. Java provides a combination of power and simplicity while sacrificing speed. However, don't worry that your programs will be slugs. Although Java is not nearly as efficient as C++ and C, it still beats Visual Basic in terms of size and speed.
BitSet
This class implements a data type that represents a collection of bits. The collection will grow dynamically as more bits are required. It is useful for representing a set of True/False values. Specific bits are identified using non-negative integers. The first bit is bit 0.
This class is most useful for storing a group of related True/False values, such as user responses to Yes/No questions. For example, if the applet had a number of radio buttons, you could slap those values into an instance of the BitSet class.
It is also useful in terms of bitmapping your own graphics. You can create bitsets that can represent a pixel at a time (of course, it would be much easier to use the Graphics class instead).
Individual bits in the set are turned on or off with the set() and clear() methods, respectively. Individual bits are queried with the get() method. These methods all take the specific bit number as their only argument. The basic Boolean operations AND, OR, and XOR can be performed on two BitSets using the and(), or(), and xor() methods. Because these methods modify one of the BitSets, one generally will use the clone() method to create a duplicate of one, and then AND, OR, or XOR the clone with the second BitSet. The result of the operation then will end up in the cloned BitSet. The BitSet1 program in Listing 13.1 illustrates the basic BitSet operations.
Listing 13.1. BitSet1.java-BitSet sample program.
import java.io.DataInputStream;
import java.util.BitSet;
class BitSet1 {
public static void main(String args[])
throws java.io.IOException
{
DataInputStream dis=new DataInputStream(System.in);
String bitstring;
BitSet set1,set2,set3;
set1=new BitSet();
set2=new BitSet();
// Get the first bit sequence and store it
System.out.println("Bit sequence #1:");
bitstring=dis.readLine();
for (short i=0;i<bitstring.length();i++){
if (bitstring.charAt(i)=='1')
set1.set(i);
else
set1.clear(i);
}
// Get the second bit sequence and store it
System.out.println("Bit sequence #2:");
bitstring=dis.readLine();
for (short i=0;i<bitstring.length();i++){
if (bitstring.charAt(i)=='1')
set2.set(i);
else
set2.clear(i);
}
System.out.println("BitSet #1: "+set1);
System.out.println("BitSet #2: "+set2);
// Test the AND operation
set3=(BitSet)set1.clone();
set3.and(set2);
System.out.println("set1 AND set2: "+set3);
// Test the OR operation
set3=(BitSet)set1.clone();
set3.or(set2);
System.out.println("set1 OR set2: "+set3);
// Test the XOR operation
set3=(BitSet)set1.clone();
set3.xor(set2);
System.out.println("set1 XOR set2: "+set3);
}
}
The output from this program looks like this:
Bit sequence #1:
1010
Bit sequence #2:
1100
BitSet #1: {0, 2}
BitSet #2: {0, 1}
set1 AND set2: {0}
set1 OR set2: {0, 1, 2}
set1 XOR set2: {1, 2}
Table 13.2 summarizes all the various methods available in the BitSet class.
Table 13.2. The BitSet interface.
Constructors | |
BitSet() | Constructs an empty BitSet |
BitSet(int) | Constructs an empty BitSet of a given size |
Methods |
|
and(BitSet) | Logically ANDs the object's bit set with another BitSet |
clear(int) | Clears a specific bit |
clone() | Creates a clone of the BitSet object |
equals(Object) | Compares this object against another BitSet object |
get(int) | Returns the value of a specific bit |
hashCode() | Returns the hash code |
or(BitSet) | Logically ORs the object's bit set with another BitSet |
set(int) | Sets a specific bit |
size() | Returns the size of the set |
toString() | Converts bit values to a string representation |
xor(BitSet) | Logically XORs the object's bit set with another BitSet |
In addition to extending the java.lang.Object class, BitSet implements the java.lang.Cloneable interface. This, of course, allows instances of the object to be cloned to create another instance of the class.
Date
You will regularly run into instances in which you will need to access and manipulate dates and times in your applets on the Web. For example, you might want an applet to display the current time or date during its execution. Or, if you are programming a game, you can use the system clock to get your elapsed time right.
The Date class is used to represent dates and times in a platform-independent fashion. For example, the current date or a specific date can be printed as shown in Listing 13.2.
Listing 13.2. Date1.java-Date sample program.
import java.util.Date;
public class Date1{
public static void main (String args[]){
Date today=new Date();
System.out.println("Today is "+today.toLocaleString()+
" ("+today.toGMTString()+")");
Date birthday=new Date(89,10,14,8,30,00);
System.out.println("My birthday is"+
birthday.toString()+" ("+birthday.toGMTString()+")");
Date anniversary=new Date("Jun 21, 1986");
System.out.println("My anniversary is "+
anniversary+" ("+anniversary.toGMTString()+")");
}
}
The output from this program looks like this:
Today is 01/21/96 19:55:17 (22 Jan 1996 01:55:17 GMT)
My birthday is Thu Nov 14 08:30:00 1989 (14 Nov 1989 14:30:00 GMT)
My anniversary is Sat Jun 21 00:00:00 1989 (21 Jun 1986 05:00:00 GMT)
The default constructor is used when the current date and time are needed. A specific date and time can be used to initialize a Date object using the constructors that take three, five, and six integers. These constructors allow the date and time to be specified using YMD, YMDHM, or YMDHMS. Any parts of the time not specified by the three- and five-integer constructors will be set to zero.
Note: |
|
Alternately, a Date object can be constructed using a single string that represents a date and time using a variety of different syntax. One of the most important is the international standard date syntax of the form, "Sun, 14 Aug 1995 9:00:00 GMT." Continental U.S. time zone abbreviations are understood, but time zone offsets should be considered for general use; for example, "Sun, 14 Aug 1995 9:00:00 GMT+0600" (six hours west of the Greenwich meridian). The local time zone to the computer executing the code is assumed if none is supplied.
Note: |
|
The date can be converted to a text representation using the methods toString(), toGMTString(), and toLocaleString(), which convert the date and time to the standard UNIX, GMT, or local time formats, respectively. The toLocaleString function is very useful since you do not have to determine what your system's date format is. This may not sound like much, but it is just another piece of the very complicated puzzle that Sun has put together to allow your applets and applications to flow seamlessly into the system on which they are running.
When a date is being converted to a string by an automatic coercion, the toString() method will be used. The resulting string returned by the toString function follows UNIX time and date standards.
The Date class also has methods for setting and querying the date and time component values once the Date object is constructed. The individual parts of the date (month, date, year) and time (hours, minutes, seconds) are always specified in local time. When referring to the various parts of the date and time, the first letter of each part typically is used in an abbreviation. For example, YMDHMS would indicate that all six parts (year, month, date, hour, minute, second) are present. Each of these parts of the date and time have a specific range of acceptable values, as illustrated in Table 13.3.
Table 13.3. Date component ranges.
Year | Year minus 1900 |
Month | 0-11 (January=0) |
Date | 1-31 |
Day | 0-6 (Sunday=0) |
Hour | 0-23 |
Minute | 0-59 |
Second | 0-59 |
The date and time also can be specified using a single integer UTC value that represents the number of milliseconds that have elapsed since a specific starting date (which might vary from system to system). For UNIX systems this date is January 1, 1970. The program Date2 in Listing 13.3 shows how this single value corresponds to the normal YMDHMS representation.
Listing 13.3. Date2.java-Date sample program.
import java.util.Date;
public class Date2{
public static void main (String args[]){
Date beginning=new Date(0);
Date anniversary=new Date("Jun 21, 1986");
Date today=new Date();
System.out.println(beginning+"="+beginning.getTime());
System.out.println(anniversary+"="+anniversary.getTime());
System.out.println(today+"="+today.getTime());
}
}
The output from this program looks like this:
Wed Dec 31 18:00:00 1969=0
Sat Jun 21 00:00:00 1986=519714000000
Sun Jan 21 19:55:17 1996=822275717000
Dates can be compared to each other by using this UTC value or
by using the methods after(),
before(), or
equals().
Caution: |
|
Table 13.4 summarizes everything available in the Date class.
Table 13.4. The Date interface.
Constructors | |
Date() | Constructs a date using today's date and time |
Date(long) | Constructs a date using a single UTC value |
Date(int, int, int) | Constructs a date using YMD |
Date(int, int, int, int, int) | Constructs a date using YMDHM |
Date(int, int, int, int, int, int) | Constructs a date using YMDHMS |
Date(string) | Constructs a date from a string |
Static Methods | |
UTC(int, int, int, int, int, int) | Calculates a UTC value from YMDHMS |
parse(string) | Returns the single UTC value of a date in text format |
Methods |
|
after(Date) | True if the date is later than the specified date |
before(Date) | True if the date is earlier than the specified date |
equals(Object) | True if the date and the specified date are equal |
getDate() | Returns the day of the month |
getDay() | Returns the day of the week |
getHours() | Returns the hour |
getMinutes() | Returns the minute |
getMonth() | Returns the month |
getSeconds() | Returns the second |
getTime() | Returns the time as a single UTC value |
getTimezoneOffset() | Returns the time zone offset, in minutes, for this locale |
getYear() | Returns the year after 1900 |
hashCode() | Computes a hash code for the date |
setDate(int) | Sets the date |
setHours(int) | Sets the hours |
setMinutes(int) | Sets the minutes |
setMonth(int) | Sets the month |
setSeconds(int) | Sets the seconds |
setTime(long) | Sets the time using a single UTC value |
setYear(int) | Sets the year |
toGMTString() | Converts a date to text using Internet GMT conventions |
toLocaleString() | Converts a date to text using locale conventions |
toString() | Converts a date to text using UNIX ctime() conventions |
One of the most helpful methods available in the Date class is the parse method. This void takes an instance of the String type and then parses that string. The result of that parse is then placed in the calling instance of the class. If you had a date called ADate, you could set its value to be the date in the SomeString class with the code line:
ADate.parse(SomeString);
You will also find the before and after functions useful. They enable you to send in another instance of the Date class and then compare that date to the value in the calling instance. The sample applet in Listing 13.4 demonstrates the use of the Date class in your own applet.
Listing 13.4. Using the Date class.
import java.awt.*;
import java.util.*;
public class MichaelSimpleClock extends java.applet.Applet {
Date TheDate = new Date();
Button DateButton = new Button(
" Click me! ");
public void init() {
add(DateButton);
}
public boolean handleEvent(Event e) {
if (e.target == DateButton) {
DateButton.setLabel(TheDate.toString());
}
return true;
}
}
Figure 13.1 is a screenshot of the MichaelSimpleClock applet. Note that the clock in the applet is wrong: it is not actually 8:00 am. There is no way I would write that early in the morning.
Figure 13.1 : The MichaelSimpleClock applet.
What about a real-time clock that updates as the clock changes? To accomplish this small feat, you need to include in the applet a loop that has each iteration reconstructing the internal Date instance. Then, regularly repaint that value inside the applet's paint method. You'll also need to include threading to prevent locking up your system during the applet's execution. Threading will not be covered until a later chapter, so a real-time clock was not included in this section.
Random
Essential to the programming of games and many other program types is the capability to generate random numbers. Java includes the capability to generate random numbers efficiently and effectively.
The Random class implements a pseudo-random number data type used to generate a stream of seemingly random numbers. To create a sequence of different pseudo-random values each time the application is run, create the Random object as follows:
Random r=new Random();
This will seed the random generator with the current time. On the other hand, consider the following statement:
Random r=new Random(326); // Pick any value
This will seed the random generator with the same value each time,
resulting in the same sequence of pseudo-random numbers each time
the application is run. The generator can be reseeded at any time
using the setSeed() method.
Tip: |
|
Pseudo-random numbers can be generated by using one of these functions: nextInt(), nextLong(), nextFloat(), nextDouble(), or nextGaussian(). The first four functions return integers, longs, and so on. (For more information on the Gaussian distribution, see the next Note.) For example, the program Random1 in Listing 13.5 will print out five pseudo-random uniformly distributed values using these functions.
Listing 13.5. Random1.java-Random sample program.
import java.lang.Math;
import java.util.Date;
import java.util.Random;
class Random1 {
public static void main(String args[])
throws java.io.IOException
{
int count=6;
Random randGen=new Random();
System.out.println("Uniform Random Integers");
for (int i=0;i<count;i++)
System.out.print(randGen.nextInt()+" ");
System.out.println("\n");
System.out.println("Uniform Random Floats");
for (int i=0;i<count;i++)
System.out.print(randGen.nextFloat()+" ");
System.out.println("\n");
System.out.println("Gaussian Random Floats");
for (int i=0;i<count;i++)
System.out.print(randGen.nextGaussian()+" ");
System.out.println("\n");
System.out.println("Uniform Random Integers [1,6]");
for (int i=0;i<count;i++)
System.out.print((Math.abs(randGen.nextInt())%6+1)+" ");
System.out.println("\n");
}
}
The output from the preceding program looks like this:
Uniform Random Integers
1704667569 -1431446235 1024613888 438489989 710330974 -1689521238
Uniform Random Floats
0.689189 0.0579988 0.0933537 0.748228 0.400992 0.222109
Gaussian Random Floats
-0.201843 -0.0111578 1.63927 0.205938 -0.365471 0.626304
Uniform Random Integers [1,6]
4 6 1 6 3 2
If you need to generate uniformly distributed random integers within a specific range, the output from nextInt(), nextLong(), or nextDouble() can be scaled to match the required range. A simpler approach is to take the remainder of the result of nextInt() divided by the number of different values plus the first value of the range. For example, if the values 10 to 20 are needed one can use the formula nextInt()%21+10. Unfortunately, although this method is much simpler than scaling the output of nextInt(), it only is guaranteed to work on truly random values. Because the pseudo-random generator might have various undesired correlations, the modulus operator might not provide acceptable results-one might get all odd numbers, for example. In other words, don't plan on simulating the detonation of your new H-bomb in Java because you might find yourself a couple of miles too close.
Note: |
|
Table 13.5 summarizes the complete interface of the Random class.
Table 13.5. The Random interface.
Constructors |
|
Random() | Creates a new random number generator |
Random(long) | Creates a new random number generator using a seed |
Methods | |
nextDouble() | Returns a pseudo-random uniformly distributed Double |
nextFloat() | Returns a pseudo-random uniformly distributed Float |
nextGaussian() | Returns a pseudo-random Gaussian distributed Double |
nextInt() | Returns a pseudo-random uniformly distributed Int |
nextLong() | Returns a pseudo-random uniformly distributed Long |
setSeed(long) | Sets the seed of the pseudo-random number generator |
Refer also to Random().
The following applet, shown in Listing 13.6, demonstrates a bit of what you can do with Random.
Listing 13.6. Using the Random Class.
import java.awt.*;
import java.util.*;
public class TheWanderer extends java.applet.Applet {
int xpos = 100;
int ypos = 100;
// Our current date.
Date D = new Date();
// The movement button
Button theButton = new Button("Click Me");
// Our random number generator.
Random R;
public void init() {
add(theButton);
setBackground(Color.white);
// Our random number generator seeded with the current seconds.
int seed = D.getSeconds();
R = new Random(seed);
}
public boolean handleEvent (Event e) {
if (e.target == theButton) {
// Move our thing.
xpos = xpos + (Math.abs(R.nextInt())%10-7);
ypos = ypos + (Math.abs(R.nextInt())%10-7);
// repaint the sucker.
repaint();
}
return super.handleEvent(e);
}
public void paint(Graphics g) {
g.setColor(Color.black);
g.fillOval(xpos,ypos, 50, 50);
}
}
Figure 13.2 shows TheWanderer applet during its execution.
Figure 13.2 : TheWanderer applet.
StringTokenizer
This section will describe the function of the StringTokenizer class, which also could have been appropriately grouped with other classes in Chapter 11, "Reading and Writing with Java," since it is so vital to the input and output functions demonstrated in that chapter. This class enables you to parse a string into a number of smaller strings called tokens. This class works specifically for what is called "delimited text," which means that each individual substring of the string is separated by a delimiter. The delimiter can be anything ranging from a "*" to "YabaDaba". You simply specify what you want the class to look for when tokenizing the string.
This class is included here because it has uses that would prove helpful in everything from a spreadsheet applet to an arcade game applet.
The delimiter set can be specified when the StringTokenizer object is created, or it can be specified on a per-token basis. The default delimiter set is the set of whitespace characters. The class would then find all of the separate words in a string and tokenize them. For example, the StringTokenizer1 code in Listing 13.7 prints out each word of the string on a separate line.
Listing 13.7. StringTokenizer1.java-StringTokenizer sample program.
import java.io.DataInputStream;
import java.util.StringTokenizer;
class StringTokenizer1 {
public static void main(String args[])
throws java.io.IOException
{
DataInputStream dis=new DataInputStream(System.in);
System.out.println("Enter a sentence: ");
String s=dis.readLine();
StringTokenizer st=new StringTokenizer(s);
while (st.hasMoreTokens())
System.out.println(st.nextToken());
}
}
Here is the output from this listing:
Enter a sentence:
Four score and seven
Four
score
and
seven
Pure excitement. The method countTokens() returns the number of tokens remaining in the string using the current delimiter set-that is, the number of times nextToken() can be called before generating an exception. This is an efficient method because it does not actually construct the substrings that nextToken() must generate.
In addition to extending the java.lang.object class, the StringTokenizer class implements the java.util.Enumeration interface.
Table 13.6 summarizes the methods of the
StringTokenizer
class.
Table 13.6. The StringTokenizer interface.
Constructors | |
StringTokenizer | Constructs a StringTokenizer given a string using |
(string) | whitespace as delimiters |
StringTokenizer | Constructs a StringTokenizer given a string and a |
(string, string) | delimiter set |
StringTokenizer (string, string, boolean) | Constructs a StringTokenizer given a string and a delimiter set |
Methods |
|
countTokens() | Returns the number of tokens remaining in the string |
hasMoreTokens() | Returns True if more tokens exist |
nextToken() | Returns the next token of the string |
nextToken(string) | Returns the next token, given a new delimiter set |
hasMoreTokens() | Returns True if more elements exist in the enumeration |
nextElement() | Returns the next element of the enumeration using the current delimiter set |
Vector
As was stated before, Java doesn't include dynamically linked list, queue, or other data structures of that type. Instead, the designers of Java envisioned the Vector class, which would be able to handle occasions when you need dynamic storage of objects. Of course, there are positive and negative consequences of this decision by the designers at Sun. On the positive side, it contributes to the simplicity of the language. The major negative point is that, at face value, it severely limits programmers from utilizing more sophisticated programs.
In any case, the Vector class implements a dynamically allocated list of objects. It attempts to optimize storage by increasing the storage capacity of the list when needed by increments larger than just one object. Typically with this mechanism, there is some excess capacity in the list. When this capacity is exhausted, the list is reallocated to add another block of objects at the end of the list. Setting the capacity of the Vector object to the needed size before inserting a large number of objects will reduce the need for incremental reallocation. Because of this mechanism, it is important to remember that the capacity (the available elements in the Vector object) and the size (the number of elements currently stored in the Vector object) usually are not the same.
For example, say a Vector with capacityIncrement equal to three has been created. As objects are added to the Vector, new space is allocated in chunks of three objects. After five elements have been added, there still will be room for one more element without the need for any additional memory allocation.
After the sixth element has been added, there is no more excess capacity. When the seventh element is added, a new allocation will be made that adds three additional elements, giving a total capacity of nine. After the seventh element is added, there will be two remaining unused elements.
The initial storage capacity and the capacity increment both can be specified in the constructor. Even though the capacity is automatically increased as needed, the ensureCapacity() method can be used to increase the capacity to a specific minimum number of elements, whereas trimToSize() can be used to reduce the capacity to the minimum needed to store the current elements. New elements can be added to the Vector using the addElement() and insertElementAt() methods. The elements passed to be stored in the Vector must be derived from type Object. Elements can be changed using the setElementAt() method. Removal of elements is accomplished with the removeElement(), removeElementAt(), and removeAllElements() methods. Elements can be accessed directly using the elementAt(), firstElement(), and lastElement() methods, whereas elements can be located using the indexOf() and lastIndexOf() methods. Information about the size and the capacity of the Vector are returned by the size() and capacity() methods respectively. The setSize() method can be used to directly change the size of the Vector.
For example, the Vector1 code in Listing 13.8 creates a Vector of integers by adding new elements to the end. Then, using a variety of techniques, it prints the Vector.
Listing 13.8. Vector1.java-Vector sample program.
import java.lang.Integer;
import java.util.Enumeration;
import java.util.Vector;
class Vector1 {
public static void main(String args[]){
Vector v=new Vector(10,10);
for (int i=0;i<20;i++)
v.addElement(new Integer(i));
System.out.println("Vector in original order using an Enumeration");
for (Enumeration e=v.elements();e.hasMoreElements();)
System.out.print(e.nextElement()+" ");
System.out.println();
System.out.println("Vector in original order using elementAt");
for (int i=0;i<v.size();i++)
System.out.print(v.elementAt(i)+" ");
System.out.println();
// Print out the original vector
System.out.println("\nVector in reverse order using elementAt");
for (int i=v.size()-1;i>=0;i++)
System.out.print(v.elementAt(i)+" ");
System.out.println();
// Print out the original vector
System.out.println("\nVector as a String");
System.out.println(v.toString());
}
}
The output from this program looks like this:
Vector in original order using an Enumeration
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Vector in original order using elementAt
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
Vector in reverse order using elementAt
19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Vector as a String
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
Note: |
|
Notice the use of the Enumeration object as one way to access the elements of a Vector. Look at the following lines:
for (Enumeration e=v.elements();e.hasMoreElements();)
System.out.print(e.nextElement()+" ");
One can see that an Enumeration object, which represents all of the elements in the Vector, is created and returned by the Vector method elements(). With this Enumeration object, the loop can check to see if there are more elements to process using the Enumeration method hasMoreElements(), and the loop can get the next element in the Vector using the Enumeration method nextElement().
The Vector2 program in Listing 13.9 illustrates some of the vector-accessing techniques. It first generates a vector of random integers; then allows the user to search for a specific value. The locations of the first and last occurrences of the value are printed by the program using the indexOf() and lastIndexOf() methods.
Listing 13.9. Vector2.java-Vector sample program.
import java.io.DataInputStream;
import java.lang.Integer;
import java.lang.Math;
import java.util.Enumeration;
import java.util.Random;
import java.util.Vector;
class Vector2 {
public static void main(String args[])
throws java.io.IOException
{
int numElements;
DataInputStream dis=new DataInputStream(System.in);
Vector v=new Vector(10,10);
Random randGen=new Random();
System.out.println("How many random elements? ");
numElements=Integer.valueOf(dis.readLine()).intValue();
for (int i=0;i<numElements;i++)
v.addElement(new Integer(Math.abs(
randGen.nextInt())%numElements));
System.out.println(v.toString());
Integer searchValue;
System.out.println("Find which value? ");
searchValue=Integer.valueOf(dis.readLine());
System.out.println("First occurrence is element "+
v.indexOf(searchValue));
System.out.println("Last occurrence is element "+
v.lastIndexOf(searchValue));
}
}
The output from this program looks like this:
How many random elements?
10
[0, 2, 8, 4, 9, 7, 8, 6, 3, 2]
Find which value?
8
First occurrence is element 2
Last occurrence is element 6
In addition to extending the java.lang.Object class, the Vector class implements the java.lang.Cloneable interface. Table 13.7 summarizes the methods of the Vector class.
Table 13.7. The Vector interface.
Variables | |
capacityIncrement | Size of the incremental allocations, in elements |
elementCount | Number of elements in Vector |
elementData | Buffer where the elements are stored |
Constructors | |
Vector() | Constructs an empty vector |
Vector(int) | Constructs an empty vector with the specified storage capacity |
Vector(int, int) | Constructs an empty vector with the specified storage capacity and capacityIncrement |
Methods |
|
addElement(Object) | Adds the specified object at the end of the Vector |
capacity() | Returns the capacity of the Vector |
clone() | Creates a clone of the Vector |
contains(Object) | True if the specified object is in the Vector |
copyInto(Object[]) | Copies the elements of this vector into an array |
elementAt(int) | Returns the element at the specified index |
elements() | Returns an Enumeration of the elements |
ensureCapacity(int) | Ensures that the Vector has the specified capacity |
firstElement() | Returns the first element of the Vector |
indexOf(Object) | Returns the index of the first occurrence of the specified object within the Vector |
indexOf(Object, int) | Returns the index of the specified object within the Vector starting the search at the index specified and proceeding toward the end of the Vector |
insertElementAt(Object, int) | Inserts an object at the index specified |
isEmpty() | True if the Vector is empty |
lastElement() | Returns the last element of the Vector |
lastIndexOf(Object) | Returns the index of the last occurrence of the specified object within the Vector |
lastIndexOf(Object, int) | Returns the index of the specified object within the Vector starting the search at the index specified and proceeding toward the beginning of the Vector |
removeAllElements() | Removes all elements of the Vector |
removeElement(Object) | Removes the specified object from the Vector |
removeElementAt(int) | Removes the element with the specified index |
setElementAt(Object, int) | Stores the object at the specified index in the Vector |
setSize(int) | Sets the size of the Vector |
size() | Returns the number of elements in the Vector |
toString() | Converts the Vector to a string |
trimToSize() | Trims the Vector's capacity down to the specified size |
Refer also to Vector, Hashtable.
Stack
The Stack data structure is key to many programming efforts, ranging from building compilers to solving mazes. The Stack class in the Java library implements a Last In, First Out (LIFO) stack of objects. Even though they are based on (extends) the Vector class, Stacks are typically not accessed in a direct fashion. Instead, values are pushed onto and popped off of the top of the "stack." The net effect is that values that were most recently pushed are the first ones to be popped.
Note: |
|
The Stack1 code in Listing 13.10 pushes strings onto the stack, and then retrieves them. The strings will end up being printed in reverse order from which they were stored.
Listing 13.10. Stack1.java-Stack sample program.
import java.io.DataInputStream;
import java.util.Stack;
import java.util.StringTokenizer;
class Stack1 {
public static void main(String args[])
throws java.io.IOException
{
DataInputStream dis=new DataInputStream(System.in);
System.out.println("Enter a sentence: ");
String s=dis.readLine();
StringTokenizer st=new StringTokenizer(s);
Stack stack=new Stack();
while (st.hasMoreTokens())
stack.push(st.nextToken());
while (!stack.empty())
System.out.print((String)stack.pop()+" ");
System.out.println();
}
}
The output from this program looks like this:
Enter a sentence:
The quick brown fox jumps over the lazy dog
dog lazy the over jumps fox brown quick The
Even though Stack objects normally are not accessed in a direct fashion, it is possible to search the Stack for a specific value using the search() method. It accepts an object to find and returns the distance from the top of the Stack where the object was found. It will return -1 if the object is not found.
The method peek() will return the top object on the Stack without actually removing it from the Stack. The peek() method will throw an EmptyStackException if the Stack has no items.
Table 13.8 summarizes the complete interface of the Stack class.
Table 13.8. The Stack interface.
Constructors | |
Stack() | Constructs an empty Stack |
Methods |
|
empty() | True if the Stack is empty |
peek() | Returns the top object on the Stack |
pop() | Pops an element off the Stack |
push(Object) | Pushes an element onto the Stack |
search(Object) | Finds an object on the Stack |
Dictionary
This class is an abstract class that is used as a base for the Hashtable class. It implements a data structure that allows a collection of key and value pairs to be stored. Any type of object can be used for the keys or the values. Typically, the keys are used to find a particular corresponding value.
Because this class is an abstract class that cannot be used directly, the code examples presented cannot actually be run. They are presented only to illustrate the purpose and use of the methods declared by this class. The following code would, hypothetically, be used to create a Dictionary with these values illustrated:
Dictionary products = new Dictionary();
products.put(new Integer(342), "Widget");
products.put(new Integer(124), "Gadget");
products.put(new Integer(754), "FooBar");
The put() method is used to insert a key and value pair into the Dictionary. The two arguments both must be derived from the class Object. The key is the first argument and the value is the second.
A value can be retrieved using the get() method and a specific key to be found. It returns the null value if the specified key is not found. For example:
String name = products.get(new Integer(124));
if (name != null) {
System.out.println("Product name for code 124 is " + name);
}
Although an individual object can be retrieved with the get() method, sometimes it is necessary to access all of the keys or all of the values. There are two methods, keys() and elements(), that will return Enumerations that can be used to access the keys and the values, respectively.
Table 13.9 summarizes the complete interface of the Dictionary class.
Table 13.9. The Dictionary interface.
Constructors | |
Dictionary() | Constructs an empty Dictionary |
Methods |
|
elements() | Returns an Enumeration of the values |
get(Object) | Returns the object associated with the specified key |
isEmpty() | True if the Dictionary has no elements |
keys() | Returns an Enumeration of the keys |
put(Object, Object) | Stores the specified key and value pair in the Dictionary |
remove(Object) | Removes an element from the Dictionary by its key |
size() | Returns the number of elements stored |
Refer also to Enumeration, Hashtable, Properties.
Hashtable
The Hashtable data structure is very useful when dealing with the search for and manipulation of data. You would want to use this class if you will be storing a large amount of data in memory and then searching it. The time needed to complete a search of a hash table is decidedly less than in the Vector class. Of course, for small amounts of data, it won't make much difference whether you use a hash table or a linear data structure, since the overhead time will be much greater than any search time would be. See the next Note for more information on search times in the different classes.
Hash table organization is based upon keys, which are computed based upon the data being stored. For example, if you were going to insert a number of words into a hash table, you could base your key upon the first letter of the word. When you came back to search for a word later on, you could then compute the key for the item being sought. By using this key, search time is drastically reduced because the items are stored based upon the value of their respective key.
The Hashtable class implements a hash table storage mechanism for storing key and value pairs. Hash tables are designed to quickly locate and retrieve information stored by using a key. Keys and values may be of any object type, but the key object's class must implement the hashCode() and equals() methods.
Note: |
|
The sample Hashtable1 in Listing 13.11 creates a Hashtable object and stores 10 key and value pairs using the put() method. It then uses the get() method to return the value corresponding to a key entered by the user.
Listing 13.11. Hashtable1.java-Hashtable sample program.
import java.io.DataInputStream;
import java.lang.Integer;
import java.lang.Math;
import java.util.Random;
import java.util.Hashtable;
class Hashtable1 {
public static void main(String args[])
throws java.io.IOException
{
DataInputStream dis=new DataInputStream(System.in);
int numElements=10;
String keys[]={"Red","Green","Blue","Cyan","Magenta",
"Yellow","Black","Orange","Purple","White"};
Hashtable ht;
Random randGen=new Random();
ht=new Hashtable(numElements*2);
for (int i=0;i<numElements;i++)
ht.put(keys[i],new Integer(Math.abs(
randGen.nextInt())%numElements));
System.out.println(ht.toString());
String keyValue;
System.out.println("Which key to find? ");
keyValue=dis.readLine();
Integer value=(Integer)ht.get(keyValue);
if (value!=null) System.out.println(keyValue+" = "+value);
}
}
The output from this program looks like this:
{Cyan=4, White=0, Magenta=4, Red=5, Black=3,
ÂGreen=8, Purple=3, Orange=4, Yellow=2, _Blue=6}
Which key to find?
Red
Red = 5
In addition to the get() method, the contains() and containsKey() methods can be used to search for a particular value or key, respectively. Both return True or False depending on whether the search was successful. The contains() method must perform an exhaustive search of the table and is not as efficient as the containsKey() method, which can take advantage of the hash table's storage mechanism to find the key quickly.
Because hash tables need to allocate storage for more data than actually is stored, a measurement called the load factor indicates the number of used storage spaces as a fraction of the total available storage spaces. It is expressed as a value between 0 and 100 percent. Typically, the load factor should not be higher than about 50 percent for efficient retrieval of data from a hash table. When specifying the load factor in a program, use a fractional value in the range 0.0 to 1.0 to represent load factors in the range 0 to 100 percent.
Hash tables can be constructed in three different ways: by specifying the desired initial capacity and load factor, by specifying only the initial capacity, or by specifying neither. If the load factor is not specified, the Hashtable will be rehashed into a larger table when it is full-otherwise it is rehashed when it exceeds the load factor. The constructors will throw an IllegalArgumentException if the initial capacity is less than or equal to zero, or if the load factor is less than or equal to zero.
The clone() method can be used to create a copy (clone) of the Hashtable. However, it creates a shallow copy of the Hashtable, which means that the keys and values themselves are not clones. This local method overrides the inherited clone() method.
Caution: |
|
The Hashtable class extends the java.util.Dictionary class and implements the java.lang.Cloneable interface. Table 13.10 summarizes the methods of the Hashtable class.
Table 13.10. The Hashtable interface.
Constructors | |
Hashtable() | Constructs an empty Hashtable |
Hashtable(int) | Constructs an empty Hashtable with the specified capacity |
Hashtable(int, float) | Constructs an empty Hashtable given capacity and load factor |
Methods |
|
clear() | Deletes all elements from the Hashtable |
clone() | Creates a clone of the Hashtable |
contains(Object) | True if the specified object is an element of the Hashtable |
containsKey(Object) | True if the Hashtable contains the specified key |
elements() | Returns an Enumeration of the Hashtable's values |
get(Object) | Returns the object associated with the specified key |
isEmpty() | True if the Hashtable has no elements |
keys() | Returns an Enumeration of the keys |
put(Object, Object) | Stores the specified key and value pair in the Hashtable |
rehash() | Rehashes the contents of the table into a bigger table |
remove(Object) | Removes an element from the Hashtable by its key |
size() | Returns the number of elements stored |
toString() | Converts the contents to a very long string |
Refer also to hashCode, equals.
Properties
The Properties class is what enables end-users to customize their Java program. For example, you can easily store values such as foreground colors, background colors, and font defaults, and then have those values available to be reloaded. This would be most useful for Java applications, but you can also implement them for applets. If you have an applet that is regularly used by multiple users, you could keep a properties file on your server for each different user, which would be accessed each time that user loaded the applet.
The Properties class is a Hashtable, which can be repeatedly stored and restored from a stream. It is used to implement persistent properties. It also allows for an unlimited level of nesting, by searching a default property list if the required property is not found. The fact that this class is an extension of the Hashtable class means that all methods available in the Hashtable class are also available in the Properties class.
The sample program Properties1 in Listing 13.12 creates two properties lists. One will be the default property list and the other will be the user-defined property list. When the user property list is created, the default Properties object is passed. When the user property list is searched, if the key value is not found, the default Properties list will be searched.
Listing 13.12. Properties1.java-Properties sample program.
import java.io.Data7InputStream;
import java.lang.Integer;
import java.util.Properties;
class Properties1 {
public static void main(String args[])
throws java.io.IOException
{
int numElements=4;
String defaultNames[]={"Red","Green","Blue","Purple"};
int defaultValues[]={1,2,3,4};
String userNames[]={"Red","Yellow","Orange","Blue"};
int userValues[]={100,200,300,400};
DataInputStream dis=new DataInputStream(System.in);
Properties defaultProps=new Properties();
Properties userProps=new Properties(defaultProps);
for (int i=0;i<numElements;i++){
defaultProps.put(defaultNames[i],
Integer.toString(defaultValues[i]));
userProps.put(userNames[i],
Integer.toString(userValues[i]));
}
System.out.println("Default Properties");
defaultProps.list(System.out);
System.out.println("\nUser Defined Properties");
userProps.list(System.out);
String keyValue;
System.out.println("\nWhich property to find? ");
keyValue=dis.readLine();
System.out.println("Property '"+keyValue+"' is '"+
userProps.getProperty(keyValue)+"'");
}
}
Notice that the getProperties() method is used instead of the inherited get() method. The get() method only searches the current Properties object. The getProperties() method must be used in order to have the default Properties list searched. An alternative form of the getProperties() method has a second argument, which is that a default Properties list is to be searched instead of the default specified when the Properties object was created.
The propertyNames() method can be used to return an Enumeration, which can be used to index through all of the property names. This Enumeration includes the property names from the default Properties list. Likewise, the list() method, which prints the Properties list to the standard output, will list all of the properties of the current Properties object and those in the default Properties object.
Properties objects can be written to and read from a stream using the save() and load() methods, respectively. In addition to the output or input stream, the save method has an additional string argument that will be written at the beginning of the stream as a header comment.
Table 13.11 summarizes the methods of the Properties class.
Table 13.11. The Properties interface.
Variables | |
defaults | Default Properties list to search |
Constructors | |
Properties() | Constructs an empty property list |
Properties(Properties) | Constructs an empty property list with specified default |
Methods |
|
getProperty(string) | Returns a property given the key |
getProperty(string, string) | Returns a property given the specified key and default |
list(PrintStream) | Lists the properties to a stream for debugging |
load(InputStream) | Reads the properties from an InputStream |
propertyNames() | Returns an Enumeration of all of the keys |
save(OutputStream, string) | Writes the properties to an OutputStream |
Observable
This class acts as a base class for objects that you wish to have observed by other objects that implement the Observer interface. An Observable object can notify its Observers whenever the Observable object is modified using the notifyObservers() method. This method accomplishes the notification by invoking the update() method of all of its Observers, optionally passing a data object that is passed to notifyObservers. Observable objects may have any number of Observers.
Table 13.12 summarizes the complete interface of the Observable class.
Table 13.12. The Observable interface.
Constructors | |
Observable() | |
Methods |
|
addObserver(Observer) | Adds an Observer to the observer list |
clearChanged() | Clears an observable change |
countObservers() | Returns the number of Observers |
deleteObserver(Observer) | Deletes an Observer from the observer list |
deleteObservers() | Deletes all Observers from the observer list |
hasChanged() | True if an observable change occurred |
notifyObservers() | Notifies all Observers if an observable change occurred |
notifyObservers(Object) | Notifies all Observers of a specific observable change |
setChanged() | Sets a flag to indicate that an observable change occurred |
Why the Java Database Connectivity (JDBC) Specification?
The Java specification, as well as the original release of the Java Developer's Kit (JDK), made no provisions for Java database access. To create access to a database, a programmer had to create an intermediary program between the database manager and the Java program that would access the data. To force a multitude of programmers all to write code that does essentially the same thing is truly a waste. Of course, each different programmer would do it his/her own way and suddenly you'd have a large mess on your hands. It is entirely inefficient not to have a standardized access format between Java code and database management systems.
Another reason that a standardized database interface proves necessary stems from security issues. By making a standard JDBC specification, database manufacturers can produce interfaces for their database, regardless of the internal storage format, so that the standardized interface could work with any Java program. This is a large bonus: Any Java program that implements database features can manipulate and access any database that has a JDBC-compliant interface. Also, the standardized classes and interfaces of the database interface classes can join the ranks of "trusted" classes that Web browsers can then safely use. Of course, home-brewed database interfaces would not be trusted or put into wide use and applets would not be able to use databases at all.
For these reasons, some kind of standardized database capability is necessary. In March 1996, Sun Microsystems, Inc. addressed this need with the draft release of the Java Database Connectivity specification, JDBC. As of this writing, the JDBC specification is still in a request-for-comments phase and is scheduled to be available soon in a full release. This chapter addresses the problem of database access in Java and demonstrates how to write code that conforms to and enhances the JDBC interface.
A hidden plus of the JDBC standard is that implementing database features in a Java program should make it easy to use that same implementation on another database. Of course, standardizing all of these functions comes at a price. It is less efficient to include the JDBC interface as another layer between your program and the database itself, but you should be accustomed to the tradeoff between standardization and simplicity and efficiency.
Currently, a number of database system developers have committed to developing JDBC-specification-compliant interfaces for their database systems. As of July 1996, database system producers ranging from Borland to tiny companies have stated their intent to develop JDBC interfaces immediately. As always, the latest information on Java database developments can be found at Java's home page:
Storing Data for the Web
Simple applets rarely need to perform database access. They are generally executed as on-off programs without the need to save any state information across executions. As Java developers move their work out of the realm of the simple applet, they will find the need to access some sort of data store. A popular yet simple example is the ubiquitous page counter. Of course, a page counter is simply an applet or CGI script that keeps track of how many times a particular page has been hit and displays that number on the page (see Figure 15.1).
Figure 15.1 : A Web page running a counter applet.
At the other extreme in complexity are the search engines with which you can perform keyword searches to find the most trivially related pages of information existing on the Internet. No matter how complex the application, the basic data management needs are the same. Many users need to gain access to the same piece of information and require an application built in such a way that it can access and/or modify centrally stored data. The developer must then take the following steps to provide users with access to data:
- Select and install a database management system (DBMS)
- Build data processing logic
- Build a user interface
Providing Access to Data
Whether Java or some other language is used to build these pieces, the DBMS used will have a direct impact on the implementation. A detailed discussion of database management systems is well beyond the scope of this book. When you choose among the various technologies, however, keep your needs (and your wallet) in mind and resist the dazzle of technology. Three basic data storage technologies that serve various needs follow:
- Object-oriented database (OODBMS)
- Relational database (RDBMS)
- Object-relational database (OORDBMS)
With the advent of the high multimedia content data storage needs of the Internet, developers have been more open to the idea of using object databases. In addition to being better suited to the unusual demands of storing multimedia data, object databases also help provide a true object paradigm from data store to client application.
What does this mean to you? You'll be concerned with a couple of issues, the first of which are the time and storage space requirements necessary for each different type of database manager. If you're trying to store a large amount of customer orders, for example, you should choose the appropriate database management system, such as a simple relational database. Or, if you're going heavy into multimedia, you'll want to use an object-based database manager.
The second issue is how the choice will affect how easily you can access the database. As you probably know, accessing a pure object database with any front-end tool is a challenge. Because the JDBC specification revolves around ANSI SQL-2 compliance and few object databases have SQL support, accessing an object database through Java will prove to be doubly challenging.
For developers not faced with the need to store complex data, any traditional relational databases should do exactly what you need. The grand trick to programming in Java with a relational database, or doing any object programming against a relational database, is mapping between the dynamic realm of objects and the static realm of pure data.
Paving the road between these two seemingly disparate technologies are the object-relational databases. For developers with complex data modeling needs, an object-relational database can provide the object modeling power of an object database while maintaining the ease of data access afforded by traditional relational systems.
The JDBC API
To provide a common base API for accessing data, Sun Microsystems, Inc., with support from a number of independent software vendors, developed JDBC. JDBC defines a number of Java interfaces to enable developers to access data independent of the actual database product being used to store the data. In theory, an application written against the basic JDBC API using only SQL-2 can function against any database technology that supports SQL-2. Of course, the key words are "in theory." The idea is that the interaction between your Java program and any database is standardized through the JDBC specification standard.
Database Requirements
You may store data in a wide variety of formats using various technologies. In addition to the three major modern database management systems, you will want to consider other systems, such as hierarchical databases and file systems. Any low-level API trying to find some common ground between all of these systems would be unsuccessful. JDBC mandates no specific requirements on the underlying DBMS, however. In other words, the JDBC doesn't care what's going on underneath the interface as long as it meets the ANSI SQL-2 standards. Rather than dictating what sort of DBMS an application must have to support JDBC, the specification places all of its requirements on the JDBC implementation.
Each platform and database-specific implementation of the JDBC by a software developer will provide a standardized environment in which your Java programs can operate. The JDBC specification primarily mandates that a JDBC implementation supports at least ANSI SQL-2 Entry Level. Because most common relational database systems and object-relational database systems support SQL-2, this requirement provides a reasonable baseline from which software developers can build Java database access. In addition, because SQL-2 is required only at the JDBC implementation level, that implementation can provide its own SQL-2 wrapper around non-SQL data stores.
The most important thing for you to remember when developing in Java is that your applications and applets are limited to accessing databases that support the ANSI SQL-2 standard. These databases naturally follow this standard or have had a specialized JDBC-compliant interface written for them.
The JDBC Interfaces
The JDBC defines eight interfaces that must be implemented to be JDBC-compliant:
- java.sql.Driver
- java.sql.Connection
- java.sql.Statement
- java.sql.PreparedStatement
- java.sql.CallableStatement
- java.sql.ResultSet
- java.sql.ResultSetMetaData
- java.sql.DatabaseMetaData
Figure 15.2 shows these interfaces and how they interact in the full JDBC object model.
The central object around which the whole concept revolves is the java.sql.DriverManager object. This object is responsible for keeping track of the various JDBC implementations that may exist for an application. If, for example, a system were aware of Sybase and Oracle JDBC implementations, the DriverManager would be responsible for tracking those implementations. Any time an application desires to connect to a database, it asks the DriverManager to give it a database connection using a database URL through the DriverManager.getConnection() method. Based on this URL, the DriverManager searches for a Driver implementation that accepts the URL. It then gets a Connection implementation from that Driver and returns it to the application.
Figure 15.2 : The JDBC object model.
Note: |
|
The DriverManager is the only instantiated class provided by JDBC other than exception objects and a few specialized subclasses of java.util.Date. Additional calls made by an application are written against the JDBC interfaces that are implemented for specific DBMSs.
java.sql.Driver
A Driver is essentially a Connection factory. The DriverManager uses the Driver to determine whether it, the DriverManager, can handle a given URL. If it can handle the URL, it should create a connection object and return it to the DriverManager. Because an application only indirectly references a Driver through the DriverManager, applications are rarely concerned with this class.
java.sql.Connection
A Connection is a single database session. As such, it stores state information about the database session it manages and provides the application with Statement, PreparedStatement, or CallableStatement objects to make calls during the session.
java.sql.Statement
A Statement is an unbound SQL call to the database. It is generally a simple UPDATE, DELETE, INSERT, or SELECT for which no columns need to bind to Java data. It provides methods for making such calls and returns to the application the results of any SELECT statements, or the number of rows affected by an UPDATE, DELETE, or INSERT.
Statement has the subclass PreparedStatement, which is in turn subclassed by CallableStatement. A PreparedStatement is a precompiled database call that requires binding parameters. An example of a PreparedStatement might be a stored procedure call that has no OUT or INOUT parameters. For stored procedures with OUT or INOUT parameters, an application should use the CallableStatement interface.
java.sql.ResultSet
A SELECT query returns data to an application by implementing this interface. Specifically, the ResultSet object enables an application to retrieve sequential rows of data returned from a previous SELECT call. It provides a multitude of methods that enable you to retrieve a given row as any data type to which it makes sense to convert it. For example, if you have a date stored in the database as a datetime, you can retrieve it through the getString() method to use it as a string.
The Meta-data Interfaces
Meta-data is data about data. Specifically, it is a set of data that gives you information about the database and data retrieved from the database. Java provides two meta-data interfaces: java.sql.ResultSetMetaData and java.sql.DatabaseMetaData. The ResultSetMetaData provides a means for getting information about a particular ResultSet. For example, among other things, it provides information on the number of columns in the result set, the name of a column, and its type. The DatabaseMetaData interface, on the other hand, gives the application information on the database in general, such as what levels of support it has, its name, version, and other features.
Simple Database Access Using the JDBC Interfaces
An application for which database independence is paramount, in other words, one in which you want to write a program that can use different databases using a JDBC interface, should be written to the JDBC specification without using database-specific calls and without making use of SQL that is not part of the ANSI SQL-2 standard. In such code, no reference should be made to a specific implementation of JDBC. Writing a simple database application using only JDBC calls involves the following steps:
- Ask the DriverManager for a Connection implementation.
- Ask the Connection for a Statement or subclass of Statement to execute your SQL.
- For subclasses of Statement, bind any parameters to be passed to the prepared statement.
- Execute the Statement.
- For queries, process the ResultSet returned from the query. Do this for each result set (if you have multiple result sets) until none are left.
- For other statements, check the return value for the number of rows affected.
- Close the Statement.
- Process any number of such statements and then close the connection.
The Counter Applet Example
The counter applet discussed earlier in this chapter provides a simple example of JDBC programming. Using the JDBC interfaces, this applet connects to a database, determines how many times the page on which it appears has been hit, updates the page to reflect the new hit, and displays the number of hits. To use this example, you need a database engine to run your database and a JDBC driver to access that database engine. If you do not have a database engine, download mSQL and JDBC, which are both free for noncommercial use. Links to mSQL and the JDBC class may be found through
http://www.imaginary.com/Java/.
In addition, you need to create a table called t_counter with the fields counter_file (chAR(100), PRIMARY KEY) and counter_num (INT, NOT NULL). The following mSQL script creates the table:
DROP TABLE t_counter\p\g
CREATE TABLE t_counter(
counter_file chAR(100) PRIMARY KEY,
counter_num INT NOT NULL
)\p\g
The applet consists of two classes, Counter and Database. The Counter class is the subclass of applet that provides the user interface to the applet. It contains two instance variables: count, which is the number this applet is supposed to display, the number of page hits, and database, which is an instance of the Database class that provides wrappers for the JDBC access needed by the applet.
Counter does not define any new methods; rather, it simply overrides the java.applet.Applet.init() and java.applet.Applet.paint() methods. The init() method is used to create a Database instance and find out from it what the page hit count is for display. The paint() method displays the page hit count.
This interesting JDBC-related work is all encapsulated inside the Database class. This class has a single instance variable, connection, which is an instance of a JDBC Connection implementation. The connection variable is initialized in the Database class constructor:
public Database(String url, String user, String pass)
throws java.sql.SQLException {
connection = DriverManager.getConnection(url, user, pass);
}
By getting an instantiated Connection object, the applet is ready to access whatever database it needs.
Tip: |
|
The applet uses the getCount() method to calculate how many page hits this particular access to the Web page represents. That seemingly benign query actually represents several steps:
- Create a Statement object.
- Formulate and execute the SELECT query.
- Process the result.
- Increment the hit count.
- Format and execute an UPDATE or INSERT statement.
- Close the Statement and Connection objects.
The Statement is created through the JDBC call:
java.sql.Statement statement = connection.createStatement();
You want the number of hits for this page from the t_counter table:
sql = "SELECT counter_num FROM t_counter " +
"WHERE counter_file = '" + page + "'";
result_set = statement.executeQuery(sql);
The result_set variable now holds the results of the query. For queries that return multiple rows, an application loops through the next() method in the result set until no more rows exist. This query should only return one row with one column, unless the page has never been hit. If the page has never been hit, the query will not find any rows and the count variable should be set to 0:
if( !result_set.next() ) count = 0;
Otherwise, you need to retrieve that row into the count variable as an integer:
else count = result_set.getInt(1);
After incrementing the count to reflect this new hit, close out the Statement object and get a new one to prepare for the UPDATE:
count++;
statement.close();
statement = connection.create Statement();
If this is the first time the page is being hit, the applet needs to INSERT a new row into the database. Otherwise, it should UPDATE the existing row:
if( count == 1 ) {
sql = "INSERT INTO t_counter " +
"(counter_file, counter_num) " +
"VALUES ('" + file + "', " + count + ")";
}
else {
sql = "UPDATE t_counter " +
"SET counter_num = " + count + " " +
"WHERE counter_file = '" + file + "'";
}
statement.executeUpdate(sql);
The method then cleans up and returns the hit count.
Listing 15.1 puts the whole applet together.
Listing 15.1. The Counter applet.
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.awt.Graphics;
public class Counter extends java.applet.Applet {
Database db;
String count;
public void init() {
String driver = getParameter("driver");
String url = getParameter("url");
String user = getParameter("user");
String pass = getParameter("password");
String page = getParameter("page");
try {
Class.forName(driver).newInstance();
db = new Database(url, user, pass);
count = db.getCount(page);
}
catch( java.sql.SQLException e ) {
e.printStackTrace();
count = "Database exception";
}
catch( Exception e ) {
e.printStackTrace();
count = "Unable to load driver";
}
}
public void paint(Graphics g) {
g.setFont(new java.awt.Font(getParameter("font"),
&nbs p; java.awt.Font.BOLD, 14));
g.drawString(count, 5, 15);
}
}
class Database {
private Connection connection;
public Database(String url, String user, String pass)
throws java.sql.SQLException {
connection = DriverManager.getConnection(url, user, pass);
}
public String getCount(String page) {
int count = 0;
try {
java.sql.Statement statement =
connection.createStatement();
java.sql.ResultSet result_set;
String sql;
sql = "SELECT counter_num FROM t_counter " +
"WHERE counter_file = '" +
page + "'";
result_set = statement.executeQuery(sql);
if( !result_set.next() ) count = 0;
else count = result_set.getInt(1);
count++;
statement.close();
statement = connection.createStatement();
if( count == 1 ) {
sql = "INSERT INTO t_counter " +
"(counter_file, counter_num) " +
"VALUES ('" + page + "', " +count+ ")";
} else {
sql = "UPDATE t_counter " +
"SET counter_num = " + count + " " +
"WHERE counter_file = '" + page + "'";
}
statement.executeUpdate(sql);
statement.close();
connection.close();
}
catch( java.sql.SQLException e ) {
e.printStackTrace();
}
return ("" + count);
}
}
Note: |
|
Result Sets and the Meta-data Interfaces
In simple applications such as the counter applet, there is no
need to perform any tricks with the results from a query-the data
is simply retrieved sequentially and processed. More commonly,
however, an application will need to process the data in a more
complex fashion. For example, a set of classes might want to deal
with data on a more abstract level than the Database
class from the counter example. Instead, such classes might not
know exactly what data is being retrieved. They can query the
meta-data interfaces to process intelligently such data that they
would otherwise not know. Listing 15.2 shows a generic database
view class that is populated with database objects based on a
result set.
Listing 15.2. A generic database view class.
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.Hashtable;
import java.util.Vector;
public class View {
private Vector objects;
public void populate(ResultSet result_set, String cl) {
ResultSetMetaData meta_data;
int i, maxi;
try {
objects = new Vector();
meta_data = result_set.getMetaData();
maxi = meta_data.getColumnCount();
while( result_set.next() ) {
Hashtable row = new Hashtable();
DataObject obj;
for(i=1; i<=maxi; i++) {
String key;
Object value;
int t;
key = meta_data.getColumnLabel(i);
t = meta_data.getColumnType(i);
value = result_set.getObject(i, t);
row.put(key, value);
}
obj = (DataObject)Class.forName(cl);
obj.restore(row);
objects.addElement(obj);
}
}
catch ( java.sql.SQLException e ) {
e.printStackTrace();
objects = new Vector();
return;
}
}
}
In the View class, reference is made to a DataObject class that implements a restore(java.util.Hashtable) method not listed.
Because many applications will use this generic class, the class knows nothing about the queries it is executing. Instead, it takes any random result set and assumes that each row corresponds to an instance of the class named by the second parameter to populate().
To get the information it needs for performing the data retrievals, the populate() method first obtains the meta-data object for this result set. This method is specifically interested in knowing how many columns, as well as the names of the columns, are in the result set.
To store the columns in a Hashtable object that the DataObject object can use for restoring itself, all data must be in the form of objects. Thus, for each column in the result set, the DataObject finds its data type from the meta-data and retrieves the column as an object. The final step is to store it in the Hashtable.
Other JDBC Functionality
The JDBC provides functionality beyond the commonly used methods already discussed in terms of the following features:
- Transaction management
- Cursor support
- Stored procedure support
- Multiple result set processing
Transaction Management
JDBC implementations should default automatically to committing transactions unless the application otherwise requests that transactions require an explicit commitment. An application may toggle the automatic commit of the JDBC implementation it is using through the Connection.setAutoCommit() method. An example follows:
connection.setAutoCommit(false);
Of course, by not setting the AutoCommit attribute or by setting it to true, the JDBC implementation will make certain that the DBMS commits after each statement you send to the database. When set to false, however, the JDBC implementation requires specific commits from the application before a transaction is committed to the database. A series of statements executed as a single transaction would look like this:
public void add_comment(String comment) {
try {
Statement s;
ResultSet r;
int comment_id;
connection.setAutoCommit(false);
s = connection.createStatement();
r = s.executeQuery("SELECT next_id " +
"FROM t_id " +
"WHERE id_name = 'comment_id'");
if( !r.next() ) {
throw new SQLException("No comment id exists " +
&nbs p; "in t_id table.");
}
comment_id = r.getInt(1) + 1;
s.close();
s = connection.createStatement();
s.executeUpdate("UPDATE t_id " +
"SET comment_id = "
+ comment_id + " " +
"WHERE next_id = 'comment_id'");
s.close();
s = connection.createStatement();
s.executeUpdate("INSERT INTO t_comment " +
"(com ment_id, comment_text) " +
"VALUES(" + comment_id + ", '" +
comment + "')");
connection.commit();
}
catch( SQLException e ) {
e.printStackTrace();
try {
connection.rollback();
} catch( SQLException e2 ) System.exit(-1);
}
}
This method adds a comment to a comment table for some applications. To insert the new comment, the method needs to generate a new comment_id and then update the table for generating IDs so that the next one will be one greater than this one. Once the program has an ID for this comment, it then inserts the comment into the database and commits the entire transaction. If an error occurs at any time, the entire transaction is rolled back.
JDBC currently has no support for a two-phase commit. Applications written against distributed databases require extra support to allow for a two-phase commit.
Cursor Support
JDBC provides limited cursor support. It enables an application to associate a cursor with a result set through the ResultSet.getCursorName() method. The application can then use the cursor name to perform positioned UPDATE or DELETE statements.
Stored Procedures
Stored procedures are precompiled SQL statements stored in the database that enable faster execution of SQL. JDBC supports stored procedures through the CallableStatement class. In the counter applet, you could have used a stored procedure to update the page hit count in the following way:
CallableStatement s = connection.prepareCall(
"{call sp_upd_hit_count[?, ?]}");
s.setStringParameter(1, "file");
s.setIntParameter(2, count);
s.executeUpdate();
Multiple Result Sets
In some cases, especially with stored procedures, an application can find a statement by returning multiple result sets. JDBC handles this through the method Statement.getMoreResults(). Although result sets are left to be processed, this method returns true. The application can then obtain the next ResultSet object by calling Statement.getResultSet(). Processing multiple result sets simply involves looping through as long as Statement.getMoreResults() returns a value of true.
Building a JDBC Implementation
Building a JDBC implementation requires a lot more in-depth knowledge of both your DBMS and the JDBC specification than does simply coding to it. Most people will never encounter the need to roll their own implementation because database vendors logically want to make them available for their product. Understanding the inner workings of JDBC can help advance your application programming, however.
JDBC is a low-level interface that provides direct SQL-level access to the database. Most business applications and class libraries abstract from that SQL-level access to provide such features as object persistence and business-aware database access. A narrow example of such an abstraction is the Database class from the counter example.
The ideal object method of accomplishing these goals is to reuse existing JDBC implementations for the DBMS in question and to add custom interfaces on top of those implementations. If the DBMS is an oddball DBMS, or perhaps if you are concerned about the available implementations that exist, writing one from scratch makes sense.
Implementing the Interfaces
The first concern of any JDBC implementation is how it will talk to the database. Figure 15.3 illustrates the architecture of three possible JDBC implementations. Depending on the design goals in question, one of these methods will suit any JDBC implementation:
Figure 15.3 : Possible JDBC implementation architectures.
- A native C library
- A socket interface
- Extending a vendor JDBC implementation
Of course, extending a vendor JDBC implementation is not really the same as building a JDBC implementation. Because a key to any object-oriented project is reusing code instead of building from scratch it is listed here.
With all three architectures, the application is apparently isolated from the actual communication mechanism. In truth, however, the native C library method places severe restrictions on any application using a JDBC implementation built on top of it. Because it uses native calls, it is naturally not portable across operating systems. In addition, due to virtual machine restrictions on most browsers, native calls are either severely limited or fully restricted.
To use one of these mechanisms for database communication, you need to construct the four basic interfaces: java.sql.Driver, java.sql.Connection, java.sql.Statement, and java.sql.ResultSet. These interfaces will provide minimum functionality so that you can test against simple queries and updates. Once these interfaces are functional, the implementation needs the meta-data interfaces as well as the Statement subclasses to be complete and JDBC-compliant.
Extending JDBC
Nothing requires an application to use the JDBC interface to access a database. In fact, before JDBC, developers programmed to Java classes written specifically to go against several major database engines. JDBC isolates the database access behind a single interface. This isolation provided developers with the ability to write database access in Java without having to know which database engine their application is actually hitting. With a single prevalent database API, finding people with experience programming against it proves much simpler than finding people to program against a proprietary API. JDBC is, however, a low-level specification that requires developers to write both SQL code as well as Java code.
Both examples in this chapter demonstrate two different ways in which you can extend JDBC. In the counter applet, a database class was created as a wrapper around the JDBC implementation. The applet itself was divided into a representational portion, the Counter class, and a functional portion, the Database class. If you make changes to the visual representation, such as making the hit count appear through an odometer graphic, you won't have to make changes to the functional logic because it is isolated in a separate class. In fact, if the applet were more complex, requiring multiple developers, all the SQL would still be isolated in a class specifically interested in the functional behavior of the application. This reduces the number of people who need to write SQL code.
The View class example was a more abstract way of extending JDBC. The View class assumes that rows in result sets translate into business objects. In an application using this class, View objects are created to make JDBC calls and populate the applications with meaningful objects.
Another manner in which you can extend JDBC is to take advantage of database-specific features. Although it is prudent to question the need to make use of any proprietary features of a given DBMS, it is equally important that you do not ignore the extra power a specific DBMS gives you. It is, after all, very rare that an application actually needs to switch database engines.
Designing a Database Application
Knowing the JDBC API and coding cute applets is naturally just the start to database programming in Java. To harness the advantages of Java, application designers need to be able to address the design issues Java raises. The entire Java paradigm empowers developers to write database applications and applets using architectures that before were either very complex or simply not supported by other tools. Two such buzzwords that have been flying around the client/server world for a while are distributed objects and three-tier client/server.
Security Issues
Before going off the edge and into the deep end, Java does put some restrictions on applets for security reasons that can appear to be particularly limiting to the database developer. The following two particular applet restrictions affect database programmers:
- Limited access to native libraries
- Limited network access
The native call limitation affects programmers who need to use some sort of C- or operating system-level library to design an applet. This is especially troublesome to applet writers who take advantage of a database-specific feature not supported outside of native calls.
To veteran client/server developers, however, the most troubling idea is likely that your Web server must be on the same machine to which your applet connects for database access. Specifically, most Java virtual machines restrict applets from connecting to any machine except the host that served the applet. The applet cannot connect directly to any local or third-machine databases. As limiting as this particular restriction seems, a three-tier architecture provides a liberating solution.
Constructing a Three-tier Application
Two-tier applications tend to push a lot of processing onto the client machines. This architecture poses several problems:
- Client-side resource requirements balloon with the extra processing needs. It is not uncommon to find business applications requiring Pentiums with 32M of RAM.
- User interface and business processing tend to get rolled together, especially with the rapid application development tools on the market. With the user interface so closely tied to business processing, changes to one end up having a direct impact on the other, making maintenance a headache.
- With all this redundant processing occurring on many client machines rather than in a central location, new applications are forced to reinvent the wheel when dealing with the same business processing.
With the guaranteed execution environment of the Java virtual machine and an easy-to-use Internet socket interface, Java is actually well suited to implementing three-tier systems. A three-tier application is one in which a third application layer exists between the client and server layers of traditional two-tier client/server development. This middle layer has a wide variety of uses depending on the application.
In the three-tier architecture, the middle layer separates business processing from the visual representation of data. This layer, called the application server, is responsible for knowing how to find and manipulate business data. The client evolves into a much leaner application, responsible only for retrieving information from the application server and displaying it on the screen.
In addition to removing a huge processing burden from client machines, this application server can be used to consolidate enterprise-wide business rules.
Where business rules had to be rewritten for each two-tier application thrust on the desktop, application servers process business rules in a single place for multiple applications to use. When the business rules change, a change to the application server takes care of that change for all the applications being run by the business.
Of specific interest to Java developers is the ability to hide any knowledge of the database server from the client. Because Internet clients view the applet or application as interfacing with a single application server, you can use that application server to determine such issues as where the data really exists. Additionally, this back-end independence enables applications to scale much easier across CPUs. Figure 15.4 shows a three-tier architecture.
Figure 15.4 : A three-tier Java applet or application.
A Three-tier Bug Tracking System
The application server forms the core of a three-tier architecture. In it, the business rules are defined and processed. Implementing the counter using a three-tier architecture would naturally be overkill. Instead, the ideal application for a three-tier design is one in which some manipulation of data occurs or where the data can be viewed in multiple fashions (or even better, by multiple applications). The first step in building an application server would thus be to identify the data processing needs of the application.
Implementing a Three-tier Application with Java
Figure 15.5 shows a bug tracking application implemented as a three-tier Java application.
Figure 15.5 : A bug tracking system using a three-tier architecture.
The only processing done on the client is the painting of GUI widgets and user data entry. On the other end, the database server runs on a machine otherwise inaccessible to the client applet. The application server bridges this gap by finding desired data, mapping it from its relational state into objects, and performing operations on those objects.
With any three-tier architecture, the greatest programming challenge is getting the three layers to communicate with one another. JDBC or some similar set of database access classes should handle the application server-to-database server communication in a manner transparent to the application developer. The client-to-application server solution is still left wanting.
The two best methods for providing such communication in Java are Java sockets or distributed objects.
Compared to sockets from other languages, Java sockets are quite simple to use. Sockets, however, force the developer to make esoteric decisions about exactly what is being communicated between client and application server because method calls and object passing are better handled by the distributed objects solution. A socket solution generally best fits an application when the scope of communication is limited and well-defined. The bug tracking system would be best implemented in this manner.
Distributed objects provide the more elegant solution. From the developer's point of view, the application server objects appear to be part of the same application as the client, just residing on a central server and available to other applications simultaneously. The developer handles communication simply through method calls.
What Is a Thread?
A thread executes a series of instructions. Every line of code that is executed is done so by a thread. Some threads can run for the entire life of the applet, while others are alive for only a few milliseconds. A thread also creates a new thread or kills an existing one. Threads run in methods or constructors. The methods and constructors themselves are lifeless. The threads go into the methods and follow their instructions. Methods and constructors reside in the computer's memory. Figure 16.1 shows threads, constructors, and methods in a typical applet.
Figure 16.1 : Two threads running through three classes.
The applet methods start(), paint(), and so on are all called by underlying threads. These threads are created by the Web browser. When there is a mouse click or a repaint() has been called, the underlying thread calls the appropriate thread in the applet. While the threads are running in the applet methods, they cannot do anything else. If the applet holds these threads too long, it locks up the browser. If an applet needs to do something that will take some time in one of its methods, it should start a new thread. Some specific uses for threads are listed below:
- Long initiations. Threads are used in applets that may take a while to initialize. The applet may need to do something like wait for an image to be loaded. A thread is used so that the system thread can handle other events.
- Repetitive or timed tasks. Threads are used to do tasks that happen repetitively. A common example of this is found in animations. Every few milliseconds a new frame is shown. A thread displays a frame, sleeps for a while, and then repeats the process.
- Asynchronous events. Threads are used to handle events. An example of this is a mouse click. If the user clicks the mouse, a new thread is created to render a new frame in an image.
- Multiple tasks. Threads are used to do more than one thing at once. One thread controls an animation, while another does a computation.
The Thread Class
The class java.lang.Thread is used to create and control threads. To create a thread, a new instance of this class must be created. However, the thread does not start running right away. Thread.start() must be called to actually make the thread run. When Thread.start() is called, the thread begins executing in the run() method of the target class. A new Thread class always starts running the public void run() method of a class. There are two ways to create a thread:
- Extend the Thread class. With this technique the new class inherits from the class Thread. The thread can start running in the class's run method.
- Implement the Runnable interface. This technique is probably more common than extending the Thread class. It is not necessary to define a new class to run the thread. If a thread is to start running in the applet, it must use the Runnable interface. The applet cannot inherit from both the Thread and Applet classes. An applet with the Runnable interface must have a run() method for the thread to start.
There isn't much difference between the two approaches. Both extending the Thread class and implementing the Runnable interface have the same functionality. The interface approach must be used if the thread is to actually start in the applet class. But if the thread is going to be running in its own class, it may be more convenient to extend the Thread class. Examples of both approaches are in this chapter.
The Thread class has seven constructors. All of them create a new thread. The thread does not start running until Thread.start() is called. When Thread.start() is called, the new thread starts running in the run() method of an object. The constructors are the following:
Thread()
Thread(Runnable)
Thread(ThreadGroup)
Thread(String)
Thread(ThreadGroup,String)
Thread(Runnable,String)
Thread(ThreadGroup,Runnable,String)
The constructors can use three possible parameters:
- String The name of the new thread is the parameter String. A thread can get its name by calling Thread.getName().
- ThreadGroup The new thread will belong to the group specified by the parameter ThreadGroup. A ThreadGroup can be used to organize a thread.
- Runnable The Runnable parameter is an object that has implemented the Runnable interface. The thread will start executing in the run() method of the Runnable parameter when Thread.start() has been called.
There are many methods in the Thread class. Some of them, such as destroy(), don't seem to have been implemented yet, and may never be. Some of the methods that control the thread execution are the following:
- start() This method starts the thread. It starts executing in the run() method of its Runnable target that was set when the constructor was called. This method can be called only once.
- suspend() This method suspends the execution of the thread. It remains suspended until resume() is called.
- resume() This method resumes the execution of a suspended thread. It has no effect on a thread that is not suspended.
- stop() This method stops and kills a running thread. Currently, the thread does not stop unless it is running. If it is suspended, it does not die until it starts running again. However, this may be fixed someday.
- sleep(int m)/sleep(int m,int n) The thread sleeps for m milliseconds, plus n nanoseconds.
Simple Thread Examples
Listing 16.1 shows how to start, stop, suspend, and resume threads. It uses the Runnable interface. Threads like this are useful for things like controlling animation sequences or repeatedly playing audio samples. This example uses a thread that counts and prints a string every second. The thread starts when the applet is initialized. It continues to run until the user leaves the page. If the user returns to the page (and all is well), the thread continues from where it left off. This allows applets to retain their states while the user is away.
Listing 16.1. Thread examples.
import java.lang.Thread;
import java.applet.Applet;
public class InfiniteThreadExample extends Applet implements Runnable
{
Thread myThread;
public void init()
{
System.out.println("in init() -- starting thread.");
myThread= new Thread(this);
myThread.start();
}
public void start()
{
System.out.println("in start() -- resuming thread.");
myThread.resume();
}
public void stop()
{
System.out.println("in stop() -- suspending thread.");
myThread.suspend();
}
public void destroy()
{
System.out.println("in destroy() -- stoping thread.");
myThread.resume();
myThread.stop();
}
public void run()
{
int i=0;
for(;;)
{
i++;
System.out.println("At " + i + " and counting!");
try {myThread.sleep(1000);}
catch (InterruptedException e ) {}
}
}
}
SimpleThreadExample Output
The output of InfiniteThreadExample is shown here. The applet ran for nine seconds until it was stopped.
in init() -- starting thread.
At 1 and counting!
in start() -- resuming thread.
At 2 and counting!
At 3 and counting!
At 4 and counting!
At 5 and counting!
At 6 and counting!
At 7 and counting!
At 8 and counting!
At 9 and counting!
in stop() -- suspending thread.
in destroy() -- stoping thread.
The applet has only five methods:
- public void init() The thread is initialized and is started in this method. In this example, the constructor Thread takes the argument this. When the Thread.start() method is called, the thread looks for a public void run() method in the this object.
- public void start() When this method is called by the system, the thread resumes execution. If the thread is already running, this method has no effect.
- public void stop() This method suspends the thread.
- public void destroy() This method stops the thread. Thread.stop() stops and kills the thread. However, it only kills a thread that is running, so Thread.resume() is called beforehand.
- public void run() This is where the thread actually starts running. This example has an infinite loop that prints a string and then sleeps for a second. Long running threads should sleep every once in a while to give other threads a chance to run. If not, the system may not even get a chance to paint the applet.
When Are the Methods in InfiniteThreadExample Called?
Unfortunately, its not always possible to know exactly when or if the methods are called. It can vary from browser to browser, or even by how the user has the browser configured. Netscape 3.0 calls init() and then calls start() the first time the applet is loaded. If the user leaves the page with the applet, stop() is called. Returning to the applet calls start(), but it is possible that init() may be the first called. It depends on whether or not the applet is still residing in memory. If it is, then only start() is called; otherwise, both init() and start() are called again.
The method destroy() is called before the applet is removed from memory. All threads should be destroyed at this time so that its resources can be used by other applets. The browsers can only handle so many threads. If the user visits too many applets that don't clean up after themselves, the browser may crash. Generally, threads should be suspended when the user leaves the page and killed when the applet is destroyed. It is possible to leave threads running while the user is visiting other pages, but the user may not appreciate it.
Listing 16.2 shows how to use threads to handle events. When an event that existing threads shouldn't take the time to handle happens in the applet, a new thread is spawned to handle that event. After the event is handled, the new thread quietly dies. Listing 16.2 uses threads to handle mouse clicks. Each thread draws a blue target, as you can see in Figure 16.2. Methods such as mouseDown() or mouseUp() are called by threads external to the applet. While the thread is running in the applet, no other mouse movements are detected. Keeping the external thread may not just affect the applet, but possibly the whole browser. Generally, these methods should be returned as soon as possible. If it is necessary to do a long computation or wait for something else, a new thread should be started. By starting a new thread, the external thread can return almost immediately.
Figure 16.2 : The applet in Listing 16.2 draws targets.
Listing 16.2. Handling an event with threads.
import java.applet.Applet;
import java.awt.*;
public class FiniteThreadExample extends Applet
{
Image offImage; /* off screen image */
Graphics offGraphics; /* Graphics for offImage */
public void init()
{
offImage=createImage(400,300);
offGraphics=offImage.getGraphics();
}
public void paint(Graphics g)
{
g.drawImage(offImage,0,0,null);
}
public void update(Graphics g)
{
paint(g);
}
public boolean mouseDown(Event e, int x, int y)
{
new DrawTarget(this,x,y,offGraphics);
return true;
}
}
class DrawTarget extends Thread
{
int xPos,yPos; /* position of the target */
Applet myMaster; /* who to repaint */
Graphics offGraphics; /* Graphics to draw on */
public DrawTarget(Applet a, int x, int y, Graphics g)
{
xPos=x; yPos=y;
myMaster=a;
offGraphics=g;
start();
}
public void run()
{
int i; /* i is direction the circles are moving */
int r; /* r is the radius of the current circle */
offGraphics.setColor(Color.white);
offGraphics.setXORMode(Color.blue);
for(r=0,i=10;i>-20;i-=20) /* i=(10,-10) */
for(r+=i;(r<90)&&(r>0);r+=i) /* r=(10,20...80,80,70...10) */
{
offGraphics.fillOval(xPos-r,yPos-r,2*r,2*r);
myMaster.repaint();
try {sleep(200);}
catch (InterruptedException e) {}
}
}
}
The class FiniteThreadExample is used to paint the applet, to catch the mouse clicks, but not to start the threads. The applet uses a class that extends the Thread class to start new threads. The class FiniteThreadExample has four methods shown below that sets up things, handles the painting, and catches the mouse clicks:
- public void init() This method creates an image and gets a Graphics context for that image.
- public void paint(Graphics) This method paints the Image offImage on the screen.
- public void update(Graphics) This method isn't really necessary. It overrides update(Graphics) in java.awt.Component, and is used to reduce flickering.
- public boolean mouseDown(Event, int, int) This method is called when there is a mouse click in the applet. It creates a new instance of the class DrawTarget. DrawTarget is defined in the next class.
DrawTarget is where the threads are created to draw the targets. DrawTarget inherits properties from java.lang.Thread and has a single constructor and method, which are listed below:
- public DrawTarget(Applet a, int x, int y, Graphics g) The constructor copies the parameters to instance variables and starts the thread. Applet is needed so the run method can tell the applet to repaint. The integers x and y are the coordinates of target. Graphics is the graphics context on which the targets are drawn. The thread is started by simply calling start().
- public void run() This method is where the thread starts and draws the targets. It is called sometime after start() is called in the constructor. The method first sets offGraphics to XOR-Mode. In this mode, if something is drawn on something previously drawn, it reverts back to its original color. Next, the thread enters the nested for loops. Each iteration draws a circle, asks the applet to repaint, and sleeps for 200ms. The radius of the circle is varied from 10 to 80, and then from 80 back to 10. The thread dies on its own after it exits the loops, so there is no need to call stop().
Problems with Multithreading
Listing 16.3 shows how data can be corrupted in a multithreaded environment. If more than one thread manipulates shared variables or objects at the same time, corruption may result. Instance variables are shared between threads. If one is modified by a thread, the change affects the other threads. Method variables are unique for each threads. Each Thread has its own copy. Listing 16.3 uses 2 classes, ProblemThreadExample and CorruptedDataExample. The class ProblemThreadExample extends the Applet class. It has two methods, which are listed after Listing 16.3.
Listing 16.3. How to get corrupted data.
import java.lang.*;
import java.applet.Applet;
public class ProblemThreadExample extends Applet
{
CorruptedDataExample CDE;
public void start()
{
int i;
CDE=new CorruptedDataExample();
for(i=0;i<20;i++) /* start 20 threads */
new Thread(CDE,new String("booThread"+i)).start();
}
public void stop()
{
CDE.stopThreads();
}
}
class CorruptedDataExample extends Object implements Runnable
{
int num=0; /* num will be corrupted */
public void run()
{
int i=0;
for(;;)
{
for(i=0;i<1000;i++)
{
num=num+10;
num=num-10;
}
try {Thread.sleep(10000);}
catch (InterruptedException e ) {}
System.out.println(Thread.currentThread().getName()+
" sees the number: " + num);
}
}
void stopThreads()
{
Thread tArray[];
int numThreads;
numThreads=Thread.activeCount();
tArray=new Thread[numThreads];
Thread.enumerate(tArray);
for(int i=0;i<numThreads;i++)
if(tArray[i].getName().startsWith("booThread"))
tArray[i].stop();
}
}
- public void start() This method gets a new instance of the CorruptedDataExample class and starts 20 threads. Each thread has an individual name that is from "booThread0" to "booThread19". The threads start running in CorruptedDataExample's run method.
- public void stop() This method calls stopThreads() in CorruptedDataExample. The class CorruptedDataExample does not guard against corruption of its instance variable num. The class has two methods, which are as follows:
- public void run() This method has an infinite loop that shows how data can be corrupted by multiple threads. Twenty threads are executed in this loop at the same time. The loop does the following:
- Adds 10 then -10 to num 1000 times
- Sleeps
- Prints a string that contains num
- The first step is the cause of the corruption. It serves no purpose other than illustrating how data is corrupted.
- void stopThreads() All of the booThreads are stopped in this method. A list of threads is fetched. All of the threads that have names that begin with booThread are stopped.
The following is the ProblemThreadExample output:
booThread0 sees the number: 0
booThread1 sees the number: 0
booThread2 sees the number: 0
booThread3 sees the number: 0
booThread4 sees the number: 10
booThread6 sees the number: 10
booThread7 sees the number: 10
booThread8 sees the number: 10
booThread9 sees the number: 10
booThread10 sees the number: 0
booThread11 sees the number: 0
booThread12 sees the number: 0
booThread13 sees the number: 0
booThread5 sees the number: 0
booThread14 sees the number: 0
booThread15 sees the number: 0
booThread16 sees the number: 0
booThread17 sees the number: 0
booThread18 sees the number: 0
booThread19 sees the number: 0
booThread0 sees the number: 0
booThread1 sees the number: 0
booThread3 sees the number: 0
booThread4 sees the number: 0
booThread6 sees the number: 0
booThread8 sees the number: 0
booThread9 sees the number: 0
booThread2 sees the number: 0
booThread7 sees the number: 0
booThread10 sees the number: 10
booThread11 sees the number: 0
booThread12 sees the number: -10
booThread13 sees the number: -10
booThread5 sees the number: -10
booThread14 sees the number: -10
booThread16 sees the number: -10
booThread17 sees the number: -10
booThread18 sees the number: -10
booThread19 sees the number: -10
What Goes Wrong?
The first step in the infinite loop would have no ill effect in a single-threaded environment. It simply adds and then subtracts 10 to a variable with the net result being no change. However, in a multithreaded environment, the operations can interfere with each other. You can see one scenario in the following steps in which two threads try to add 10 at the same time.
Step | Thread A | Thread B | num |
1. | Atmp num | 0 | |
2. | Atmp Atmp+10 | 0 | |
3. | Btmp num | 0 | |
4. | Btmp Btmp+10 | 0 | |
5. | num Btmp | 10 | |
. | . | . | . |
. | . | . | . |
. | . | . | . |
10. | num Atmp | 10 |
Two num=num+10; operations have been executed, but the value of num has only increased by 10. The problem here is that Thread A was interrupted in the middle of its operation before it could save its results. Threads A and B have added 10 to the same number.
This type of problem is somewhat rare, but should not be ignored. In the previous example, 10 is added and subtracted 1000 times in 20 threads, and the problem still did not occur that often. The bad thing about these types of bugs is that they can be extremely difficult to find. Imagine that num is the index of an array. The problem may not show up until long after num has been corrupted. Generally, these bugs are not reproducible, so they are hard to catch. In some ways the problem is amplified in Java because Java is expected to run on so many platforms. On some systems, num=num+10 may be an atomic operation (cannot be interrupted). In this case, everything works fine. A developer may create an applet on such a system thinking that everything is fine, but it may not work when someone from a different system views the applet. Data corruption can also be more common with other data types. Integer operations are simple compared to many others. Arrays or other data structures can take much longer to process so it is much more likely to be corrupted.
Thread Names and Current Threads
In Listing 16.3, the threads are given names. They are named booThread0 through booThread19 when they are created by the constructor Thread(Runnable,String). The names can be used to identify the threads. The thread names are printed along with num in the run() method of CorruptedDataExample. The Thread method current.Thread() is called to get a reference to the currently running thread.
The names are also used to stop the threads. A reference is needed for each thread so that stop() can be called to kill it. Thread.enumerate(Thread[]) gets a reference to every thread in this group. Some of these threads are the booThreads, but they may be others. The other threads should not be killed. Before each thread is killed it is checked to see if its name starts with booThread.
Java's synchronized
A way to prevent data from being corrupted by multiple threads is to prevent the interruption of critical regions. Critical regions are places like num=num+10 above, where only one thread should be running at once. Java's synchronized can be used to ensure that only one thread is in a critical region at once. When the thread enters a synchronized code block, it tries to get a lock on that region. While the thread is in the critical region, no other thread can enter the critical region. If a thread tries to enter and the code is already locked, the thread has to wait for the other thread to leave the critical region. Listing 16.3 can be fixed by synchronizing the access to num. The run() method in CorruptedDataExample can be modified to the following:
public void run()
{
int i=0;
int tmp; /*new*/
for(;;)
{
for(i=0;i<1000;i++)
{
synchronized (this) /*new*/
{
num=num+10;
num=num-10;
}
}
try {Thread.sleep(10000);}
catch (InterruptedException e ) {}
synchronized (this) /*new*/
{tmp=num;} /*new*/
System.out.println(Thread.currentThread().getName()+
" sees the number: " + tmp); /*new*/
}
}
The following lines make up a protected critical region:
synchronized (this)
{
num=num+10;
num=num-10;
}
synchronized (this) ensures that only one thread can be in the following code block. The argument this tells the thread to use the lock for this this object.
The variable num is also referenced when the string is printed. The new code is as follows:
synchronized (this) /*new*/
{tmp=num;} /*new*/
System.out.println(currentThread().getName()+
" sees the number: " + tmp); /*new*/
A critical region is used to copy num to temporary storage. The string is then printed using the temporary storage. It would have been possible to synchronize the print line directly, but it would cut performance because the print line does many other things that have nothing to do with referencing num. All the threads waiting to enter the critical region will needlessly wait longer while the print line is executed. Generally, the synchronized blocks should be as small as possible while still protecting the critical region.
You may also think that the other variables in the run method, i and tmp, also need to be synchronized, but it's not necessary. Both i and tmp are method variables so each running thread has its own private copy. There are 20 i's and tmp's but there is only one num.
Synchronizing Threads
Listing 16.4 can be seen in Figure 16.3. This example shows how synchronized can be used to control critical regions. There are two synchronized methods: drawRoundTarget() and drawSquareTarget(). If a thread is in a synchronized method, no other thread can be in any synchronized method that uses the same lock. This example draws only one square or circle at a time. The seven methods of the SynchronizedThreadExample applet are shown after Listing 16.4.
Figure 16.3 : Listing 16.4 draws a square.
Listing 16.4. Using synchronized.
import java.applet.Applet;
import java.awt.*;
public class SynchronizedThreadExample extends Applet
implements Runnable
{
Image offImage; /* off screen image */
Graphics offGraphics; /* Graphics for offImage */
Thread Thr1, Thr2; /* threads */
public void start()
{
offImage=createImage(400,300);
offGraphics=offImage.getGraphics();
offGraphics.setColor(Color.white);
offGraphics.setXORMode(Color.blue);
Thr1=new Thread(this); Thr1.start();
Thr2=new Thread(this); Thr2.start();
}
public void stop()
{
Thr1.stop();
Thr2.stop();
}
public void paint(Graphics g)
{
g.drawImage(offImage,0,0,null);
}
public void update(Graphics g)
{
paint(g);
}
public void run()
{
for(;;)
{
drawRoundTarget();
drawSquareTarget();
}
}
synchronized void drawRoundTarget()
{
for(int r=0,i=10;i>-20;i-=20) /* i=(10,-10) */
for(r+=i;(r<90)&&(r>0);r+=i) /* r=(10,20...80,80,70...10) */
{
offGraphics.fillOval(200-r,150-r,2*r,2*r);
repaint();
try {Thread.currentThread().sleep(200);}
catch (InterruptedException e) {}
}
}
synchronized void drawSquareTarget()
{
int i,r;
for(r=0,i=10;i>-20;i-=20) /* i=(10,-10) */
for(r+=i;(r<90)&&(r>0);r+=i) /* r=(10,20...80,80,70...10) */
{
offGraphics.fillRect (200-r,150-r,2*r,2*r);
repaint();
try {Thread.currentThread().sleep(250);}
catch (InterruptedException e) {}
}
}
}
The methods of the SynchronizedThreadExample applet are as follows:
- public void start()/stop() The threads are started and stopped in these methods.
- public void paint/update(Graphics) These methods paint the applet.
- public void run() Both threads start executing in this method. It is an infinite loop that draws round and then square targets.
- synchronized void drawRoundTarget() This method is synchronized. Only one thread can be inside drawing circles.
- synchronized void drawSquareTarget() This method is like drawRoundTarget, but draws squares instead of circles.
Multiple Locks
What if two locks are needed?
The current applet only allows one target to be drawn at a time, be it round or square. Suppose that you want the applet to draw a round and a square target at the same time; you would need two locks for two independent critical regions. The problem is that each object has only one lock. Creating separate classes for drawRoundTarget() and drawSquareTarget() could solve the problem, but it may not be convenient. A better way is to create new objects, and to use their locks to control the methods. This is done by modifying Listing 16.4 as follows:
.
.
.
Object RoundSync,SquareSync; /*new*/
public void start()
{
. .
. .
. .
RoundSync=new Object(); /*new*/
SquareSync=new Object(); /*new*/
Thr1=new Thread(this); Thr1.start();
Thr2=new Thread(this); Thr2.start();
}
void drawRoundTarget()
{
synchronized (RoundSync) /*new*/
{
for(int r=0,i=10;i>-20;i-=20)
. .
. .
. .
}
}
void drawSquareTarget()
{
synchronized (SquareSync) /*new*/
{
for(r=0,i=10;i>-20;i-=20)
. .
. .
. .
}
}
}
Two new Objects are created: RoundSync and SquareSync. The Objects don't actually do anything themselves, but their locks are used when drawing the targets. The instances of Object are obtained in the start method. synchronized (RoundSync) is put in front of the for loops in the drawRoundTarget() method and drawSquareTarget() is modified similarly. When a thread tries to execute the body of the target drawing methods, it first has to get a lock. drawRoundTarget() gets a lock from the object RoundSync and drawSquareTarget() gets a lock from SquareSync(). After these modifications have been made, the drawRoundTarget() and drawSquareTargets() methods do not block each other. The applet draws round and square targets at the same time. But it is not able to draw two round targets or two square targets at the same time. Figure 16.4 shows the results of the modifications.
Figure 16.4 : A square and round target being drawn at the same time.
The Dining Philosophers Problem
The Dining Philosophers problem is a classic concurrent programming problem. In the problem, a philosopher only does two things, think and eat. A philosopher thinks for a while, and then gets hungry. Then it eats until it gets full, and then starts thinking again. Each philosopher is so involved in its own thinking or eating that it's oblivious to anything else. The problem has five philosophers that are sitting at a table. Between neighboring philosophers there is a single chop stick. A philosopher must have both the stick on its right and the stick on its left in order for it to eat. Obviously, no two neighboring philosophers can be eating at the same time. The philosophers cannot disturb each other. If a philosopher wants to start eating and its neighbor has one of the sticks, the philosopher must wait until the stick becomes available. To solve the problem, an algorithm must be designed to let all of the philosophers eat.
A simple algorithm for the philosophers could be:
Think Get right chopstick
Get left chopstick
Eat
Drop left chopstick
Drop right chopstick
Repeat
There is one very serious problem with this algorithm. Suppose that each philosopher picks up the right chopstick at the same time. When they try to get the left stick, it won't be there. The neighbor to the left has it. All of the philosophers starve to death with a chopstick in one hand and food on the table. This is known as a deadlock.
Deadlocks
Deadlocks are always a danger in multithreaded environments. A deadlock has occurred because
- Each thread needed exclusive use of the chopsticks.
- One thread is not allowed to take a chopstick from its neighbor.
- Each thread is waiting while holding a chopstick that another thread is waiting for.
All deadlocks in any problem have the same reasons for deadlocking. Instead of waiting for chopsticks, they are waiting for some other resource or resources. If only one of the conditions can be broken, a deadlock will not occur.
The first condition is usually hard to break. In the previous algorithm this could done by allowing philosophers to share a chopstick, but that isn't really possible. Sometimes threads need exclusive use of a resource. Things like the instance of a class or a socket may require that only one thread may use it at once.
The second condition can sometimes be used to avoid deadlocks. If a philosopher was allowed to take a chopstick from its neighbor, there would not be a deadlock. However, if the philosophers keep taking sticks from each other, they may never get a chance to take a bite. Some problems can be solved by allowing resource stealing. It depends on the resource and the problem.
If the deadlock cannot be avoided with the other conditions, it should be avoided by breaking the third condition. The philosophers can avoid a deadlock if there is a special chopstick that they aren't allowed to hold while they are waiting for the second chopstick. They are allowed to eat with the special stick, but they can't just hold it. An algorithm should not be too strict, otherwise the resources may be underused. For example, an algorithm could be made that only allows one philosopher to eat at once. Obviously, there would be no deadlocks, but a philosopher may have to wait longer before it can eat.
A Solution to the Dining Philosophers Problem
There are many solutions to the Dining Philosophers problem. One solution follows.
One of the chopsticks is marked as gold, while the rest are wood. No philosopher is allowed to hold the gold stick without eating. This prevents the philosophers from deadlocking. The philosophers picks up one chopstick then the other. If the gold stick is picked up first, it is put down and the other stick is picked up. It is possible that in the time between putting down the gold chopstick and picking up the other chopstick, all the other philosophers will have eaten and moved the gold chopstick all the way around the table, so when the philosopher picks up the other chopstick, it too is the gold chopstick. If this happens, the philosopher starts over. The solution is as follows:
Think
Pick up right chopstick
If right chopstick is gold
Drop right chopstick
Pick up left chopstick
If left chopstick is gold
Start over
Pick up right chopstick
Else
Pick up left chopstick
Eat
Switch chopsticks in hands
Drop right chopstick
Drop left chopstick
Repeat
The chopsticks are switched when the philosopher puts down the chopsticks. This allows the philosophers equal chances to eat. Otherwise, the philosopher to the left of the gold chopstick would be disadvantaged.
The philosophers may interfere with each other when they try to pick up a chopstick. A critical region is used to ensure that one philosopher can pick up or put down a chopstick. If one philosopher is picking up or putting down a stick, its neighbor is not allowed to touch the stick. What if a philosopher enters the critical section to get a chopstick, but the chopstick is not there? The philosopher must wait until the stick returns. This is done by a special wait in the critical section. The philosopher releases its lock and waits in the critical section. This allows the other philosopher to enter the critical section to return the chopstick. After the chopstick is returned, the waiting philosopher is woken up. After awakening, the philosopher reacquires the lock and continues executing. At any one time there can be only one philosopher running in the critical section, but it is OK if another philosopher is also sleeping in the critical section.
Java's wait() and notify()
Java has three wait() and two notify() methods that aid in synchronizing threads. The wait() methods cause the thread to pause in the critical region. While paused, the thread releases its lock. It must get the lock again before it starts executing again. The notify() methods wake up threads that have called wait(). Calling notify() when no wait() has been called has no effect. The methods shown below are in the Object class and can only be called in a synchronized block or method.
- public final void wait() This method causes the thread to wait forever until a notify() or notifyAll() is called. The thread releases its lock on the critical regions so that other threads may enter.
- public final void wait(long m) This method causes the thread to wait m milliseconds for a notify() or notifyAll() to be called. After the time is expired, the thread tries to resume execution. However, it must first reobtain the lock for the critical region. Another thread may have entered the critical section while the thread was waiting.
- public final void wait(long m, int n) This method is similar to the previous one except that it waits for m milliseconds plus n nanoseconds.
- public final void notify() This method wakes up a thread that has previously called wait(). The thread that was waiting has to get the lock before it can resume execution. It has to wait until the current thread leaves the critical region. Only one thread that has called wait() is woken up. It is not guaranteed that the first thread that called wait() is the first one woken up.
- public final void notifyAll() This method wakes up all the threads that have called wait(). Each waiting thread has to get the lock for the critical region before it resumes. There can still be only one thread running in the critical section at once.
Dining Philosophers Example
The Dining Philosophers applet uses four classes: the ones shown in Listings 16.5, 16.6, 16.7, and 16.8. The first class, DiningPhilosophers, extends the Applet class. The structure of this class is similar to the first example. Philosopher threads are created when the applet is initialized. They are suspended if the user leaves the page and resumed if the user returns. However, unlike the first example, no threads are created in this class. The Dining Philosophers example can be seen in Figure 16.5.
Figure 16.5 : The Dining Philosophers example.
Listing 16.5. The class DiningPhilosophers.
public class DiningPhilosophers extends Applet
{
final int numPhils=5;
Image offImage; /* off screen image */
Graphics offGraphics; /* Graphics for offImage */
Philosopher Phil[] = new Philosopher[numPhils];
Chopstick Stick[] = new Chopstick[numPhils];
ScenePainter painter;
public void init()
{
int i;
offImage=createImage(400,300);
offGraphics=offImage.getGraphics();
painter=new ScenePainter(offGraphics,this,numPhils);
for(i=0;i<numPhils;i++)
Stick[i]=new Chopstick (i==0 ? Chopstick.gold :
Chopstick.wood,
painter,i);
for(i=0;i<numPhils;i++)
Phil[i]= new Philosopher(Stick[i],Stick[(i+1)%numPhils],
painter,i);
}
public void start()
{
int i;
for(i=0;i<numPhils;i++)
Phil[i].resume();
}
public void stop()
{
int i;
for(i=0;i<numPhils;i++)
Phil[i].suspend();
}
public void destroy()
{
int i;
for(i=0;i<numPhils;i++)
{
Phil[i].resume();
Phil[i].stop();
}
}
public void paint(Graphics g)
{
g.drawImage(offImage,0,0,null);
}
public void update(Graphics Dijkstra)
{
paint(Dijkstra);
}
}
The class DiningPhilosophers has six methods and is similar to the InfiniteThreadExample.
- public void init() This method initializes the applet. It creates five instances of the classes Chopstick and Philosopher. One of the Chopstick classes is created as a gold chopstick, the rest are wood. Each Philosopher can reach two Chopsticks. On the right is Chopstick i, and on the left is Chopstick i+1 mod 5.
- public void start() This method resumes philosopher execution.
- public void stop() This method suspends philosopher execution.
- public void destroy() This method kills the philosophers.
- public void paint()/update() These two methods paint the state of the philosophers.
Each philosopher at the table is its own thread. The thread is created in the class Philosopher by extending Thread. The methods in the class control the philosopher's life of thinking and eating. Initially, the philosopher is thinking. After some time, the philosopher picks up the two chopsticks next to it and starts eating. It calls methods in the Chopstick class (see Listing 16.7) to get the chopsticks. The philosopher also paints its state by calling methods in the ScenePainter class (see Listing 16.8).
Listing 16.6. The class Philosopher.
class Philosopher extends Thread
{
final int ThinkTime=5000, EatTime=3000;
Chopstick rightStick,leftStick;
ScenePainter painter;
int rightHand,leftHand;
int myNum;
public Philosopher(Chopstick right, Chopstick left,
ScenePainter p, int n)
{
painter=p;
myNum=n;
rightStick=right;
leftStick=left;
start();
}
public void run()
{
for(;;)
{
think();
PickUpSticks();
eat();
PutDownSticks();
}
}
void think()
{
painter.drawThink(myNum);
try {sleep((int)(ThinkTime*Math.random()));}
catch (InterruptedException e) {}
painter.drawThink(myNum);
}
void PickUpSticks()
{
for(boolean gotem=false;!gotem;)
{
painter.drawFirstGrab(myNum);
rightHand=rightStick.grabStick();
painter.drawSecondGrab(myNum);
if(rightHand==Chopstick.gold)
{
painter.drawGoldGrab(myNum);
rightStick.dropStick(rightHand);
leftHand=leftStick.grabStick();
if(leftHand==Chopstick.gold)
{
leftStick.dropStick(leftHand);
continue; /* gold stick went around table */
}
rightHand=rightStick.grabStick();
painter.drawGoldGrab(myNum);
}
else leftHand=leftStick.grabStick();
painter.drawSecondGrab(myNum);
painter.drawFirstGrab(myNum);
gotem=true;
}
}
void eat()
{
painter.drawEat(myNum);
try {sleep((int)(EatTime*Math.random()));}
catch (InterruptedException e) {}
painter.drawEat(myNum);
}
void PutDownSticks()
{/* swap sticks and put them down */
rightStick.dropStick(leftHand);
leftStick.dropStick(rightHand);
}
}
The class Philosopher is used to start the threads for the Dining Philosophers applet. The class has the following five methods:
- public void run() This method defines the philosophers actions. Each philosopher thinks, waits to pick up its chopsticks, eats, returns the chopsticks, and repeats the cycle.
- void think() This method is where the philosopher thinks. The thinking image is drawn and the philosopher sleeps for a random amount of time.
- void PickUpSticks() In this method, the philosopher picks up the chopsticks in a way that is fair and avoids deadlocks.
- void eat() This method is where the philosopher eats. The eating image is drawn and the philosopher sleeps for a random amount of time.
- void PutDownSticks() In this method, the philosopher returns the sticks. The chopsticks are switched when they are put down so that the gold stick is not always in the same place.
All of the synchronization is done in the Chopstick class. There is one instance of this class for each chopstick on the table. The class is used by the philosophers when they want to pick up or return a chopstick. The three states of the chopstick are represented by the variable stickHolder: noStick means the chopstick is gone, wood means this is the wooden stick, and gold means this is the golden stick. stickHolder is an instance variable. More than one philosopher may be trying to get/drop it at once, so there is a danger of data corruption. The methods of this class are synchronized to ensure that stickHolder does not get corrupted (see Listing 16.7).
Listing 16.7. The class Chopstick.
class Chopstick extends Object
{
final static int noStick=0;
final static int wood=1;
final static int gold=2;
ScenePainter painter;
int stickHolder;
int myNum;
public Chopstick(int stick, ScenePainter p, int n)
{
painter=p;
myNum=n;
dropStick(stick);
}
synchronized int grabStick()
{
int Thuy;
if(stickHolder==noStick)
try {wait();} catch (InterruptedException e) {}
painter.drawStick(myNum,stickHolder);
Thuy=stickHolder;
stickHolder=noStick;
return Thuy;
}
synchronized void dropStick(int stick)
{
stickHolder=stick;
painter.drawStick(myNum,stickHolder);
notify();
}
}
The class Chopstick is used to synchronize the threads in the Dining Philosophers applet. The class has the following two methods:
- synchronized int grabStick() Philosophers (threads) will come into this method when they attempt to get a stick. If the stick is there, the method gives the stick to the philosopher. If the stick has already been taken by its neighbor, the philosopher waits for the stick to be returned.
- synchronized void dropStick() The philosophers use this method to return the sticks. If the other philosopher is waiting for this chopstick, it is woken.
The class ScenePainter is used by the philosophers to paint their state. If a philosopher starts eating, puts down a stick, or does anything else, it calls methods in this class. The states of philosophers (for example, eating or waiting for a stick) are represented by different shapes. When a philosopher starts thinking, it calls drawThink(myNum) to draw the philosopher in its thinking state. Then, when it is done thinking, it calls drawThink(myNum) again to erase the philosopher. All of the methods in this class are called in pairs. The first call draws a state and the second erases it.
Listing 16.8. The class ScenePainter.
class ScenePainter extends Object
{
int sX1[], sY1[], sX2[], sY2[], pX[], pY[];
final int xOffset=150, yOffset=150, Tscale=70;
final int rg=2, rEating=15, rThinking=8, rFirst=7;
final int rSecond=3, rGold=5;
Graphics G;
Component C;
public ScenePainter(Graphics g, Component c, int numPhils)
{
int i;
pX = new int[numPhils]; pY = new int[numPhils];
sX1 = new int[numPhils]; sY1 = new int[numPhils];
sX2 = new int[numPhils]; sY2 = new int[numPhils];
double arc=Math.PI/numPhils;
G=g; C=c;
for(i=0;i<numPhils;i++)
{
pX[i]= (int)(xOffset+ Tscale*Math.cos(i*2*arc));
pY[i]= (int)(yOffset+ Tscale*Math.sin(i*2*arc));
sX1[i]=(int)(xOffset+ Tscale*Math.cos(i*2*arc+arc));
sY1[i]=(int)(yOffset+ Tscale*Math.sin(i*2*arc+arc));
sX2[i]=(int)(xOffset+.7*Tscale*Math.cos(i*2*arc+arc));
sY2[i]=(int)(yOffset+.7*Tscale*Math. sin(i*2*arc+arc));
}
G.setColor(Color.white);
G.setXORMode(Color.blue);
}
void drawStick(int num, int stick)
{
G.drawLine(sX1[num],sY1[num],sX2[num],sY2[num]);
if(stick==Chopstick.gold)
G.fillOval(sX1[num]-rg,sY1[num]-rg,rg+rg,rg+rg);
C.repaint();
}
void drawEat(int num)
{
fillCircle(num,rEating);
}
void drawThink(int num)
{
fillCircle(num,rThinking);
}
void drawFirstGrab(int num)
{
fillSquare(num,rFirst);
}
The class ScenePainter is used to draw the state of all the philosophers in the Dining Philosophers applet.
- public ScenePainter(Graphics, Component, int) The constructor for this class calculates the position of each philosopher and chopstick.
- void drawStick(num, stick) This method draws/erases the chopstick specified by num. A stick is represented by drawing a line between the two philosophers that may use it. If stick is the gold chopstick, a small circle is added to the end of the chopstick.
- void drawEat(num) This method draws/erases the philosopher specified by num in its eating state. A large circle represents the eating state.
- void drawThink(num) This method draws/erases a small circle that represents a philosopher in its thinking state.
- void drawFirstGrab(num) This method draws/erases a small square. The method is called when the philosopher tries to grab the first stick.
- void drawSecondGrab(num) This method erases/draws a smaller square inside the square drawn by drawFirstGrab(num). The method is called after the philosopher already has one chopstick and is trying to grab the second. A philosopher in this state is represented by a small hollow square.
- void drawGoldGrab(num) This method is called if the first stick the philosopher picked up is the gold stick. It is only called after both drawFirstGrab(num) and drawSecondGrab(num) have been called. A philosopher in this state is represented by a small hollow square with a tiny circle in the middle.
The Java Language
The first place that security is implemented in Java is, appropriately, in its language. Java.net provides the interfaces to handle the various network protocols (FTP, HTTP, Telnet, and so on). This package guards against tampering at the network interface level. The networking package can be configured at different levels of security to
- Disallow all network accesses.
- Allow network accesses to only the sources from which the code was imported.
- Allow network accesses only outside the firewall if the code came from outside.
- Allow unlimited network accesses.
Java.io provides many classes that have already been extensively tested, so it is recommended that you use these abstract classes in your code. Using Java.io classes to receive and send data to different input and output devices ensures that such activities are performed in the most secure manner. This holds true for all of Java's built-in packages. They have all been tested and should be used to ensure that your code does not violate any rules.
The Java language also adds security by providing access restrictions for encapsulation of classes, methods, and variables. Any class not declared public, for instance, is inaccessible by foreign classes. Any class declared protected is accessible only by its objects and subclasses. Any class declared private limits access to objects instantiated from it.
The Java language also eliminates pointer arithmetic and prevents you from explicitly controlling pointers in any manner. Instead, the compiler assigns symbolic references to methods, and the interpreter automatically assumes the responsibility of managing memory allocation and deallocation. As you have learned, pointers and pointer arithmetic used in other C-type programs are a leading source of bugs that crash systems.
Moreover, you can use the Java language to create your own security manager. The Java runtime environment has its own security manager that is constantly active at runtime. This security manager is an object that authorizes all operations before they are executed. The security manager throws a SecurityException if it rejects an operation. Otherwise, it passes the operation and allows it to run.
The Java.lang package provides an abstract security manager class that you can subclass to create your own security manager. The class provides methods that inspect classloaders on the execution stack.
Note that you cannot install a new security manager in an applet. Applets are subject to the security manager of the application in which they are running (your browser or Java AppletViewer).
The Java Compiler
The Java compiler not only checks that your syntax is correct in your source code that was created in the Java language, but it ensures that the code doesn't violate the language's safety rules. The compiler ensures that you have not made any errors, such as casting objects that are incompatible or using incorrect parameters.
As discussed in Chapter 2, "Getting Started," the Java Compiler works similarly to compilers in C-type languages in that it takes intelligible source code and converts it to code for a machine to interpret. The difference is that the machine that the Java compiler compiles for is the Java Virtual Machine, and the code is not native machine code for your CPU, it is bytecode for the JVM. Additionally, the Java compiler does not convert references to numbers and does not create a memory layout for the program at compile time. Although performance takes a hit since references in Java must be looked up in an object index at runtime instead of referring to exact memory addresses with the code, these changes were made for security reasons.
The compiler enforces sizes for bytecode commands and symbolic address references it creates. Each bytecode command consists of an opcode and an operand. The opcode is the command that the interpreter recognizes. The operand is the data needed by the opcode. Opcodes are executed sequentially and stored in 8-bit numbers. Operands vary in length, but are divided into bytes. Each opcode has a 32-bit symbolic address reference, or handle. The interpreter is able to locate pieces of code in memory using the opcodes assigned by the compiler. It is important that these sizes remain constant for portability, and the compiler ensures that they are.
The Java Interpreter
The Java interpreter performs many functions, some of which are performed solely for the purpose of the security of the system, and others that are performed as a part of the execution of the Java application, but require that security is enforced at each step.
One function that the interpreter performs for the purpose of security is laying out the memory map at runtime. This is unlike C and C++, in which the memory map is laid out by the compiler. The interpreter's allocation of memory in a Java application might vary depending on the user's hardware and software platform. This prevents a hacker from predicting where a class exists in memory, and then directly manipulating it.
Because memory is allocated by the runtime interpreter, Java has the luxury of eliminating the use of pointers in the language that explicitly addresses memory space. This prevents an innocent programmer from accidentally placing the wrong memory address in the code for a method, which would result in crashing your system. The compiled code references memory with handles that are resolved to exact memory addresses at runtime by the Java interpreter. You are unable to forge pointers to memory in Java, because the memory layout and object index do not exist until runtime and are controlled entirely by the Java interpreter.
Without pointers to locate a method, for example, the Java interpreter's memory layout is used to locate the method during runtime. When a method is called for the first time in a program, the interpreter refers to an object index of symbolic references, created by the compiler, that it checks against the memory layout it has created and finds where it placed the method in memory when the class was loaded. Subsequent calls to this method do not require such a lookup because the index contains the proper memory address.
Such symbolic references solve the fragile superclass security problem, which occurs in programs created in C and C++ when a superclass has been updated, possibly changing the memory layout. If a subclass tries to call a method from the updated superclass, its placement might be different in memory, and the program jumps to an obscure area of memory, inadvertently jeopardizing the system. In Java, the subclass calls methods symbolically from the superclass, and the interpreter locates the method using its memory layout and object index. Therefore, the correct method is called from the correct area of memory every time.
In addition to the security inherent in its runtime memory layout and object index referencing, the interpreter enforces security in three layers: the class loader, bytecode verifier, and runtime system. The class loader brings in the Java file, plus any classes referenced or inherited by the classes in the code. The bytecode verifier ensures that the code adheres to Java standards and doesn't violate the integrity of your system. The runtime system executes the code on your hardware.
The Class Loader
The class loader is responsible for loading classes that are called while a Java program is executing and laying them out in memory in such a way that they are not able to interfere with each other without explicit measures set forth in the language. It loads both local classes and foreign classes that have been determined clean by the bytecode verifier.
You can think of the execution environment of a Java application as a set of classes that are partitioned into separate namespaces. The class loader provides a layer of security by placing incoming classes in their own namespaces. Classes do not interfere with classes in other namespaces, or partitions, without explicit calls to their symbolic references and the permission of the target class to be accessed by the foreign classes (the target class must not have declared any access restrictions).
The class loader assigns one namespace for all of the classes that come from the local file system (built-in Java classes), and a separate namespace for the each source of imported classes. This protects local classes from foreign classes. When a class references another class, it first searches the local system's namespace, then the namespace of the referencing class. Foreign classes have no way of simulating a local class. Likewise, built-in classes cannot interfere with imported namespaces without referencing their classes explicitly. Foreign classes are similarly partitioned from each other because they are each assigned their own namespaces.
The Bytecode Verifier
The Java Interpreter passes all incoming code to a bytecode verifier. The responsibility of the bytecode verifier is to subject every piece of code that the interpreter passes it to a rigorous series of integrity tests. It performs a variety of tests that run from simple verification that the format of a line of code fragment is consistent with the language specification, to passing each line of code through a theorem prover to trap the following types of problems:
- Forged pointers
- Access restriction violations (private, public, or protected)
- Mismatching of object types
- Operand stack overflows and underflows
- Incorrect bytecode parameters
- Illegal data conversion
After code has been approved by the bytecode verifier, you can be reasonably sure that the language does not violate your system with harmful instructions that fit any of these conditions. To maintain system performance, after code passes the verifier tests and is approved it will not be checked again. This enables the interpreter to reliably execute the code at full speed without stopping to check its integrity.
The Java class loader and bytecode verifier make no assumptions about the primary source of the bytecode stream. The code may have come from the local system, or it may have come from a system in another country. The bytecode verifier is the last line of defense against errant code. Java requires that imported code passes the verifier's tests before it is executed by any means on the system.
The Execution of Code
Once the code has been loaded, laid out in memory, and verified, it is executed a piece at a time by the interpreter. The interpreter can execute bytecodes that have been coded for the Java Virtual Machine specification directly. It also provides a just-in-time compiler that compiles intermediate bytecode to native machine code at runtime for cases that you are willing to sacrifice portability to allow the bytecode to run at full speed. Security can be implemented at runtime by coding traps and exception handlers into your program.
Java Virtual Machine
At this point in the book, you might be curious as to how the Java Virtual Machine actually works. A grasp of the fine points of the JVM gives you a greater understanding of the security structure of Java. This section unravels the mystery of the JVM.
The JVM is intended to provide a set of specifications that the Java language, compiler, and interpreter adhere to in order to ensure secure, portable programs and runtime environments. The JVM provides a strict set of rules that can be used by a developer to create an original implementation of an interpreter that runs Java code on any machine it is installed on. These rules require that the runtime interpreter include all of the following pieces:
- A set of bytecode instructions similar to that of a CPU, which contains opcodes and operands, and their values and alignments
- A set of registers that tracks the state of the program at a given time
- A Java stack, which stores information about the states of methods in stack frames
- A garbage collection heap, which stores memory that is to be allocated to objects
- Memory areas for storage, which store constants and methods
The Bytecode Instruction Set
When Java code is compiled, it is converted to bytecode, which
is similar to the assembly
language created by C and C++ compilers. Each instruction in the
bytecode contains an opcode followed by an operand. The
following list contains examples of opcodes and their descriptions:
- iload loads an integer
- aload loads a pointer
- ior logically or two integer
Opcodes are represented by 8-bit numbers. Operands vary in length. They are aligned to eight bits, and therefore, operands larger than eight bits are divided into multiple bytes. The reason Java uses such small memory spaces is to maintain compactness of memory. The Java team felt that compact code was worth the performance hit on the CPU while locating each instruction, a hit that results from the inability of the interpreter to judge exactly where each instruction is due to the varying lengths of instructions. This decision reclaims lost performance as compact bytecode travels across networks more quickly than code found in other programming languages that contains unused memory space left free as a result of larger, fixed instruction lengths. Of course, code with fixed instruction lengths runs more quickly on the CPU because the interpreter can jump through instructions, anticipating their lengths and exact locations.
The instruction set provides specifications for opcode and operand syntax and values, and identifier values. It also includes instructions for invoking methods.
Opcode recognizes the primitive data types described in Chapter 1, " An Overview of Java." In addition, it recognizes the symbolic object reference, which is a type of 32-bit length. The Java compiler manages these types. It assigns bytecodes that are appropriate for each type and each method.
The JVM Register Set
The JVM contains four 32-bit registers that store information about the current state of the system. These registers are updated after the execution of each bytecode.
- pc The counter that keeps track of which bytecode in the program is currently being executed.
- optop The pointer to the top of the operand stack in the Java stack that is used when the program performs operations.
- frame The pointer to the current execution environment of the current method in the Java stack.
- vars The pointer to the first local variable of the current method that is executing in the Java stack.
The processor of your machine deals quickly with these registers.
The Java Stack
The Java stack provides the current parameters to bytecodes during execution of methods. Each method of a class is assigned a stack frame that is stored in the Java stack. Each stack frame holds the current status of local variables, the operand stack, and the execution environment.
The local variables for the method are stored in an array of 32-bit variables indexed by the vars register. Larger variables are divided across two local variables. When local variables are used, they are loaded onto the operand stack for the method. The operand stack is a 32-bit first in, first out (FIFO) stack that stores operands for opcodes in the JVM instruction set. These operands are both parameters used in methods' instructions, as well as results of instructions. The execution environment provides information about the current state of the method in the Java stack. It stores pointers to the previous method, pointers to its local variables, and pointers to the top and bottom of the operand stack. It might also contain debugging information.
The Garbage Collection Heap
As you learned in Chapter 4, "Creating Your Own Objects," Java's garbage collector keeps track of references to objects allocated in memory using symbolic handles. When an object is no longer being referenced during the execution of the program, the garbage collector returns the memory used by the object to its garbage collection heap. This heap is a separate area of memory in Java that is allocated when the runtime system is started. It is provided specially for allocation of memory to new objects. If the system the interpreter runs on supports virtual memory, the size of the garbage collection heap can grow as necessary.
The JVM Memory Areas
The other memory areas provided in the JVM are for storing methods and the constant pool. All of the bytecode for Java methods is stored in the method area. It also stores symbol tables for dynamic linking of classes and additional debugging information associated with a method. The constant pool area encodes string constants, class names, method names, and field names for each class. It is created by the Java compiler. These memory areas are not required to be laid out in any particular location to avoid exposure to hackers who would be able to find their code if they knew the memory map before runtime.
Limitations
The JVM in JDK 1.01 has a few limitations due to its fixed operand and stack sizes that may be resolved in future releases of the JDK:
- Stack width of 32-bits limits the JVM's internal addressing to 4G of memory.
- 8-bit offsets into objects limit the number of methods in a class to 256.
- 16-bit offsets for branching and jumping instructions limit the size of a method to 32K.
- Unsigned 16-bit indexes into a constant pool limit the number of constant pool entries per method to 32K.
- Unsigned 8-bit argument counts limit the size of an argument to 255 32-bit words (only 127 long or double words).
These limitations are not issues today because 4G of internal addressing space is not necessary on today's machines that typically have 16 or 32M RAM. However, technology advances quickly, so this limit could conceivably become an issue in the near future. Keep in mind that these limitations might be relaxed in later releases of Java.
Known Bugs
It is important that you are aware of the bugs in Java that have already been discovered and reported before you spend time and energy struggling with them. The latest release of the Java Developer's Kit is version 1.0.1. You should be aware of several bugs that exist in this release. This section lists the open bugs in the JDK 1.0.1 as reported by Sun Microsystems, some workarounds and patches that Sun suggests using, the JDK 1.0 security bugs that have been announced by Sun as fixed in JDK version 1.0.1, and some newer security bugs that have not yet made it to Sun's official list of open bugs.
Additional bugs will be discovered as Java is used by an increasing number of programmers in increasing capacities. It is important that you regularly check Java's Web site for bugs that Sun confirms, and additionally, unofficial Java Web sites, like Digital Espresso, report bugs that have not yet been confirmed by Sun.
Keep in mind that the bulk of these bugs address minor problems with the functionality of Java, and not with Java's security.
An updated list of the open bugs in the JDK can be found at http://java.sun.com/products/JDK/1.0.2/KnownBugs.html.
Future Java Security
Many organizations are currently racing to produce tools that will improve Java security. These organizations are creating their own bytecode verifiers, runtime environments, compilers, and virus scanning software. They are working on providing encryption of binaries that will make it difficult for hackers to reverse engineer Java code. They are developing internal versioning systems and object directory services that will allow for authentication of applets.
Two examples of such organizations are Symantec, which has recently announced its virus scan software package for Java, and The University of Illinois Systems Software Research Group, which has recently announced the release of a package that gives the Java programmer access to a security API. The details as announced are as follows in this excerpt from Symantec Corporation's home page:
"Symantec Corporation, a leading supplier of utilities software products, today announced the development of new technology that lays the foundation for delivery of leading edge antivirus solutions. The Symantec AntiVirus Research Center (SARC) has developed the first native-Java virus scanner for Java applets sent over the Internet. In addition, SARC has also designed an in-house automation technology that can be used to analyze, replicate, detect and define a large subset of the most common computer viruses.
One of the fastest growing development environments is Java. While no current Java virus threats exist, there is a possibility that a virus could be written. In addition, due to Java's inherent portability, a virus of this type could spread over a wide variety of platforms. To address this possibility, SARC has produced a Java class file scanner extension for NAV. This will enable NAV to provide real-time protection and monitor for Java virus activity within Netscape or any other Java supported Web browser.
The current AutoProtect capability in Norton AntiVirus (NAV) is configured to scan Java applets sent over the Internet in .CLASS files, and can detect a potential type of Java (Java Type I) virus that can be propagated by modifying HTML pages.
The new Java scanner technology can detect another, more complex, type of Java virus (Java Type II) that parasitically infects .CLASS files. The Java .CLASS file scanner provides a much faster and more efficient scan than that achieved with conventional brute-force scanning technology and represents the best scanning technology available today for the Java environment. At the first sign of a Java virus threat, Symantec will make this technology available to customers via an immediate virus definition update."
http://choices.cs.uiuc.edu/Security/JGSS/jgss.html
Caution: |
|
Java 16 - New Features
Java 16 is a major feature release and it has brought many JVM specific changes and language specific changes to JAVA. It followed the Java release cadence introduced Java 10 onwards and it was released on Mar 2021, just six months after Java 15 release.
Following are the major new features which are introduced in Java 16.
· JEP 338 - Vector API (Incubator) − New Vector APIs introduced allowing developers to perform the vector operations explicitly.
· JEP 347 - Enable C++14 Language Features − C++ 14 features can be used in c++ source code with the JDK 16.
· JEP 357, JEP 369 - Migrate from Mercurial to Git/GitHub − OpenJDK source code is moved from mercurial to Git/GitHub
· JEP 376 - ZGC - Concurrent Thread-Stack Processing − Z Garbage Collector improved by moving its thread-stack processing from safepoints to concurrent phase.
· JEP 380 - Unix-Domain Socket Channels − SocketChannel and ServerSocketChannel now supports Unix Domain sockets.
· JEP 386 - Alpine Linux Port − Now JDK is available for Alpine Linux and other Linux distributions which use musl implementation.
· JEP 387 - Elastic Metaspace − Metaspace memory management is improved by returning unused HotSpot class-metadata or metaspace memory to the operating system quickly, reduces the metaspace footprint, and simplify the metaspace code.
· JEP 388 - Windows/AArch64 Port − Now JDK can run on AArch64, on ARM hardware server or ARM based laptops.
· JEP 389 - Foreign Linker API (Incubator) − Java code can be called by C/C++ or vice versa using new API replacing the JNI.
· JEP 390 - Warnings for Value-Based Classes − Warnings are raised in case of value-based classes are synchronised using synchronize.
· JEP 392 - Packaging Tool − jpackage is now a standard instead of incubator feature.
· JEP 393 - Foreign-Memory Access API (Third Incubator) − Minor enhancements to Foreign Memory Access API.
· JEP 394 - Pattern Matching for instanceof − Pattern matching for instanceOf is now a standard feature.
· JEP 395 - Records − records are now a standard feature.
· JEP 396 - Strongly Encapsulate JDK Internals by Default − default mode of --illegal-access option is now deny. Earlier it was permit.
· JEP 397 - Sealed Classes (Second Preview) − Minor enhancements to sealed classes.
Java 16 enhanced numerous APIs with new methods and options.
Java 16 - Other Enhancements
JEP 338 − Vector API (Incubator)
JIT Compiler optimizes the arithmetic algorithms, by transforming some scalar operations (one item at a time) into vector operations (multiple items at a time) automatically. But developers had no control over this process. Even not all scalar operations can be converted into vector operations. With this JEP, a new VECTOR API is introduced to allow developers to perform Vector operations explicitly.
It is an incubator module, jdk.incubator.vector, to express vector computations to reliably compile at runtime to optimal vector hardware instructions.
JEP 347 − Enable C++14 Language Features
Till JDK 15, JDK supports C++98/03 language standards. With JEP 347, now Java formally allow C++ source code changes within the JDK to use C++14 language features, and to provide specific guidance about which of those features may be used in HotSpot code.
JEP 357/369 − Migrate from Mercurial to GitHub
With JEP 357/369, OpenJDK Source code is moved from Mercurial to Git/GitHub. Following are the primary factors for this movement.
· Large File size of version control system metadata (Mercurial)
· Available tooling
· Available hosting
JEP 380 − Unix-Domain Socket Channels
The Unix-domain sockets are for inter-process communication (IPC) on the same host, to exchange data between processes. These sockets are similar to TCP/IP sockets except being addressed by filesystem pathnames rather than the Internet Protocol (IP) addresses and port numbers. Most Unix platforms, Windows 10 and Windows Server 2019, supports the Unix-domain sockets. JEP 380 added Unix-domain socket support to SocketChannel and ServerSocketChannel.
Java 16 - Deprecation and Removals
Deprecation
· ThreadGroup methods like stop, destroy, isDestroyed, setDaemon and isDaemon methods are deprecated and will be removed in future release. These API/mechanism to destroy a threadgroup is flawed and such method which supports explicitly or automatically destroying a thread group are terminally deprecated.
· Signal Chaining APIs like sigset, signal are obsolete and their use is deprecated. sigaction is cross-platform and is supported API for multi-threaded processes.
· java.security.cert APIs representing DNs as Principal or String objects are deprecated.
· elliptic curves which are either obsolete or not implemented using modern formulas and techniques of SunEC provider are removed.
Removals
· The non-public class java.awt.PeerFixer is removed. Its purpose was to provide deserialization support of ScrollPane objects created prior JDK 1.1.1.
· jaotc, an experimental Java Ahead-of-Time compilation tool is removed. Experimental Java-based JIT compiler, Graal, is also removed.
· root certificates with weak 1024-bit RSA public keys have been removed from the cacerts keystore.
FIRST EDITION
All rights reserved. No part of this book shall be reproduced,
stored in a retrieval system, or transmitted by any means, electronic,
mechanical, photocopying, recording, or otherwise, without written
permission from the publisher. No patent liability is assumed
with respect to the use of the information contained herein. Although
every precaution has been taken in the preparation of this book,
the publisher and author assume no responsibility for errors or
omissions. Neither is any liability assumed for damages resulting
from the use of the information contained herein. For information,
address Sams.net Publishing, 201 W. 103rd St., Indianapolis, IN
46290.
International Standard Book Number: 1-57521-113-0
HTML conversion by : M/s. LeafWriters (India) Pvt. Ltd.
http://leaf.stpn.soft.net
Publisher and President: | Richard K. Swadley |
Development Manager: | Dean Miller |
Managing Editor: | Cindy Morrow |
Director of Marketing: | John Pierce |
Assistant Marketing Managers: | Kristina Perry, Rachel Wolfe |
Acquisitions Editor | Grace M. Buechlein | Development Editor | Brian-Kent Proffitt |
Software Development Specialist | Cari Skaggs | Production Editor | Deborah Frisby |
Copy Editors | Miriam Bishop, Fran Blauw, Stacey Houston, Kristen Ivanetich, Howard Jones, Nanci Sears Perry | ||
Indexer | Tim Griffin | Technical Reviewer | Christopher Stone |
Editorial Coordinator | Bill Whitmer | Technical Edit Coordinator | Lynette Quinn |
Editorial Assistants | Carol Ackerman, Andi Richter, Rhonda Tinch-Mize | Cover Designer | Tim Amrhein |
Book Designer | Alyssa Yesh | Copy Writer | Peter Fuller |
Production Team Supervisor | Brad Chinn | ||
Production | Stephen Adams, Debra Bolhuis, Michael Brumitt, Kevin Cliburn, Jason Hand, Daniel Harris, Sonja Hart, Chris Livengood, Casey Price, Laura Robbins, Bobbi Satterfield, Mark Walchle |
All terms mentioned in this book that are known to be trademarks or service marks have been appropriately capitalized. Sams.net Publishing cannot attest to the accuracy of this information. Use of a term in this book should not be regarded as affecting the validity of any trademark or service mark. Java is a trademark of Sun Microsystems, Inc.
Acknowledgments
A special acknowledgment goes to Lawrence Harris for his work on the table of contents of this book. Thank you!
Thanks to Brian Proffitt and to the rest of the gang at Sams.net. Most of all, thanks to Grace Buechlein, who called me on that May afternoon to talk about this Java book. Everyone has been very kind, and it is great to be involved with a team so committed to producing quality products.
The other authors and contributors to this book also worked hard to produce a high-quality, informative book. I was responsible only for about a half of this book, but it was originally going to be only a chapter or two. My portion just kept growing and growing. This book, above all, was a team effort of authors and editors, many of whom I've never met or spoken to. The efforts from all sides to produce Web Programming with Java are something I have a great deal of respect for, and I appreciate all the work people did to make my 50 percent read well.
I would also like to thank Professor Liew for providing my first introduction to the Java language, along with the rest of the faculty at Lafayette, including Professor Bjorling-Sachs and Professor Collins. Kelly Anne has also been great when I was unavailable to run. My employers at Southwest Research Institute were also great, even though I walked in some mornings so bleary-eyed from late-night writing that I could barely stand. And to Mom and Dad who bought that first Apple IIe fifteen Christmases ago. And to everyone else, friends, family, grandparents: thank you.
The Authors
Michael Girdley is from San Antonio, Texas. He is currently pursuing a Bachelor of Science in computer science at Lafayette College in Easton, Pa. He is also the chief consultant at Allwilk Consulting (http://www.allwilk.com/), an organization specializing in Web site creation and Java programming. He is a member of the Lafayette College varsity swimming team and will earn his fourth varsity letter in 1996-1997. Michael hopes to find a job or go to graduate school after possibly graduating on time in May of 1997. He can be reached at [email protected] and on the Web at http://www.lafayette.edu/~girdleyj/.
Kathryn A. Jones is a senior technical specialist in the New York office of Smith Barney, where she designs and develops NT-based client/server systems.
Jim Morey is a graduate student in pure mathematics at the University of British Columbia. He got his undergraduate degree from University of Guelph, Ontario. He is a hacker in the sense that he learns programming by hacking through other people's code. Catching the Java bug early, he hacked his way through the alpha version of Java and wrote "Pythagorus's Haven," a geometrical proof of the Pythagorean Theorem. This program won the Grand Prize in the Applet Programming Contest (http://java.sun.com/contest/results.html) sponsored by Sun. And recently, he won first place in the individual entertainment and games category in the Java Cup International (http://javacontest.sun.com/winners_circle/index.html) for CopyCat, a 3-D geometrical game. With all the computer equipment he has won, Jim will be in hacker heaven for quite some time at [email protected] or http://www.math.ubc.ca/~morey/.
Keith Orpen is a student of mathematics and coffee and lives in Vancouver. He can be pestered at [email protected], which is handy for sending him money and stuff. His favorite thing in the world is to answer Java newbie questions for free, especially if a ton of effort is involved.
Thomas Fredell is the consulting manager for the Atlanta office of Brainstorm Technologies, a leading provider of Groupware tools and consulting services. Thomas has the distinction of being the first graduate with a degree in cognitive science from the University of Virginia. His interests include software development using C, C++, and Java, and he is interested in language design and artificial intelligence. His noncomputer hobbies include skiing, sailing, squash, and chess. He can be contacted via e-mail at [email protected].
Brian Gloyer placed first in Sun's Java Applet Programming Contest with his Dining Philosophers applet. He is currently pursuing his Ph.D. at the University of California, Irvine. His research interests include artificial neural networks, image processing, and information systems. Some of his work, along with the original Dining Philosophers applet, can be seen on his home page at http://www.eng.uci. edu/~bgloyer.
Richard Lesh ([email protected]) is an instructor with the Microcomputing Program at the University of Missouri, St. Louis. He has developed a variety of applications for the Macintosh, IBM pc, and various UNIX platforms. A number of software products that he has developed are in national distribution, including PLANMaker, a business plan building product, and a number of screen-saver modules published by Now Software in Now Fun! and by Berkeley Systems in After Dark.
George Reece ([email protected]) holds a philosophy degree from Bates College in Lewiston, Maine. He works as a consultant with York and Associates, Inc., and as a magazine columnist for the Java Developer's Journal. He has written some of the most popular MUD software on the Internet, include the Nightmare Object Library and the Foundation Object Library. For Java, he was the creator of the first JDBC implementation, the Imaginary JDBC Implementation for mSQL. His Internet publications include the free textbooks on the Lpc programming language, Lpc Basics and Intermediate Lpc.
|
|
Home/ Info/ Products/ BIG TECH Metaverse Metaverse Vs. Virtual Reality PC Buyers Guide/ IEEE 802 Standards Social Media Platforms Technology Videos/ Computer & IT Certifications Processor Generations Memory SSD Vs. HDD HTML JAVA Python Angular.js Venus Project/ Computer Security and Law Glossary Contact
Certified Enterprise Blockchain Professional (CEBP) Web 3.0 Satoshi Nakamoto Cryptocurrency Ethereum NFT Merkle Tree El-Salvador eNaira
AWS Certification Google Certification Oracle Certifications cisco certifications Huawei Certification Microsoft Certifications Linux Certification Business Certifications
Google-Cloud-Platform-Guide Amazon-Web-Services-Guide Global-Cloud-Infrastructure-Of-AWS Amazon-Web-Services-Cli-Guide AWS-Cloudformation Devops Microsoft-Azure Oracle-Cloud Digitalocean-Cloud Openstack-Cloud
Active Components Passive Components Test Electrical Components Electronics Classification
Copyright BICT Solutions Privacy Policy. | Terms and Conditions apply | All rights reserved.