Grinding Java - Back to Basics

From EDM2
Revision as of 01:20, 15 March 2018 by Ak120 (Talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

By Shai Almog

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 behaviour is so every Object in Java will have a certain standard behaviour.

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 white spaces, 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.