|
Back to basics
In the old cheap karate B movies, the wise old Chinese man sits with his
student and shows him how to catch a fly with chopsticks and then teaches
him the basics of marshal arts. In essence the tutor is grabbing attention
using a cheap trick and then goes back to teach the really important
stuff, which is what I am trying to do right now (it's either this or Ed
Wood where I'd have to wear a dress). In this article we will go back from
the AWT focus and learn some basic things about Java. However now all our
hello world applications will look better with the AWT look and feel.
The basics
When you build an object in C++, its real structure is very simple. Your
class is built by what you inherit and what you define within it. In Java
it is slightly more involved. As you may have already noticed Java classes
have functionality that seems to be a part of the language such as the
method equal() which exists for every object in Java.
These capabilities don't come out of thin air. In Java all classes are
derived from java.lang.Object, and for every class you have a
java.lang.Class instance.
If you were to create a class that inherits from no class it will still
be derived from java.lang.Object. This will sound very familiar to
SmallTalk programmers out there but a bit fishy to C++ programmers. The
reason for this behavior is so every Object in Java will have a certain
standard behavior.
java.lang.Object
The Object class contains the following capabilities (these are based on
the Java API documentation):
- clone()
- Creates a new object of the same class as this object.
In Java the assignment operator copies the pointer to the object
so clone is needed to create a new object from an existing object.
- equals(Object)
- Compares two Objects for equality.
Since the == operator checks the objects for physical equality and
not contents equality a method to do that is needed. Since Java does
not support operator overloading a standard method to be overloaded
can make a big difference.
- finalize()
- Called by the garbage collector on an object when garbage collection
determines that there are no more references to the object.
As you may have noticed Java has no destructors. the finalize method
is the next best thing. The problem with finalize is that you never
know when it will be called, that is why it should only be overridden
when there is no other way!
- getClass()
- Returns the runtime class of an object.
As I said every class in Java has an instance of the class
- java.lang.Class
- this method will return to you that instance for this Object.
- hashCode()
- Returns a hash code value for the object.
A hash table is an age old algorithm which is based on the notion
that every object has a unique number identifying it. This method
will produce a unique number suitable for this purpose.
- notify()
- Wakes up a single thread that is waiting on this object's monitor.
This allows us to synchronize threads correctly. I will soon explain
the multithreaded capabilities of Java and this will make more sense
then.
- notifyAll()
- Wakes up all threads that are waiting on this object's monitor.
This method is quite similar to the method above.
- toString()
- Returns a string representation of the object.
- wait()
- Waits to be notified by another thread of a change in this object.
Like notify() these last three methods will make more sense once we
discuss threads.
- wait(long)
- Waits to be notified by another thread of a change in this object.
- wait(long, int)
- Waits to be notified by another thread of a change in this object.
As we can see from the structure of the Object class every class in Java
provides a certain base functionality which is:
- Threading support i.e.: wait, notify.
- Continuance - The ability to interact with other objects
and to expand i.e.: clone, equals, hashCode, toString and to a degree
even finalize.
- Power - getClass gives you total power in Java. You will understand
what I mean by power when I will explain what the Class class can do
for you.
java.lang.Class
Every data type in Java has an instance of class Class to fit it. What
does Class do? Every thing that Java can do that Object does not provide!
I removed many of the methods in this class to make it easier to
swallow. Except for the first two just browse over the rest. It is more
important right now to learn the concept of this class rather than its
capabilities which are what gives Java its power.
- forName(String)
- Returns the Class object associated with the class with the given
string name.
This will sound insane to C++ programmers, yet very familiar to
you CORBA people out there. This method will actually return the
Class instance for the Class name you give (as a string).
This means that you can dynamically request a class to be loaded
and linked and the language can do that for you!
This is very useful in making your programs dynamic and for
distributed systems.
- newInstance()
- Creates a new instance of a class.
This is a very powerful capability which allows you to create an
instance of a class you have never seen.
- getComponentType()
- If this class represents an array type, returns the Class
object representing the component type of the array otherwise
returns null.
- getDeclaredField(String)
- Returns a Field object that reflects the specified declared field
of the class or interface represented by this Class object.
- getDeclaredFields()
- Returns an array of Field objects reflecting all the fields
declared by the class or interface represented
by this Class object.
- getDeclaredMethod(String, Class[])
- Returns a Method object that reflects the specified declared method
of the class or interface represented by this Class object.
- getDeclaredMethods()
- Returns an array of Method objects reflecting all the methods
declared by the class or interface represented by this Class object.
- getDeclaringClass()
- If the class or interface represented by this Class object is a
member of another class, returns the Class object representing the
class of which it is a member (its declaring class).
- getField(String)
- Returns a Field object that reflects the specified public
member field of the class or interface represented by this
Class object.
- getInterfaces()
- Determines the interfaces implemented by the class or interface
represented by this object.
- getMethod(String, Class[])
- Returns a Method object that reflects the specified public member
method of the class or interface represented by this Class object.
- getMethods()
- Returns an array containing Method objects reflecting all the
public member methods of the class or interface represented by
this Class object, including those declared by the class or
interface and those inherited from superclasses and superinterfaces.
- getModifiers()
- Returns the Java language modifiers for this class or interface,
encoded in an integer.
- getName()
- Returns the fully-qualified name of the type (class, interface,
array, or primitive) represented by this Class object, as a String.
- getResourceAsStream(String)
- Find a resource with a given name.
- getSuperclass()
- If this object represents any class other than the class Object,
then the object that represents the superclass of that class is
returned.
- isArray()
- If this Class object represents an array type, returns true,
otherwise returns false.
- isAssignableFrom(Class)
- Determines if the class or interface represented by this Class
object is either the same as, or is a superclass or superinterface
of, the class or interface represented by the specified Class
parameter.
- isInstance(Object)
- This method is the dynamic equivalent of the Java language
instanceof operator.
- isInterface()
- Determines if the specified Class object represents an interface
type.
- isPrimitive()
- Determines if the specified Class object represents a primitive Java
type.
What purpose does class Class serve? It makes Java dynamic. Using Class
you can dynamically load a class from memory, disk, the net and use it.
Most of the methods here (new to JDK 1.1) are for discovering what
properties methods and superclasses a class has. Applets actually work in
a very similar way.
Let's make a small program that will demonstrate the power of the Class
class:
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class DynamicLoader extends Frame implements ActionListener
{
public static void main(String argv[])
{
new DynamicLoader();
}
DynamicLoader()
{
setLayout(new BorderLayout());
buttonToolbar.setLayout(new FlowLayout());
buttonToolbar.add(loadButton);
buttonToolbar.add(exitButton);
add("South",buttonToolbar);
add("Center",message);
add("North",className);
exitButton.addActionListener(this);
loadButton.addActionListener(this);
pack();
show();
}
public void actionPerformed(ActionEvent E)
{
if (E.getSource() == exitButton) System.exit(0);
if (E.getSource() == loadButton)
{
try
{
String name = className.getText();
Class TargetClass = getClass().forName(name);
Object o = TargetClass.newInstance();
message.setText(o.toString());
}
catch (ClassNotFoundException C)
{
message.setText("Class not found!!!");
}
catch (InstantiationException C)
{
message.setText("Class cannot be created!!!");
}
catch (IllegalAccessException C)
{
message.setText("Class cannot be accessed!!!");
}
}
}
Label message = new Label();
TextField className = new TextField();
Button loadButton = new Button("Load");
Button exitButton = new Button("Exit");
Panel buttonToolbar = new Panel();
}
Try running this class with things such as java.awt.Frame. Don't forget to
give the full name of the class or it will not find it. Notice the
exceptions that I must catch.
A few things before we go on
I did not mention all the keywords and terms available in Java, and now is
a good time to do that.
synchronized - In a multithreaded environment many problems can
arise when one thread disturbs the work of another. The synchronized
keyword allows us to hide some this complexity. When a method is declared
synchronized only one thread can access it at a time, and when a property
is declared synchronized only one thread can use it at a time.
abstract - When a method is declared abstract it has no body.
That means that it will be defined by a class that inherits this class. A
class which has abstract methods is an abstract class which cannot be
instantiated and can only be derived. This is similar to creating an
interface.
transient - A transient property/object is an object which is
temporary or local. This is related to the object serialization and maybe
to future persistence. When you wish to transfer a class over the net or
into a file through a stream of data, there might be some members that you
don't need to transfer (they are used internally only) you mark them as
transient.
protected - This keyword should be familiar to C++ programmers.
Protected members may be accessed by all subclasses of the current class,
but are not visible to classes outside the current package.
final - The final keyword prevents overriding of members and
subclassing when applied to a class. The reasons for this keyword are for
both security and to prevent people from mistakenly overriding important
members.
friendly - The default access right given to members. The member
is fully accessible within the current package but not at all outside the
package.
static - The static keyword should be familiar to C++
programmers. When applied to properties the static keyword means that the
member is identical to all instances of the class, and when applied to
methods it means that the method belong to the class but not to the
instance, meaning they cannot modify or read non-static members, and they
can be reached globally. I.e. when I use Color.red red is a static
property of class Color.
Threads
Java does make multi threading much easier, but it's still a pain. That is
why I've waited so long to introduce threading in Java.
Threading in Java is built around a class and an interface: interface
Runnable and class Thread. To implement runnable all you have to do is
create a method called run which will run on the newly created thread. You
can subclass Thread or you can create a new instance of it; I always
create it manually.
The classic thread racer program which shows how to set priorities and
to kill threads is presented here. It creates three threads responsible
for different canvases that compete against each other.
import java.lang.*;
import java.awt.*;
import java.awt.event.*;
public class ThreadRacer extends Frame implements ActionListener
{
public static void main(String argv[])
{
new ThreadRacer();
}
ThreadRacer()
{
setLayout(new BorderLayout());
racers.setLayout(new FlowLayout());
racer1.setColor(Color.blue);
racer2.setColor(Color.red);
racer3.setColor(Color.yellow);
racers.add(racer1);
racers.add(racer2);
racers.add(racer3);
killButton.setEnabled(false);
buttonToolbar.setLayout(new FlowLayout());
buttonToolbar.add(exitButton);
buttonToolbar.add(startButton);
buttonToolbar.add(killButton);
buttonToolbar.add(suspendButton);
exitButton.addActionListener(this);
startButton.addActionListener(this);
killButton.addActionListener(this);
suspendButton.addActionListener(this);
add("South",buttonToolbar);
add("North",racers);
pack();
show();
}
public void actionPerformed(ActionEvent E)
{
if (E.getSource() == exitButton)
{
System.exit(0);
}
if (E.getSource() == startButton)
{
threads[0] = new Thread(racer1);
threads[1] = new Thread(racer2);
threads[2] = new Thread(racer3);
threads[1].setPriority(Thread.MIN_PRIORITY);
threads[2].setPriority(Thread.MIN_PRIORITY + 5);
for (int i = 0; i < 3; i++)
threads[i].start();
startButton.setEnabled(false);
killButton.setEnabled(true);
}
if (E.getSource() == killButton)
{
for (int i = 0; i < 3; i++)
threads[i].stop();
startButton.setEnabled(true);
killButton.setEnabled(false);
}
if (E.getSource() == suspendButton)
{
threads[2].suspend();
}
}
Racer racer1 = new Racer(),
racer2 = new Racer(),
racer3 = new Racer();
Thread [] threads = new Thread[3];
Button exitButton = new Button("Exit");
Button startButton = new Button("Start racing");
Button killButton = new Button("Kill threads");
Button suspendButton = new Button("Suspend thread 2");
Panel buttonToolbar = new Panel(),
racers = new Panel();
}
class Racer extends Canvas implements Runnable
{
Racer()
{
setSize(100,100);
}
public void run()
{
while (true)
{
counter++;
repaint();
if (counter > 100) counter = 0;
}
}
public void paint(Graphics g)
{
g.setColor(barColor);
g.fill3DRect(0,100 - counter,100,100,true);
}
public void setColor(Color barColor)
{
this.barColor = barColor;
}
private Color barColor;
private int counter = 0;
}
Things to notice:
- I used the Thread.MIN_PRIORITY but not the Thread.MAX_PRIORITY. I found
out the hard way that you can crash the computer using that, priority. I
only hope that browsers are protected from that kind of "feature".
- I used a separate class for the threads, but it wasn't necessary, it
was simpler to implement a Canvas this way.
There is much more to learn about threads, yet I don't want to get into
these classes at this time.
If you wish to learn more of the threads capabilities try running javap
java.lang.Thread.
Streams
What is a stream?
The Oxford dictionary describes a stream as: Flow (of liquid, people).
C++ programmers should be well aware of streams, in Java they are quite
similar but much more generic.
Java uses streams to control a flow of data from an origin i.e. data
from a disk file is read through a stream, as is data from a network and
such.
We have used streams before, when we used the System.out.println
method, we were writing into a stream.
Java has a huge selection of types of streams, we will go over the main
choices and explain the differences:
- BufferedInputStream
- The BufferedInputStream uses a buffer to read data into memory and then
accesses the data from memory rather than forwarding reads to the disk,
or any other data source.
- BufferedOutputStream
- This is similar to the BufferedInputStream ,data is written to the
buffer rather than to the target. This stream needs to be flushed
or data will be lost.
- BufferedReader
- This class is optimized to reading text data and lines. It uses a buffer
but does not suffer so much from its drawbacks.
- BufferedWriter
- This class is similar to the BufferedReader where text can be written
using a buffer, with ease.
- FileInputStream
- A file input stream is an input stream built for file reads. It takes
advantage of the fact that the content of the file is known and adds
methods to support that.
- FilterInputStream
- This class sits on top of an input stream and filters the necessary
information straight out.
- FilterOutputStream
- This class sits on top of an output stream and filters all of our
output.
- InputStream
- This is an abstract class that provides most of the functionality an
input stream should provide.
- InputStreamReader
- An InputStreamReader is a bridge from byte streams to character streams,
it translates bytes into characters. [C and C++ programmers should
recall that characters are 16 bit (Unicode) values in Java -- EDM]
- ObjectInputStream
ObjectOutputStream
ObjectStreamClass
- These three classes I will explain when we reach object serialization.
Right now all you have to know is that you can write instances of objects
into them.
- RandomAccessFile
- This class allows you to treat a file as random access, meaning that any
part of the file can be read from or written to using seeks.
- StreamTokenizer
- Reads a stream and converts it into tokens. Tokens are words separated
by a character of some type. This class is very useful for parsing
purposes.
- Reader
- An abstract class that reads input from files. It's the parent of most
of the reader classes.
As you can see I tried my best to avoid the word file when writing about a
stream. Streams are usually created by a method whose purpose is to open
communication routes. For example in a TCP or HTTP connection a stream can
be created, and thus code that deals with random access files can easily
be used to deal with an html file retrieved by HTTP.
To demonstrate I will write a small class that reads standard windows
INI files and acts on their commands. The parsing of INI files can be very
annoying but the StreamTokenizer class can make this very simple.
import java.awt.*;
import java.awt.event.*;
import java.io.*;
public class iniFileReader extends Frame implements
ActionListener,WindowListener
{
public static void main(String argv[])
{
new iniFileReader();
}
public iniFileReader()
{
setLayout(new BorderLayout());
add("North",inifileName);
add("Center",output);
add("South",buttonLoad);
buttonLoad.addActionListener(this);
addWindowListener(this);
pack();
show();
}
public void actionPerformed(ActionEvent E)
{
if (E.getSource() == buttonLoad)
loadIniFile(inifileName.getText());
}
private void loadIniFile(String fileName)
{
try
{
// Remove all the messages on the output panel.
output.removeAll();
// FileReader opens a stream to a file.
FileReader iniFile = new FileReader(fileName);
// Tokenizing the file stream.
StreamTokenizer iniFileTokens = new StreamTokenizer(iniFile);
// The id of the current token.
int currentToken = iniFileTokens.nextToken();
// Treats the = operator as a token.
iniFileTokens.wordChars('=','=');
// Treats the ; char as a comment.
iniFileTokens.commentChar(';');
// Treat the end of line char as a token.
iniFileTokens.eolIsSignificant(true);
// This will be used for output.
String currentLabel = "";
while (currentToken != StreamTokenizer.TT_EOF)
{
// Get the next word.
currentToken = iniFileTokens.nextToken();
// if it's the end of line.
if (currentToken == StreamTokenizer.TT_EOL)
{
// Add this label to the output panel.
output.add (currentLabel);
currentLabel = new String("");
}
else
if (iniFileTokens.sval == null)
currentLabel = currentLabel + " " + iniFileTokens.nval;
else
currentLabel = currentLabel + " " + iniFileTokens.sval;
}
}
catch (Exception e)
{ // Catching a generic exception and displaying a generic message.
output.add("File error :" + e.getMessage());
}
output.setSize(200,400);
pack();
}
public void windowOpened(java.awt.event.WindowEvent e)
{
}
public void windowClosing(java.awt.event.WindowEvent e)
{
System.exit(0);
}
public void windowClosed(java.awt.event.WindowEvent e)
{
}
public void windowIconified(java.awt.event.WindowEvent e)
{
}
public void windowDeiconified(java.awt.event.WindowEvent e)
{
}
public void windowActivated(java.awt.event.WindowEvent e)
{
}
public void windowDeactivated(java.awt.event.WindowEvent e)
{
}
TextField inifileName = new TextField("c:\\OS2\\mdos\\winos2\\system.ini");
List output = new List(30,true);
Button buttonLoad = new Button("Load");
}
Things to notice:
- When I write the default path to the file I use double slashes. The
reason for the double slashes is simple. Remember how we wrote a new line
using "\n" the \ operator has an important role in Java and C strings. It
marks exceptional characters, so if you wish a string to contain one \ you
have to write it twice. NOTE: You shouldn't normally write absolute paths,
since unix uses the / as directory separator. It is possible to check
which char is the directory separator but its best not to do so.
- Stream tokenizer accepts a stream and not a file. This is to abstract
the inner workings and is good object oriented practice.
- We catch a generic exception. No matter what exception will be thrown
we will catch them all.
- The StreamTokenizer has interesting methods: check them out using javap
java.io.StreamTokenizer. It supports whitespaces comments and more.
- I use the WindowListener to exit the program using the close button
rather than building an exit button.
- I use the list Component which is a very useful component for this type
of data.
String, StringBuffer and String tokenizer
We have used the String class a lot. Whenever we use the "" operator we
create an instance of the String class. Java's Strings are very powerful,
yet quite eccentric in a way.
Most tutorials say that you cannot change a string which is slightly
confusing. A String cannot be changed to a different length but it can be
replaced with another string. There exists a StringBuffer class whose
length can change.
A string supports the following methods:
- charAt(int)
- Returns the character at the specified index.
- compareTo(String)
- Compares two strings lexicographically. This is quite similar to the
equal method.
- concat(String)
- Concatenates the specified string to the end of this string.
As I said Strings cannot change their size but they can be replaced
so this method will actually return a new instance of the String
class. That is why s = s.concat("this goes at the end"); will work
it will change s to a new instance.
- endsWith(String)
- Tests if this string ends with the specified suffix.
- equalsIgnoreCase(String)
- Compares this String to another String ,and ignores different cased
letters i.e. "THIS" will equal "this".
- indexOf(int)
- Returns the index within this string of the first occurrence of
the specified character. The parameter which is an int is actually
a character and this method will find the first occurrence of that
character.
- indexOf(int, int)
- Returns the index within this string of the first occurrence of
the specified character, starting the search at the specified index.
- indexOf(String)
- Returns the index within this string of the first occurrence of the
specified substring.
- indexOf(String, int)
- Returns the index within this string of the first occurrence of the
specified substring, starting at the specified index.
- lastIndexOf(int)
- Returns the index within this string of the last occurrence of the
specified character.
- lastIndexOf(int, int)
- Returns the index within this string of the last occurrence of the
specified character, searching backward starting at the specified
index.
- lastIndexOf(String)
- Returns the index within this string of the rightmost occurrence of
the specified substring.
- lastIndexOf(String, int)
- Returns the index within this string of the last occurrence of the
specified substring.
- length()
- Returns the length of this string.
- replace(char, char)
- Returns a new string resulting from replacing all occurrences of
oldChar in this string with newChar.
- startsWith(String)
- Tests if this string starts with the specified prefix.
- startsWith(String, int)
- Tests if this string starts with the specified prefix.
- substring(int)
- Returns a new string that is a substring of this string.
- substring(int, int)
- Returns a new string that is a substring of this string.
- toLowerCase()
- Converts this string to lowercase.
- toUpperCase()
- Converts this string to uppercase.
- trim()
- Removes white space from both ends of this string.
- valueOf(boolean)
- Returns the string representation of the boolean argument.
- valueOf(char)
- Returns the string representation of the char * argument.
- valueOf(double)
- Returns the string representation of the double argument.
- valueOf(float)
- Returns the string representation of the float argument.
- valueOf(int)
- Returns the string representation of the int argument.
- valueOf(long)
- Returns the string representation of the long argument.
- valueOf(Object)
- Returns the string representation of the Object argument.
This listing is mostly dry facts taken out of the JDK 1.1 API The point is
that you have in the String class plenty of methods to perform all the
types of String manipulation you might need.
Conclusions
Well this time wasn't as fun as the last time but we are getting
somewhere. We still haven't finished the basics, but from now on I feel
that you should have a certain feel for Java so I'll make our purpose
slightly more interesting.
Next time we will go into java.net. We will build a program called the
WWW utility, which (hold on to your seats) will do something useful!
Til next month... good night.
|
|