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.
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.
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.
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.
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 |
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.
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); |
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.
import java.io.*; public class upper { public static void main(String args[]) { // check command-line argument if (args.length != 1) { System.err.println("usage: upper file"); System.exit(1); } String in_file = args[0]; try { // create temporary and mark "delete on exit" File tmpf = File.createTempFile("tmp"); tmpf.deleteOnExit(); System.err.println("temp file = " + tmpf); // copy to temporary file, // converting to upper case File inf = new File(in_file); FileReader fr = new FileReader(in_file); BufferedReader br = new BufferedReader(fr); FileWriter fw = new FileWriter(tmpf.getPath()); BufferedWriter bw = new BufferedWriter(fw); String s = null; while ((s = br.readLine()) != null) { s = s.toUpperCase(); bw.write(s, 0, s.length()); bw.newLine(); } br.close(); bw.close(); // rename temporary file back to original file if (!inf.delete() || !tmpf.renameTo(inf)) System.err.println("rename failed"); } catch (IOException e) { System.err.println(e); } } } |
# German greeting file (greet_de.properties) morn=Guten Morgen # English greeting file (greet_en.properties) morn=Good morning |
import java.util.*; public class bundle { public static String getGreet(String f, String key, Locale lc) { String s = null; try { ResourceBundle rb = ResourceBundle.getBundle(f, lc); s = rb.getString(key); } catch (MissingResourceException e) { s = null; } return s; } public static void main(String args[]) { String fn = "greet"; String mornkey = "morn"; Locale ger = Locale.GERMAN; Locale eng = Locale.ENGLISH; System.out.println("German locale = " + ger); System.out.println("English locale = " + eng); System.out.println(getGreet(fn, mornkey, ger)); System.out.println(getGreet(fn, mornkey, eng)); } } |
German locale = de English locale = en Guten Morgen Good morning |
A JAR archive is created with the following:
$ jar cf archive.jar file1 file2 ... |
$ jar tf archive.jar |
$ jar xf archive.jar [file1 file2 ...] |
CLASSPATH="lib1.jar;lib2.jar;" |
// applet.java import java.awt.*; import java.applet.*; public class applet extends Applet { public void paint(Graphics g) { String s = Message.getIntro(getParameter("intro")); g.drawString(s, 25, 25); } } |
// Message.java public class Message { public static String getIntro(String t) { if (t != null) return t; else return "Hello world!"; } } |
<html> <head><title>Hello World Example Applet </title> <body> <applet code="applet.class" archive="applet.jar" width=150 height=150> <param name="intro" value="good morning"> </body> </html> |
In this example, you prepare archive.jar with the following:
$ javac applet.java $ jar cf applet.jar *.class |
A final note: JAR files are also used with JavaBeans.
You print via the Abstract Window Toolkit (AWT). A simple example for printing a filled-in oval looks like this:
import java.awt.*; public class print { public static void main(String args[]) { Frame f = new Frame("test"); f.pack(); PrintJob pj = f.getToolkit().getPrintJob(f, "print1", null); if (pj != null) { Graphics g = pj.getGraphics(); g.fillOval(5, 5, 150, 100); g.dispose(); pj.end(); } System.exit(0); } } |
Once you create the toolkit, you can initiate a print job via getPrintJob, which causes a window to pop up on the screen asking the user for information about which printer is being used, the number of copies to print, and so on. This process is similar to printing in conventional Windows applications.
Given a PrintJob object, you can obtain a graphics context and draw to it. Such drawing, using normal graphics primitives such as fillOval, goes to the printer instead of the screen. Calling dispose on the graphics context object sends the page to the printer, and the print job is terminated by calling end.
If you're using your own custom AWT components, you can use the paint methods you define for printing without change--by passing them the graphics context returned by getGraphics. But if you want different behavior when you print, specialized methods can also be defined for components as necessary.
-- Printing from applets
The example given above is a standalone Java program. But what about printing from applets? The Java security system contains a feature that may lock out an applet from initiating its own print job, requiring that the initiation be done via a Web browser or applet viewer. The security area is in a state of flux at present, and your experiences with this process may be different.
Finally, another less portable approach to printing text is to open the special device file that represents the printer, and do conventional file I/O to that file. For example, "lpt1" can be used for this purpose with Windows systems, and "/dev/lp" can be used with UNIX.
import java.awt.*; import java.awt.event.*; public class menu implements ActionListener { public void actionPerformed(ActionEvent e) { String lab = ((MenuItem)e.getSource()).getLabel(); System.out.println("label = " + lab); if(lab.equals("Exit")) { System.exit(0); } } public static void main(String args[]) { Frame f = new Frame("testing"); Menu m = new Menu("File"); menu acl = new menu(); MenuItem mi1 = new MenuItem("Open"); mi1.addActionListener(acl); m.add(mi1); MenuItem mi2 = new MenuItem("Save"); mi2.addActionListener(acl); m.add(mi2); MenuShortcut ms3 = new MenuShortcut(KeyEvent.VK_E); MenuItem mi3 = new MenuItem("Exit", ms3); mi3.addActionListener(acl); m.add(mi3); MenuBar mb = new MenuBar(); mb.add(m); f.setMenuBar(mb); f.setSize(200, 200); f.setVisible(true); } } |
How the shortcut gets invoked varies, depending on the platform in question. For example, with Windows you use Ctrl-E so that when a user types Ctrl-E within the window containing the menu bar, the Exit command is invoked.
One last thing: this example doesn't actually have a command structure set up for it, but instead invokes actionPerformed to demonstrate that the command processing structure is in place.
#include |
How does the technique work in practice? Consider this example:
interface Action { public void doit(); } class func1 implements Action { public void doit() {System.out.println("func1");} } class func2 implements Action { public void doit() {System.out.println("func2");} } public class call { private static Action alist[] = { new func1(), new func2() }; public static void main(String args[]) { for (int i = 0; i < alist.length; i++) alist[i].doit(); } } |
In C++ programming Action would be a base class, with func1 and func2 being derived classes, and object manipulation would be performed via a base class pointer. With Java, an Action reference supports a similar manipulation, although Action is not a superclass of func1 and func2.
The Java technique given here sometimes goes by the name "method wrappers," and is quite different from the C/C++ approach. There are some tradeoffs, of course, as to which approach works the best in a given situation.
A second example
Another example of the method wrappers technique is used fairly often in the Abstract Windowing Toolkit (AWT). Consider the following:
import java.awt.*; import java.awt.event.*; public class buttonlistener implements ActionListener { public void actionPerformed(ActionEvent e) { System.exit(0); } public static void main(String args[]) { Frame f = new Frame("testing"); Button b = new Button("OK"); b.addActionListener(new buttonlistener()); f.add(b); f.pack(); f.setVisible(true); } } |
Finally, the listeners concept is extremely important in the AWT. The underlying basis of listeners involves implementing specified interfaces and thereby guaranteeing that a listener will be able to respond to a particular known method invocation on it.
public class Stack { private static final int MAXLEN = 10; private Object stk[] = new Object[MAXLEN]; private int stkp = -1; public void push(Object p) {stk[++stkp] = p;} public Object pop() {return stk[stkp--];} } |
To remedy this problem, you can rewrite pop like so:
public Object pop() { Object p = stk[stkp]; stk[stkp--] = null; return p; } |
(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()); } } } } |