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.
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
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.
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!
|
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.
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).
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";
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.
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).
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.
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 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.
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().
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
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 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.
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
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
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.
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.
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.
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.
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.
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 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.
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.
The java.awt.image package
allows device-independent loading and filtering of bitmapped images.
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.
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.
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.
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.
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.
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.
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.
Streams read and write bytes but quite often you want to send
other primitive Java types. The interfaces DataInput
and DataOutput specify that
a class knows how to read and write the various primitive Java
types in a machine-independent way. The most useful methods in
DataInput and DataOutput
are shown in Tables 3.1 and 3.2. All of the DataOutput
methods in Table 3.2 return void.
Table 3.1. Useful DataInput methods.
Method | Description
|
boolean readBoolean()
| Read a single boolean.
|
byte readByte()
| Read a single byte (8 bits).
|
char readChar()
| Read a single char (16 bits).
|
float readFloat()
| Read a single float (32 bits).
|
double readDouble()
| Read a single double (64 bits).
|
int readInt()
| Read a single int (32 bits).
|
long readLong()
| Read a single long (64 bits).
|
short readShort()
| Read a single short (16 bits).
|
int readUnsignedByte()
| Read a byte and interpret as an unsigned integer.
|
int readUnsignedShort()
| Read a short and interpret as an unsigned integer.
|
void readFully(byte b[])
| Read bytes into an array until it is full.
|
void readFully(byte b[],int,int)
| Read bytes into a sub-array until full. |
String readUTF()
| Read a UTF-encoded string. |
String readLine()
| Read a sequence of bytes terminated by new line.
|
All but one of these input methods throws a java.io.EOFException
if the end of the input stream is reached before all the bytes
in the specified object can be read. The lone exception is readLine().
It returns null to indicate
that EOF occurred before a new line was seen.
Table 3.2. Useful DataOutput methods (all return void).
Method | Description
|
writeBoolean(boolean)
| Write a single boolean.
|
write(int)
| Write a single byte (8 bits).
|
writeByte(int)
| Write a single byte (8 bits).
|
write(byte[])
| Write a sequence of bytes.
|
write(byte[],int,int)
| Write a sequence of bytes from a sub-array.
|
writeChar(int)
| Write a single char (16 bits).
|
writeFloat(float)
| Write a single float (32 bits).
|
writeDouble(double)
| Write a single double (64 bits).
|
writeInt(int)
| Write a single int (32 bits).
|
writeLong(long)
| Write a single long (64 bits).
|
writeShort(int)
| Write a single short (16 bits).
|
writeUTF(String)
| Write a String in UTF-encoded format.
|
writeBytes(String)
| Write a String as a sequence of bytes.
|
writeChars(String)
| Write a String as a sequence of chars.
|
Using these methods, you can read and write primitive Java types
with ease. Your classes can use these methods to write themselves
onto a stream by writing each instance variable.
Caution: |
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.
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.
This is a base class for writing lexical analyzers. It scans an
InputStream and breaks it
into a sequence of tokens of predefined types.
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.
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.
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.
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.
The java.util package is
explored in Chapter 13, "General
Purpose Classes." It contains some utility classes and useful
data structures, described briefly here.
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.
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.
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).
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.
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.
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
|