Software Design/Construction -- Week 11

Introduction

This week we'll look at input and output and nested classes in Java.

Note that the links from this page are to handouts that will be distributed the night of class.  Only print out those handouts if you could not attend class.

Main topics this week:

Java Input & Output Streams
Some Useful Input Streams
FilterInputStreams
Some Useful Output Streams
Filter Output Streams
Streams UML Diagram
Readers and Writers
Some Useful Readers
Some Useful Writers
Readers and Writers UML Diagrams
Object Serialization
Multiple Classes in a .java File
Independent Classes
Nested Classes
Inner Classes
Next Week

Java Input & Output Streams

From CS II, you know about using some of the Java input and output classes.  Now we'll talk about the hierarchy of input and output classes so you can see how they all relate to each other. 

When we talk about input and output in Java, the terminology used is "streams".  A stream of data is simply a sequence of bytes.  So if we have an input stream, we are reading from a sequence of bytes.  If we have an output stream, we are writing to a sequence of bytes.  Most of the time we do not care where the sequence of bytes come from.  It may come from a file, or it may come from the keyboard, or it may come from a network connection.  Reading or writing the stream is the same in most cases. 

You should recognize that any time we can operate on a number of classes in the same way, we are probably using inheritance and polymorphism, and the classes probably have a common base class.  That is true here, for input and output streams. 

InputStream

The class InputStream is the base class for all input streams.  Regardless of what type of stream you create, you can treat it as an InputStream.

Note that an InputStream is not a very useful class by itself.  Most of the time, you do not want to read bytes, you want to read characters, strings, integers, doubles, etc.  So usually you will create an input stream and give it to another class that knows how to convert bytes to what you really want to read.

OutputStream

The class OutputStream is the base class for all output streams.  Again, usually you do not want to output bytes directly, so you will use another class that will then write to the output stream.

Some Useful Input Streams

Here are some of the more useful input streams. 

FileInputStream -- This input stream allows you to read bytes from a file.   

ByteArrayInputStream -- If you have a byte array that contains data, you can wrap it in a ByteArrayInputStream. The stream will read data directly from the byte array.

SequenceInputStream -- This input stream is a collection of other input streams.  The SequenceInputStream will read from the first input stream in its collection until it reaches the end of the stream.  Then it will move to the next input stream and read from it.  This will continue until all input streams in the collection have been read.  In this way, you could read from two files as if they were one large file.

FilterInputStreams

A filter input stream is a class that looks like an InputStream, but really only contains another InputStream.  The purpose of the filter input stream is to allow some added processing on the data.  Here are some of the more useful FilterInputStreams.

BufferedInputStream -- The BufferedInputStream allows you to remember a point in the input data, and start reading from that point again.  Remembering a location is called "marking" the location, and starting to read from the mark point again is done by calling the reset method.

CheckedInputStream -- The CheckedInputStream maintains a checksum of the data being read.  This is like an identifying number that has a low chance of being the same for any other sequence of data.  If you know what the checksum is supposed to be for the data, you can compare the checksum you get with it to see if the data has been altered.

CipherInputStream -- The CipherInputStream allows you to read encrypted data.    You must provide the CipherInputStream with an instance of the Cipher class that maches the instance used to encrypt the data.

ZipInputStream -- The ZipInputStream class allows you to read data that has been compressed using the Zip protocol.

Some Useful Output Streams

ByteArrayOutputStream -- This output streams writes its data to a byte array.

FileOutputStream -- This output stream writes data to a file on the hard disk.    The stream must be closed before data is actually written to the hard disk.

Filter Output Streams

Just like filter input streams, filter output streams allow some processing of the data before it gets written.

CheckedOutputStream -- This output stream generates a checksum for the data being written.

CipherOutputStream -- This output stream will encrypt the data being written, using an instance of Cipher.

ZipOutputStream -- This output stream compresses the data in Zip format before writing it.

Streams UML Diagram

Here are UML diagrams picturing the relationship between the various stream classes.

wpe3.jpg (20497 bytes)

wpe4.jpg (15791 bytes)

Readers and Writers

The stream classes are all byte oriented.  Usually, you do not want to read and write bytes, but want to read and write other things.  The reader and writer classes will use a byte oriented stream to read and write other types of data.

Some Useful Readers

Here are the most useful Readers:

InputStreamReader -- This class will convert data from a byte stream into characters.  You usually do not use this class directly, but as part of using a BufferedReader.

BufferedReader -- This class will convert data from a character stream into strings.  This is the class most commonly used when reading input from the user or from files.  You must supply a character oriented reader to this one when you create it.

CharArrayReader -- This class will read data from a character array.

StringReader -- This class will read data from a String, one character at a time.

Some Useful Writers

Here are the most useful Writers:

BufferedWriter -- Used for writing characters and strings to a byte oriented stream.

CharArrayWriter -- This class will write data to a character array.

