In a language like C or C++, the compilation model is fairly simple.
You compile a source file to an object file, and then later invoke a
linker to combine the object files with libraries into an executable
program. More recently, shared libraries and so forth have been used
in the compilation model.
Java is a bit different in its approach. We will be describing how it
handles program packaging and compilation in this and subsequent
issues.
The first thing to mention is that the Java compiler (javac) will
compile more than just the one source program given to it on the
command line. It will go off and do other compilation as necessary.
The result of compilation is an xxx.class file, containing the
bytecodes and a description of the interface (set of methods and so
on) offered by the class contained within.
A public class should be defined within a file of the same name as the
class, with lower/upper case significant. A public class uses the
keyword "public". An example of a public class is given in the above
example on applets. "ga" is a public class, "AppFrame" is not. We
will mention more about public classes in a moment. A Java source
file may contain only one public class definition.
The next item of importance is the CLASSPATH environment variable.
This is a set of directories that is used by the compiler and
interpreter to find classes. For example, in a Windows NT
installation, the setting might be:
CLASSPATH=".;d:/java/lib" |
meaning that the current directory and then the directory d:/java/lib
are searched. In UNIX the separator is ":" instead of ";".
Searched for what? Source files, class files, and packages. What are
packages? Packages are groupings of related classes. If I say:
// file A.java package X; public class A {} // file B.java package X; public class B {} |
then A and B are grouped in the package X. A somewhat similar feature
in C++ is namespaces.
Packages are tied in with the CLASSPATH variable and the file system.
Declaring a package X as in the example means that somewhere along the
CLASSPATH directories there will be a directory X, with files A.java,
B.java, A.class, and B.class in it. If the current directory is first
in the CLASSPATH list, then this means creating subdirectories of the
current directory, each subdirectory as the name of a package.
This works for system directories as well. For example, looking under
d:/java/lib, there are directories java/applet, java/awt, java/io,
java/lang, and so on. These tie in directly with import directives of
the form:
import java.io.*; import java.awt.*; |
and so on. Note that:
import java.lang.*; |
is supplied implicitly and so does not have to be specified by the
programmer. "*" means to import all classes in the package.
Package names can be used to qualify program entities. For example, I
can say:
java.lang.System.out.println("Howdy!"); |
In fact, there is some discussion about making the highest levels of
the package hierarchy correspond to Internet domain names, as in:
COM.glenmccl.java.lang.System.out.println("Howdy!"); |
If this is done, then you won't have to worry about creating packages
and classes that interfere with those produced by others!
We mentioned above the distinction between public and non-public
classes. Non-public classes in packages cannot be imported into a
program. For example, this sequence is illegal:
// file y1.java in Z/y1.java relative to current directory package Z; /*public*/ class y1 {} // file y2.java in current directory import Z.*; public class y2 { public static void main(String args[]) { y1 y = new y1(); } } |
To wrap up this discussion for now, here is a longer example. There
are two classes that do statistical analysis, one for descriptive
statistics like mean and standard deviation and the other for doing
correlation. Don't worry too much if you don't know statistics; this
example is really about packaging.
We use a package called Stat for grouping these classes.
// file Stat/descr.java in subdirectory package Stat; public class descr { private long n; // count of numbers seen private double x; // sum of X private double x2; // sum of X^2 // constructor public descr() { n = 0; x = x2 = 0.0; } // add a number to the pool public void add(double d) { n++; x += d; x2 += d * d; } // retrieve the count of numbers seen public long cnt() { return n; } // return mean (average) public double mean() { if (n < 1) throw new ArithmeticException(); return x / (double)n; } // return standard deviation public double stdev() { if (n < 2) throw new ArithmeticException(); double d1 = (double)n * x2 - x * x; double d2 = (double)n * (double)(n - 1); return Math.sqrt(d1 / d2); } } // file Stat/corr.java in subdirectory package Stat; public class corr { private long n; // number of values seen private double x; // sum of X private double y; // sum of Y private double x2; // sum of X^2 private double y2; // sum of Y^2 private double xy; // sum of X*Y // constructor public corr() { n = 0; x = y = x2 = y2 = xy = 0.0; } // add in a pair of numbers public void add(double a, double b) { n++; x += a; y += b; x2 += a * a; y2 += b * b; xy += a * b; } // return count public long cnt() { return n; } // get correlation public double getcorr() { if (n < 2) throw new ArithmeticException(); double d0 = (double)n * xy - x * y; double d1 = Math.sqrt((double)n * x2 - x * x); double d2 = Math.sqrt((double)n * y2 - y * y); return d0 / (d1 * d2); } // get a for y = ax + b public double geta() { if (n < 2) throw new ArithmeticException(); return ((double)n * xy - x * y) / ((double)n * x2 - x * x); } // get b for y = ax + b public double getb() { if (n < 2) throw new ArithmeticException(); return y / (double)n - x / (double)n * geta(); } } // file stest.java in current directory import Stat.*; public class stest { public static void main(String args[]) { // test descriptive statistics descr sd = new descr(); for (int i = 1; i <= 10; i++) sd.add((double)i); System.out.println("count = " + sd.cnt()); System.out.println("mean = " + sd.mean()); System.out.println("std dev = " + sd.stdev()); // test correlation corr co = new corr(); for (int j = 1; j <= 10; j++) co.add((double)j, (double)j * 2.5 + 9.0); System.out.println("count = " + co.cnt()); System.out.println("correlation = " + co.getcorr()); System.out.print("y = " + co.geta()); System.out.println("x + " + co.getb()); } } |
Java 1.1 also introduces inner classes, which are classes defined within
another class. This is a complicated topic that we will spend several
issues covering in full. One type of inner class is an anonymous class,
which we can illustrate with an example:
import java.awt.*; import java.awt.event.*; public class button { public static void main(String args[]) { Frame f = new Frame("testing"); Panel p = new Panel(); Button b = new Button("OK"); b.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { System.err.println("got here"); System.exit(0); } } ); p.add(b); f.add(p); f.pack(); f.setVisible(true); } } |
This example sets up an AWT frame with a panel, and adds a button
containing "OK" to the panel. When the button is selected, the program
terminates. With the 1.1 event delegation model, we want to bind an
action listener to the button, such that a method actionPerformed() will
be called when the button is selected. That is, we want to implement
the interface ActionListener.
This could be done via a separate class, or by having button implement
ActionListener. We've seen examples of such usage before. But a more
contained approach is to use an anonymous class. When I said:
new ActionListener() { ... } |
I in fact declared a new class, with no name, that implements the
interface ActionListener. To actually implement this interface,
actionPerformed() must of course be defined.
If ActionListener was a class, rather than an interface, saying something like:
new ActionListener(arg1, arg2, ...) { ... } |
would declare a subclass of ActionListener, and the arguments would be
passed to the superclass constructor.
An anonymous class has no name, and therefore has no constructor. An
anonymous class may refer to fields and methods in its containing class.
Whether anonymous classes are a "good" feature is a hard one to call.
They are very convenient, as this example illustrates. But they make
code harder to read, cause problems for debuggers and documentation
tools, and so on.
Another type of class is what is called a nested
class, where a class is declared within another class as a sort of
helper. Nested classes are declared using the "static" modifier. To
see what this looks like, consider an example of managing school records
consisting of name/grade pairs:
import java.util.Vector; public class RecordList { private Vector recs = null; public RecordList() { recs = new Vector(); } public void addRecord(String name, int grade) { recs.addElement(new rec(name, grade)); } public void dumpAll() { int sz = recs.size(); for (int i = 0; i < sz; i++) { rec r = (rec)recs.elementAt(i); System.out.println(r.name + " " + r.grade); } } public static void main(String args[]) { RecordList rl = new RecordList(); rl.addRecord("Jane Smith", 57); rl.addRecord("John Jones", 43); rl.addRecord("Nancy Williams", 51); rl.dumpAll(); } private static class rec { private String name = null; private int grade = 0; private rec(String n, int g) { name = n; grade = g; } } } |
There is a public class RecordList that we create an instance of and
then feed name/grade pairs to. It in turn uses a helper class "rec" to
actually record the pairs. The individual records are stored in a
Vector object for later retrieval.
rec is a separate class, nested within RecordList. It has access to the
static members, if any, of its containing class. But it does not have
access to the non-static members in RecordList, because this would have
no meaning -- object instances of rec are created independently of those
of RecordList (another variation, the member class, does allow such
access).
There are various other ways in which this application could be
implemented. For example, rec could be broken out as a separate
top-level public class. But sometimes it's better to hide a helper
class, if its use is limited to a narrow application.
The 1.1 edition of David Flanagan's "Java in a Nutshell" has a good
discussion of nested/member/local/anonymous classes.
In the previous two issues we discussed the use of anonymous and nested
classes. These provide for the nesting of one class inside of another.
Another variation on this feature is local classes, where a class is
defined within a method.
As a simple example of how local classes work, consider an application
that needs to create a listener for an AWT object:
import java.awt.*; import java.awt.event.*; public class local1 { public static void main(String args[]) { Frame f = new Frame("testing"); Panel p = new Panel(); class listen implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0); } } Button b = new Button("Exit"); b.addActionListener( new listen() ); p.add(b); f.add(p); f.pack(); f.setSize(100, 100); f.setVisible(true); } } |
For our example, the listener must implement the ActionListener
interface, and define actionPerformed(). But realize that the listener
class could be local1 itself, a top-level non-public class in the same
compilation unit, an anonymous class, or a local class. We have chosen
the last of these.
A local class is visible only within the method where it's defined, and
cannot have public/protected/private modifiers on the class definition
(they wouldn't mean anything). Such a class can use fields from its
enclosing class, and final (unchangeable) local variables from its
enclosing method. For example:
public class local2 { private static int a = 37; private int b = 47; public void f() { int c = 57; final int d = 67; class Z { Z() { System.out.println(a); System.out.println(b); //System.out.println(c); // error System.out.println(d); } } Z z = new Z(); } public static void main(String args[]) { local2 x = new local2(); x.f(); } } |
The reason that non-final local variables are not allowed is because the
implementation of local classes makes a hidden copy of local variables
for use by the class. So the local class is accessing a copy of the
local variables rather than the variables themselves, and thus only
access to unchanging variables makes sense.
Anonymous and local classes have some overlap, and both can be used to
solve similar problems. An anonymous class is somewhat cryptic in
nature, while a local class is more verbose and easier to understand.
In Java, a class organization such as:
class A {} class B extends A {} |
results in a superclass (A) and a subclass (B). References to B objects
may be assigned to A references, and if an A reference "really" refers
to a B, then B's methods will be called in preference to A's. All of
this is a standard part of the object-oriented programming paradigm
offered by Java.
But there is a way to modify this type of organization, by declaring a
class to be final. If I say:
final class A {} |
then that means that A cannot be further extended or subclassed.
This feature has a couple of big implications. One is that it allows
control over a class, so that no one can subclass the class and possibly
introduce anomalous behavior. For example, java.lang.String is a final
class. This means, for example, that I can't subclass String and
provide my own length() method that does something very different from
returning the string length.
There is also a big performance issue with final classes. If a class is
final, then all of its methods are implicitly final as well, that is,
the method is guaranteed not be overridden in any subclass. A Java
compiler may be able to inline a final method. For example, this
program:
final class A { private int type; public int getType() {return type;} } public class test { public static void main(String args[]) { int N = 5000000; int i = N; int t = 0; A aref = new A(); while (i-- > 0) t = aref.getType(); } } |
runs about twice as fast when the class is declared final.
Of course, much of the time it's desirable to use the superclass /
subclass paradigm to the full, and not worry about wringing out the last
bit of speed. But sometimes you have heavily used methods that you'd
like to have expanded inline, and a final class is one way of achieving
that.
In previous issues we've discussed nested, local, and anonymous classes,
all part of the new inner class feature of Java 1.1. There is one more
type of inner class to consider, the member class.
A member class is a class defined within another class. Each instance
of the member class is associated with a corresponding instance of the
defining class. Unlike a nested class, methods of a member class can
access instance fields in a containing class.
A nested class must be declared static, and is used to group related
classes together. A member class, on the other hand, implies a more
intimate relationship, with each instance of the class corresponding to
an instance of the enclosing class.
To see how this works, consider an example like:
public class mem1 { private int i = 37; class mem11 {} public static void main(String args[]) { mem1 ref1 = new mem1(); mem11 ref11 = ref1.new mem11(); } } |
mem1 is an ordinary top-level class, and mem11 a member class within it.
New instances of mem1 are created in the usual way, but new instances of
mem11 require new syntax:
mem11 ref11 = ref1.new mem11(); |
That is, an instance of mem11 is being created in the context of a
specific enclosing instance of mem1, which is referred to by ref1.
A further example of how member classes are used is illustrated by this
code:
public class mem2 { int i = 0; class mem22 { int i = 0; void f() { this.i = 37; mem2.this.i = 47; System.out.println(this.i); System.out.println(mem2.this.i); } } public static void main(String args[]) { mem2 m2 = new mem2(); mem22 m22 = m2.new mem22(); m22.f(); } } |
We create the new top-level and member class instances, and then call
the f() method in the member class. Both the enclosing and member
classes have an "i" field, and to access each of these, we say:
this.i = 37; mem2.this.i = 47; |
"mem2.this" is the reference to the enclosing instance of mem2.
Member classes are most useful as helper classes to other classes, in
situations where the helper class needs to get at the instance variables
of the containing class. An example might be some type of a data
structure class like a tree, that defines a member class implementing
java.util.Enumeration to traverse the structure.
Java 1.2 adds a new feature for manipulating object references, the
Reference class and related support classes. This feature is a little
hard to describe, but we could say that "Reference is to object
references as Class is to Java classes". That is, a Reference
represents an object reference.
To see how this feature works, let's look at an example:
import java.lang.ref.*; public class ref1 { public static Reference ref = null; public static ReferenceQueue rq = new ReferenceQueue(); public static void f() { Object obj = new Object(); System.out.println(obj); ref = new GuardedReference(obj, rq); } public static void main(String args[]) { f(); try { Reference r = rq.remove(500); // 500ms timeout Object obj = (r == null ? null : r.get()); System.out.println(obj); } catch (InterruptedException e) { System.err.println(e); } System.gc(); try { Reference r = rq.remove(500); Object obj = (r == null ? null : r.get()); System.out.println(obj); } catch (InterruptedException e) { System.err.println(e); } } } |
In this example, main() calls a method f(), and f() creates a local
Object instance. We then create a GuardedReference wrapper for this
local object, and specify a ReferenceQueue as well. The wrapper has the
usual get() and set() methods for obtaining the object (the "referent")
that has been set, and for setting a new one.
But there's another aspect of Reference that goes beyond simple support
for wrapping a reference in a wrapper class. When we created the
GuardedReference object, we also specified a queue. This process is
known as "registering" the Reference object.
In this example, when f() returns, the object created locally is
garbage, that is, has no valid references to it. At some point the
garbage collector will realize this, and will add the Reference object
to the specified queue ("enqueue" it).
Why is this feature useful? One example would be a caching mechanism,
where objects represent in-memory disk files, and it's important to know
when a cached object is no longer in use (so that it can be replaced in
the cache by a higher-priority object).
Queues are used to represent Reference objects, so that for example a
separate thread can continually check the queue. Queues can also be
polled using the mechanism illustrated above (500 millisecond timeout in
this example).
There are several types of Reference objects. GuardedReference is one,
and WeakReference and PhantomReference two others. These differ in
their various properties, for example in whether the no-longer-reachable
referent can actually be reclaimed by the garbage collector.
This mechanism can be used for various types of caching and for
implementing object cleanup schemes.
Speaking of the use of final variables, another new feature in 1.1 is
the ability to initialize a final class field after its declaration. It
used to be that you'd have to say:
final int BUFSIZ = 1024; |
with the initializer in the declaration itself. That restriction has
been relaxed, so that a final variable can be initialized via an
instance initializer or a constructor. As an example, consider this:
public class blank { final int x; public blank(int i) { x = i; // OK x = 47; // error here } public blank() // error here { } public void f() { x = 57; // error here } } |
The final variable in this example needs to be initialized exactly once
in each of the constructors, and cannot be initialized in a method such
as f().
This feature offers some flexibility when initializing member fields,
for example via constructor arguments.
Java uses exceptions to signal error conditions. Some of these errors
are at user level, for example when a data file cannot be accessed.
Others originate in the Java runtime system, for example when memory is
exhausted or an invalid subscript is applied to an array. There are
four different exception classes defined in java.lang that are important
to understand when using exceptions.
Throwable is the superclass for all exceptions. If a program catches an
exception of type Throwable:
try { ... } catch (Throwable e) { ... } |
it will catch all exceptions.
Error is a subclass of Throwable, used to group together exceptions that
indicate serious problems which a normal application should not try to
catch. An example of an exception subclass of Error is
VirtualMachineError.
Exception is a subclass of Throwable used to group "normal" kinds of
exceptions, such as IOException thrown when an I/O problem occurs.
RuntimeException is a subclass of Exception, and indicates an exception
that is not required to be mentioned in a "throws" clause of a method.
Exceptions in this category are known as "unchecked" exceptions. For
example, if I have a method:
void f(String fn) throws IOException { FileOutputStream fos = new FileOutputStream(fn); ... } |
I must either catch any IOException myself, or else declare that f()
propagates this exception to its caller. On the other hand, there is no
requirement that f() declare that it might throw NullPointerException
(which could happen if fn is null). NullPointerException is a subclass
of RuntimeException and thus is unchecked.
These classes have constructors that allow the specification of an error
message, as in:
throw new Error("out of memory"); |
There is also a provision for dumping out a stack traceback:
try { ... } catch (Error e) { e.printStackTrace(); } |
One of the interesting issues that comes up with Java programming is how
to combine Java classes with code written in other languages such as C
or C++. It's common to have a way to do mixed-language programming on a
given platform, and so it's worth asking how this can be done in Java.
This is a complex topic, with some system-specific aspects to it. But
we will attempt to illustrate some of the basics with a simple example.
Suppose that you have a Java class:
public class test { public static native short f(short a, short b); public static void main(String args[]) { short num1 = 37; short num2 = 47; System.load("test"); // load the DLL short s = f(num1, num2); System.out.println(s); } } |
and you'd like to call a method defined in another language ("native"
method). In the above example f() represents such a method.
The first thing to do is declare the method, using the native modifier.
A native method has no body, because the body will be provided by some
other module written in some other language. Declaring the method in
Java allows for type checking and so on to be performed.
We then compile this class:
javac test.java |
The JDK tool "javah" is next run over the class file:
javah -jni test |
The output of this is a file test.h:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class test */ #ifndef _Included_test #define _Included_test #ifdef __cplusplus extern "C" { #endif /* * Class: test * Method: f * Signature: (SS)S */ JNIEXPORT jshort JNICALL Java_test_f (JNIEnv *, jclass, jshort, jshort); #ifdef __cplusplus } #endif #endif |
This header describes the prototype for a C++ function to be
implemented. We then create test.c to implement the function:
#include <jni.h> #include "test.h" JNIEXPORT jshort JNICALL Java_test_f(JNIEnv*, jclass, jshort a, jshort b) { return a * b; } |
and make a shared library (DLL) out of it by saying (Borland C++ 5.2):
bcc32 -tWD -DWIN32 -Ij:/java/include -Ij:/java/include/win32 test.c |
picking up JNI headers found in j:/java/include and j:/java/include/win32.
Finally, we execute the Java program:
java test |
The program loads the DLL and then calls the f() method within it.
This approach involves a certain amount of magic. The details of
creating DLLs, picking up JNI header files, and actually loading shared
libraries into a running Java program will vary from system to system.
There are also big issues with parameter passing, return values,
accessing object instances, and so on. The above example gives the
flavor of how JNI works. The basic idea is to declare native methods,
use javah to create a header that declares function prototypes for them,
implement the prototypes, create a shared library, and then load the
library into a Java program.
public class test1 { public static void main(String args[]) { long a = 123456; short b = (short)a; } } |
and Java allows the long to be converted to a short only via an explicit
cast.
The cast rule is suspended in some cases where the value to be assigned
is known to the compiler:
public class test2 { public static void main(String args[]) { short a = 12345; } } |
In this example, "12345" is a constant expression (see 15.27 in the Java
Language Specification), and the compiler knows that this value is
representable in a short (which supports values -32768 - 32767).
But implicit narrowing is not done on method invocation, so that:
public class test3 { public static void f(short s) {} public static void main(String args[]) { f(0); // error because "0" is an int, not short } } |
is invalid without a cast. This restriction is intended to simplify the
overloaded method matching process. In C++, with many more conversion
possibilities, argument matching of overloaded functions is very
complex.
Narrowing also applies to reference types. For example, if A is a
superclass of B, then converting an A reference to a B reference is a
narrowing conversion. In this case, the converted reference value is
checked as to whether it is actually a legitimate value of type B.
Narrowing is often quite useful. Java provides mechanisms for narrowing
values, with the desirable requirement that such narrowing be explicitly
identified via casts.
Float and Double are wrapper classes defined in java.lang. They can be
used to wrap individual values of float and double types, and insert
such values into object collections such as those represented by Vector.
These classes are also used to convert floating-point values to and from
strings.
But these classes have another purpose, which is to represent properties
of floating-point types. For example, they define the constant
MAX_VALUE that specifies the maximum float or double value, and
POSITIVE_INFINITY to represent an infinite value.
One of the most interesting features of these classes are methods that
convert to and from bit representations of floating-point values. For
example, this code:
public class floatbits { public static void main(String args[]) { int bits = Float.floatToIntBits(12.34f); int sign = bits >>> 31; int exp = (bits >>> 23) & 0xff; int mant = bits & 0x7fffff; System.out.println("sign = " + sign); System.out.println("exponent = " + exp); System.out.println("mantissa = " + mant); } } |
picks apart a 32-bit floating-point value, and displays its sign,
exponent, and mantissa. Representation of floating-point values uses
the IEEE 754 format (see 4.2.3 in the Java Language Specification). In
this particular case, the sign is in bit 31, the exponent in bits 30-23,
and the mantissa in bits 22-0.
For a 32-bit floating-point value, the bit representation of the value
also doubles as the hash code, used for example when inserting a Float
object into a Hashtable.
In the last issue we mentioned the "deprecation" of some AWT methods
as of the 1.1 release. Some of these methods are isolated, but the
basic event handling model of the AWT has itself changed in 1.1, and
it's worth illustrating the new model.
The old model was based on inheritance, with subclassing of AWT
components and overriding of the action() and handleEvent() methods.
An overriding method either would handle events and pass back "true",
or else pass back "false" and propagate the event up to the next
component in the hierarchy.
This model is somewhat unwieldy, with a requirement for lots of
subclasses, complex logic to process events, and lack of separation
between application and interface.
As of 1.1, a new "delegation" model has been implemented. In the new
model, a "listener" object expresses interest in events from one or
more "source" objects. When an event occurs, it is passed from the
source to all registered listeners. Since the listener object
implements a standard interface (like ActionListener), the source can
invoke a known method on the listener object, to pass the details of
the event.
To illustrate this further, consider the following example:
import java.awt.*; import java.awt.event.*; class Dispatch implements ActionListener { static final int COMMAND1 = 1; static final int COMMAND2 = 2; static final int EXIT = 3; int id; Delegate d; public Dispatch(int id, Delegate d) { this.id = id; this.d = d; } public void actionPerformed(ActionEvent e) { switch (id) { case COMMAND1: d.command1(); break; case COMMAND2: d.command2(); break; case EXIT: d.exit(); break; } } } class Interface { public Interface(Delegate d) { Frame f = new Frame(); f.setLayout(new FlowLayout()); Dispatch cmd1 = new Dispatch(Dispatch.COMMAND1, d); Dispatch cmd2 = new Dispatch(Dispatch.COMMAND2, d); Dispatch exit = new Dispatch(Dispatch.EXIT, d); Button b = null; b = new Button("Command #1"); f.add(b); b.addActionListener(cmd1); b = new Button("Command #2"); f.add(b); b.addActionListener(cmd2); b = new Button("Exit"); f.add(b); b.addActionListener(exit); f.pack(); f.show(); } } public class Delegate { public void command1() { System.out.println("command #1 invoked"); } public void command2() { System.out.println("command #2 invoked"); } public void exit() { System.exit(0); } public static void main(String args[]) { Delegate d = new Delegate(); Interface i = new Interface(d); } } |
In this example, Delegate is our application, and Interface the
interface for it. Dispatch is a helper class that implements the
ActionListener interface. We tie particular object instances of
Dispatch to individual buttons. When a button is selected, an event
is passed to the listener for the button, and it in turn invokes the
appropriate application method.
There is a lot more that can be said about this approach. A paper
that describes it in some detail can be found at:
http://www.javasoft.com/products/jdk/1.1/docs/guide/awt/designspec/events.html
We will be using the delegation model in future AWT examples.
We saw a simple example above of printing text. Another approach to
printing uses the AWT:
import java.awt.*; public class print { public static void main(String args[]) { Frame f = new Frame("test"); PrintJob pj = f.getToolkit().getPrintJob(null, "print1", null); Graphics g = pj.getGraphics(); g.drawLine(0, 0, 150, 150); pj.end(); } } |
This example creates a frame, and retrieves its toolkit. The toolkit
is the interface between the AWT proper and an actual GUI/window
implementation.
From the toolkit we get a PrintJob instance. This causes a print job
screen to be popped up. It asks about how many copies are desired and
similar types of questions.
Given a PrintJob instance, we can get a Graphics object, and draw a
line to it, and then end the print job.
This feature is in 1.1 and 1.1.1, but without any documentation in the
source code, so this presentation is a bit sketchy. There are
additional method calls for getting page dimensions and resolution.
As compared to the method illustrated above, using device files, the
AWT approach to printing offers platform independence (you don't have
to worry about pathnames like "/dev/lp"). This approach also offers
graphics and Unicode support.
We will likely have more to say about printing in the future.
In previous issues we've discussed some of the aspects of applet
programming, and also mentioned a new technique for serializing
objects, that is, turning an object into a stream of bytes that can be
saved to a file or sent across a network.
We can combine these techniques in order to serialize an applet, that
is, to capture it in some current state, and then restore that state
later. This technique has various uses, for example to perform
one-time initialization.
Consider first an applet:
import java.awt.*; public class applet extends java.applet.Applet { private int cnt = 1; public void paint(Graphics g) { String s = "Hello world #" + cnt++; g.drawString(s, 25, 25); } } |
and some HTML that drives it:
<html> <head> <title>Applet Example</title> </head> <body> <applet code="applet.class" width=150 height=150></applet> </body> </html> |
If we run this HTML with the JDK 1.1.2 appletviewer program (1.1 gives
an exception for what we're going to do), we can Stop and Start the
applet a few times, using the appletviewer menu. Each start results
in the displayed count being bumped up by one.
Then, if we select Save, with a name like "applet.ser", the applet
object is serialized and written to the indicated file. We can start
up the appletviewer again with HTML code of:
<html> <head> <title>Applet Example</title> </head> <body> <applet object="applet.ser" width=150 height=150></applet> </body> </html> |
and the applet will take up where it left off.
This technique is quite powerful and useful in a variety of
applications. But if you are considering using this approach, you
should investigate some of the issues around serialization. For
example, not all objects can be serialized. One case of this would be
machine-specific resources like file descriptors.
In this issue we will use a longish example of a calculator program to
illustrate how one would go about designing a custom AWT component
using JDK 1.1.
For a calculator program, where there are several buttons representing
digits and operators like "+", one approach would be use the Button
component in the AWT. But we have chosen to design our own component
for this purpose, which consists of a reddish oval with a number or
operator inside. This class is called my_Button in the code below.
When we design our own component, there are several important points
to note. One is that the component should have a constructor, to
create new instances.
Another is that the component needs a paint() method, to draw itself
on the screen.
The component should define getPreferredSize() and getMinimumSize(),
to declare its size to the AWT. This is important in setting up the
bounding box for the component and for doing layout.
A custom component may be a subclass of an AWT class like Canvas, or
in 1.1, it may extend java.awt.Component directly. This latter
approach results in what is called a "lightweight" component, one
without a native peer in the local windowing system. Lightweight
components can be more efficient, because of less overhead. my_Button
in the example below is lightweight.
Finally, we should mention how event handling is done in this
example. Each of the calculator buttons has a single instance of
"docalc" registered as a listener for it, so that when the button is
selected, docalc.actionPerformed() is called. This is the method that
actually does all the calculating work.
How do we know when a button is selected, given that we are doing our
own buttons as custom components? For this, we have another level of
event handling, where we register a mouse listener for each instance
of my_Button. When the mouse is clicked, we detect that, and dispatch
in turn to the listener for the selected button. We could have the
mouse clicks dispatch straight through to the docalc instance, but
such an architecture is not clean. It's desirable that my_Button act
like a regular button, handling its selection of itself within itself
and dispatching to a registered listener.
import java.awt.*; import java.awt.event.*; class my_Button extends Component implements MouseListener { private static final Font f = new Font("Monospaced", 12, Font.PLAIN); private static final FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(f); private static final int asc = fm.getLeading() + fm.getMaxAscent(); private static final int h = asc + fm.getMaxDescent(); private static final int PAD = 3; private String str = null; private int w = 0; private ActionListener listen = null; public my_Button(String s, ActionListener al) { str = s; w = fm.stringWidth(s); listen = al; addMouseListener(this); } public void mouseEntered(MouseEvent e) {} public void mouseExited(MouseEvent e) {} public void mousePressed(MouseEvent e) {} public void mouseReleased(MouseEvent e) {} public void mouseClicked(MouseEvent e) { listen.actionPerformed(new ActionEvent(this, 0, str)); } public Dimension getPreferredSize() { return new Dimension(w + PAD * 2, h + PAD * 2); } public Dimension getMinimumSize() { return getPreferredSize(); } public void paint(Graphics g) { Color c = g.getColor(); g.setFont(f); g.setColor(Color.red); g.fillOval(0, 0, w + PAD * 2 - 1, h + PAD * 2 - 1); g.setColor(Color.white); g.drawString(str, PAD, asc + PAD); g.setColor(c); } } class docalc implements ActionListener { private String str_accum = ""; private String str_num = ""; private double accum = 0.0; private double num = 0.0; private String oper = null; private void setText(String s) { calc.tf.setText(s); calc.tf.setCaretPosition(s.length()); } public void actionPerformed(ActionEvent e) { String str = e.getActionCommand(); if (str.equals("Exit")) System.exit(0); if (Character.isDigit(str.charAt(0))) { str_num += str; str_accum += str; setText(str_accum); } else { if (str_num.length() >= 1) { num = Long.parseLong(str_num); str_num = ""; if (oper != null) { switch (oper.charAt(0)) { case '+': accum += num; break; case '-': accum -= num; break; case '*': accum *= num; break; case '/': accum /= num; break; } } else { accum = num; } str_accum += str; setText(str_accum); oper = str; } if (str.equals("=")) { String s = Double.toString(accum); setText(s); str_accum = ""; accum = 0.0; oper = null; } } } } public class calc { public static TextField tf = null; private static my_Button b(String s, docalc dc) { return new my_Button(s, dc); } private static void setup(Frame f, docalc dc) { tf = new TextField(25); tf.setEditable(false); tf.setBackground(Color.blue); tf.setForeground(Color.white); GridBagConstraints gbc = new GridBagConstraints(); f.setLayout(new GridBagLayout()); gbc.insets = new Insets(2, 2, 2, 2); gbc.gridx = 0; gbc.gridy = 0; gbc.gridwidth = 10; gbc.anchor = GridBagConstraints.WEST; f.add(tf, gbc); gbc.gridy = 1; gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.CENTER; gbc.gridx = 0; f.add(b("0", dc), gbc); gbc.gridx = 1; f.add(b("1", dc), gbc); gbc.gridx = 2; f.add(b("2", dc), gbc); gbc.gridx = 3; f.add(b("3", dc), gbc); gbc.gridx = 4; f.add(b("4", dc), gbc); gbc.gridy = 2; gbc.gridx = 0; f.add(b("5", dc), gbc); gbc.gridx = 1; f.add(b("6", dc), gbc); gbc.gridx = 2; f.add(b("7", dc), gbc); gbc.gridx = 3; f.add(b("8", dc), gbc); gbc.gridx = 4; f.add(b("9", dc), gbc); gbc.gridy = 3; gbc.gridx = 0; f.add(b("+", dc), gbc); gbc.gridx = 1; f.add(b("-", dc), gbc); gbc.gridx = 2; f.add(b("*", dc), gbc); gbc.gridx = 3; f.add(b("/", dc), gbc); gbc.gridx = 4; f.add(b("=", dc), gbc); gbc.gridy = 2; gbc.gridx = 6; gbc.weightx = 1; f.add(b("Exit", dc), gbc); } public static void main(String args[]) { Frame f = new Frame("calculator"); setup(f, new docalc()); f.pack(); f.show(); } } |
In previous issues we've seen some examples of the use of scrollbars in
GUI applications. With Java 1.1, there is an additional technique for
performing scrolling within an application. This approach uses the
ScrollPane class in the AWT. ScrollPane does "automatic" scrolling of a
specified AWT component, with some user customization possible.
For example, with this code:
import java.awt.*; class CirclePanel extends Component { public Dimension getPreferredSize() { return new Dimension(150, 150); } public Dimension getMinimumSize() { return getPreferredSize(); } public void paint(Graphics g) { g.fillOval(5, 5, 125, 125); } } public class scroll { public static void main(String args[]) { Frame f = new Frame("testing"); ScrollPane sp = new ScrollPane(); sp.setSize(100, 100); sp.add(new CirclePanel()); f.add(sp); f.pack(); f.setVisible(true); sp.setScrollPosition(65, 65); Adjustable a = sp.getVAdjustable(); a.setUnitIncrement(5); } } |
we create our our own component (CirclePanel) with a filled-in circle
inside of it, of size 150 x 150. We create a 100 x 100 ScrollPane
object and add this component to it. Without any further settings, this
will create a scroll pane with scrollbars, and unit increments for the
horizontal and vertical scrollbars.
In this particular example, we have modified the default settings in two
ways, one to set the current scroll position to (65,65), and the other to
set the vertical scroll increment to 5.
There are several other available features for controlling ScrollPane
objects.
A new feature in JDK 1.1 is popup menus, where a menu is popped up over
a GUI component in response to a user mouse action. A simple example of
this looks like:
import java.awt.*; import java.awt.event.*; class MyPopup extends Component implements ActionListener { PopupMenu pum = null; String labels[] = {"Command 1", "Command 2", "Command 3"}; String cmds[] = {"command1", "command2", "command3"}; public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); System.out.println("command was: " + cmd); } public MyPopup() { pum = new PopupMenu(); for (int i = 0; i < labels.length; i++) { MenuItem mi = new MenuItem(labels[i]); mi.setActionCommand(cmds[i]); mi.addActionListener(this); pum.add(mi); } add(pum); enableEvents(AWTEvent.MOUSE_EVENT_MASK); } public void processMouseEvent(MouseEvent e) { if (e.isPopupTrigger()) pum.show(this, e.getX(), e.getY()); else super.processMouseEvent(e); } public Dimension getPreferredSize() { return new Dimension(300, 300); } } public class popup { public static void main(String args[]) { Frame f = new Frame("testing"); Panel p = new Panel(); p.add("Center", new MyPopup()); f.add(p); f.pack(); f.setVisible(true); } } |
In this example we create a lightweight component of size 300 x 300, and
add a popup menu to it. The menu is created by adding MenuItems to it,
with separation of menu labels and commands (a rudimentary step toward
internationalization).
We also set up to handle mouse events, which is required to catch the
event that triggers the popup. The actual event handling is found in
processMouseEvent(), where popup events are handled directly, and others
are passed on. When actionPerformed() is called, we can retrieve the
action command set earlier.
SystemColor is a new 1.1 class that supports access to system colors,
that is, colors configured by a user for such things as text, window
borders, scrollbars, and so on.
For example, this simple program draws a graphical object that is the
same color as a window title bar:
import java.awt.*; class MyCanvas extends Canvas { public Dimension getPreferredSize() { return new Dimension(200, 200); } public void paint(Graphics g) { g.setColor(SystemColor.activeCaption); g.fillOval(50, 50, 100, 100); } } public class color { public static void main(String args[]) { Frame f = new Frame("testing"); Panel p = new Panel(); p.add(new MyCanvas()); f.add(p); f.pack(); f.setVisible(true); } } |
The actual RGB values of a system color may change over time, because a
user can customize such colors (for example, using Control Panel with
Windows NT). The method getRGB() can be applied to a color to obtain
its current color settings.
We talked earlier in this issue about focus traversal, a new AWT feature
in 1.1. Another new 1.1 feature is custom cursors. With 1.0, one could
only set a customized cursor at the Frame level, but with 1.1, a cursor
may be set for any component. For example, in this code:
import java.awt.*; import java.awt.event.*; public class testcursor implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0); } public static void main(String args[]) { Frame f = new Frame("testing"); f.setLayout(new BorderLayout()); Button b = new Button("OK"); b.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR)); b.addActionListener(new testcursor()); f.add("North", b); f.setSize(200, 200); f.setVisible(true); } } |
we set a crosshair cursor that is effective only within the area of the
Button object that we create.
We've been spending some time discussing some new features of Java 1.1.
Another of these is keyboard focus traversal, which is a fancy way of
saying that you can use Tab to move between AWT components, giving each
the focus in turn.
For example, with this simple program:
import java.awt.*; public class focus { public static void main(String args[]) { Frame f = new Frame("testing"); f.setLayout(new FlowLayout()); Button b1 = new Button("Button #1"); Button b2 = new Button("Button #2"); f.add(b1); f.add(b2); f.pack(); f.show(); } } |
you can move between the two buttons by pressing Tab, if you are using
Java 1.1. Tab is ignored for this same program using 1.0.
Components are traversed in the order added, so button #1 will be
visited before button #2. No other knowledge or setup is required to
use this feature.
public void testChoice() { } |
public void testChoice() { Button bu_ok = new Button( " OK " ); Choice ch_one = new Choice(); Choice ch_two = new Choice(); dlg = new DialogBase( form.frame, "testChoice", true ); dlg.setSize( 400, 300 ); dlg.setLayout( new FlowLayout() ); // add listeners // bu_ok.addActionListener( this ); ch_one.addItemListener( this ); ch_two.addItemListener( this ); ch_one.setName( "ONE" ); // set name of Choice ch_one.add( "1" ); // add items ch_one.add( "2" ); ch_one.add( "3" ); ch_two.setName( "TWO" ); // set name of Choice ch_two.add( "A" ); // add items ch_two.add( "B" ); ch_two.add( "C" ); // add objects to the Dialog dlg.add( ch_one ); dlg.add( ch_two ); dlg.add( bu_ok ); dlg.setVisible( true ); } // manage Choices public void itemStateChanged( ItemEvent e ) { if( e.getSource() instanceof Choice ) { String obj = ((Component)e.getSource()).getName(); String item = (String) e.getItem(); if( obj.equals( "ONE" ) ) { if( item.equals( "1" ) ) System.out.println( "(UNO:1)" ); if( item.equals( "2" ) ) System.out.println( "(UNO:2)" ); if( item.equals( "3" ) ) System.out.println( "(UNO:3)" ); } else if( obj.equals( "TWO" ) ) { if( item.equals( "A" ) ) System.out.println( "(UNO:A)" ); if( item.equals( "B" ) ) System.out.println( "(UNO:B)" ); if( item.equals( "C" ) ) System.out.println( "(UNO:C)" ); } } } |
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize(); awindow.move( ( screen.width - awindow.size().width ) / 2, ( screen.height - awindow.size().height ) / 2 ); |
In previous issues we've discussed various aspects of applet
programming. Starting with this issue, we're going to branch out a
bit and consider the more general topic of constructing Graphical User
Interfaces (GUIs) using the AWT (Abstract Windowing Toolkit). The
examples we present will be actual Java standalone programs rather
than applets. That is, they'll have their own main() method.
The AWT can be viewed as a high-level abstract means of describing
what a window or windows should look like. The AWT is actually
implemented differently on different platforms, using windowing
primitives appropriate to each platform. You can view GUI programming
using the AWT as construction of a complex data structure describing a
window, which is interpreted at program execution time.
Let's start out by looking at a simple example. This program takes a
single argument, which is the name of a text file, and displays that
file in a scrollable window. This is kind of like the "more" program
available in UNIX and DOS:
import java.awt.*; import java.io.*; public class More extends Frame { private static Button b1 = new Button( "New File" ); private static Button b2 = new Button( "Exit" ); private static TextArea ta = new TextArea( 24, 80 ); // handle mouse events public boolean action(Event e, Object arg) { if (e.target instanceof Button) { if (e.target == b1) { new_file(); return true; } if (e.target == b2) { System.exit(0); return true; } return false; } else { return false; } } // select a new file to view private void new_file() { FileDialog fd = new FileDialog(this, "Open File", FileDialog.LOAD); fd.show(); if (fd.getDirectory() == null || fd.getDirectory().equals("")) return; if (fd.getFile() == null || fd.getFile().equals("")) return; if (fd.getFile().equals("*.*")) return; String fn = fd.getDirectory() + File.separator + fd.getFile(); load(fn); } // load a file private void load(String fn) { RandomAccessFile raf = null; StringBuffer sb = new StringBuffer(); try { String s = null; raf = new RandomAccessFile(fn, "r"); while ((s = raf.readLine()) != null) { sb.append(s); sb.append('\n'); } raf.close(); } catch (Throwable e) { System.err.println("file I/O error"); System.exit(1); } ta.setText(sb.toString()); } // constructor public More(String title, String fn) { super(title); resize(600, 450); Panel p1 = new Panel(); p1.setLayout(new FlowLayout( FlowLayout.CENTER, 100, 0) ); p1.add(b1); p1.add(b2); Panel p2 = new Panel(); ta.setEditable(false); p2.add(ta); setLayout(new FlowLayout()); add(p1); add(p2); load(fn); show(); } public static void main(String args[]) { Frame f = new More( "More", args[0] ); } } |
We start by constructing a Frame, an AWT type suitable for
representing a top-level application window. We resize the frame, and
then add a couple of panels to it. What are panels? A panel is an
AWT entity useful for holding or representing a logical chunk of a
larger interface. In this example, one of the panels holds a couple
of buttons used for switching files and for exiting, and the other
holds the text area that actually displays the file.
Given two buttons within a panel, or two panels in a frame, how does
the AWT know how to order or arrange these items? We tell the AWT
about this via a layout manager, in this case by establishing a
FlowLayout object and attaching it to the frame. This type of layout
arranges objects in rows left to right, and goes to the next row when
it runs out of space. So the two buttons are arranged left to right
within panel p1. Panel p2 is laid out on the next "row" after p1,
because you can't fit a large text area right next to a set of buttons.
Actual file loading is straightforward. We read the lines in from the
file, append them together, and when done call setText() to set the
text for the TextArea object. A TextArea object already has scroll
bars with it and we don't need to worry about those.
If we want to switch files, we can use a FileDialog entity, which
handles most of the work of file selection. This brings up a menu of
files that looks similar to what you see on a PC running Windows.
Finally, we define an action() method for handling events in the
frame, in this case file switching or exiting.
This program has a couple of deficiencies, most notably that it reads
a whole file in at one time. With fairly slow I/O this can be a
problem for very large files. But the program does serve to
illustrate some of the basics of AWT use. We'll be looking at some of
these areas in more detail in future issues.
In the last issue we started a series on AWT (Abstract Window Toolkit)
programming. We presented an example of writing a "more" program, one
that can be used to page through text from a file. In this issue
we'll show another more complicated and powerful way of achieving the
same end.
Before diving into this, a few general comments are in order. This
code is written against JDK 1.1. It gives some "deprecation"
warnings, about methods which have changed in the JDK. As such, the
code needs to be updated, but doing so would make it not work with
earlier versions. This is simply something that we have to put up
with in an evolving language.
Also, this code reveals a bug in the JDK 1.1 implementation for
Windows NT. If you compile this program and run it, you will notice
that scroll bars are flaky. This is a known bug that is supposed to
be fixed in a bug fix release of 1.1.
Finally, we are getting into an area that is perhaps not as stable as
some other parts of Java, and still evolving (as is my understanding
of it). Some aspects of the example below, notably layout manager
usage, are a bit tricky.
As you will recall, the previous version of the "more" program had one
big deficiency, namely, that it read in a whole file before displaying
it. This is not acceptable for a very large file, especially with
relatively slow I/O. We've fixed that problem, and to do so, set up
our own text windowing scheme with scroll bars which we manage.
Here is the actual code. If you scan down the left side, you can see
interspersed comments starting with "//" in the left margin.
import java.awt.*; import java.io.*; // a text area in the window // This is the class used to manage a text area, with scroll bars. // It is passed a RandomAccessFile object to read lines from, and // reads them on demand only. // A Canvas is an AWT object suitable for displaying text on. class Text extends Canvas { private static final int LISTSIZ = 16; private String list[] = null; private int scnt = 0; private int currpos = 0; private RandomAccessFile raf = null; private boolean ateof = false; // initialize void init(RandomAccessFile r) { if (raf != null) { try { raf.close(); } catch (Throwable e) { System.err.println("close err"); System.exit(1); } } raf = r; ateof = false; currpos = 0; scnt = 0; list = new String[LISTSIZ]; } // We need to detab Strings that are displayed, because tabs // are handled in a funny way by Graphics.drawString(). private static String detab(String s) { StringBuffer sb = new StringBuffer(); int len = s.length(); int pos = 0; for (int i = 0; i < len; i++) { char c = s.charAt(i); if (c == '\r' || c == '\n') { } else if (c == '\t') { do { sb.append(' '); pos++; } while (pos % 8 != 0); } else { sb.append(c); pos++; } } return sb.toString(); } // This is the internal list used to store lines to be displayed. // We could also use java.util.Vector to manage this list, and // System.arraycopy() to copy the list. // add a String to the list void add(String s) { if (scnt == list.length) { String x[] = new String[list.length * 3 / 2]; for (int i = 0; i < scnt; i++) x[i] = list[i]; list = x; } list[scnt++] = detab(s); } // Called by the handleEvent() method below. // scroll up or down a line void scroll(int dir) { currpos += dir; if (currpos < 0) currpos = 0; repaint(); } // This is the paint() method, used to draw lines in the text window. // We use FontMetrics to determine how tall a character is, and // display as many lines as will fit. public void paint(Graphics g) { Dimension d = size(); int startpos = More.HEIGHT - d.height; int pos = startpos; int sp = g.getFontMetrics().getHeight(); int i = currpos; while (pos + sp < d.height) { while (i >= scnt && !ateof) { String s = null; try { s = raf.readLine(); } catch (Throwable e) { System.err.println("I/O err"); System.exit(1); } if (s == null) ateof = true; else add(s); } if (i >= scnt) break; g.drawString(list[i], 5, pos); i++; pos += sp; } } } // We use a Panel to contain the text area, along with scroll bars. // A Panel is an AWT object that can contain other objects. // a Panel for the text area, with scroll bars class Panel_ta extends Panel { private Scrollbar vbar = null; private Text t = null; // constructor Panel_ta(Text ta) { vbar = new Scrollbar(Scrollbar.VERTICAL); // Put the text area in the center, and the scroll bar on the "East" side. setLayout(new BorderLayout(0, 0)); add("Center", ta); add("East", vbar); t = ta; } // handleEvent() is called for actual scrolling. public boolean handleEvent(Event e) { if (e.target == vbar) { switch (e.id) { case Event.SCROLL_LINE_UP: t.scroll(-1); break; case Event.SCROLL_LINE_DOWN: t.scroll(1); break; } return true; } else { return super.handleEvent(e); } } } // The actual "More" class. public class More extends Frame { private Button b1 = new Button("New File"); private Button b2 = new Button("Exit"); private Text ta = new Text(); static final int WIDTH = 600; static final int HEIGHT = 450; // A dialog invoked when the user selects "New File". // open a new file private void new_file() { FileDialog fd = new FileDialog(this, "Open File", FileDialog.LOAD); fd.show(); if (fd.getDirectory() == null || fd.getDirectory().equals("")) return; if (fd.getFile() == null || fd.getFile().equals("")) return; if (fd.getFile().equals("*.*")) return; String fn = fd.getDirectory() + File.separator + fd.getFile(); load(fn); } // Called when buttons are selected. // handle an action public boolean action(Event e, Object arg) { if (e.target instanceof Button) { if (e.target == b1) { new_file(); return true; } if (e.target == b2) { dispose(); System.exit(0); return true; } return false; } else { return false; } } // Load a new file in. private void load(String fn) { try { RandomAccessFile raf = new RandomAccessFile(fn, "r"); ta.init(raf); } catch (Throwable e) { System.err.println("open err"); System.exit(1); } ta.repaint(); } // Constructor for More. public More(String title, String fn) { super(title); resize(WIDTH, HEIGHT); // Set a layout manager for the buttons, flowing across the screen. Panel p1 = new Panel(); p1.setLayout(new FlowLayout(FlowLayout.CENTER,50,0)); p1.add(b1); p1.add(b2); // Set a fixed-width font, size 12. ta.setFont(new Font("Courier", Font.PLAIN, 12)); Panel p2 = new Panel_ta(ta); // Set a layout manager for the buttons at the top, and the text area // at the bottom. GridBagLayout is the most complicated and powerful // of the layout managers. You can specify cell positions, cell // weights, and so on. Each object to be laid out has its constraints // set for it. // In this case, we give the top row of buttons a weight of 1, and the // text area a weight of 7, so the text area will take 7/8 of the // total window. GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); setLayout(gbl); gbc.gridx = 0; gbc.gridy = 0; gbc.weightx = 1; gbc.weighty = 1; gbc.fill = GridBagConstraints.BOTH; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints(p1, gbc); gbc.gridy = 1; gbc.weighty = 7; gbl.setConstraints(p2, gbc); add(p1); add(p2); load(fn); show(); } // Driver main() method. public static void main(String args[]) { Frame f = new More("More", args[0]); } } |
This example is fairly complicated, but illustrates several important
points about the AWT. There are other ways to approach this problem.
Another feature we could add would be one to search through the file
for a specified string, and display the line it's found on.
public class XXX extends Applet { public void init() { ... } } |
public class XXX { public static void main( String args[] ) { xxx = new XXX(); ... } } |
public class XXX extends Applet { static XXX xxx; GUI gui; //---------------------------------------------------------------------------- // main for start as applet public void init() { gui = new GUI(); } //---------------------------------------------------------------------------- // main for start as standalone application public static void main( String args[] ) { xxx = new XXX(); xxx.init(); } } |
Suppose that we want to take the above calculator program and call it
from an applet. How would we do this? Here's a simple example of an
applet that will interface with the calculator code.
import java.awt.*; public class applet extends java.applet.Applet { public void paint(Graphics g) { String input_expr = getParameter("input_expr"); calc c = new calc(input_expr); String out = c.get_value(); g.drawString("Input = " + input_expr, 25, 50); g.drawString("Value = " + out, 25, 75); } } |
This is similar to the applet illustrated in the last issue, save for
the lines:
String input_expr = getParameter("input_expr"); calc c = new calc(input_expr); String out = c.get_value(); |
The last two of these we saw in the example above. The first line
illustrates how one can get parameters passed to the applet from HTML
code, kind of similar to command-line parameters. The corresponding
HTML to run this applet would be:
<html> <head> <title>Interface to Calculator Applet </title> </head> <body> <applet code="applet.class" width=150 height=150> <param name=input_expr value="1/2/3*4"> </applet> </body> </html> |
This HTML is similar to that illustrated in newsletter #001, save for
the line:
<param name=input_expr value="1/2/3*4"> |
which actually passes in the parameter value. When this applet is
executed, the result will be something like:
Input = 1/2/3*4 Value = 0.666667 |
In the last issue we presented a simple drawing applet, that tracks
mouse input and draws on the screen accordingly. Suppose that we'd
like to modify this applet so that the user can toggle between two
different colors. How might we do this? One approach looks like so:
import java.applet.*; import java.awt.*; public class draw extends Applet { int prev_x = 0; int prev_y = 0; boolean color = false; Button ccb; public void init() { ccb = new Button("Toggle Color"); add(ccb); } public boolean action(Event e, Object o) { if (e.target == ccb) { color = !color; return true; } else { return false; } } public boolean mouseDown(Event e, int x, int y) { prev_x = x; prev_y = y; return true; } public boolean mouseDrag(Event e, int x, int y) { Graphics g = getGraphics(); g.setColor(color == false ? Color.red : Color.blue); g.drawLine(prev_x, prev_y, x, y); prev_x = x; prev_y = y; return true; } } |
using this HTML interface to the applet:
<html> <head> <title>Interface to Text Applet </title> </head> <body> <applet code="draw.class" width=300 height=300> </applet> </body> </html> |
This code is similar to the previous example, but we've added a button
that has "Toggle Color" in it. The action() method is called in
response to user events, and we check whether the user in fact clicked
on the button. If so, we toggle the color state internally, and the
color switches from red to blue or vice versa. If the event passed to
action() is not a button click, then we pass it back to the parent to
handle.
In the next issue we'll get into the area of text input, showing a
low-level and a higher-level way of entering text.
In the last issue we talked about button input, where the user clicks
on a button. In this issue we'll continue our discussion of input and
discuss text input a bit, starting with low-level character input. To
illustrate what such input looks like, here is a simple applet:
import java.applet.*; import java.awt.*; public class text1 extends Applet { int x = 40; int y = 40; public boolean keyUp(Event e, int key) { if ((e.modifiers & Event.CTRL_MASK) != 0) { char buf[] = new char[2]; buf[0] = '^'; key += 0x40; buf[1] = (char)key; getGraphics().drawChars(buf, 0, 2, x, y); x += getFontMetrics(getFont()).charWidth('^'); x += getFontMetrics(getFont()).charWidth(key); x += 2; } else { char buf[] = new char[1]; buf[0] = (char)key; getGraphics().drawChars(buf, 0, 1, x, y); x += getFontMetrics(getFont()).charWidth(key); x++; } return true; } } |
and the HTML that drives it:
<html> <head> <title>Interface to Text Applet</title> </head> <body> <applet code="text1.class" width=350 height=125></applet> </body> </html> |
keyUp() is a method called in response to a keyboard event. Once
inside keyUp(), we go through some machinations to determine what sort
of a key was pressed, for example whether a control key was hit. The
code in this example is incomplete but illustrative of the technique.
Once we have a key, we want to echo it in the applet window. We use
drawChars() for this. Then we need to update the X position in the
window for drawing the next character, and to do that, we use the
FontMetrics class with the current font, to determine the width of a
character that's just been drawn.
In other words, this applet simply echoes its input to the window, and
throws in a "^" in front of control characters.
There are higher-level methods for entering text, which we will look
at in future issues.
In the previous issue we showed how to use low-level character input
within an applet. In this issue we'll discuss higher-level input
using TextFields. Consider the following example, an applet that
gathers name and address from a user and writes the data to a local
file:
import java.applet.*; import java.awt.*; import java.io.*; public class text4 extends Applet { TextField tf1; // text fields for address TextField tf2; TextField tf3; Button db; // Done button Label lb; // Label used as message Color bg = new Color(0xffffff); // background/foreground clrs Color fg = new Color(0x000000); public void init() { // set colors setForeground(fg); setBackground(bg); // create text fields tf1 = new TextField(35); tf2 = new TextField(35); tf3 = new TextField(35); // create button and label db = new Button("Done"); lb = new Label(); // arrange items in window setLayout(new GridLayout(4, 2, 10, 10)); add(new Label("Name:")); add(tf1); add(new Label("Address #1:")); add(tf2); add(new Label("Address #2:")); add(tf3); add(db); add(lb); } private boolean write() { // check input fields if (tf1.getText() == null || tf1.getText().length() == 0) return false; if (tf2.getText() == null || tf2.getText().length() == 0) return false; if (tf3.getText() == null || tf3.getText().length() == 0) return false; // write out name and address to end of file try { RandomAccessFile raf = new RandomAccessFile("datafile", "rw"); raf.seek(raf.length()); raf.writeBytes(tf1.getText()); raf.writeByte('\n'); raf.writeBytes(tf2.getText()); raf.writeByte('\n'); raf.writeBytes(tf3.getText()); raf.writeByte('\n'); raf.writeByte('\n'); raf.close(); } catch (Throwable e) { } return true; } public boolean action(Event e, Object o) { // Done button selected if (e.target == db) { if (write()) lb.setText("Written ..."); else lb.setText("Invalid field value"); return true; } else { return false; } } } |
This applet uses the following HTML code to drive it:
<html> <body bgcolor="#ffffff" text="#000000" link="#ff0000" vlink="#000000"> <head> <title>Interface to Text Applet</title> </head> <body> Please enter your name and address then select Done. <br> <br> <applet code="text4.class" width=350 height=125></applet> </body> </html> |
In this example, we use the init() method of the applet to set the
background and foreground colors, and then we create the individual
items of the window. We use a layout manager for this purpose. A
layout manager is a mechanism for arranging items in a window, or more
precisely, Component objects in a Container. Applet is derived from
Panel which is derived from Container. And an item like TextField is
derived from TextComponent which is derived from Component.
So when we create text fields and buttons and so on, we are creating
objects of classes derived from Component, and using a layout manager
to arrange these objects in a Container object.
After we've set up the window items, we can then add some logic within
the action() method to check whether the user selected the Done
button, and if so, then we interrogate the text fields using getText()
and extract the input data. Finally, we write this data to a file.
We append to the end of the file by obtaining its length and
positioning at the end of file.
In previous issues we've talked about the use of basic graphics in a
Java applet, and considered some techniques for the input of text. In
this issue we'll talk about something quite different, namely
animation. This is a technique that can be used to make applets do
"neat" things.
First of all, let's look at the source for an animation applet:
import java.applet.*; import java.awt.*; public class Animate extends Applet implements Runnable { // thread identifier private Thread an_thread = null; // Start and Stop buttons private Button start_button = null; private Button stop_button = null; // current state of animation private int st = 0; // initialize the applet public void init() { start_button = new Button("Start"); stop_button = new Button("Stop"); add(start_button); add(stop_button); } // handle mouse actions public boolean action(Event e, Object arg) { if (e.target == start_button) { start(); return true; } else if (e.target == stop_button) { stop(); return true; } else { return super.action(e, arg); } } // start the applet public void start() { if (an_thread == null) { an_thread = new Thread(this); an_thread.start(); } } // stop the applet public void stop() { if (an_thread != null && an_thread.isAlive()) an_thread.stop(); an_thread = null; st = 3; } // run a thread public void run() { for (;;) { // get graphics for the applet window Graphics g = this.getGraphics(); try { switch (st++) { case 0: g.setColor(Color.red); g.fillOval(25, 35, 250, 250); break; case 1: g.setColor(Color.yellow); g.fillOval(125, 135, 150, 150); break; case 2: g.setColor(Color.blue); g.fillOval(225, 235, 50, 50); break; case 3: g.clearRect(0, 0, 300, 300); st = 0; break; } // sleep for a second Thread.sleep(1000); } catch (InterruptedException e) { } g.dispose(); } } |
along with the HTML code that drives the applet:
<html> <head> <title>Interface to Animation Applet</title> </head> <body> <applet code="Animate.class" width=300 height=300></applet> </body> </html> |
We've seen parts of the applet code before, for example the graphics
that draws ovals (actually circles in this example), the init()
method, the setting up of buttons, and so on. This particular
animation draws three circles of different sizes and colors, and then
repeats itself.
What's different in this example is the use of threads. A thread,
which also is called names like "lightweight process" and "task", is a
distinct execution of Java code that is taking place at a given time.
A program or applet may use threads to allow multiple streams of code
to execute simultaneously.
Normally a Java applet responds to events such as mouse clicks or
keyboard input. But we'd like this animation applet to do something
continuously without waiting for events, so we create a thread and
start it running. The lines:
an_thread = new Thread(this); an_thread.start(); |
create a thread, specifying a reference for a class object that
implements the Runnable interface (that is, defines a method run()).
This method is executed as the "body" of the thread. In the case at
hand, the method simply checks the current state and draws the
appropriate circle or else clears the window in preparation for
starting another cycle.
We've set up a couple of Start and Stop buttons, tied to start() and
stop() methods in the applet. If Stop is selected, the thread
execution stops, and Start will restart the animation by reestablishing
the thread.
Threads are an important part of Java about which we will say more in
future issues.
In this issue we'll show a simple example of client/server
programming. The client will be an applet and the server a regular
Java program with a main() method in it.
In the client/server model, there is a server program running on some
computer. It accepts connections from clients across the network, and
serves each of them. For example, the client may be used to accept
some input from a user, which is sent to the server and entered into a
database. The server can also send information back to the client,
such as an acknowledgment.
Server and client communicate via what is known as a socket. More
technically, a socket is an endpoint of a communications channel, and
thus there is a socket on each end of the channel, one socket for the
server and one for the client.
Given a socket, it's possible to do conventional Java I/O between
server and client.
Let's now look at the programs:
// server.java import java.io.*; import java.net.*; public class server extends Thread { public static final int DEF_PORT = 1234;// port private int port; private ServerSocket listen; // server socket public server() { // set up the server socket port port = DEF_PORT; try { listen = new ServerSocket(port); } catch (IOException e) { System.err.println("socket creation error"); System.exit(1); } // start the server thread running start(); } public void run() { try { // accept connections and process them for (;;) { Socket cs = listen.accept(); connection c = new connection(cs); } } catch (IOException e) { System.err.println("connection error"); } } public static void main(String[] args) { new server(); } } class connection extends Thread { private Socket client; // client socket private DataInputStream in; // input from socket private PrintStream out; // output to socket public connection(Socket cs) { // set up an individual connection try { client = cs; InputStream is = client.getInputStream(); in = new DataInputStream(is); OutputStream os = client.getOutputStream(); out = new PrintStream(os); } catch (IOException e) { try { client.close(); } catch (IOException ee) { System.err.println("close error"); } System.err.println("socket stream error"); return; } // start it running start(); } public void run() { // read from socket input and write back to output try { for (;;) { String ln = in.readLine(); if (ln == null) break; if (ln.length() == 0) out.println("empty input"); else out.println("OK: " + ln); } } catch (IOException e) { System.err.println("server I/O error"); } // close connection finally { try { client.close(); } catch (IOException ee) { System.err.println("close error"); } } } } // client.java import java.applet.*; import java.awt.*; import java.io.*; import java.net.*; public class client extends Applet { public static final int DEF_PORT = 1234; // port Socket s; // socket DataInputStream in; // socket input private PrintStream out; // socket output TextField input_field; // input field TextArea out_area; // output display area public void init() { try { // set up socket String host = getCodeBase().getHost(); s = new Socket(host, DEF_PORT); in = new DataInputStream(s.getInputStream()); out = new PrintStream(s.getOutputStream()); // set up window input_field = new TextField(); out_area = new TextArea(); out_area.setEditable(false); setLayout(new BorderLayout()); add("North", input_field); add("Center", out_area); } catch (IOException e) { System.err.println("exception during setup"); } } public boolean action(Event e, Object o) { if (e.target == input_field) { // we have some input from the user try { // dump it to the server out.println((String)e.arg); input_field.setText(""); // read response String status = in.readLine(); out_area.setText(status); return true; } catch (IOException ee) { out_area.setText("server I/O error"); } } return false; } } |
The server is compiled as usual and simply started as a Java program:
$ java server |
while the client is driven via some HTML:
<html> <head> <title>Client Applet</title> </head> <body> <applet code="client.class" width=300 height=300></applet> </body> </html> |
If you start the client without the server being present, it will give
an error because of failure to connect to the server.
This particular server/client combination simply validates input from
the client and sends back an acknowledgment. Each client connection
runs as a separate thread, so that many clients can be served
simultaneously without the need for polling.
Note that the server knows to close a given client connection (the
client has gone away) by receipt of a "null" when reading input from
that connection. By contrast, the server "never" goes away; it must
be explicitly terminated by the user.
Note also that the socket port must be unique on the system running
the server, and that the server and client must agree on port
assignments.
An interesting aspect of applet programming that we've not talked
much about is the use of images. In this issue we'll present a simple
example of how images are loaded and manipulated.
Let's first look at some actual applet code:
// Filter.java import java.applet.*; import java.awt.*; import java.awt.image.*; public class Filter extends Applet { Image img; Image black; boolean flag = true; public void init() { img = getImage(getDocumentBase(), "image.gif"); ImageFilter f = new BlackFilter(); ImageProducer ip = new FilteredImageSource( img.getSource(), f); black = createImage(ip); repaint(); } public void update(Graphics g) { g.clearRect(0, 0, size().width, size().height); g.drawImage(flag ? img : black, 10, 10, this); flag = !flag; } public boolean mouseUp(Event e, int x, int y) { repaint(); return true; } } class BlackFilter extends RGBImageFilter { public BlackFilter() { canFilterIndexColorModel = true; } public int filterRGB(int x, int y, int rgb) { int a = rgb & 0xff000000; int r = (((rgb & 0xff0000) + 0xff0000) / 2) & 0xff0000; int g = (((rgb & 0xff00) + 0xff00) / 2) & 0xff00; int b = (((rgb & 0xff) + 0xff) / 2) & 0xff; //return a | r | g | b; return a | 0 | 0 | 0; } } |
which is driven by HTML code:
<html> <head> <title>Interface to Filter Applet</title> </head> <body> <applet code="Filter.class" width=300 height=300></applet> </body> </html> |
"image.gif" is not supplied in this newsletter and you'll need to find
your own image if you want to try this example.
This particular applet draws an image, and then toggles the image to
black and back to its original color at each mouse click. The
commented-out line of code at the bottom of the applet would instead
"gray out" the image by averaging its RGB (red/green/blue) color
values with white.
The applet is conventional in structure save for the image filtering
and the color manipulation.
The image is retrieved using getImage(), specifying a name
("image.gif") and a URL document base. We then create a black filter
and filter the loaded image through it. With image filtering, an
ImageProducer is a source of an image. A filter can be applied to the
ImageProducer, resulting in another ImageProducer from which an actual
image can be created using createImage(). This is a bit of
simplification of what the java.awt.image package is really about, but
it's sufficient for our purposes.
The RGB transformation averages each color with white, if graying out
is desired, or else simply returns 0 | 0 | 0 (black in the RGB color
scheme). The high 8 bits (mask 0xff000000) represent the "alpha"
value, used to represent transparency.
An example of another type of transformation on images would be
CropImageFilter(), that crops an image to a specified rectangle.
Use javac -O. This inlines functions (which makes the bytecode bigger) and removes line numbers (which makes it smaller). Unless you use lots of inlinable functions, the removal of line numbers will dominate and your bytecode will get smaller. However, note that things can sometimes be inlined when they shouldn't -- see the compilers page for details. |
Another such aid is memory usage advisement. This feature is used to
inform an application about where it stands with its use of memory, for
example whether it is close to exhausting memory. In such a case the
application might for example choose to flush some of its caches,
freeing up memory.
To see how this works, consider the following example:
class Rec { double data1; double data2; double data3; double data4; double data5; double data6; double data7; double data8; double data9; double data10; Rec next; } public class mem { private static int last_adv = -1; public static void getmem() { Runtime rt = Runtime.getRuntime(); int adv = rt.getMemoryAdvice(); if (adv == last_adv) return; last_adv = adv; switch (adv) { case Runtime.MemoryAdvice.GREEN: System.out.println("green"); break; case Runtime.MemoryAdvice.YELLOW: System.out.println("yellow"); break; case Runtime.MemoryAdvice.ORANGE: System.out.println("orange"); break; case Runtime.MemoryAdvice.RED: System.out.println("red"); break; } } public static void main(String args[]) { Runtime rt = Runtime.getRuntime(); Rec head = null; for (;;) { Rec r = new Rec(); r.next = head; head = r; getmem(); } } } |
Rec is a data record of some type, and main() allocates a long linked
list of these records. They cannot be garbage collected because all are
active (head points at the first, the first points at the second, and so
on).
At each iteration of the loop, getmem() is called to check the current
status of memory. When the program is run using JDK 1.2 beta 3, output
is:
green orange red java.lang.OutOfMemoryError at java.io.FileOutputStream.write(Compiled Code) |
The returned advice on memory goes through increasingly serious stages
(green, yellow, orange, red), with the latter stages indicating that the
application had better do something to free up some of its memory. Java
uses garbage collection rather than explicit freeing of memory, and to
free memory implies removing references from no-longer-used objects, so
that the garbage collector can free them.
Java supports the use of exceptions as an integral part of the language.
When an invalid condition is detected, an exception is thrown, and later
caught by an exception handler (or else the program aborts).
Exceptions can be used for more than error handling, however, and it's
interesting to consider their use in improving performance (see also the
March 1998 issue of Byte magazine for an article on this topic).
Suppose that you want to add up all the values of the elements in an
array. One obvious, and one not-so-obvious, approach to doing this is
as follows:
public class test { public static int sum1(int vec[]) { int s = 0; int len = vec.length; for (int i = 0; i < len; i++) s += vec[i]; return s; } public static int sum2(int vec[]) { int s = 0; try { for (int i = 0; ;i++) s += vec[i]; } catch (ArrayIndexOutOfBoundsException e) { } return s; } public static void main(String args[]) { int x[] = new int[1000000]; long t = 0; t = System.currentTimeMillis(); int s1 = 0; for (int i = 1; i <= 10; i++) s1 = sum1(x); System.out.println(System.currentTimeMillis() - t); System.out.println(s1); t = System.currentTimeMillis(); int s2 = 0; for (int i = 1; i <= 10; i++) s2 = sum2(x); System.out.println(System.currentTimeMillis() - t); System.out.println(s2); } } |
sum1() represents the obvious approach, while sum2() uses an exception
to break out of the loop. Note that no loop ending condition is
specified, because the Java language guarantees that array indices will
be checked, with an exception thrown if an invalid one is found. Using
JDK 1.1.3 without Just In Time compilation, the second method runs about
15% faster than the first.
This technique fits into the category of "clever" programming, of the
sort that can make code hard to understand, and that relies on certain
assumptions about how exceptions are implemented. Given this, it can't
be recommended as a general technique. But it may be useful if you're
desperate to improve performance, and it illustrates how exceptions are
integrated into the Java language.
In previous issues we've seen how Java programs are composed of ASCII
characters, with \uxxxx used to represent Unicode escapes for native
characters. Unicode escapes are translated in the first phase of Java
compilation.
The JDK contains a small utility program that translates native
character sets into ASCII and the reverse. For example, if I have in a
file the line:
\u0061b\u0063 |
and I say:
$ native2ascii -reverse infile outfile |
then the output file will have:
abc |
In other words, I converted an ASCII representation using Unicode
escapes back into its native representation.
This tool doesn't really have much use if you're already using ASCII to
formulate your Java programs, but is of value if you're using some other
character set.
In issue #013 we talked about reflection, a new feature whereby a
running program can examine and manipulate class and primitive types
within the program. Reflection relies on hooks in java.lang.Class.
Beyond added support for reflection, Class also has some new methods for
querying properties of types. To see how these work, consider an
example such as:
class A {} class B extends A {} public class test { public static void main(String args[]) { Object obj1 = new int[10]; Class c1 = obj1.getClass(); System.out.println(c1.isArray()); System.out.println(c1.getComponentType()); Class c2 = Double.TYPE; System.out.println(c2.isPrimitive()); Object obj3 = new A(); Object obj4 = new B(); System.out.println(obj3.getClass().isInstance(obj4)); Class c3 = A.class; Class c4 = B.class; System.out.println(c3.isAssignableFrom(c4)); System.out.println(c4.isAssignableFrom(c3)); } } |
In the first block of code, we create an array and assign it to an
Object reference. We then create a Class object to represent the array,
and query the object. isArray() returns true if the Class object is an
array, and getComponentType() returns the underlying primitive type
("int" in this case).
isPrimitive() is used to determine whether a Class object type
represents a primitive type such as "double".
isInstance() is the programmatic equivalent of the "instanceof"
operator. In the example above, we are essentially saying:
B object instanceof A |
which is true, because a subclass object is an instance of its
superclass type.
Finally, isAssignableFrom() is used to check whether one class reference
can be assigned to another. In this example, a B reference can be
assigned to an A one, but not the other way around.
The program output is:
true int true true true false |
Even if you don't use reflection, these methods are sometimes useful in
figuring out what sort of a type is being manipulated. Given a non-null
Object reference, you can get its Class object by calling getClass(),
and then use these methods.
In issue #016 we talked about the use of Hashtable to store lists.
java.util.Properties is a subclass of Hashtable optimized for storing
and manipulating name=value pairs.
A simple example of using the Properties class is this:
import java.util.Properties; public class prop { public static void main(String args[]) { Properties pr = System.getProperties(); pr.list(System.out); } } |
which displays system properties. For example, on my system the output
is:
-- listing properties -- user.language=en java.home=e:\java\bin\.. awt.toolkit=sun.awt.windows.WToolkit file.encoding.pkg=sun.io java.version=1.1.3 file.separator=\ line.separator= user.region=US file.encoding=8859_1 java.compiler=symcjit java.vendor=Sun Microsystems Inc. user.timezone=GMT user.name=glenm os.arch=x86 os.name=Windows NT java.vendor.url=http://www.sun.com/ user.dir=G:\tmp java.class.path=.;e:/java/lib/classes.zip;e:\java\bin... java.class.version=45.3 os.version=4.0 path.separator=; user.home=g:/. |
Besides the features of Hashtable (such as the get() and put() methods),
Properties supports the loading/saving of property lists to streams
(typically disk files), and supports default properties that are used if
a given property cannot be found.
Beyond the use for representing system characteristics, properties are
heavily used in internationalization, to provide locale-specific
settings. For example, a menu label can be given a tag such as
"exitTag", and this name looked up in a property file to find the
corresponding value that represents the label text for a given language.
PropertyResourceBundle is an example of a class that uses Properties and
that is targeted at internationalization support.
hashCode() is a method defined in java.lang.Object, the superclass of
all Java class types. It returns a hash code such as is needed by
java.Util.Hashtable.
For a given object, hashCode() always returns the same value, when
invoked multiple times during one Java program execution. However, the
value returned may be different from one execution to another, and
therefore permanently storing a hash code value, for example via
serialization, does not make sense. It's interesting to note that when
a Hashtable is serialized, only the key/value pairs are saved, and not
the internally saved hash code values, which may change at the point of
deserialization.
Also, hashCode() has the property that if calling Object.equals() to
compare two objects returns true, then the hashCode() methods for the
two objects must return identical results. Of course, hashCode() can
also return identical values for unequal objects.
The Object version of hashCode() will typically calculate the hash code
by converting the internal object address into an integer, but there is
no guarantee that the code will be computed in this way. Subclasses
such as java.lang.String reimplement hashCode() to calculate a value
that takes into account the specific characteristics of the particular
type of data being manipulated.
Another aspect of hashCode() is a new feature in 1.1, the method
System.identityHashCode(). This method can be used to compute the hash
code that Object.hashCode() would have come up with, even if the given
object's class overrides hashCode().
So, for example, running this program:
public class test { public static void main(String args[]) { String s = "this is a test"; System.out.println(s.hashCode()); System.out.println(System.identityHashCode(s)); System.out.println(System.identityHashCode(null)); } } |
results in three different values being printed. The first one is
computed via String.hashCode(). The second one computes the value that
Object.hashCode() would come up with, and the third value printed is 0
for the null reference.
This feature is used for doing object serialization, among other places.
It is useful when dealing with a variety of object types. Each type may
have its own hashCode() suitable for representing that type, but not
optimal across other types.
With this issue, we will be starting a series on internationalization
using Java. Internationalization refers to constructing applications
such that they can operate using other languages, currencies, dates and
times, and so on, with only minimal changes required.
The first aspect of this topic is Unicode, the character set used by
Java. Unicode represents characters as 16 unsigned bits, with values in
the range 0 - 65535. Every character uses two bytes. Printable ASCII
characters have an easy mapping to Unicode, by adding a 0 high byte to
the ASCII low byte. So, for example, a space is 0x0020.
Characters can be converted to integers without any cast, while
converting the other way requires a cast (and may not always make sense,
because an integer is 32 bits). For example, this program:
public class test1 { public static void main(String args[]) { char c = '\uffff'; int i = c; System.out.println(i); } } |
prints 65535. Note that \uNNNN is used to represent arbitrary Unicode
characters in hex format.
Unicode can be used to express program identifiers, in addition to its
use to express the values of specified characters. For example, this
program:
public class test2 { public static void main(String args[]) { int x\u0430 = 37; System.out.println(x\u0430); } } |
is legal. \u0430 is a letter (Cyrillic small letter A), and therefore
can be part of an identifier. Unicode translation takes place early in
the Java compilation process. Another aspect of this is that:
public class test3 { public static void main(String args[]) { char c = '\u000D'; } } |
is not a valid program, even though some Java compilers accept it.
Section 3.10.4 in the Java Language Specification says that a carriage
return (0x000D) may not appear as part of a character literal, and the
early Unicode translation means that a literal carriage return does in
fact appear, that is, Unicode translation results in:
char c = '<actual carriage return>'; |
\r needs to be used as a substitute for \u000D in this example.
java.lang.Character is a class for manipulating Java characters. It has methods for classifying characters, for example as to whether they are digits or letters. There is also a Web site http://www.unicode.org
that presents a lot of detail on how Unicode works. Note also that Java
support for Unicode doesn't necessarily mean that a tool using Java
(like a Web browser) will have the necessary fonts to display all
Unicode characters.
In the next issue we will be looking at some additional aspects of Java
character support.
In the last issue we saw how Java represents character data with the
Unicode character set. Each character requires two bytes or 16 bits.
The ability to support a wide range of different characters is
desirable, but potentially wasteful when targeting the many systems that
use 8-bit characters, and that have huge volumes of 8-bit textual data
stored in databases. To deal with this problem, Java supports the UTF-8
encoding as part of the DataInputStream and DataOutputStream classes.
With this encoding, characters are represented as follows:
\u0000 - \u007F 1 byte 0xxxxxxx \u0080 - \u07FF 2 bytes 110xxxxx 10xxxxxx \u0800 - \uFFFF 3 bytes 1110xxxx 10xxxxxx 10xxxxxx |
So the printable ASCII character set is represented as itself, that is,
using only one byte per character (except for the null byte, which is
encoded using a two-byte format to avoid embedded nulls in strings).
This encoding is thus quite efficient when the bulk of the characters
are ASCII.
An example of using UTF-8 looks like this:
import java.io.*; public class utf { public static void main(String args[]) { String tmp = "tmpfile"; try { FileOutputStream fos = new FileOutputStream(tmp); DataOutputStream dos = new DataOutputStream(fos); dos.writeUTF("testing\n"); dos.close(); FileInputStream fis = new FileInputStream(tmp); DataInputStream dis = new DataInputStream(fis); String instr = dis.readUTF(); dis.close(); System.out.print(instr); } catch (Throwable e) { System.err.println(e); } } } |
This example writes a string to a file and then reads it back. The
string length is written out as a two-byte value before the actual
string, so at most 65535 bytes of UTF-8 encoding are supported per
string.
We saw earlier how Java represents characters as 16-bit unsigned values.
This supports the representation of a variety of character sets.
But typically Unicode is not used to actually store characters in a disk
file. For example, in the United States, 8-bit ASCII is widely used
instead, both for text and binary data. We can say that ASCII
represents a "local encoding", and there needs to be some way to convert
a local encoding into Unicode and back.
One way that this is done is via I/O stream readers and writers. For
example, consider this application:
import java.io.*; public class encode { public static void main(String args[]) { String infile = args[0]; String outfile = args[1]; String encin = "8859_1"; // Latin-1 String encout = "8859_5"; // Cyrillic try { InputStream istr = new FileInputStream(infile); InputStreamReader ird = new InputStreamReader(istr, encin); BufferedReader br = new BufferedReader(ird); OutputStream ostr = new FileOutputStream(outfile); OutputStreamWriter owr = new OutputStreamWriter(ostr, encout); BufferedWriter bw = new BufferedWriter(owr); char buf[] = new char[4096]; int len; while ((len = br.read(buf)) != -1) bw.write(buf, 0, len); br.close(); bw.flush(); bw.close(); } catch (Throwable e) { System.err.println(e); } } } |
This program copies its input file to its output file, applying
different encodings to each. That is, input bytes are decoded using the
8859_1 encoding (ISO nomenclature for the Latin-1 character set) and
output bytes are encoded using the 8859_5 encoding (Cyrillic). So input
bytes will be converted to 16 bits via filling the top byte with 0, and
output characters (16 bits) will be converted to whatever representation
Cyrillic uses.
This approach is more expensive than doing low-level byte I/O, but is
important if you're concerned about supporting character sets other than
the default one for your locale.
A "locale" is a means of encapsulating information about a particular
country or geographical region or language or culture. In Java the
Locale class is used to represent such information. Objects of the
class do not provide internationalization behavior in and of themselves,
but are used by various other classes as a means of identifying what
behavior is desired. Examples might be varying date/time or currency
formats.
A simple example of Locale usage is this:
import java.util.Locale; public class testlocale { public static void main(String args[]) { Locale def = Locale.getDefault(); System.out.println("Default locale = " + def); Locale ger = Locale.GERMAN; System.out.println(def.getDisplayName(ger)); } } |
This program first retrieves the default locale and displays it. Then
it displays the default locale, using German as the display language.
Output is this:
Default locale = en_US Englisch (Vereinigte Staaten) |
There are a set of standard locales defined in Locale. You can also
create your own locales, based on strings representing a country,
language, and local variant. A variant is specific to a particular
implementation and platform.
We saw in a previous issue how locales could be represented using the
java.util.Locale class. A locale is a representation of a distinct
culture or language or region or set of customs.
To see how locales are used in practice, consider the problem of writing
a program that handles calendar dates and currency formats in a
locale-independent way. That is, the program should always do the right
thing, no matter where it's executed.
An example of handling local customs would be this:
import java.util.*; import java.text.*; public class date { public static void main(String args[]) { Date now = new Date(); DateFormat df_fr = DateFormat.getDateInstance(DateFormat.LONG, Locale.FRANCE); DateFormat df_us = DateFormat.getDateInstance(DateFormat.LONG, Locale.US); System.out.println(df_fr.format(now)); System.out.println(df_us.format(now)); NumberFormat pr_fr = NumberFormat.getCurrencyInstance(Locale.FRANCE); NumberFormat pr_us = NumberFormat.getCurrencyInstance(Locale.US); double d = 123.45; System.out.println(pr_fr.format(d)); System.out.println(pr_us.format(d)); } } |
In this program, we get the current date, and then set up a couple of
DateFormat objects, that encapsulate the desired format of the date
(SHORT for mm/dd/yy or LONG to have the date spelled out), along with
the locale the date is targeted for. We also obtain a NumberFormat
object to format currency values.
These objects then have particular values applied to them, for example,
the current date, or the value 123.45 in local units of currency (such
as francs or dollars). The output of the program is:
13 avril 1998 April 13, 1998 123,45 F $123.45 |
The above example could also be used without specifying particular
locales. In such a case, the default locale is used. Note that this
approach, using default locales, is not at all the same as assuming
particular customs. For example, if I say:
double d = 123.45; System.out.println("$" + d); |
then I am assuming particular currency-formatting customs. Making such
assumptions may be acceptable, as long as you realize that your
application will not work correctly in some other environment that has a
different set of customs.
If you've used the language C very much, you will be familiar with the
ubiquitous printf() library function:
printf("%s %3d %-4ld\n", a, b, c); |
for doing formatted output. A related function sprintf() does
formatting into a string. C++ has these facilities along with
additional ones for doing formatting.
What about formatting in Java? A 1.1 package called java.text is used for
this purpose. To see how it works, consider a simple example of
formatting error messages in two different styles:
Error at line 23 of file Test.java: ... File Test.java, line 23: ... |
An example of this type of formatting is:
import java.text.*; public class format { static String fmt1 = "Error at line {1} of file {0}: {2}"; static String fmt2 = "File {0}, line {1}: {2}"; public static String format(String fmt, String file, int ln, String msg) { Object values[] = new Object[3]; values[0] = file; values[1] = new Integer(ln); values[2] = msg; return MessageFormat.format(fmt, values); } public static void main(String args[]) { String err1 = format(fmt1, "Test.java", 37, "msg #1"); System.out.println(err1); String err2 = format(fmt2, "Test.java", 47, "msg #2"); System.out.println(err2); } } |
MessageFormat.format() takes a format string, along with a vector of
Objects that contain the values to be formatted. Primitive types like
integers are represented via wrappers (Integer in this case). Entries
in the format string like "{1}" are replaced with the corresponding
value.
The output of this program is:
Error at line 37 of file Test.java: msg #1 File Test.java, line 47: msg #2 |
The format string itself might be read from a property file (see issue
#025) or a resource bundle, and thus formatting can be customized on a
per-locale basis. In other words, you'd read in the format string from
a resource bundle, and then use it along with a set of object values to
create an actual string to display in an application.
Java 1.1 offers no standard way to sort lists of elements. This
deficiency has been remedied in 1.2 using a combination of mechanisms.
The first of these is the java.lang.Comparable interface, that imposes
an ordering on object instances of classes that implement the interface.
Such classes include all the standard wrapper classes such as Float and
Long, together with the String class. It's therefore possible to say,
for example, whether one String is ordered before another for purposes
of sorting.
There is an analogous interface java.util.Comparator for specifying
user-defined orderings. Comparator.compare(Object, Object) returns a
value indicating whether the first object is ordered before the second,
is equal to it, or comes after it.
The third new feature is the whole Collections framework. In 1.2
java.util has a number of important new classes and interfaces, that
implement collections of elements. These include lists, maps, sets, and
trees. The existing Vector class has also been brought into this
framework. Collections are a topic of their own that we may discuss at
some point.
One of the algorithms implemented for collections is sort(), and thus
we can say:
import java.util.*; public class compare { public static void main(String args[]) { Vector vec = new Vector(); vec.addElement("apple"); vec.addElement("peach"); vec.addElement("orange"); vec.addElement("banana"); vec.addElement("tangerine"); Collections.sort(vec); for (int i = 0; i < vec.size(); i++) System.out.println((String)vec.elementAt(i)); } } |
This uses the standard java.lang.Comparable ordering defined on Strings.
We could impose our own ordering for sort() by specifying a class
object that implements java.util.Comparator.
We've been looking at some of the classes contained in the Java core
libraries. Another interesting one of these is java.io.File. This
class is used to represent files, with a variety of operations
supported. To illustrate some of the operations, consider a program for
walking through a directory structure:
import java.io.*; import java.util.*; public class FileWalker { private Stack stk = new Stack(); public FileWalker(String fn) { stk.push(fn); } public synchronized String getNext() { if (stk.empty()) return null; String fn = (String)stk.pop(); File f = new File(fn); if (f.isDirectory()) { String lst[] = f.list(); int len = lst.length - 1; for (int i = len; i >= 0; i--) { File f2 = new File(fn, lst[i]); stk.push(f2.getPath()); } } return f.getPath(); } public static void main(String args[]) { FileWalker fw = new FileWalker(args[0]); String path = null; while ((path = fw.getNext()) != null) System.out.println(path); } } |
The idea here is to keep a stack of directories/files to be visited, and
then visit them in turn. When subdirectories are encountered, their
entries are stacked up as well.
File represents a file or directory, and operations such as asking
whether a given path is a directory or not are supported. The contents
of a directory can be looked up using the list() method, and this
feature is used to traverse subdirectories and get their entries. The
entries are pushed on the stack and traversed in turn.
Note that we use a File constructor that takes a directory and an entry
in that directory, to form a new item to be pushed on the stack for
later traversal. And then later we use getPath() to return the actual
paths. What we avoid doing is making assumptions about path separators
like "\" or "/". This issue is handled internally within the File
class.
getNext() is a synchronized method, meaning that the Java Virtual
Machine obtains a lock on the underlying object before the method is
entered. Why does this matter? If two threads are using a FileWalker
object, and both of them call getNext() simultaneously without any
synchronization being done, confusion may result due to the fact that
getNext() updates its internal state (the stack). Synchronization
guarantees that only one thread will execute getNext() at a time.
In a previous issue we saw an example of static initializers for
classes, where one-time initialization of a class can be performed.
Java 1.1 extends this idea to instance initialization, that is, blocks
of code in a class that are executed every time a new class instance is
created. To get an idea of how this works, consider an example such as:
class A { public A() { System.out.println("A ctor"); } } class B extends A { static { System.out.println("B static init"); } public B() { super(); System.out.println("B ctor"); } { System.out.println("B instance init"); } } public class init { public static void main(String args[]) { B b1 = new B(); B b2 = new B(); } } |
Here we have a superclass A, and a subclass B. We create two instances
of B within main(). The output from running this program is:
B static init A ctor B instance init B ctor A ctor B instance init B ctor |
The static code block in B is executed first, and only one time. Then,
for each instance of B that is created, the superclass constructor is
run, then the block of code representing an instance initializer in B,
and then B's constructor.
Such an instance initializer is similar to a no-argument constructor.
So why would you use an initializer like this instead of a constructor?
One reason is for initializing instances of anonymous classes (see next
section), which do not have names and cannot define their own
constructors.
Another use of instance initializers is to support initialization of
object fields near the definition of those fields, rather than
performing initialization in a constructor which may be some distance
away.
Here is a very simple one. The core Java libraries contain several
interfaces of this type:
public interface Cloneable {} |
alone in a source file. Why would you want to do this, given that the
interface doesn't define anything?
This technique can be used to "mark" a class, to specify that it has a
given property, and sometimes goes by the name of "marker interface".
In the case of Cloneable, Object.clone(), a native method for making a
copy of an object, requires that the object implement the Cloneable
interface, with an CloneNotSupportedException thrown if not.
So if you'd like a class that you develop to be cloneable, you need to
say:
public class MyClass implements Cloneable { ... } |
and this property can then be tested using the "instanceof" operator.
Similar considerations apply for Serializable, that is, a class that
supports conversion to/from a byte stream needs to implement
Serializable.
import java.io.*; public class test4 { public static void main(String args[]) { try { FileInputStream fis = new FileInputStream(args[0]); DataInputStream dis = new DataInputStream(fis); int cnt = 0; while (dis.readLine() != null) cnt++; fis.close(); System.out.println(cnt); } catch (Throwable e) { System.err.println("exception"); } } } |
This is quite slow, in part because DataInputStream.readLine() does a
method call (read()) for each character.
A faster way in Java 1.1 is to say:
import java.io.*; public class test3 { public static void main(String args[]) { try { FileReader fr = new FileReader(args[0]); BufferedReader br = new BufferedReader(fr); int cnt = 0; while (br.readLine() != null) cnt++; fr.close(); System.out.println(cnt); } catch (Throwable e) { System.err.println("exception"); } } } |
This runs about 4 times as fast as the old approach, and avoids a read()
call for each character. readLine() in this case grabs the underlying
data in large buffered chunks.
In Java the class "java.lang.Class" has always been used to represent
class and interface types. A statement like:
Class c = null; try { c = Class.forName("java.lang.String"); } catch (Throwable e) { } |
will dynamically load the specified class if it is not already loaded.
In Java 1.0 a Class object can also represent arrays, as a special type
of class.
In Java 1.1, this notion has been broadened to include any Java type, and
there is an easier way to obtain the Class object for a type. For
example, the following code prints "equal":
public class test1 { public static void main(String args[]) { try { Class c1 = Class.forName("java.lang.String"); Class c2 = java.lang.String.class; if (c1 == c2) System.out.println("equal"); } catch (Throwable e) { } } } |
In this example the new approach uses ".class" appended to a class
specification to get the Class object reference.
This feature can also be used on fundamental types, for example:
public class test2 { public static void main(String args[]) { Class c1 = Integer.TYPE; Class c2 = int.class; if (c1 == c2) System.out.println("equal"); } } |
That is, a type like "int" can be represented via a Class object.
One reason why this new feature is important is that Java 1.1 also
introduces nested or inner classes, classes defined within other
classes. Java uses a name like "A$B.class" to name a disk file that
refers to a class B nested in a class A, and using the older
Class.forName() approach requires knowledge of this encoding.
A simple but valuable class in java.util is BitSet, used for keeping
track of a set of boolean values. For example, this code:
import java.util.*; public class test5 { public static void main(String args[]) { BitSet b = new BitSet(); b.set(57); b.set(109); b.set(0); b.clear(109); for (int i = 0; i < 200; i++) { if (b.get(i)) System.out.print(i + " "); } System.out.println(); } } |
displays the output:
0 57 |
The set of values grows dynamically as needed. It is also possible to
compare two sets, or perform AND, OR, and XOR operations.
We are going to spend a few columns looking at some of the new 1.1
language features. The biggest of these is inner classes, which
warrants its own set of columns.
One new feature is anonymous arrays. In Java 1.0, you could say
things like:
int x[] = {1, 2, 3}; |
while saying:
int x[]; x = {1, 2, 3}; |
was illegal, because the {} was supported only in initializer
expressions following a declaration.
This usage is not legal in 1.1 either, but instead you can say:
int x[]; x = new int[] {1, 2, 3}; |
That is, instead of using "new int[3]", leave out the size and follow
the [] with a list of the actual initializer values.
In Java the Vector class (java.util.Vector) is widely used in a
variety of ways, to store lists of objects.
A lesser-known class, that is a subclass of Vector, is Stack. Stack
supports a last-in-first-out (LIFO) set of objects, and uses the usual
operations -- push(), pop(), and so on. For example, this program
will display a list of numbers in descending order:
import java.util.*; public class stack { public static void main(String args[]) { Stack st = new Stack(); for (int i = 1; i <= 10; i++) st.push(new Integer(i)); while (!st.empty()) System.out.println(st.pop()); } } |
There is also a peek() method for examining the top of stack value
without popping it.
Stack is derived from Vector, and as such, the methods of Vector are
also available to a Stack user. But it is perhaps not a good idea to
take advantage of this fact, for example by inserting a value into the
middle of the stack. Doing so breaks the stack paradigm and makes it
harder to reason about the operation of a program.
Consider a bit of code like:
public class perf1 { private char buf[] = new char[4096]; public int find(char c) { for (int i = 0; i < buf.length; i++) { if (buf[i] == c) return i; } return -1; } public static void main(String args[]) { perf1 p = new perf1(); int n = 2500; while (n-- > 0) p.find('x'); } } |
where there is an object instance of a class, with an internal buffer
that you want to search repeatedly. This code runs pretty slowly
(around 14.5 seconds on a slow Pentium with JDK 1.1.2). Is there any
way to speed it up?
There are a couple of improvements that can be made, centering around
accessing instance variables (fields) of an object. It turns out that
such access is quite slow, with calls to the "getfield" instruction in
the Java Virtual Machine. That is, the above code accesses "this.buf"
twice in each iteration of the loop in find(), once for determining
buf.length and the other for accessing buf[i].
Rewriting the code as:
public class perf2 { private char buf[] = new char[4096]; public int find(char c) { char xbuf[] = buf; for (int i = 0; i < xbuf.length; i++) { if (xbuf[i] == c) return i; } return -1; } public static void main(String args[]) { perf2 p = new perf2(); int n = 2500; while (n-- > 0) p.find('x'); } } |
results in around a 15% speedup, with the reference to "this.buf"
replaced by a local stack-based variable "xbuf". This simply adds
another reference to buf, with no copying of data or anything
implied. A Java compiler might be able to automatically supply this
optimization.
A further speedup can be realized by saying:
public class perf3 { private char buf[] = new char[4096]; public int find(char c) { char xbuf[] = buf; int n = xbuf.length; for (int i = 0; i < n; i++) { if (xbuf[i] == c) return i; } return -1; } public static void main(String args[]) { perf3 p = new perf3(); int n = 2500; while (n-- > 0) p.find('x'); } } |
to eliminate calls to the JVM "arraylength" instruction. This gives
another 10%, or a total of 25% from the original code.
Of course, such tweaking has a downside, leading to more obscure code.
But it's a useful performance improvement in certain cases where
instance variables in a class are heavily accessed.
In Java, it is possible to represent a set of primitive types like
integers in at least two ways. One is simply as an array of actual
values:
int x[] = {1, 2, 3}; |
while the other uses a wrapper class:
Integer x[] = {new Integer(1), new Integer(2), new Integer(3)}; |
In terms of actually representing a set of values, the first of these
is more efficient in speed and space, while the second is more general.
As a rule of thumb, representing an integer via a wrapper takes about
12-16 bytes, compared to 4 in an actual array of ints. And
retrieving the value of the integer using Integer.intValue() may
involve a method call.
But if you need to store a list of integers in a Vector or Hashtable,
or you'd like to store an integer in a mixed list of values (for
example, Integers and Strings), then the wrapper is the only way to go.
Wrapper classes exist for all primitive types, with some of the
wrapper classes such as Short added in 1.1.
These classes have another important function, which is to group
together a set of methods that operate on actual primitive types. For
example, consider the class (static) method Integer.toHexString(int).
This can be used to display an integer in hex format:
public class test { public static void main(String args[]) { int i = 123456; System.out.println(Integer.toHexString(i)); } } |
Beyond the usual methods for converting primitive types to Strings and
back again, and the accessor methods like intValue(), the wrapper
classes also define constants for a given primitive type. For
example, Short.MAX_VALUE is 32767, the largest value that may be
represented by a short. Float and Double also have constants for
negative and positive infinities and for NaN (a special constant,
not-a-number).
The Character class has a variety of methods for categorizing
characters, for example the isLetter(char) method. These methods work
by consulting a large table found within Character.java in the JDK
source code. If you are used to using C/C++, these methods may be
familiar, but their implementation is far more complex because the
Unicode character set is used. What constitutes a letter in Unicode
is quite different than asking a similar question when using the ASCII
character set.
In previous issues we've mentioned that Java has no sizeof() operator
like C/C++. With uniform sizes for primitive data types, and a
different style of memory allocation, the need for sizeof() really
isn't there. And it's hard to define what sizeof() would mean anyway,
given that an object may not contain other objects, but only
references to them.
But it's interesting to experiment with the 1.1 reflection feature and
see whether a method can be devised that will return useful
information about object sizes.
The Sizeof class below tries to do this, for a passed-in data
structure. It walks the structure and tallies up the total size in
bytes. It ignores alignment and packing issues and hidden fields in
structures, and assumes a boolean is of size 1 and a reference of size
4 (reference sizes may vary; for example SZ_REF might be 8 on a
machine with 64-bit pointers).
It does not count static data members of class instances, but does
include members inherited/implemented from superclasses and interfaces.
It does not follow references in object instances or in arrays, except
for the case of a multi-dimensional array, where the reference is to
another array.
Included are some tests that illustrate what results are expected in
given cases.
import java.lang.reflect.*; class test_Class1 { private int a1; public byte a2; protected char a3[]; static byte a33; } class test_Class2 extends test_Class1 { static float a33; byte a3; short a4; double a5[] = new double[10]; } class test_Class3 extends test_Class2 { boolean a4; static int a44; char a5; long a6; } interface test_Class4 { public byte x = 1; } interface test_Class5 extends test_Class4 { public int x = 2; } class test_Class6 { public double x; } class test_Class7 extends test_Class6 implements test_Class5 { char x[] = null; } public class Sizeof { private static final int SZ_REF = 4; public static int sizeof(boolean b) { return 1; } public static int sizeof(byte b) { return 1; } public static int sizeof(char c) { return 2; } public static int sizeof(short s) { return 2; } public static int sizeof(int i) { return 4; } public static int sizeof(long l) { return 8; } public static int sizeof(float f) { return 4; } public static int sizeof(double d) { return 8; } private static int size_inst(Class c) { Field flds[] = c.getDeclaredFields(); int sz = 0; for (int i = 0; i < flds.length; i++) { Field f = flds[i]; if (!c.isInterface() && (f.getModifiers() & Modifier.STATIC) != 0) continue; sz += size_prim(f.getType()); } if (c.getSuperclass() != null) sz += size_inst(c.getSuperclass()); Class cv[] = c.getInterfaces(); for (int i = 0; i < cv.length; i++) sz += size_inst(cv[i]); return sz; } private static int size_prim(Class t) { if (t == Boolean.TYPE) return 1; else if (t == Byte.TYPE) return 1; else if (t == Character.TYPE) return 2; else if (t == Short.TYPE) return 2; else if (t == Integer.TYPE) return 4; else if (t == Long.TYPE) return 8; else if (t == Float.TYPE) return 4; else if (t == Double.TYPE) return 8; else if (t == Void.TYPE) return 0; else return SZ_REF; } private static int size_arr(Object obj, Class c) { Class ct = c.getComponentType(); int len = Array.getLength(obj); if (ct.isPrimitive()) { return len * size_prim(ct); } else { int sz = 0; for (int i = 0; i < len; i++) { sz += SZ_REF; Object obj2 = Array.get(obj, i); if (obj2 == null) continue; Class c2 = obj2.getClass(); if (!c2.isArray()) continue; sz += size_arr(obj2, c2); } return sz; } } public static int sizeof(Object obj) { if (obj == null) return 0; Class c = obj.getClass(); if (c.isArray()) return size_arr(obj, c); else return size_inst(c); } private static void err(String s) { System.err.println("*** " + s + " ***"); } private static void test() { if (sizeof(null) != 0) err("null"); if (sizeof(true) != 1) err("boolean"); if (sizeof((byte)37) != 1) err("byte"); if (sizeof('x') != 2) err("char"); if (sizeof((short)37) != 2) err("short"); if (sizeof(37) != 4) err("int"); if (sizeof(37L) != 8) err("long"); if (sizeof(37.0f) != 4) err("float"); if (sizeof(37.0) != 8) err("double"); if (sizeof(new boolean[0]) != 0) err("boolean[0]"); if (sizeof(new byte[10]) != 10) err("byte[10]"); if (sizeof(new char[10][10]) != 200 + 10 * SZ_REF) err("char[10][10]"); if (sizeof(new short[10][11][12]) != 2640 + 120 * SZ_REF) err("short[10][11][12]"); if (sizeof(new int[0][10]) != 0) err("int[0][10]"); if (sizeof(new String[100]) != 100 * SZ_REF) err("String[100]"); if (sizeof(new String[10][10]) != 110 * SZ_REF) err("String[10][10]"); Object ov[] = new Object[3]; ov[0] = new byte[10]; ov[2] = new double[10]; if (sizeof(ov) != 90 + 3 * SZ_REF) err("Object[3]"); String sv[] = new String[10]; for (int i = 0; i < 10; i++) sv[i] = new String(); if (sizeof(sv) != 10 * SZ_REF) err("String[10]"); if (sizeof(new Object()) != 0) err("Object"); if (sizeof(new Integer(37)) != 4) err("Integer(37)"); if (sizeof(new test_Class1()) != 5 + SZ_REF) err("test_Class1"); if (sizeof(new test_Class2()) != 8 + 2 * SZ_REF) err("test_Class2"); if (sizeof(new test_Class3()) != 19 + 2 * SZ_REF) err("test_Class3"); if (sizeof(new test_Class7()) != 13 + SZ_REF) err("test_Class7"); } public static void main(String args[]) { test(); } } |
People often ask about how to do printing in Java. There are at least
two ways this can be done. We will illustrate one approach here, and
another later in this issue.
One simple way to print is to use device files. With UNIX, this
involves writing into the file /dev/lp, and with Windows, use of a file
called "lpt1". Bytes written into such a file show up on the
printer. For example:
import java.io.*; public class lpr { public static void main(String args[]) { try { FileOutputStream fos = new FileOutputStream("lpt1"); String s = args[0]; for (int i = 0; i < s.length(); i++) fos.write((byte)s.charAt(i)); fos.write((byte)0xC); fos.close(); } catch (Throwable e) { System.err.println("*** exception ***"); } } } |
This will print the specified string on the printer, and then issue a
form feed (0xC).
Normally, "lpr" would be expanded to do several additional things, for
example expanding tabs, putting margins at the top and bottom and
sides of the page, and handling multiple copies.
This particular example sidesteps the issues of coordinating printing
with a print queue manager, displaying Unicode, remote printing, and
so on.
Below is another printing example, this one using the Abstract Windowing Toolkit.
In Java String objects are immutable, that is, they cannot be changed
once created. For example:
String s = "abc"; String t = s; s = s.toUpperCase(); System.out.println(t); |
displays "abc", not "ABC". Both s and t start out referring to a
String object. That object does not change when the upper case
conversion is done, but instead, another object is created and s is
made to refer to it.
For operations involving mutable strings, the class StringBuffer can
be used. Some of its methods, like charAt(), overlap those found in
String, but many of the operations focus on building up new strings of
characters. There are many append() methods, for example.
For example, the "+" operator on strings in Java is implemented by
replacing a line such as:
String s = "xyz" + 37; |
with:
s = (new StringBuffer("xyz")).append(37).toString(); |
For accumulating strings, a quick and dirty approach like:
String s = ""; s += "xyz"; |
can be used, but this is a lot more expensive than using
StringBuffers, because the String must be converted to a StringBuffer
and back again. In issue #004 we gave an example where using a
StringBuffer resulted in about a 6X speedup. In this example, we
would say:
StringBuffer sb = new StringBuffer(); sb.append("xyz"); String s = sb.toString(); |
In the next section we will look further at StringBuffer performance.
One of the most common uses of StringBuffer is to accumulate a set of
characters, and then convert the result to a String:
StringBuffer sb = new StringBuffer(); sb.append('a'); sb.append('b'); sb.append('c'); String s = sb.toString(); |
Is it possible to improve on the performance of the StringBuffer
append() method? At the moment, the answer appears to be yes:
public class AppendChar { private char buf[] = null; private int len = 0; public AppendChar() { buf = new char[16]; } public synchronized void append(char c) { if (len == buf.length) { char x[] = new char[len * 2]; System.arraycopy(buf, 0, x, 0, len); buf = x; } buf[len++] = c; } public String toString() { return new String(buf, 0, len); } } |
This runs about twice as fast as the standard version, because it
internally handles checking of overflow, instead of calling
ensureCapacity().
Whether it is "right" to roll your own version of a library method
depends on how desperate for speed you are. Most of the time,
append() speed doesn't matter, but when it does, this approach may be
useful. One of the classic performance bottlenecks is that of doing
lots of operations for every character of input in a program.
If you're particularly interested in Java performance, see the Web page http://www.glenmccl.com/~glenm/perflib/index.html
for further information about this type of approach. This page
describes a Java performance library.
Java is similar to other programming languages in that it consists of
the actual Java language, with libraries added on. We're going to use
the generic term "library" to refer to what is often called an "API"
or a "package".
The term "core" is also used to refer to certain of the libraries that
are required to run a Java program. Core libraries include:
java.lang java.util java.io |
The language proper "knows" about some of these. For example, the
operator "+" has a certain meaning when applied to Strings. And
standard I/O that is made available to a running Java program relies
of course on the java.io package.
We will be exploring the use of some of the library classes in a
series of columns. In the first one, we'll look at two classes found
in java.util, Hashtable and StringTokenizer, and a helper interface
known as Enumeration.
Suppose that you'd like to write a program that reads from a file and
displays a list of all the unique words in the file. How might this
be done? We'll assume that a "word" is any sequence of characters
delimited by whitespace.
One approach looks like this:
import java.util.*; import java.io.*; public class Word { public static void doit(String fn) { Hashtable ht = new Hashtable(); try { FileInputStream fis = new FileInputStream(fn); DataInput d = new DataInputStream(fis); String s = null; while ((s = d.readLine()) != null) { StringTokenizer st = new StringTokenizer(s); while (st.hasMoreTokens()) ht.put(st.nextToken(), ""); } fis.close(); } catch (Throwable e) { System.err.println("*** exception ***"); } Enumeration en = ht.keys(); while (en.hasMoreElements()) System.out.println((String)en.nextElement()); } public static void main(String args[]) { doit(args[0]); } } |
We first set up to read lines from a disk file. As each line is read,
we create a StringTokenizer class instance. This class is used to
split a String into pieces. By default, the delimiters are space,
tab, carriage return, and newline (" \t\r\n"), but you can specify
your own set of delimiters to the StringTokenizer constructor.
We iterate based on hasMoreTokens(), picking off each token in turn.
These tokens, as Strings, are added to the hash table. put() takes
two Object parameters, a key and a value. A String is derived from
Object, and may be supplied as a key argument. We don't care about the
associated value with the String key, and so we use "" for this. If
we did care, for example to count how many times each word occurs, we
could set up a Counter class type and supply an instance of it as the
value.
A hash table contains only one instance of each key. To find out what
words were encountered, we can retrieve the keys and display them.
This is done via an Enumeration. Enumeration is not a class, but an
interface. That is, it specifies a set of methods that a class that
"implements" it must define. The two methods in this case are
hasMoreElements() and nextElement(). The keys() method in Hashtable
returns an HashTableEnumerator class instance. Because it implements
Enumeration, it can be manipulated in a way similar to the superclass
/ subclass paradigm, through a reference to an Enumeration.
The key hook required to make a Hashtable work is the hashCode()
method defined in Object and overridden by derived classes such as
String. hashCode() supplies the raw value used to insert an entry
into the table.
The approach outlined here is quite simple to use. If you are
desperate for performance, some lower-level techniques are faster,
though not as convenient.
Suppose that you'd like to take the program from the previous section
and run it in the Windows environment, using an icon to invoke it
(rather than from the command line). How would you do this? There is
a program called "javaw" that comes with the JDK. You can use it to
run standalone AWT programs using a Windows interface.
To do this, you need to set up a new Program Item in the standard way,
with entries like:
Description: Delegate Command Line: e:\java\bin\javaw Delegate (this will vary) Working Directory: g:\tmp (this will vary) |
When you click on the icon, the program will come up.
There's a certain amount of magic in making something like this work,
but if you're interested in this area, you might try experimenting
with this technique.
Imagine that you have a complex data structure in memory, one with
many internal links and fields in it. At the end of the execution of
an application, you'd like to somehow save this data structure
permanently and then restore it for the next execution of the
application.
Java serialization, new in version 1.1, is a way of doing this. It's a
mechanism for turning a data structure into a stream of bytes, which
can be written to a file, and then read back in to another data
structure.
Let's look at a simple example of this:
// file write.java import java.io.*; public class write { private static final int N = 25; public static void main(String args[]) { int x[][] = new int[N][2]; for (int i = 0; i < N; i++) { x[i][0] = i; x[i][1] = i * i; } try { FileOutputStream fos = new FileOutputStream("xxx"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(x); oos.flush(); fos.close(); } catch (Throwable e) { System.err.println("exception thrown"); } } } // file read.java import java.io.*; public class read { public static void main(String args[]) { int x[][] = null; try { FileInputStream fis = new FileInputStream("xxx"); ObjectInputStream ois = new ObjectInputStream(fis); x = (int[][])ois.readObject(); fis.close(); } catch (Throwable e) { System.err.println("exception thrown"); } for (int i = 0; i < x.length; i++) System.out.println(x[i][0] + " " + x[i][1]); } } |
In this example, we build a table of squares in a two-dimensional
array, then write the array to a file using writeObject(). We then
read the array back into a separate program using readObject(), casting
the Object reference to the appropriate type.
This operation may not seem like much, but it's hard to do in other
languages, and it's necessary to devise various ad hoc methods for
doing so.
For a class to be serializable, it must implement the Serializable
interface:
public class xxx implements java.io.Serializable { // stuff } |
This interface is empty, and simply serves as a flag to allow you to
specify which classes are serializable. In the example above, the
object we serialized was an array, treated as a class type by Java.
Classes which require special handling during serialization can
implement their own writeObject() and readObject() methods.
There are several interesting quirks with serialization which we may
discuss at some point.
One of the interesting 1.1 features is something known as reflection,
where it is possible to query a class at run time to determine its
properties. For example, with this code:
import java.lang.reflect.*; public class Dump { public static void main(String args[]) { try { String s = "java.lang." + args[0]; Class c = Class.forName(s); Method m[] = c.getMethods(); for (int i = 0; i < m.length; i++) System.out.println(m[i].toString()); } catch (Throwable e) { } } } |
one can query a class for the names and properties of its public
methods, using the new package "java.lang.reflect". There is also
support for accessing private and package level methods.
Additionally, you can dynamically invoke methods on a given object.
Running this program by saying:
$ java Dump Object |
results in:
public final native java.lang.Class java.lang.Object.getClass() public native int java.lang.Object.hashCode() public boolean java.lang.Object.equals(java.lang.Object) public java.lang.String java.lang.Object.toString() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll() public final native void java.lang.Object.wait(long) public final void java.lang.Object.wait(long,int) public final void java.lang.Object.wait() |
This type of feature simply doesn't exist in a language like C or
C++. Certain programming environments or debuggers may offer an
equivalent, but not as part of the language or its core libraries.
In issue #011 we talked about Java's lack of a preprocessor, such as
used in C and C++. This lack is both a blessing and a curse. One
common idiom in C is to use conditional compilation, for example, the
use of DEBUG to control program execution and possibly take different
action such as dumping out more information to the user:
#ifdef DEBUG debugging stuff ... #endif |
The preprocessor runs before the compiler proper and so the use of
#ifdef results in different subsets of the program actually being
compiled based on which constants (such as DEBUG) are set.
In Java, we could achieve a similar end by saying something like:
public class test1 { public static final boolean DEBUG = false; public static void main(String args[]) { for (int i = 1; i <= 10; i++) { if (DEBUG) System.out.println("i = " + i); } } } |
which will dump out the loop index at each loop iteration, if DEBUG is
true.
There is a quirk with this worth noting. A program like:
public class test2 { void f() { int x = 0; while (false) x = -37; } } |
is invalid, even thought it's superficially like the one above, in
that in one case we have:
if (false) statement; |
and in the other:
while (false) statement; |
In both cases the statement is unreachable, but Java special cases the
"if" case, in order to support conditional compilation. See 14.19 in
the "Java Language Specification" for a more detailed discussion of
this point.
This approach to "conditional compilation" still requires that the
code be valid. That is, with use of the C/C++ preprocessor
conditional compilation is textually based, in that excluded code is
never presented to the actual compiler, whereas the scheme above is
logically based -- the code is simply never executed.
Lex and Yacc are widely-used tools for doing lexical scanning and
parsing of input. You describe the structure of the input and these
tools generate programs (in C) that will accept and structure such
input. For example, an identifier might be lexically described as:
[a-zA-Z_][a-zA-Z_0-9]* |
meaning that the identifier starts with a letter or underscore,
followed by zero or more letters, underscores, or digits.
Similarly, a simple expression in BNF (Backus Naur Form) could look
like:
E -> T | E + T T -> F | T * F F -> number | ( E ) |
This is known as a grammar, and programming languages like Java are
formally described via a grammar. O'Reilly and others publish books
on Lex/Yacc, if you want information on these tools.
Jack is a new tool that combines Lex and Yacc capabilities. It's written in Java and produces Java output, that is, produces Java programs for parsing input described via a grammar. Jack is available on the Web at http://www.suntest.com/Jack
You need the Java Development Kit 1.0.2 to use this program. With
Jack you describe the lexical and syntactic structure of your program,
and then have it produce Java programs that understand this structure.
As an example of a problem made simple by a tool of this type,
consider the issue of extracting from Java programs basic information
about what is defined where, for example, where a specific class or
method is found in the source code. With Jack, it's simply a matter
of dumping out key information whenever a particular construct like a
method declaration is found.
For example, we might have this format for the output:
String.charAt method 204 java/src/java/lang/String.java |
meaning that the charAt() method of class String is defined at line
204 of String.java.
An index produced in this format, representing all the classes, interfaces, methods, and constructors for JDK 1.0.2, is available via FTP at ftp://ftp.glenmccl.com/morespace1/glenmccl/jack
along with a simple UNIX shell script browser to search and access any
of the symbols in the database.
There are many other possibilities for the use of a tool of this type,
for example test coverage or generation of class metrics. If you have
interest in this area, please send me a note (glenm@glenmccl.com).
You may have heard the term "JavaScript". What is its relation to
Java and HTML? JavaScript started out as LiveScript and its
connection to Java is fairly loose, sharing some Internet heritage but
not that much else.
HTML (Hyper Text Markup Language) is not really a programming language
in the usual sense of the term, but rather a language that describes
what a page ought to look like. You can't really "do anything" with
HTML.
Java is a general purpose programming language. One type of Java
program, an applet, is downloadable by a Web browser and then executed
locally by the browser. This is useful, for example, in communicating
with remote databases. An applet's invocation is described via the
HTML tags <applet> and </applet>.
JavaScript can be viewed as a language something like Perl, or as an
extension of HTML to allow customized functionality. As an example of
what a JavaScript application would look like, consider this example:
<html> <body> <a href="link1.html">Link #1</a> <br> <a href="link2.html">Link #2</a> <br> <script> <!-- function doit() { document.write("<br>This page's links are:<br><br>"); for (var i = 0; i < document.links.length; i++) { var s = ""; s += (i + 1) + ". "; s += document.links[i]; s += "<br>"; document.write(s); } } doit() <!-- --> </script> </body> <html> |
This particular application iterates over the links in a page and
displays them in a numbered list:
1. link1.html 2. link2.html |
JavaScript as a programming language has a flavor something like Perl
or Awk, with weak typing and domain-specific system variables like
"document.links[]".
Note that the output of a JavaScript script is HTML, so for example we
use "<br>" instead of "\n" to go to the next line. In the above
example, the function doit() is defined, and then called to produce
HTML.
We will probably not say more about JavaScript, but there are a
variety of books available on the topic. You need a Web browser like
Netscape 3.0 to execute JavaScript programs.
As we've discussed previously, there are still some issues around the
performance of Java, and thus there is a premium on the use of
efficient algorithms.
One such algorithm is the familiar one of binary search, where a
sorted list is searched by continually halving the search area until
the desired element is found or it is determined that the element is
not in the list.
In Java this might look like:
import java.util.Vector; public class Search { // no one should instantiate this class // since it exists only as a packaging vehicle // for the static method search() private Search() {} public synchronized static int search(Vector v, Object objp, Orderable op) { if (v == null || objp == null || op == null) throw new IllegalArgumentException("null arg"); int low = 0; int high = v.size() - 1; while (low <= high) { int mid = (low + high) / 2; int c = op.compareTo(objp, v.elementAt(mid)); if (c < 0) high = mid - 1; else if (c > 0) low = mid + 1; else return mid; } return -1; } } |
where an index 0 <= index < N is returned, or -1 if the element is not
found. The Vector class found in Java does not automatically maintain
a sorted order for the items in the vector.
This algorithm is straightforward save for the notion of an
Orderable. What is this? An Orderable is an interface:
public interface Orderable { public int compareTo(Object p1, Object p2); } |
If a given class implements an interface, that means that it defines
the methods of the interface. This is the method of choice at the
moment for passing a method reference as an argument, or in C/C++
terms, passing a pointer to a function. In other words, we create an
instance of a class that implements the Orderable interface, which
means that the instance will have a compareTo() method that we can
call out to.
To see how this works, consider this example that uses the search
class:
import java.util.*; class Ord implements Orderable { public int compareTo(Object p1, Object p2) { int n1 = ((Integer)p1).intValue(); int n2 = ((Integer)p2).intValue(); if (n1 > n2) return 1; else if (n1 < n2) return -1; else return 0; } } public class test { public static void main(String args[]) { Vector v = new Vector(); int N = 10000; int i = 0; for (i = 0; i < N; i++) v.addElement(new Integer(i)); for (i = 0; i < N; i++) Search.search(v, new Integer(i), new Ord()); } } |
We create a Vector of 10000 integers, each wrapped in an Integer
wrapper. We then search for each in turn, passing to Search.search()
an object instance of Ord, a class that implements the Orderable
interface and thus is guaranteed to contain a compareTo() method for
comparing two Object references that refer to Integer wrappers.
This approach is somewhat like the bsearch() library function in ANSI
C. A similar technique can be used for sorting. The above search
class is part of a Java class library described on the Web page:
http://www.glenmccl.com/~glenm/javalib/index.html |
Java has no "goto" statement, though this identifier is reserved in
the language. There are several ways in which goto is used in C and
C++, and it's interesting to consider the Java alternatives to such
usage. In this section we will discuss one alternative, and in the
next section another.
One way that goto is used is to jump to the end of a function body,
where cleanup can be done. For example, suppose that we are
manipulating calendar dates, and have a function where we want a year
in the range 1900-99 and evenly divisible by 4. In C, we might have:
void f(int d) { if (d < 1900) goto err; if (d > 1999) goto err; if (d % 4) goto err; /* do stuff with date ... */ return; err: fprintf(stderr, "invalid date %d\n", d); } |
In Java, we can achieve a similar end without goto, by using a form of
the try-catch-finally statement used in exception handling:
public class test { public static void f(int d) { boolean err = true; try { if (d < 1900) return; if (d > 1999) return; if (d % 4 != 0) return; err = false; // do stuff with date ... } finally { if (err) System.err.println("invalid date " + d); } } public static void main(String args[]) { f(1852); f(1976); f(1989); } } |
The code within the try block is "tried", that is, executed. After
the execution of this code, the finally block is executed -- no matter
what happens in the try block. In the example above, we exit the
method (return) for various error conditions, but when the method is
exited, the finally block is executed. In this way, we can execute a
series of Java statements, and guarantee that no matter what happens
in those statements, some other processing will follow.
Whether this programming style is "good" is a matter of opinion.
Saying "return" with the idea that some cleanup will be done by a
finally block could be viewed as a little sneaky or confusing, or
alternatively this might turn out to be a common Java idiom in a few
months or years. At the least, you might put in a comment like:
if (condition) return; // proceed to cleanup phase of method |
If an exception is thrown in a try block, and there is a local catch
block to handle it, the catch block is executed, and then the finally
block. If there is not a local catch block, the finally block is
executed, and then the exception is propagated to the nearest catch
block that can handle the exception (that is, the stack is unwound, as
in other exception processing).
We will say more about Java exception handling at some future point.
In issue #001 we talked about Java garbage collection, where the
runtime system automatically reclaims dynamic storage that is no longer
in use. For example:
public class test { public void f() { char buf[] = new char[1024]; // stuff that uses buf } } |
When method f() exits, the space that buf uses is garbage, because
only a local variable in an invalid stack frame references it. The
runtime system may eventually reclaim the storage.
Garbage collection is automatic. But sometimes there are ways to help
it out. Consider a case where you are managing a stack of Object
references:
public class Stack { private static final int MAXLEN = 10; private Object stk[] = new Object[MAXLEN]; private int stkp = -1; // add in logic for error checking, stack growing, etc. ... public void push(Object p) { stk[++stkp] = p; } public Object pop() { return stk[stkp--]; } } |
Consider a case where the stack has two elements on it, and you pop
one of them. At this point stk[0] will have a valid element in it,
and stk[1] will have the element just popped. That is, stk[1] will
have a reference to an Object, which could be a reference to anything,
including a large data structure of many thousands of bytes. In such
a case, this data structure cannot be garbage collected, even though
it may no longer be in use.
To remedy this problem, we can rewrite pop() like so:
public Object pop() { Object p = stk[stkp]; stk[stkp--] = null; return p; } |
We haven't nullified the Object itself, just a no longer valid
reference to it. The Stack object itself may have a long lifetime,
and rewriting the pop() method in this way helps ensure that garbage
collection can be done in a timely way.
In issue #002 we traced through the steps involved in doing Java
character output. The Java I/O system offers flexibility in
structuring various sorts of I/O, with the possibility of adding
filtering layers and so on.
But this flexibility has a performance cost, caused in part by the
layers themselves and in part by the method call overhead. As we've
said previously, Java is a young language and it's not totally clear
how various pieces will shake out. But it's worth considering how
some common types of I/O might be speeded up.
As an example, consider text lines. These are lines of characters
ending in \n while being manipulated in a program, and ending in \r\n
or \n when residing in a disk file. \r\n is used with Microsoft
system software on PCs, while \n is used on UNIX systems.
Suppose that we wish to read and write sets of text lines
sequentially, for example, reading all the lines in a file, one after
another, or writing to a file in a similar way.
One way to do this is illustrated in the following example. We set up
our own file output buffer, and move characters from a passed-in
String object directly to the buffer. We use the low-level
FileOutputStream class to actually do the I/O; it has a write() method
that takes a vector of bytes and outputs them to a file or to standard
output.
We determine whether \r\n or just \n is needed on output, by looking
at the system properties list. JDK 1.0 running on Windows NT has an
anomaly or feature in the way that standard output is treated with
respect to line delimiters, and so if output is to standard out it's
treated differently.
This particular example, with the driver program below, runs about 5X
faster than the equivalent using System.out.print(). The code for
doing input of text lines is analogous.
import java.io.*; class StdioOutput { private static final int BUFSIZ = 4096; private FileOutputStream fos; private byte buf[] = new byte[BUFSIZ]; private int left; private int pos; private static boolean need_cr = false; // figure out whether we have \r or \r\n static { String s = System.getProperty("line.separator"); need_cr = (s.length() >= 1 && s.charAt(0) == '\r'); } // open a file public StdioOutput(String fn) throws IOException { fos = new FileOutputStream(fn); left = BUFSIZ; pos = 0; } // open standard output public StdioOutput() throws IOException { fos = new FileOutputStream(FileDescriptor.out); left = BUFSIZ; pos = 0; need_cr = false; } // close a file public synchronized void close() throws IOException { flush(); fos.close(); fos = null; } // flush output public synchronized void flush() throws IOException { if (pos > 0) fos.write(buf, 0, pos); left = BUFSIZ; pos = 0; } // output a character public synchronized void putc(int c) throws IOException { // flush output buffer if needed if (left <= 0) flush(); // handle simple case if (c != '\n' || !need_cr) { left--; buf[pos++] = (byte)c; } // handle \r\n else { left--; buf[pos++] = '\r'; if (left <= 0) flush(); left--; buf[pos++] = '\n'; } } // output a line public synchronized void putline(String s) throws IOException { int len = (s == null ? 0 : s.length()); // empty string if (len < 1) return; // whole string will fit in buffer if (len + 1 <= left) { if (len >= 2) { s.getBytes(0, len - 1, buf, pos); pos += len - 1; left -= len - 1; } putc(s.charAt(len - 1)); } // whole string won't fit, do a character at a time else { for (int i = 0; i < len; i++) putc(s.charAt(i)); } } } public class testio2 { public static void main(String args[]) { StdioOutput fout = null; String s; try { fout = new StdioOutput(); } catch (Throwable e) { System.err.println("* file opening error *"); } try { int N = 10000; for (int i = 1; i <= N; i++) // System.out.print("xxxxxxxxxxxxxxx\n"); fout.putline("xxxxxxxxxxxxxxx\n"); } catch (Throwable e) { System.err.println("*** file I/O error ***"); } try { fout.close(); } catch (Throwable e) { System.err.println("* file closing error *"); } } } |
In the last issue we illustrated a way of invoking subprograms from
within Java, and gave an example of listing the contents of a
directory using the command "ls". Someone pointed out that this
example is a bit misleading, because "ls" is unique to UNIX systems
and because there are portable Java library techniques for achieving
the same end.
To illustrate this point, here is a bit of Java code that lists the
contents of a directory in a portable way, using the java.io.File
class:
import java.io.*; public class dir { public static void main(String args[]) { File f = new File("."); String lst[] = f.list(); for (int i = 0; i < lst.length; i++) System.out.println(lst[i]); } } |
We create a File object based on the current directory, and then
retrieve a vector of Strings that name the files found in that
directory.
The File class has methods for checking for the existence and
attributes of a file, such as whether it can be read or written and
whether it's a directory. It's also possible to filter the names
returned by list() in an arbitrary user-defined way, using the
FileNameFilter interface. File system attributes like filename and
pathname separators are queryable as well.
It's often the case that when writing programs, you'd like to invoke
another program from within the first and send it input and get output
from it. How might this be done in Java? A simple example that runs
the UNIX program "ls" to retrieve a listing of files in the current
directory looks like this:
import java.io.*; public class test1 { public static void main(String args[]) { try { Process cp = Runtime.getRuntime().exec("ls"); DataInputStream d = new DataInputStream(cp.getInputStream()); String line = null; while ((line = d.readLine()) != null) System.out.println(line); } catch (Throwable e) { } } } |
The call to exec() starts the process running, and returns a Process
object. Then we query the Process object to get a buffered input
stream that is connected to the output of the child process. We then
can simply iterate over this stream, reading lines in turn, that are
the output of ls. In a similar way, it's possible to send input to
the child process or capture its error output.
The Process class has several additional methods available for
controlling processes:
waitFor() wait for the child process to complete exitValue() return the exit value for the process destroy() kill the process |
There is also a variant of exec() that supports passing of environment
variables to the child process.
In C++ there is a facility known as enumerations or enumerated types
that can be used to represent a set of integral values:
enum Color {CO_R = 1, CO_G = 2, CO_B = 3}; |
After this declaration, Color can be used as a type (including for
function overloading purposes), and values like CO_R can be used
wherever integral constants would be used. Type checking is done, so
that for example:
Color c = CO_R; c = 37; |
is invalid.
Java does not have enumerations. One way to simulate them is simply
by defining constants in a class:
public class Color { public static final byte CO_R = 1; public static final byte CO_G = 2; public static final byte CO_B = 3; }; |
public means that these values are accessible to all, static means
that they're shared across all object instances, final means that
they're immutable once set, and byte means that they're represented in
bytes in the virtual Java machine.
Once a class like this is set up, you can say things like:
byte b = Color.CO_G; |
But notice that this simulation is only a simulation. There's nothing
to stop me from saying:
byte b = Color.CO_R; b = 29; |
and thereby introduce an invalid color value. A solution to this
problem would be to define Color as a "real" class, that represents
the actual current value of an enumeration:
public class Color { private byte value = 0; public static final byte CO_R = 1; public static final byte CO_G = 2; public static final byte CO_B = 3; public Color(byte b) { if (b == CO_R || b == CO_G || b == CO_B) value = b; else throw new IllegalArgumentException(); } public byte getvalue() { return value; } } |
This approach works, but can be cumbersome to use. However, enums,
like other language features, add to the total complexity and
implementation cost of a language, and leaving them out of Java is a
reasonable design tradeoff to make.
You may have heard of the term "JavaDoc" in relation to Java. What is
this? JavaDoc is a tool that comes with the Java Development Kit.
It's used for preparing HTML (the language of the Web) documentation
for Java classes. That is, it generates HTML code that documents a
class or package (set of related classes), including their methods,
interfaces, exceptions, class hierarchy, and so on.
JavaDoc extracts structured comments from a Java source file. For
example:
/** * Create a hash table. * * @param sz initial table size in hash slots (>= 1) * @param load_factor (average elements per slot) * (0.25 - 10.0) * @param growth_factor (how much to grow the table * when load factor is exceeded) (1.1 - 2.0) * @exception IllegalArgumentException for invalid arguments */ public Hash(int sz, float load_factor, float growth_factor) { // stuff } |
This is documentation for a constructor for a hash table class.
JavaDoc keys off of "/**" and tags like "@param". Page 182 of David
Flanagan's "Java in a Nutshell" (O'Reilly) describes the tags that can
be used.
You can run JavaDoc on individual Java source files, or on whole
packages. It generates a set of HTML files that can be used on a Web
page or with a Web browser whether connected to the Web or not. The
files have hyperlinks between related classes and methods, so that a
user can navigate easily through the documentation.
In the last issue we talked about sorting in Java and said that
there's no approach to sorting that's really similar to qsort() in
C/C++. A couple of people wrote to me about this and suggested a
technique that does in fact have some similarities.
The idea is to write a sort method that accepts a Vector of Object
references, along with a class object instance that is a wrapper for a
compareTo() method as illustrated above. The sort routine will
iterate over the vector and call out to the compare method to
determine the ordering of any two objects. Specifically, this would
look like:
import java.util.Vector; // Orderable interface interface Orderable { // return < 0 if p1 < p2, 0 if equal, > 0 if p1 > p2 public int compareTo(Object p1, Object p2); }; // wrapper for a compareTo() method for ordering Strings class Ord implements Orderable { public int compareTo(Object p1, Object p2) { return ((String)p1).compareTo(((String)p2)); } } public class Sort { public static void sort(Vector v, Orderable or) { if (v == null || or == null) ; // give error of some kind // get vector size int n = v.size(); // sort for (int i = 0; i < n - 1; i++) { for (int j = i + 1; j < n; j++) { // do comparison if (or.compareTo(v.elementAt(i), v.elementAt(j)) > 0) { Object ti = v.elementAt(i); Object tj = v.elementAt(j); v.setElementAt(tj, i); v.setElementAt(ti, j); } } } } // driver program public static void main(String args[]) { Vector v = new Vector(); int N = 100; int i = 0; // add some strings for (i = 0; i < N; i++) v.addElement("xxx" + i); // sort them Sort.sort(v, new Ord()); // display the sorted list for (i = 0; i < N; i++) System.out.println((String)v.elementAt(i)); } } |
This code can be tuned in various ways (starting with the sort
algorithm) but is illustrative of the technique. We can implement any
sort strategy we want on a Vector of objects simply by changing the
ordering method. For example, saying:
class Ordrev implements Orderable { public int compareTo(Object p1, Object p2) { return -((String)p1).compareTo(((String)p2)); } } ... Sort.sort(v, new Ordrev()); |
will reverse the sorting order for Strings. In this particular
example, Strings have a compareTo() method already defined, and we
simply cast the Object references to Strings and call this method.
Note that if the wrong compareTo() wrapper instance is used, then an
illegal cast will be attempted, resulting in an exception being
thrown. For example, the above case expects a String, and will
generate an exception ("ClassCastException") if the objects we pass in
are actually Integers. The "instanceof" operator can be used to help
sort things out.
In production code we'd have Orderable defined in a separate source
file. Ord might or might not be in its own file, depending on
stylistic preferences. Ord is simply a wrapper for a particular
compareTo() method. In C or C++ we would pass in a function pointer
directly, but Java has no user-visible pointers and no global
functions.
If we critique this approach and compare it to qsort(), there are some
notable differences and some similarities:
1. This approach is higher-level than qsort(), because it doesn't fool with pointers and sizes of objects and so on. 2. This approach cannot be used to directly sort vectors of fundamental data types like int or double. They must be sorted using object wrappers. 3. Both approaches require calling out to a function or method that is used to order elements. Such calls are expensive. 4. This approach has some overhead in accessing and setting Vector element slots. There is method call overhead, as well as the subscript range checking done each time within a method like elementAt(). |
It's possible to write a similar type of method for doing binary
searches, kind of like bsearch() in the C library.
In issue #004 we talked about public and private fields. When public
is applied to a method or variable:
public int x = 37; public void f() {} |
it means that the method or variable is visible everywhere, while a
private method or variable:
private int x = 37; private void f() {} |
is visible only within the class where it is defined.
Two other levels of visibility are protected:
protected int x = 37; |
and the default when no keyword is specified:
void f() {} |
These are identical except in one case. For both of these levels, the
method or variable is visible in the class where it's defined and to
subclasses and non-subclasses from the same package. For example:
// file pack1/A.java package pack1; public class A { protected int x = 0; int f() {return x;} public static void main(String args[]) {} } class B extends A { void g() { A p = new A(); int i = p.x + p.f(); } } class C { void g() { A p = new A(); int i = p.x + p.f(); } } |
while not being accessible from other packages:
// file pack1/AA.java package pack1; public class AA { protected int x = 0; int f() {return x;} public static void main(String args[]) {} } // file pack2/BB.java package pack2; import pack1.AA; class BB extends AA { void g() { AA p = new AA(); int i = p.x + p.f(); // error here } } class CC { void g() { AA p = new AA(); int i = p.x + p.f(); // error here } } |
Where protected and the default differ is in whether they are
inherited by a subclass in a different package. For example:
// file pack1/D.java package pack1; public class D { int x = 37; protected int y = 47; public static void main(String args[]) {} } // file pack2/E.java package pack2; import pack1.D; class E extends D { void f() { int i = x; // error here int j = y; // OK here } } |
There are a couple more issues with packaging that we will explore in
future issues.
Random numbers are useful in many contexts in programming. One common
use is in games, and another is for testing program algorithms such as
those used in searching or sorting.
Java has random number support in the library class java.util.Random.
Basic use is:
import java.util.*; Random rn = new Random(); ... int r = rn.nextInt(); // 32-bit random number double d = rn.nextDouble(); // random value in range 0.0 - 1.0 |
The random number generation algorithm is based on the linear
congruential method (see for example Chapter 3 in Volume 2 of Knuth's
"Art of Computer Programming"). This method has a starting value (a
"seed"), and each value of the sequence is generated from the last.
Whether such a stream of random numbers are in fact truly random is an
interesting question beyond the scope of this discussion. If you're
interested in that question you might consult Knuth's book.
If the default Random() constructor is used, the random number
generator is seeded from the current time of day. You can also
specify your own seed:
Random rn = new Random(1234); |
to generate a repeatable sequence of random numbers.
To show how random numbers might be used, here is a class Test that
will generate random numbers in a range using the rand() method, or
generate random strings composed of characters 'a' ... 'z'.
import java.util.*; public class Test { private static Random rn = new Random(); private Test() { } public static int rand(int lo, int hi) { int n = hi - lo + 1; int i = rn.nextInt() % n; if (i < 0) i = -i; return lo + i; } public static String randomstring(int lo, int hi) { int n = rand(lo, hi); byte b[] = new byte[n]; for (int i = 0; i < n; i++) b[i] = (byte)rand('a', 'z'); return new String(b, 0); } public static String randomstring() { return randomstring(5, 25); } } |
Actual random numbers are obtained using nextInt(), and then knocked
down to the relevant range using the modulo ("%") operator.
Note that Test is simply a packaging vehicle with all of the methods
as class methods ("static"). To obtain a random String of between 10
and 40 characters, you would say:
String s = Test.randomstring(10, 40); |
In the last issue we talked about CLASSPATH and Java packages. In
this and subsequent issues, we'll be discussing visibility specifiers
for instance (per object) and class (shared across all object
instances) variables and methods.
The first two of these specifiers are "public" and "private".
Specifying public means that the variable or method is accessible
everywhere, and is inherited by any subclasses that extend from the
class. "Everywhere" means subclasses of the class in question and
other classes in the same or other packages. For example:
// file A.java public class A { public void f() {} public static int x = 37; } // file B.java public class B { public static void main(String args[]) { A a = new A(); a.f(); // calls public method A.x = -19; // sets static variable in A } } |
By contrast, "private" means that no other class anywhere can access a
method or variable. For example:
// file A.java public class A { private void f() {} private static int x = 37; } // file B.java public class B { public static void main(String args[]) { A a = new A(); a.f(); // illegal A.x = -19; // illegal } } |
Private instance variables are not inherited. This means something
slightly different in Java than in C++. In both languages private
data members are in fact part of any derived class, but in Java the
term "not inherited" in reference to private variables does double
duty to mean "not accessible".
One crude way of figuring out just how much space an object instance
requires is to use a technique like this one, where the amount of free
memory is saved, many object instances are allocated, and then a
calculation is done to determine the number of bytes per object
instance:
class x1 { private double d1; private double d2; private double d3; private double d4; private double d5; } public class x2 extends x1 { public static void main(String args[]) { int N = 10000; x2 vec[] = new x2[N]; long start_mem = Runtime.getRuntime().freeMemory(); for (int i = 0; i < N; i++) vec[i] = new x2(); long curr_mem = Runtime.getRuntime().freeMemory(); long m = (start_mem - curr_mem) / N; System.out.println("memory used per object = " + m); } } |
This technique is not without its pitfalls (notably issues related to
garbage collection), but sometimes can provide useful information
about object sizes.
In future issues we will be talking about other kinds of visibility,
such as the default visibility level and "protected".
Java has no global variables or functions, unlike some other common
languages. Every variable and function must be part of some class.
Within a class, a variable or function ("method") may be a regular
member of that class, or may be a class variable or class method.
Class methods and variables do not operate on particular object
instances of a class. A class method is typically used as a utility
function within the class, while a class variable is shared by all the
instances of the class. For example, if you're writing a Date class
to represent calendar dates, a class method might be used for the
method that determines whether a given year is a leap year.
Using class methods and variables, it is possible to synthesize
variables and methods somewhat similar to globals. For example, you
can say:
// file "Global.java" public final class Global { public static int x = 37; public static int f() { return 47; } } // file "globtest.java" public class globtest { public static void main(String args[]) { int i = Global.x + Global.f(); System.out.println("i = " + i); Global.x = 0; System.out.println("x = " + Global.x); } } |
"static" is the keyword used to denote that methods or variables are
class ones. The Global class is declared as final, meaning that it
cannot be extended (derived from). The class variable and method
names in Global are denoted by prepending "Global." to them.
If we wanted to get a bit fancier, we could add a line to Global:
private Global() {} |
This declares a private constructor for Global, meaning that anyone
who tries to create an object instance of Global will get an error
message at compile time. The error will occur because they are trying
to create an object whose constructor is inaccessible. This is
reasonable behavior since we don't care about creating object
instances of Global; it's just a wrapper for some variables and
methods.
Another change that could be made would be to have all access to class
variables in Global done through methods:
private static int x = 47; public static void setx(int i) {x = i;} public static int getx() {return x;} |
This makes it easy to trap all changes to the global variable.
A technique similar to that shown in this section is possible in C++,
but is not required. C has no similar mechanism at all, though you
can enforce your own rules via naming conventions and access functions.
It is possible to get into a long argument about the desirability of
global variables. They are best avoided, except to track
application-wide information, such as the current program state or
resources that are to be shared between threads. The Java I/O system
uses the technique illustrated here to set up System.out for use in
code like:
System.out.println("xxx"); |
System is a final class with a private constructor, and out is a class
variable defined within that class.
Well, fortunately there is a way, illustrated using this brief example:
// source file link.java public class link { public int value; // value of element public link next; // reference to next // constructor public link(int n, link ln) { value = n; next = ln; } public static void main(String args[]) { // initialize list head link head = null; // add some entries to list for (int i = 1; i <= 10; i++) head = new link(i, head); // dump out entries link p = head; while (p != null) { System.out.println(p.value); p = p.next; } } } |
Java does not have pointers, but instead uses implicit references. A
line like:
link next; |
does not actually declare an object instance of class link, but rather
a reference to an object instance. The line:
head = new link(i, head); |
creates a new element for the list, sets its value, and then sets the
object reference in "next" to point at the old list head (this
approach means that the last element inserted will be at the head of
the list). Saying:
p = p.next; |
simply picks up the "next" reference from the current node and makes
it the current element being worked on.
When we're done using this list, we can simply leave the list with all
its elements lying around -- garbage collection will eventually
reclaim it.
If what you really want to do in a Java application is manage a list
of numbers, there are higher-level techniques in the language and
library (such as the Vector class) for doing so more cleanly than the
approach illustrated here. This example shows some of what can be
done underneath to implement the higher-level schemes.
import java.io.*; public class uni { public static void main(String args[]) { InputStream istr = null; try { istr = new FileInputStream("testfile"); } catch (FileNotFoundException e) { System.err.println("*** file not found ***"); System.exit(1); } try { int b; String s = ""; while ((b = istr.read()) != -1) { s += (char)b; } System.out.print(s); } catch (IOException e) { } System.exit(0); } } |
In this example, we attempt to open a file input stream to an input
file "testfile", catching an exception and bailing out if the open
fails (more about exceptions below). Note that we don't close the
file explicitly. This is done by something akin to a C++ destructor,
a method called finalize() that is invoked when garbage collection is
done. We will talk about this area at some point; the semantics of
resource cleanup and freeing are different in Java because of delayed
object destruction.
Then we read bytes from the file using the read() method. The bytes
are returned as ints, so that -1 can be used to indicate end of file
(C has a similar trick with EOF). We take each int (byte) and cast it
to a character and append it to a String object that we'd initialized
to the empty string. Finally, we print the string.
A String object has a sequence of characters in it, and we have
converted the input bytes that were read into characters and shoved
them into the string. Since characters are Unicode, we have converted
a sequence of input bytes into Unicode.
But it's not quite this easy. In casting to a character, there is the
implicit supplying of a 0 to fill the high byte of the character,
resulting in code that's not very portable. A better way to express
the line:
s += (char)b; |
would be:
byte x[] = {(byte)b}; s += new String(x, 0); |
In other words, build a vector of bytes and construct a String from
them, with the high byte fill value explicitly specified.
We will be saying more about Java I/O in the future. The Java library
has a variety of classes and methods for dealing with input and output
of various types. The I/O example shown above illustrates a way of
doing low-level input. There are higher-level mechanisms available in the library.
In C++ one can use constructors to initialize class object instances
when they're created, and employ static data members that are
initialized when the program starts. But what if you'd like some code
to be executed once for a given class, to kind of set things up for
the class? One way of doing this in Java is to say:
public class A { static { System.out.println("got to static initialization"); } public static void main(String args[]) { System.out.println("start of main()"); A c1 = new A(); A c2 = new A(); } } |
No matter how many instances of objects of class A are created, the
block of code at the top will be executed only one time. It serves as
a hook to do class setup at the beginning of execution.
The technique shown in the previous section has one very important
use. When you say:
System.out.println("x"); |
what happens? It's interesting to trace through the sequence of
operations used to output a character.
In the first place, System is a class defined in the Java library. It
is a wrapper class that you do not actually create object instances
of, nor may you derive from the System class, because it is declared
as "final". In C++ such a class is sometimes referred to as a "static global class".
System.out is defined as:
public static PrintStream out; |
meaning that it's available to all and that there is only one object
instance of PrintStream for "out". This PrintStream stream
corresponds to standard output, kind of like file descriptor 1 in
UNIX, stdout in C, or cout in C++. Similar streams are established
for input and standard error output.
The output stream is initialized via a static initialization block of
the type illustrated above. The actual code is:
out = new PrintStream( new BufferedOutputStream( new FileOutputStream( FileDescriptor.out ), 128 ), true ); |
This is a mouthful that says that a PrintStream is based on a
BufferedOutputStream (with a buffer 128 long) which is based on a
FileOutputStream with a specified file descriptor, and that output is line buffered.
Saying:
System.out.println("xxx"); |
means that you're invoking the println(String) method for a
PrintStream. Doing so immediately results in the sequence:
PrintStream.print("xxx"); PrintStream.write('\n'); |
PrintStream.print("xxx") contains a loop that iterates over the
characters in the String ("xxx" is a String, not a vector of
characters) calling PrintStream.write() for each. PrintStream.write()
calls out.write(), implementing line buffering as it goes.
What is out.write()? When the output stream was initialized, we
created a PrintStream object and said that it should be based on a
BufferedOutputStream. "out" is an instance variable of a class
FilterOutputStream from which PrintStream derives ("extends"), and out
is set to reference a BufferedOutputStream object. In a similar way,
BufferedOutputStream is based on FileOutputStream.
out.write() in BufferedOutputStream collects characters into a buffer
(specified in the creation line illustrated above). When the buffer
becomes full, out.flush() is called. This results in a different
write() being called in the FileOutputStream package. It writes a
sequence of bytes to the file descriptor specified when the stream was
created. This last method is native, that is, is implemented in C or
assembly language and not in Java code itself.
This approach to I/O is quite flexible and powerful, and names like
"stream nesting" and "stream filtering" are used to describe it. It's
not a terribly efficient approach, however, especially since Java
itself is interpreted and many of the higher layers of the system are
written in Java itself.
One other note: when trying to figure out just what methods are
called in an example like the one in this section, it's helpful to use
the profiling feature of JDK:
$ java -prof xxx |
This shows called methods, who called them, and how many times they
were called.
(1 + 2) * (3 + 4) |
yields a value of 21.
If you're not familiar with this sort of programming, similar to what
is found in language compilers themselves, a brief explanation is in
order. The program takes input and splits it into what are called
tokens, logical chunks of input. For the input above, the tokens are:
( 1 + 2 ) * ( 3 + 4 ) |
and the white space is elided. Then the program tries to make sense
of the stream of input tokens. It implicitly applies a grammar:
expr -> term | expr [+-] term term -> fact | term [*/] fact fact -> number | ( expr ) |
Don't worry too much if you don't understand this. It's a way of
describing the structure of input. You can think of it as a way of
converting an input expression into the Reverse Polish Notation that
some older calculators used to use.
Here is the actual program, in a file "calc.java". We will have more
to say about this program in the next section below. Annotations are
given in /* */ comments, while regular program comments use //.
(Note: we're not trying to do anything fancy with comments for JavaDoc
purposes, a subject to be presented another time).
import java.io.*; public class calc { private String in_line; // input line private int in_len; // input line length private int currpos; // position in line // The input line, its length, and the current position in it. private byte curr_tok; // current token private int val_token; // value if num // The current token and its value if it's a number. private boolean had_err; // error in parsing // Used to record whether a parsing error occurred on the input. // Exception handling could also be used for this purpose, and is used for another type of error (divide by 0). private static final byte T_NUM = 1; // token values private static final byte T_LP = 2; private static final byte T_RP = 3; private static final byte T_PLUS = 4; private static final byte T_MINUS = 5; private static final byte T_MUL = 6; private static final byte T_DIV = 7; private static final byte T_EOF = 8; private static final byte T_BAD = 9; /* Possible token values. These are private (available only to the class), static (shared across all class object instances), and final (constant). */ // get next token from input line private void get_token() { // skip whitespace while (currpos < in_len) { char cc = in_line.charAt(currpos); // in_line.charAt(currpos) returns the current character from the string. if (cc != ' ' && cc != '\t') break; currpos++; } // at end of line? if (currpos >= in_len) { curr_tok = T_EOF; return; } // grab token char cc = in_line.charAt(currpos); currpos++; if (cc == '+' || cc == '-') curr_tok = (cc == '+' ? T_PLUS : T_MINUS); else if (cc == '*' || cc == '/') curr_tok = (cc == '*' ? T_MUL : T_DIV); else if (cc == '(' || cc == ')') curr_tok = (cc == '(' ? T_LP : T_RP); /* This block of code could also be handled via a switch statement or in a couple of other ways. */ else if (Character.isDigit(cc)) { int n = Character.digit(cc, 10); while (currpos < in_len) { cc = in_line.charAt(currpos); if (!Character.isDigit(cc)) break; currpos++; n = n * 10 + Character.digit(cc, 10); } val_token = n; curr_tok = T_NUM; /* The above code grabs a number. Character.isDigit(char) is a method of the character class that returns a boolean if the character is a digit. Character.digit(char, int) converts a character to a number for a given number base (10 in this case). The primitive types like char have corresponding class types, though you cannot call a method directly on a primitive type object. You must instead use the techniques illustrated here. */ } else { curr_tok = T_BAD; } // The case where the token can't be recognized. } // constructor, used to set up the input line public calc(String s) { in_line = s; in_len = in_line.length(); currpos = 0; had_err = false; get_token(); } /* The constructor sets up an object instance for doing calculations. We set up the input line, clear any error condition, and grab the first token. */ // addition and subtraction private double expr() { // get first term double d = term(); // additional terms? while (curr_tok == T_PLUS || curr_tok == T_MINUS) { byte t = curr_tok; get_token(); if (t == T_PLUS) d += term(); else d -= term(); } return d; } /* This and the next method are similar. They grab a term() or fact() and then check to see if there are more of them. This matches input like: 1 + 2 + 3 + 4 ... As each token is consumed, another one is grabbed. */ // multiplication and division private double term() { // get first factor double d = fact(); // additional factors? while (curr_tok == T_MUL || curr_tok == T_DIV) { byte t = curr_tok; get_token(); if (t == T_MUL) d *= fact(); else { double d2 = fact(); if (d2 == 0.0 && !had_err) throw new ArithmeticException(); d /= d2; /* This code is similar to expr() above but we check for division by 0 and throw an arithmetic exception if we find it. We will see below where this exception is handled. */ } } return d; } // numbers and parentheses private double fact() { double d; // numbers if (curr_tok == T_NUM) { d = val_token; get_token(); } // If a number, retrieve the value stored in val_token. // parentheses else if (curr_tok == T_LP) { get_token(); d = expr(); if (curr_tok != T_RP) { had_err = true; d = 0.0; } get_token(); } /* If (, then grab the expression inside and check for ). If not found, record that we had an error. We could also throw an exception at this point. */ // garbage else { had_err = true; get_token(); d = 0.0; } // The token was not recognized, so we have bad input. return d; } // parse input and get and print value public String get_value() { double d; try { d = expr(); } catch (ArithmeticException ae) { return new String("*** divide by 0 ***"); } if (had_err || curr_tok != T_EOF) return new String("*** syntax error ***"); else return String.valueOf(d); /* Here is where we actually try to get the value of the expression. We convert its value back to a String for reasons of flexibility in handling error conditions. Division by 0 will result in an exception being thrown and caught here. If we encountered an error, or if we've not exhausted the input string (for example, for input "((0)))"), then we also flag an error. Otherwise, we return the string value of the double using the method String.valueOf(double). */ } // get a line of input from the keyboard private static String getline() { DataInput di = new DataInputStream(System.in); String inp; try { inp = di.readLine(); } catch (IOException ignored) { inp = null; } // This is a wrapper function to get a line of input from the keyboard. return inp; } // driver public static void main(String args[]) { String inp = ""; // command line arguments if (args.length > 0) { for (int i = 0; i < args.length; i++) inp = inp + args[i]; calc c = new calc(inp); System.out.println(c.get_value()); /* If there are command-line arguments, we will append them into one string using the "+" operator and then evaluate the value of the expression. args.length is the number of command-line arguments, and args[i] is the i-th argument. The line: calc c = new calc(inp); creates a new calc object and calls its constructor with inp as the String argument to the constructor. c.get_value() returns the expression value as a String. */ } // no command line arguments, prompt user else { for (;;) { System.out.print("input string: "); System.out.flush(); // We flush output here because it's normally line buffered and we've not // output a newline character. inp = getline(); if (inp == null) break; // End of input. calc c = new calc(inp); System.out.println(c.get_value()); } } } } |
If you've paid much attention at all to coverage of Java in the
technical and business press, you might have encountered discussions
on Java security, and read about malicious applets and so on. This is
an interesting and complex subject to contemplate, and we'll spend
several issues looking at it. There are multiple types and levels of
security to consider.
Let's first look at a short C program:
void main() { char c; int fd; char* p = 0; char buf[1024]; fd = open("data", 0); while (read(fd, &c, 1) == 1) *p++ = c; close(fd); } |
This is a mixture of C code and UNIX system calls. It opens a data
file, reads from it, and places the read characters into a character
buffer.
Unfortunately, this program has a bug, in that the statement:
p = buf; |
was omitted. So the program will write via the pointer p, but p is
null (0), and the bytes will get written into memory location 0 and
succeeding locations. If you're running on a DOS machine, this will
probably silently crash your computer. If you are on a UNIX machine
with virtual address space starting at 0, with text (the program
itself) in low memory, then perhaps you'll get a segmentation
violation error.
The point is that a language like C is "close" to the hardware. This
is literally true if running with DOS, somewhat less true if using
virtual memory and process protection. Direct use of system calls for
doing I/O in the example above is another illustration of this point.
This feature of C is both a strength and a weakness. For example, C
is a good language for writing I/O device drivers, because it's close
to the hardware.
Java takes a different tack. It has no user-visible pointers, and no
notion of an explicit memory address that you can manipulate or print
out. With Java it's still possible to make the dumb mistake found in
the example above, but the result would simply be an immediate null
pointer exception. There is no way to write a byte to physical or
virtual address 0.
Similarly, there is no direct access to system calls. A Java program
can certainly do I/O to disk files (applets are restricted in this
area), but it can't make system calls itself.
This insulation from the physical machine, via the Java Virtual
Machine, means that program execution tends to be safer as far as
program crashes and interference with other programs go. The tradeoff
is inability to control the actual physical machine, and some loss of
efficiency. This implies that Java is suited for a somewhat different
set of applications than a language like C, though of course there is
much overlap between the two.
We saw last time how the Java virtual machine model insulates a Java
program from hardware, and how this is both desirable and undesirable.
In this issue we'll talk a bit about verification, another aspect of
security. A java class is compiled into platform-independent ".class"
files containing byte streams. These streams contain the actual
program codes (bytecodes, constants, debugging information, and so on).
Suppose that I have the hello program:
public class hello { public static void main(String args[]) { System.out.println("Hello World"); } } |
and I compile it:
$ javac hello.java
resulting in a "hello.class" file of 461 bytes (in JDK 1.1). And I'm
feeling malicious and decide to tweak one of the bytes:
import java.io.*; public class tweak { public static void main(String args[]) { int offset = Integer.parseInt(args[0]); try { RandomAccessFile raf = new RandomAccessFile("hello.class", "rw"); raf.seek(offset); raf.writeByte(97); raf.close(); } catch (Throwable e) { System.err.println("*** exception ***"); } } } |
by saying:
$ javac tweak.java $ java tweak 0 |
thereby writing the byte "97" into location 0 in "hello.class".
Obviously, this is not how Java was intended to be used. What will
happen?
It turns out that the first four bytes of a Java .class file must be
the hex value "0xCAFEBABE", and writing 0 into one of these will
invalidate the file. If I then try to run the program:
$ java hello |
I will get an immediate error. This particular feature is fairly
common in program binaries and often goes by the name of "magic
number".
When a Java program is run, the interpreter first invokes the Java
Verifier. The Verifier checks the magic number along with other
properties of the .class file, including:
There are many other checks done by the Verifier on .class files.
This list is merely illustrative. Verifying a class not only improves
security, but speeds up actual bytecode interpretation because the
checks don't have to be repeated.
An interesting book that goes into detail on Java security is "Java
Security - Hostile Applets, Holes, and Antidotes", by Gary McGraw and
Edward Felten, published 1997 by Wiley for $20.
We will be discussing security further in future issues.
In previous issues we've looked at how the Java programming model
insulates one from hardware, and how various types of checks are
performed before executing a Java program.
In this issue we'll look at the Java security manager. This is a
class in java.lang, that can be used to impose a specific security
policy on running Java programs (including applets loaded by a Web
browser).
The SecurityManager class contains a set of methods, such as
checkRead(), that are called by Java core libraries. In a standalone
program, typically no security manager is installed, and the program
is wide open as to what it's allowed to do. An instance of a
SecurityManager class object can be installed exactly once, after
which the security policies in force are those of the class instance.
By default, nothing is allowed, and so a class derived from
SecurityManager needs to override some of the methods.
Let's see how this works in practice. There is a method:
public void checkRead(String file) { throw new SecurityException(); } |
found in SecurityManager. We can override this method as follows:
import java.io.*; class test_SecurityManager extends SecurityManager { public void checkRead(String file) { if (file.charAt(0) == '/') throw new SecurityException(); } } public class Security { public static void main(String args[]) { System.setSecurityManager(new test_SecurityManager()); try { FileInputStream fis = new FileInputStream("/xxx"); fis.read(); fis.close(); } catch (SecurityException e) { System.err.println("security exception"); } catch (IOException e) { System.err.println("I/O exception"); } catch (Throwable e) { System.err.println("some other exception"); } } } |
to check the pathnames of files that an application accesses. In this
example, we check that the pathname does not start with "/". If it
does, we throw a SecurityException.
As we said, a security manager can only be established once in a given
session (if this was not so, the old manager could simply be replaced
by a more liberal one). By default, the check methods disallow
operations, so an overriding method will typically relax some
restriction. A Web browser may or may not have mechanisms for
changing the default security policies it imposes on applets that are
downloaded and executed.
This particular example does not work correctly with JDK 1.1 running
on Windows 3.51, and requires JDK 1.1.1.
In previous issues we've looked at some of the aspects of Java
security, namely Java's use of an abstract machine removed somewhat
from the hardware, verification of loaded code, and the security
manager.
Another aspect of security centers around class loaders. A class
loader in Java is responsible for converting a stream of bytes (for
example, as found in a .class file) into a class known to the Java
runtime system. When you run a Java standalone program, there is a
built-in loader that converts .class files into a running program.
Similarly, when an applet is downloaded from the Web, there is a
loader responsible for installing it on your system.
Other languages such as C also have the notion of loading, where a
binary image is turned into an executing program. But with Java there
are some security aspects with loading as well.
For example, suppose that I have malicious intentions, and I decide to
fool the Java system by trying to override one of the core classes,
java.lang.Runtime, supplying my own version. How is this prevented by
the class loader?
One way this is done is by keeping local built-in classes distinct
from those loaded from a URL somewhere on the Web, that is, keeping
classes in separate name spaces, according to where they were loaded
from. When a class is looked up, the local name space can be checked
first.
There's more we could say about class loaders, but this brief
introduction gives an idea of what is going on behind the scenes.
Another type of security problem in Java, that's a bit different from
what we've already looked at in this series, is denial of service.
This involves an applet that meets the security requirements
previously discussed, but which essentially takes over the user's
whole machine and prevents anything else from being done. That is, I
as a user am surfing the Web, and come to a page with a Java applet
call embedded in the HTML. The applet starts up and prevents me from
using my computer for any other function.
A couple of examples of such applets would be one that does some sort
of number crunching function, or one that creates a large number of
very large windows. McGraw and Felten's "Java Security" book gives
some examples of such applets, which we won't reproduce here.
The simplest solution to this type of problem is to by default disable
your Web browser's ability to execute Java applets, and only
selectively enable this feature for Web sites that you trust. Another
possible longer-term approach would involve being able to establish
some sort of resource usage limits.