Базы данныхИнтернетКомпьютерыОперационные системыПрограммированиеСетиСвязьРазное
Поиск по сайту:
Подпишись на рассылку:

Назад в раздел

Chapter 3 -- An Introduction to Java Classes

Chapter 3

An Introduction to Java Classes


CONTENTS


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. This chapter describes the classes found in the standard Java packages. The programming interface is listed online at http://java.sun.com/JDK-1.0/api/packages.html. Chapter 4, "Creating Your Own Objects," describes how you can use these classes as the foundation on which to build your own custom classes.

As described in Chapter 1, "An Overview of Java," a class in Java is a description of how to create and operate on a certain kind of program object. An object is a chunk of computer memory with various values stored in it, which the program knows how to access and use. These program objects only really exist when your applet or Java application is actually running, but you as the programmer have to tell the compiler how to use them.

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:
In theory, packages establish separate name spaces, but see http://java.sun.com/JDK-1.0/knownbugs.html#Compiler for some known bugs.

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:
Think about why the String class is so special. It is impossible to write it yourself. It has a special form of constructor call: the double-quoted string literal actually results in the construction of a String object. This lexical tie-in requires special compiler design and makes the compiler depend on the class design!

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:
Be careful when using the finalize() method. Merely dropping all references to an object does not guarantee that it is ever garbage-collected. The garbage collector can decide not to bother, if memory is plentiful. So don't rely on finalize() to be called in a timely way. See Chapter 4 for more on garbage collection.

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:
In fact, mouse events can't be intercepted correctly for some components in the JDK release 1.0: see http://java.sun.com/tutorial/ui/components/peer.html

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:
To give hints to the layout manager about your component's preferred or minimum size, override the preferredSize() and minimumSize() methods in class Component.

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:
Always use the two argument form of add() when using BorderLayout (for example, add("North," myButton)); the single argument version is ignored.

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.
MethodDescription
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).
MethodDescription
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:
Beware of the difference between byte and char. A String which is written using writeChars() must be read as a sequence of char. Don't use readLine() or readBytes() for this. In fact, avoid doing this altogether. Use writeBytes() or writeUTF() instead.

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 03" );
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:
The constructor is called as ServerSocket(int port) or as ServerSocket(int port, int max). The optional second argument is the maximum number of pending connections to hold in a queue before refusing further new connections. It is not the number of seconds to wait for a connection, as stated in the Java API 1.0.

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.

Summary

In this chapter, you learned what each of the standard Java packages can do, and you at least heard mention of just about every different kind of class contained in them. These classes play a huge role in reducing the amount of boring code you need to write, and consequently they help to cut down on bugs. You get to have more spare time for surfing (the ocean or the Internet, as applicable). The next chapter (Chapter 4, "Creating Your Own Objects") deals with the mechanics of coding subclasses of these classes tailored to your own needs.


Next Previous Contents




  • Главная
  • Новости
  • Новинки
  • Скрипты
  • Форум
  • Ссылки
  • О сайте




  • Emanual.ru – это сайт, посвящённый всем значимым событиям в IT-индустрии: новейшие разработки, уникальные методы и горячие новости! Тонны информации, полезной как для обычных пользователей, так и для самых продвинутых программистов! Интересные обсуждения на актуальные темы и огромная аудитория, которая может быть интересна широкому кругу рекламодателей. У нас вы узнаете всё о компьютерах, базах данных, операционных системах, сетях, инфраструктурах, связях и программированию на популярных языках!
     Copyright © 2001-2024
    Реклама на сайте