PrintWriter -- This class is more flexible than BufferedWriter, and can output any type of data using the print and println methods.

StringWriter -- This class will write data to a String.

Readers and Writers UML Diagrams

wpe5.jpg (19192 bytes)

Object Serialization

Object serialization is a special case of input and output.  The term "serialize" means to take an object and write it to or read it from a byte oriented stream.  All the information needed to reconstruct the object needs to be written, including the values of any data members and the information about the class used to create the object.  This allows you to transmit, for example, an entire object from one computer to another over a network connection. 

In order to be serialized, an object must implement the Serializable interface.    This interface has no methods, and is just a way of telling Java that you want this object to be able to be serialized.  You write no other code.

There are two streams used in object serialization.  One is the ObjectOutputStream, used to write an object out to a stream, and the other is the ObjectInputStream, used to read an object from a stream.

Object serialization is one way of doing client/server programming, but it is not the most common way. 

Multiple Classes in a .java File

You know that you can only have one public class in a .java file, and that class has to have the same name as the file.  But sometimes you really do want to have a second class in that file, one that is only useful in combination with the public class in the file.  There are a few possibile reasons for this:

  1. The public class might need another class for internal use only.  For example, if you were writing a linked list class, you might need a Node class for storing data.   But the Node class probably would never be seen by the outside world, so you do not want it to be in a separate .java file. 
  2. The public class might need to return an instance of another class.  For example, a linked list class needs to return an instance of Iterator.  But Iterator is just an interface, and the real class that implements that interface is defined in the same .java file as the linked list class.

There are lots of other reasons that we'll discover as we go on. 

There are several ways to include multiple classes in a single .java file. 

Independent Classes

The easiest way to include multiple classes in a .java file is to have independent classes.  These are classes that are not public, but otherwise look just like a public class.  For example:

public class Example
{
}

class AnotherClass
{
}

Both of these classes would be in the Example.java file.  Only the Example class or other classes in the same package would be able to see the AnotherClass class. 

Nested Classes

A nested class is defined inside the public class for the .java file.  For example:

public class Example
{
    public static class AnotherClass
    {
    }
}

The nested class may be private, public, protected, or have package level access.    The normal rules for access apply.  If the nested class is public, other classes may use it as well.  The syntax used for this is as if the enclosing class is a package.  For example:

public class Test
{
    public static void main (String [] args)
    {
        Example.AnotherClass a = new Example.AnotherClass ();
    }
}

A nested class may extend another class and be extended by other classes.

Inner Classes

An inner class is a non-static class defined inside another class.  There are three types of inner classes:

  1. Member inner class
  2. Local inner class
  3. Anonymous inner class

All inner classes can access the private data members of the enclosing class.    Inner classes may not have static data members or static methods.

Member Inner Class

A member inner class is defined at the same level as the data members in the enclosing class.  For example:

public class Example
{
    public class AnotherClass
    {
    }
}

The member inner class may be public, private, protected, or package level access.    If the member inner class is public, other classes can create instance of it.    The syntax is quite odd, however, and it's probably best if you don't do it.    But, for the curious, here it is:

public class Test
{
    public static void main (String [] args)
    {
        Example e = new Example ();
        Example.AnotherClass a = e.new AnotherClass ();
    }
}

Note that since the member inner class is not static, you have to use an instance of the enclosing class to create an instance of the member inner class.

Local Inner Class

A local inner class is defined in a block of code, and can only be used inside that block of code.  It is not public, private, protected, etc.

A local inner class has access not only to the private data members of the enclosing class, but also to any local variables or parameters that are marked as final. 

public class Example
{
    public static void main (String [] args)
    {
        class Inside
        {
            public void method1 ()
            {
            }
        }

        Inside i = new Inside ();
        i.method1 ();
    }
}

The most common use of this is for setting callbacks to Java graphical user interface objects.  We'll see more about that when we talk about building graphical user interfaces.

Anonymous Inner Classes

An anonymous inner class is much like a local inner class, except that is does not have a name and uses a different syntax.  Anonymous inner classes have access to the private data members of the enclosing class, and to any local variables or parameters that are final. 

An anonymous inner class is defined and instantiated in the same place.  They do not have constructors, instead you use an instance initializer that runs like a constructor.  You can use an anonymous inner class to create an instance of a class that implements an interface.

public class Example
{
    public Iterator iterator ()
    {
        Iterator iter = new Iterator ()
        {
            {
                /* If I wanted to do any intialization, I
                   could do it in this block of code.  I
                   can also leave off this block if I do not
                   need to do any initialization. */
            }

            public boolean hasNext ()
            {
                return false;
            }

            public Object next ()
            {
                return null;
            }

            public void remove ()
            {
            }
        };

        return iter;
    }
}

This looks a bit strange, but is the most common way of setting callback for graphical user interfaces.  We'll see much more of anonymous inner classes when we talk about writing graphical user interfaces.

Next Week

Next week we'll look at graphical user interface programming in Java.