11 Files and Streams

11.1 Input and Output

The java.io package provides an extensive library of classes for dealing with input and output. Java provides streams as a general mechanism for dealing with data I/O. Streams implement sequential access of data. There are two kinds of streams: byte streams and character streams (a.ka. binary streams and text streams, respectively). An input stream is an object that an application can use to read a sequence of data, and an output stream is an object that an application can use to write a sequence of data. An input stream acts as a source of data, and an output stream acts as a destination of data. The following entities can act as both input and output streams:

• an array of bytes or characters

• a file

• a pipe (a mechanism by which a program can communicate data to another progam during execution)

• a network connection

Streams can be chained with filters to provide new functionality. In addition to dealing with bytes and characters, streams are provided for input and output of Java primitive values and objects. The java.io package also provides a general interface to interact with the file system of the host platform.

11.2 The File Class

The File class provides a general machine-independent interface for the file system of the underlying platform. A File object represents the pathname of a file or directory in the host file system. An application can use the functionality provided by the File class for handling files and directories in the file system. The File class is not meant for handling the contents of files. For that purpose, there are the FileInputStream and FileOutputStream classes, which are discussed later in this chapter.

The pathname for a file or directory is specified using the naming conventions of the host system. However, the File class defines platform-dependent constants that can be used to handle file and directory names in a platform-independent way:

public static final char separatorChar
public static final String separator

Defines the character or string that separates the directory and the file components in a pathname. This separator is '/', '' or ':' for Unix, Windows, and Macintosh, respectively.

public static final char pathSeparatorChar
public static final String pathSeparator

Defines the character or string that separates the file or directory names in a “path list.” This character is ':' or ';' for Unix and Windows, respectively.

Some examples of pathnames are:

/book/chapter1              on Unix
C:ookchapter1         on Windows
HD:book:chapter1         on Macintosh

Some examples of path lists are:

/book:/manual:/draft                       on Unix
C:ook;D:manual;A:draft       on Windows

Files and directories can be referenced using both absolute and relative pathnames, but the pathname must follow the conventions of the host platform. On Unix platforms, a pathname is absolute if its first character is the separator character. On Windows platforms, a path is absolute if the ASCII '' is the first character, or follows the volume name (e.g., C:), in a pathname. On the Macintosh, a pathname is absolute if it begins with a name followed by a colon. Java programs should not rely on system-specific pathname conventions. The File class provides facilities to construct pathnames in a platform-independent way.

The File class has various constructors for associating a file or a directory pathname to an object of the File class. Creating a File object does not mean creation of any file or directory based on the pathname specified. A File instance, called the abstract pathname, is a representation of the pathname of a file and directory. The pathname cannot be changed once the File object is created.

File(String pathname)

The pathname (of a file or a directory) can be an absolute pathname or a pathname relative to the current directory. An empty string as argument results in an abstract pathname for the current directory.

// "/book/chapter1" – absolute pathname of a file
File chap1 = new File(File.separator + "book" + File.separator + "chapter1");
// "draft/chapters" – relative pathname of a directory
File draftChapters = new File("draft" + File.separator + "chapters");

File(String directoryPathname, String fileName)

This creates a File object whose pathname is as follows: directoryPathname + separator + fileName.

// "/book/chapter1" – absolute pathname of a file
File updatedChap1 = new File(File.separator + "book", "chapter1");

File(File directory, String fileName)

If the directory argument is null, the resulting File object represents a file in the current directory. If the directory argument is not null, it creates a File object that represents a file in the given directory. The pathname of the file is then the pathname of the directory File object + separator + fileName.

// "chapter13" – relative pathname of a file
File parent = null;
File chap13 = new File(parent, "chapter13");

// "draft/chapters/chapter13" – relative pathname of a file
File draftChapters = new File("draft" + File.separator + "chapters");
File updatedChap13 = new File(draftChapters, "chapter13");

An object of the File class provides a handle to a file or directory in the file system, and can be used to create, rename, and delete the entry.

A File object can also be used to query the file system for information about a file or directory:

• whether the entry exists

• whether the File object represents a file or directory

• get and set read, write, or execute permissions for the entry

• get pathname information about the file or directory

• list all entries under a directory in the file system

Many methods of the File class throw a SecurityException in the case of a security violation, for example if read or write access is denied. Some methods also return a boolean value to indicate whether the operation was successful.

Querying the File System

The File class provides a number of methods for obtaining the platform-dependent representation of a pathname and its components.

String getName()

Returns the name of the file entry, excluding the specification of the directory in which it resides.

On Unix, the name part of "/book/chapters/one" is "one".

On Windows platforms, the name part of "c:javainjavac" is "javac".

On the Macintosh, the name part of "HD:java-tools:javac" is "javac".

The strings "." and ".." generally designate the current directory and the parent directory in pathnames, respectively.

String getPath()

The method returns the (absolute or relative) pathname of the file represented by the File object.

String getAbsolutePath()

If the File object represents an absolute pathname, this pathname is returned, otherwise the returned pathname is constructed by concatenating the current directory pathname, the separator character and the pathname of the File object.

String getCanonicalPath() throws IOException

Also platform-dependent, the canonical path usually specifies an absolute pathname in which all relative references have been completely resolved.

For example, if the File object represented the absolute pathname "c:ook chapter1" on Windows, this pathname would be returned by these methods. On the other hand, if the File object represented the relative pathname "..ookchapter1" and the current directory had the absolute pathname "c:documents", the pathname returned by the getPath(), getAbsolutePath(), and getCanonicalPath() methods would be "..ookchapter1", "c:documents..ookchapter1" and "c:ookchapter1", respectively.

String getParent()

The parent part of the pathname of this File object is returned if one exists, otherwise the null value is returned. The parent part is generally the prefix obtained from the pathname after deleting the file or directory name component found after the last occurrence of the separator character. However, this is not true for all platforms.

On Unix, the parent part of "/book/chapter1" is "/book", whose parent part is "/", which in turn has no parent.

On Windows platforms, the parent part of "c:java-tools" is "c:", which in turn has no parent.

On the Macintosh, the parent part of "HD:java-tools" is "HD:", which in turn has no parent.

boolean isAbsolute()

Whether a File object represents an absolute pathname can be determined using this method.

The following three methods can be used to query the file system about the modification time of a file or directory, determine the size (in bytes) of a file, and ascertain whether two pathnames are identical.

long lastModified()

The modification time returned is encoded as a long value, and should only be compared with other values returned by this method.

long length()

Returns the size (in bytes) of the file represented by the File object.

boolean equals(Object obj)

This method just compares the pathnames of the File objects, and returns true if they are identical. On Unix systems, alphabetic case is significant in comparing pathnames; on Windows systems it is not.

File or Directory Existence

A File object is created using a pathname. Whether this pathname denotes an entry that actually exists in the file system can be checked using the exists() method:

boolean exists()

Since a File object can represent a file or a directory, the following methods can be used to distinguish whether a given File object represents a file or a directory, respectively:

boolean isFile()
boolean isDirectory()

File and Directory Permissions

Write, read and execute permissions can be set by calling the following methods. If the first argument is true, the operation permission is set; otherwise it is cleared. If the second argument is true, the permission only affects the owner; otherwise it affects all users. These methods throw a SecurityException if permission cannot be changed. It should be noted that the exact interpretation of these permissions is platform dependent.

boolean setReadable(boolean readable)
boolean setReadable(boolean readable, boolean owner)

boolean setWritable(boolean writable)
boolean setWritable(boolean writable, boolean owner)

boolean setExecutable(boolean executable)
boolean setExecutable(boolean executable, boolean owner)

To check whether the specified file has write, read, or execute permissions, the following methods can be used. They throw a SecurityException if general access is not allowed, i.e., the application is not even allowed to check whether it can read, write or execute a file.

boolean canWrite()
boolean canRead()
boolean canExecute()

Listing Directory Entries

The entries in a specified directory can be obtained as an array of file names or abstract pathnames using the following list() methods. The current directory and the parent directory are excluded from the list.

String[] list()
String[] list(FilenameFilter filter)
File[]   listFiles()
File[]   listFiles(FilenameFilter filter)
File[]   listFiles(FileFilter filter)

The filter argument can be used to specify a filter that determines whether an entry should be included in the list. These methods return null if the abstract pathname does not denote a directory, or if an I/O error occurs. A filter is an object of a class that implements either of these two interfaces:

interface FilenameFilter {
    boolean accept(File currentDirectory, String entryName);
}
interface FileFilter {
    boolean accept(File pathname);
}

The list() methods call the accept() methods of the filter for each entry to determine whether the entry should be included in the list.

Creating New Files and Directories

The File class can be used to create files and directories. A file can be created whose pathname is specified in a File object using the following method:

boolean createNewFile() throws IOException

It creates a new, empty file named by the abstract pathname if, and only if, a file with this name does not already exist. The returned value is true if the file was successfully created, false if the file already exists. Any I/O error results in an IOException.

A directory whose pathname is specified in a File object can be created using the following methods:

boolean mkdir()
boolean mkdirs()

The mkdirs() method creates any intervening parent directories in the pathname of the directory to be created.

Renaming Files and Directories

A file or a directory can be renamed, using the following method which takes the new pathname from its argument. It throws a SecurityException if access is denied.

boolean renameTo(File dest)

Deleting Files and Directories

A file or a directory can be deleted using the following method. In the case of a directory, it must be empty before it can be deleted. It throws a SecurityException if access is denied.

boolean delete()

Example 11.1 Listing Files Under a Directory

import java.io.File;
import java.io.IOException;

public class DirectoryLister {
  public static void main(String[] args) {
    if (args.length == 0) {                                         // (1)
      System.err.println("Please specify a directory name.");
      return;
    }
    File entry = new File(args[0]);                                 // (2)
    listDirectory(entry);
  }

  public static void listDirectory(File entry) {
    try {
      if (!entry.exists()) {                                        // (3)
        System.out.println(entry.getName() + " not found.");
        return;
      }
      if (entry.isFile()) {
        // Write the pathname of the entry:
        System.out.println(entry.getCanonicalPath());               // (4)
      } else if (entry.isDirectory()) {
        // Create list of entries for this directory:
        String[] entryNames = entry.list();                         // (5)
        for (String entryName : entryNames) {
          // Create a File object for each entry name:
          File thisEntry = new File(entry.getPath(), entryName);    // (6)
          // List this entry by a recursive call:
          listDirectory(thisEntry);                                 // (7)
        }
      }
    } catch(IOException e) { System.out.println("Error: " + e); }
  }
}

Running the program on a Windows platform:

java DirectoryLister D:docsJC-Bookspecial

produces the following output:

D:docsJC-Bookspecialook19990308JC-14-applets.fm
D:docsJC-Bookspecialook19990308JC-16-swing.fm
D:docsJC-BookspecialJC-11-awtlayout.fm

The class DirectoryLister in Example 11.1 lists all entries in a directory specified in the command line. If no directory is given, an error message is printed; either by the print statement at (1) or as a result of an exception at (2). In the method listDirectory(), each entry is tested to see if it exists, as shown at (3). The entry could be an alias (symbolic link in Unix or shortcut in Windows terminology) and its destination might not exist. The method determines whether the entry is a file, in which case the absolute pathname is listed, as shown at (4). In the case of a directory, an array of entry names is created, as shown at (5). For each entry in the directory, a File object is created, as shown at (6). The method listDirectory() is called recursively for each entry, as shown at (7).

11.3 Byte Streams: Input Streams and Output Streams

The abstract classes InputStream and OutputStream are the root of the inheritance hierarchies for handling the reading and writing of bytes (Figure 11.1). Their subclasses, implementing different kinds of input and output streams, override the following methods from the InputStream and OutputStream classes to customize the reading and writing of bytes, respectively:

The InputStream class:

int read() throws IOException
int read(byte[] b) throws IOException
int read(byte[] b, int off, int len) throws IOException

Note that the first read() method reads a byte, but returns an int value. The byte read resides in the eight least significant bits of the int value, while the remaining bits in the int value are zeroed out. The read() methods return the value –1 when the end of the stream is reached.

The OutputStream class:

void write(int b) throws IOException
void write(byte[] b) throws IOException
void write(byte[] b, int off, int len) throws IOException

The first write() method takes an int as argument, but truncates it down to the eight least significant bits before writing it out as a byte.

Figure 11.1   Partial Byte Stream Inheritance Hierarchies

Partial Byte Stream Inheritance Hierarchies

void close() throws IOException
void flush() throws IOException           Only for OutputStream

A stream should be closed when no longer needed, to free system resources. Closing an output stream automatically flushes the stream, meaning that any data in its internal buffer is written out. An output stream can also be manually flushed by calling the second method.

Read and write operations on streams are synchronous (blocking) operations, i.e., a call to a read or write method does not return before a byte has been read or written.

Many methods in the classes contained in the java.io package throw the checked IOException. A calling method must therefore either catch the exception explicitly, or specify it in a throws clause.

Table 11.1 and Table 11.2 give an overview of the byte streams. Usually an output stream has a corresponding input stream of the same type.

Table 11.1   Selected Input Streams

Selected Input Streams

Table 11.2   Selected Output Streams

Selected Output Streams

File Streams

The classes FileInputStream and FileOutputStream define byte input and output streams that are connected to files. Data can only be read or written as a sequence of bytes.

An input stream for reading bytes can be created using the following constructors:

FileInputStream(String name) throws FileNotFoundException
FileInputStream(File file) throws FileNotFoundException
FileInputStream(FileDescriptor fdObj)

The file can be specified by its name, through a File object, or using a FileDescriptor object.

If the file does not exist, a FileNotFoundException is thrown. If it exists, it is set to be read from the beginning. A SecurityException is thrown if the file does not have read access.

An output stream for writing bytes can be created using the following constructors:

FileOutputStream(String name) throws FileNotFoundException
FileOutputStream(String name, boolean append) throws FileNotFoundException
FileOutputStream(File file) throws IOException
FileOutputStream(FileDescriptor fdObj)

The file can be specified by its name, through a File object, or using a File Descriptor object.

If the file does not exist, it is created. If it exists, its contents are reset, unless the appropriate constructor is used to indicate that output should be appended to the file. A SecurityException is thrown if the file does not have write access or it cannot be created.

The FileInputStream class provides an implementation for the read() methods in its superclass InputStream. Similarly, the FileOutputStream class provides an implementation for the write() methods in its superclass OutputStream.

Example 11.2 demonstrates usage of writing and reading bytes to and from file streams. It copies the contents of one file to another file. The input and the output file names are specified on the command line. The streams are created at (1) and(2). The input file is read one byte at a time and written straight to the output file, as shown in the try block at (3). The end of file is reached when the read() method returns the value -1. The streams are explicitly closed, as shown at (4). Note that most of the code consists of try-catch constructs to handle the various exceptions. The example could be optimized by using buffering for reading and writing several bytes at a time.

Example 11.2   Copy a File

/* Copy a file.
   Command syntax: java CopyFile <from_file> <to_file>
 */
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

class CopyFile {
  public static void main(String[] args) {
    FileInputStream fromFile;
    FileOutputStream toFile;

    // Assign the files
    try {
      fromFile = new FileInputStream(args[0]);       // (1)
      toFile = new FileOutputStream(args[1]);        // (2)
    } catch(FileNotFoundException e) {
      System.err.println("File could not be copied: " + e);

      return;
    } catch(ArrayIndexOutOfBoundsException e) {
      System.err.println("Usage: CopyFile <from_file> <to_file>");
      return;
    }

    // Copy bytes
    try {                                            // (3)
      while (true) {
        int i = fromFile.read();
        if(i == -1) break;             // check end of file
        toFile.write(i);
      }
    } catch(IOException e) {
      System.err.println("Error reading/writing.");
    }

    // Close the files
    try {                                            // (4)
      fromFile.close();
      toFile.close();
    } catch(IOException e) {
      System.err.println("Error closing file.");
    }
  }
}

Filter Streams

A filter is a high-level stream that provides additional functionality to an underlying stream to which it is chained. The data from the underlying stream is manipulated in some way by the filter. The FilterInputStream and FilterOutputStream classes, together with their subclasses, define input and output filter streams. The subclasses BufferedInputStream and BufferedOutputStream implement filters that buffer input from and output to the underlying stream, respectively. The subclasses DataInputStream and DataOutputStream implement filters that allow binary representation of Java primitive values to be read and written, respectively, to and from an underlying stream.

Reading and Writing Binary Values

The java.io package contains the two interfaces DataInput and DataOutput, that streams can implement to allow reading and writing of binary representations of Java primitive values (boolean, char, byte, short, int, long, float, double). The methods for writing binary representations of Java primitive values are named writeX, where X is any Java primitive data type. The methods for reading binary representations of Java primitive values are similarly named readX. Table 11.3 gives an overview of the readX() and writeX() methods found in these two interfaces. A file containing binary values (i.e., binary representation of Java primitive vales) is usually called a binary file.

Table 11.3   The DataInput and DataOutput Interfaces

The DataInput and DataOutput Interfaces

Note the methods provided for reading and writing strings. Whereas the methods readChar() and writeChar() handle a single character, the methods readLine() and writeChars() handle a string of characters. The methods readUTF() and writeUTF() also read and write characters, but use the UTF-8 character encoding. However, the recommended practice for reading and writing characters is to use character streams, called readers and writers, that are discussed in Section 11.4.

The filter streams DataOutputStream and DataInputStream implement DataOutput and DataInput interfaces, respectively, and can be used to read and write binary representations of Java primitive values to and from an underlying stream. Both the writeX() and readX() methods throw an IOException in the event of an I/O error. In particular, the readX() methods throw an EOFException (a subclass of IOEXception) if the input stream does not contain the correct number of bytes to read. Bytes can also be skipped from a DataInput stream, using the skipBytes(int n) method which skips n bytes. The following constructors can be used to set up filters for reading and writing Java primitive values, respectively, from an underlying stream:

DataInputStream(InputStream in)
DataOutputStream(OutputStream out)

Writing Binary Values to a File

To write the binary representation of Java primitive values to a binary file, the following procedure can be used, which is also depicted in Figure 11.2.

Figure 11.2   Stream Chaining for Reading and Writing Binary Values to a File

Stream Chaining for Reading and Writing Binary Values to a File

1. Create a FileOutputStream:

FileOutputStream outputFile = new FileOutputStream("primitives.data");

2. Create a DataOutputStream which is chained to the FileOutputStream:

DataOutputStream outputStream = new DataOutputStream(outputFile);

3. Write Java primitive values using relevant writeX() methods:

outputStream.writeBoolean(true);
outputStream.writeChar('A'),                  // int written as Unicode char
outputStream.writeByte(Byte.MAX_VALUE);       // int written as 8-bits byte
outputStream.writeShort(Short.MIN_VALUE);     // int written as 16-bits short
outputStream.writeInt(Integer.MAX_VALUE);
outputStream.writeLong(Long.MIN_VALUE);
outputStream.writeFloat(Float.MAX_VALUE);
outputStream.writeDouble(Math.PI);

Note that in the case of char, byte, and short data types, the int argument to the writeX() method is converted to the corresponding type, before it is written (see Table 11.3).

4. Close the filter stream, which also closes the underlying stream:

outputStream.close();

Reading Binary Values From a File

To read the binary representation of Java primitive values from a binary file the following procedure can be used, which is also depicted in Figure 11.2.

1. Create a FileInputStream:

FileInputStream inputFile = new FileInputStream("primitives.data");

2. Create a DataInputStream which is chained to the FileInputStream:

DataInputStream inputStream = new DataInputStream(inputFile);

3. Read the (exact number of) Java primitive values in the same order they were written out, using relevant readX() methods:

boolean v = inputStream.readBoolean();
char        c = inputStream.readChar();
byte        b = inputStream.readByte();
short       s = inputStream.readShort();
int            i = inputStream.readInt();
long         l = inputStream.readLong();
float         f = inputStream.readFloat();
double    d = inputStream.readDouble();

4. Close the filter stream, which also closes the underlying stream:

inputStream.close();

Example 11.3 uses both procedures described above: first to write and then to read some Java primitive values to and from a file. It also checks to see if the end of the stream has been reached, signalled by an EOFException. The values are also written to the standard input stream.

Example 11.3   Reading and Writing Binary Values

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class BinaryValuesIO {
  public static void main(String[] args) throws IOException {
    // Create a FileOutputStream.
    FileOutputStream outputFile = new FileOutputStream("primitives.data");

    // Create a DataOutputStream which is chained to the FileOutputStream.
    DataOutputStream outputStream = new DataOutputStream(outputFile);

    // Write Java primitive values in binary representation:
    outputStream.writeBoolean(true);
    outputStream.writeChar('A'),                                    // int written as Unicode char
    outputStream.writeByte(Byte.MAX_VALUE);    // int written as 8-bits byte
    outputStream.writeShort(Short.MIN_VALUE);  // int written as 16-bits short
    outputStream.writeInt(Integer.MAX_VALUE);
    outputStream.writeLong(Long.MIN_VALUE);
    outputStream.writeFloat(Float.MAX_VALUE);
    outputStream.writeDouble(Math.PI);

    // Close the output stream, which also closes the underlying stream.
    outputStream.flush();
    outputStream.close();

    // Create a FileInputStream.
    FileInputStream inputFile = new FileInputStream("primitives.data");

    // Create a DataInputStream which is chained to the FileInputStream.
    DataInputStream inputStream = new DataInputStream(inputFile);

    // Read the binary representation of Java primitive values
    // in the same order they were written out:
    boolean v = inputStream.readBoolean();
    char        c = inputStream.readChar();
    byte        b = inputStream.readByte();
    short       s = inputStream.readShort();
    int            i = inputStream.readInt();
    long         l = inputStream.readLong();
    float         f = inputStream.readFloat();
    double    d = inputStream.readDouble();

    // Check for end of stream:
    try {
      int value = inputStream.readByte();
      System.out.println("More input: " + value);
    } catch (EOFException eofe) {
      System.out.println("End of stream");
    } finally {
      // Close the input stream, which also closes the underlying stream.
      inputStream.close();
    }

    // Write the values read to the standard input stream:
    System.out.println("Values read:");
    System.out.println(v);
    System.out.println(c);
    System.out.println(b);
    System.out.println(s);
    System.out.println(i);
    System.out.println(l);
    System.out.println(f);
    System.out.println(d);
  }
}

Output from the program:

End of stream
Values read:
true
A
127
-32768
2147483647
-9223372036854775808
3.4028235E38
3.141592653589793

Review Questions

Review Questions

11.1 Which of these can act both as the source of an input stream and as the destination of an output stream, based on the classes provided by the java.io package?

Select the four correct answers.

(a) A file

(b) A network connection

(c) A pipe

(d) A string

(e) An array of chars

11.2 Which of these statements about the constant named separator of the File class are true?

Select the two correct answers.

(a) The variable is of type char.

(b) The variable is of type String.

(c) It can be assumed that the value of the variable always is the character '/'.

(d) It can be assumed that the value of the variable always is one of '/', '' or ':'.

(e) The separator can consist of more than one character.

11.3 Which one of these methods in the File class will return the name of the entry, excluding the specification of the directory in which it resides?

Select the one correct answer.

(a) getAbsolutePath()

(b) getName()

(c) getParent()

(d) getPath()

(e) None of the above.

11.4 What will the method length() in the class File return?

Select the one correct answer.

(a) The number of characters in the file.

(b) The number of kilobytes in the file.

(c) The number of lines in the file.

(d) The number of words in the file.

(e) None of the above.

11.5 Given the following program:

import java.io.File;
import java.io.IOException;

public final class Filing {

  public static void main (String[] args) throws IOException {
    File file = new File("./documents","../book/../chapter1");
    System.out.println(file.getPath());
    System.out.println(file.getAbsolutePath());
    System.out.println(file.getCanonicalPath());
    System.out.println(file.getName());
    System.out.println(file.getParent());
  }
}

Assume that the current or working directory has the absolute path "/wrk". Which lines below will not be included in the output from the program?

Select the two correct answers.

(a) ./documents/../book/../chapter1

(b) ./documents/book/chapter1

(c) /wrk/./documents/../book/../chapter1

(d) /wrk/documents/book/chapter1

(e) /wrk/chapter1

(f) chapter1

(g) ./documents/../book/..

11.6 Given the following program:

 import java.io.File;
public class ListingFiles {
  public static void main(String[] args) {
    File currentDirectory = new File(".");
    printFiles1(currentDirectory);
    printFiles2(currentDirectory);
    printFiles3(currentDirectory);
  }

  public static void printFiles1(File currentDirectory) {
    String[] entryNames = currentDirectory.list();
    for (String entryName : entryNames) {
      System.out.println(entryName);
    }
  }

  public static void printFiles2(File currentDirectory) {
    File[] entries = currentDirectory.listFiles();
    for (File entry : entries) {
      System.out.println(entry);
    }
  }

  public static void printFiles3(File currentDirectory) {
    File[] entries = currentDirectory.listFiles();
    for (File entry : entries) {
      System.out.println(entry.getPath());
    }
  }
}

Assume that the current or working directory has the absolute path "/wrk" and contains only one file with the name "ListingFiles.class".

Which statement is true about the program?

Select the one correct answer.

(a) All three methods printFiles1(), printFiles2(), and printFiles3() will produce the same output.

(b) Only the methods printFiles1() and printFiles2(), will produce the same output.

(c) Only the methods printFiles2() and printFiles3(), will produce the same output.

(d) Only the methods printFiles1() and printFiles3(), will produce the same output.

(e) The program does not compile because the list() method does not exist in the File class.

11.7 A file is readable but not writable on the file system of the host platform. What will be the result of calling the method canWrite() on a File object representing this file?

Select the one correct answer.

(a) A SecurityException is thrown.

(b) The boolean value false is returned.

(c) The boolean value true is returned.

(d) The file is modified from being unwritable to being writable.

(e) None of the above.

11.8 What is the type of the parameter given to the method renameTo() in the class File?

Select the one correct answer.

(a) File

(b) FileDescriptor

(c) FileNameFilter

(d) String

(e) char[]

11.9 If write(0x01234567) is called on an instance of OutputStream, what will be written to the destination of the stream?

Select the one correct answer.

(a) The bytes 0x01, 0x23, 0x34, 0x45, and 0x67, in that order.

(b) The bytes 0x67, 0x45, 0x34, 0x23, and 0x01, in that order.

(c) The byte 0x01.

(d) The byte 0x67.

(e) None of the above.

11.10 Given the following code, under which circumstances will the method return false?

public static boolean test(InputStream is) throws IOException {
  int value = is.read();
  return value >= 0;
}

Select the one correct answer.

(a) A character of more than 8 bits was read from the input stream.

(b) An I/O error occurred.

(c) Never.

(d) The end of the stream was reached in the input stream.

11.11 Which of these classes provides methods for writing binary representations of Java primitive values?

Select the two correct answers.

(a) DataOutputStream

(b) FileOutputStream

(c) ObjectOutputStream

(d) PrintStream

(e) BufferedOutputStream

11.12 Given the following program:

import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Endings {
  public static void main(String[] args) {
    try {
      FileInputStream fos = new FileInputStream("info.dat");
      DataInputStream dis = new DataInputStream(fos);
      int i = dis.readByte();
      while (i != -1) {
        System.out.print((byte)i + "|");
        i = dis.readByte();
      }
    } catch (FileNotFoundException fnf) {
      System.out.println("File not found");
    } catch (EOFException eofe) {
      System.out.println("End of stream");
    } catch (IOException ioe) {
      System.out.println("Input error");
    }
  }
}

Assume that the file "info.dat" exits in the current directory and has only the byte values 10, 20 and 30, stored in that order.

Which statement is true about the program?

Select the one correct answer.

(a) The program will not compile because a certain unchecked exception is not caught.

(b) The program will compile and print 10|20|30|Input error.

(c) The program will compile and print 10|20|30|End of stream.

(d) The program will compile and print 10|20|30|, and then block in order to read from the file.

(e) The program will compile and print 10|20|30|, and terminate because of an uncaught exception.

11.4 Character Streams: Readers and Writers

A character encoding is a scheme for representing characters. Java programs represent values of the char type internally in the 16-bit Unicode character encoding, but the host platform might use another character encoding to represent and store characters externally. For example, the ASCII (American Standard Code for Information Interchange) character encoding is widely used to represent characters on many platforms. However, it is only one small subset of the Unicode standard.

The abstract classes Reader and Writer are the roots of the inheritance hierarchies for streams that read and write Unicode characters using a specific character encoding (as shown in Figure 11.3). A reader is an input character stream that reads a sequence of Unicode characters, and a writer is an output character stream that writes a sequence of Unicode characters. Character encodings are used by readers and writers to convert between external encoding and internal Unicode characters. Table 11.4 and Table 11.5 give an overview of some selected character streams found in the java.io package.

Figure 11.3 Partial Character Stream Inheritance Hierarchies

Partial Character Stream Inheritance Hierarchies

Table 11.4 Selected Readers

Selected Readers

Table 11.5 Selected Writers

Selected Writers

Readers use the following methods for reading Unicode characters:

int read() throws IOException
int read(char cbuf[]) throws IOException
int read(char cbuf[], int off, int len) throws IOException

Note that the read() methods read the character as an int in the range 0 to 65535 (0x0000–0xFFFF). The value –1 is returned if the end of the stream has been reached.

long skip(long n) throws IOException

A reader can skip over characters using the skip() method.

Writers use the following methods for writing Unicode characters:

void write(int c) throws IOException

The write() method takes an int as argument, but writes only the least significant 16 bits.

void write(char[] cbuf) throws IOException
void write(String str) throws IOException
void write(char[] cbuf, int off, int length) throws IOException
void write(String str, int off, int length) throws IOException

These methods write the characters from an array of characters or a string.

void close() throws IOException
void flush() throws IOException

Like byte streams, a character stream should be closed when no longer needed to free system resources. Closing a character output stream automatically flushes the stream. A character output stream can also be manually flushed.

Like byte streams, many methods of the character stream classes throw an IOException that a calling method must either catch explicitly or specify in a throws clause.

Print Writers

The capabilities of the OutputStreamWriter and the InputStreamReader classes are limited, as they primarily write and read characters.

In order to write a text representation of Java primitive values and objects, a PrintWriter should be chained to either a writer, a byte output stream, a File, or a String file name, using one of the following constructors:

PrintWriter(Writer out)
PrintWriter(Writer out, boolean autoFlush)
PrintWriter(OutputStream out)
PrintWriter(OutputStream out, boolean autoFlush)
PrintWriter(File file)
PrintWriter(File file, String charsetName)
PrintWriter(String fileName)
PrintWriter(String fileName, String charsetName)

The autoFlush argument specifies whether the PrintWriter should be flushed when any println() method of the PrintWriter class is called.

When the underlying writer is specified, the character encoding supplied by the underlying writer is used. However, an OutputStream has no notion of any character encoding, so the necessary intermediate OutputStreamWriter is automatically created, which will convert characters into bytes, using the default character encoding.

When supplying the File object or the file name, the character encoding can be specified explicitly.

The PrintWriter class provides the following methods for writing text representation of Java primitive values and objects (Table 11.6):

Table 11.6  Print Methods of the PrintWriter Class

Print Methods of the PrintWriter Class

The println() methods write the text representation of their argument to the underlying stream, and then append a line-separator. The println() methods use the correct platform-dependent line-separator. For example, on Unix platforms the line-separator is ' ' (newline), while on Windows platforms it is " " (carriage return + newline) and on the Macintosh it is ' ' (carriage return).

The print() methods create a text representation of an object by calling the toString() method on the object. The print() methods do not throw any IOException. Instead, the checkError() method of the PrintWriter class must be called to check for errors.

In addition, the PrintWriter class provides the format() method and the convenient printf() method to write formatted values. Details on formatting values can be found in Section 12.7, p. 593.

Writing Text Files

When writing text to a file using the default character encoding, the following four procedures for setting up a PrintWriter can be used.

Setting up a PrintWriter based on an OutputStreamWriter which is chained to a FileOutputStream (Figure 11.4a):

1. Create a FileOutputStream:

FileOutputStream outputFile = new FileOutputStream("info.txt");

2. Create an OutputStreamWriter which is chained to the FileOutputStream:

OutputStreamWriter outputStream = new OutputStreamWriter(outputFile);

The OutputStreamWriter uses the default character encoding for writing the characters to the file.

3. Create a PrintWriter which is chained to the OutputStreamWriter:

PrintWriter printWriter1 = new PrintWriter(outputStream, true);

Setting up a PrintWriter based on a FileOutputStream (Figure 11.4b):

1. Create a FileOutputStream:

FileOutputStream outputFile = new FileOutputStream("info.txt");

2. Create a PrintWriter which is chained to the FileOutputStream:

PrintWriter printWriter2 = new PrintWriter(outputFile, true);

The intermediate OutputStreamWriter to convert the characters using the default encoding is automatically supplied.

Setting up a PrintWriter based on a FileWriter (Figure 11.4c):

1. Create a FileWriter which is a subclass of OutputStreamWriter:

FileWriter fileWriter = new FileWriter("info.txt");

This is equivalent to having an OutputStreamWriter chained to a FileOutputStream for writing the characters to the file, as shown in Figure 11.4a.

2. Create a PrintWriter which is chained to the FileWriter:

PrintWriter printWriter3 = new PrintWriter(fileWriter, true);

Setting up a PrintWriter, given the file name (Figure 11.4d):

1. Create a PrintWriter, supplying the file name:

PrintWriter printWriter3 = new PrintWriter("info.txt");

Figure 11.4 Setting up a PrintWriter to Write to a File

Setting up a PrintWriter to Write to a File

The underlying OutputStreamWriter is created to write the characters to the file in the default encoding, as shown in Figure 11.4d. In this case, there is no automatic flushing.

If a specific character encoding is desired for the writer, the first procedure (Figure 11.4a) can be used, the encoding being specified for the OutputStreamWriter:

FileOutputStream        outputFile       = new FileOutputStream("info.txt");
OutputStreamWriter   outputStream = new OutputStreamWriter(outputFile, "8859_1");
PrintWriter                   printWriter4    = new PrintWriter(outputStream, true);

This writer will use the 8859_1 character encoding to write the characters to the file. Alternatively, we can use one of the two PrintWriter constructors that accept a character encoding:

PrintWriter printWriter5 = new PrintWriter("info.txt", "8859_1");

A BufferedWriter can be also used to improve the efficiency of writing characters to the underlying stream (explained later in this subsection).

Reading Text Files

Java primitive values and objects cannot be read directly from their text representation. Characters must be read and converted to the relevant values explicitly. One common strategy is to write lines of text and tokenize the characters as they are read, a line at a time (see the subsection The java.util.Scanner Class, p. 571). Such files are usually called text files.

When reading characters from a file using the default character encoding, the following two procedures for setting up an InputStreamReader can be used.

Setting up an InputStreamReader which is chained to a FileInputStream (Figure 11.5a):

1. Create a FileInputStream:

FileInputStream inputFile = new FileInputStream("info.txt");

2. Create an InputStreamReader which is chained to the FileInputStream:

InputStreamReader reader = new InputStreamReader(inputFile);

The InputStreamReader uses the default character encoding for reading the characters from the file.

Figure 11.5  Setting up Readers to read Characters

Setting up Readers to read Characters

Setting up a FileReader which is a subclass of InputStreamReader (Figure 11.5b):

1. Create a FileReader:

FileReader fileReader = new FileReader("info.txt");

This is equivalent to having an InputStreamReader chained to a FileInputStream for reading the characters from the file, using the default character encoding. Other constructors of the FileReader class accept a File or a FileDescriptor.

If a specific character encoding is desired for the reader, the first procedure must be used (Figure 11.5a), the encoding being specified for the InputStreamReader:

FileInputStream inputFile = new FileInputStream("info.txt");
InputStreamReader reader = new InputStreamReader(inputFile, "8859_1");

This reader will use the 8859_1 character encoding to read the characters from the file. A BufferedReader can also be used to improve the efficiency of reading characters from the underlying stream, as explained later in this section.

Using Buffered Writers

A BufferedWriter can be chained to the underlying writer by using one of the following constructors:

BufferedWriter(Writer out)
BufferedWriter(Writer out, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

Characters, strings, and arrays of characters can be written using the methods for a Writer, but these now use buffering to provide efficient writing of characters. In addition, the BufferedWriter class provides the method newLine() for writing the platform-dependent line-separator.

The following code creates a PrintWriter whose output is buffered and the characters are written using the 8859_1 character encoding (Figure 11.6a):

FileOutputStream      outputFile           = new FileOutputStream("info.txt");
OutputStreamWriter outputStream      = new OutputStreamWriter(outputFile, "8859_1");
BufferedWriter           bufferedWriter1 = new BufferedWriter(outputStream);
PrintWriter                  printWriter1        = new PrintWriter(bufferedWriter1, true);

Figure 11.6  Buffered Writers

Buffered Writers

The following code creates a PrintWriter whose output is buffered, and the characters are written using the default character encoding (Figure 11.6b):

FileWriter          fileWriter             = new FileWriter("info.txt");
BufferedWriter bufferedWriter2 = new BufferedWriter(fileWriter);
PrintWriter        printWriter2        = new PrintWriter(bufferedWriter2, true);

Note that in both cases, the PrintWriter is used to write the characters. The Buffered Writer is sandwiched between the PrintWriter and the underlying OutputStream Writer.

Using Buffered Readers

A BufferedReader can be chained to the underlying reader by using one of the following constructors:

BufferedReader(Reader in)
BufferedReader(Reader in, int size)

The default buffer size is used, unless the buffer size is explicitly specified.

In addition to the methods of the Reader class, the BufferedReader class provides the method readLine() to read a line of text from the underlying reader:

String readLine() throws IOException

The null value is returned when the end of the stream is reached. The returned string must explicitly be converted to other values.

The following code creates a BufferedReader that can be used to read text lines from a file, using the 8859_1 character encoding (Figure 11.7a):

FileInputStream           inputFile                = new FileInputStream("info.txt");
InputStreamReader     reader                     = new InputStreamReader(inputFile, "8859_1");
BufferedReader           bufferedReader1   = new BufferedReader(reader);

Figure 11.7  Buffered Readers

Buffered Readers

The following code creates a BufferedReader that can be used to read text lines from a file, using the default character encoding (Figure 11.7b):

FileReader           fileReader                = new FileReader("lines.txt");
BufferedReader  bufferedReader2    = new BufferedReader(fileReader);

Note that in both cases the BufferedReader object is used to read the text lines.

In contrast to Example 11.3, which demonstrated the reading and writing of binary representations of primitive data values, Example 11.4 illustrates the reading and writing of text representations of primitive data values.

The CharEncodingDemo class in Example 11.4 writes text representations of Java primitive values, using the 8859_1 character encoding (Figure 11.6a). The PrintWriter is buffered. Its underlying writer uses the specified encoding, as shown at (1). Values are written out with the text representation of one value on each line, as shown at (2), and the writer is closed, as shown at (3). The example uses the same character encoding to read the text file. A BufferedReader is created (Figure 11.7a). Its underlying reader uses the specified encoding, as shown at (4). The text representation of the values is read in the same order the values were written out, one value per line. The characters in the line are explicitly converted to an appropriate type of value, as shown at (5). An alternate approach to extracting values from a text line is to use a scanner (p. 571).

We check for the end of the stream at (6), which is signalled by the null value returned by the readLine() method of the BufferedReader class. The BufferedReader is closed, as shown at (7), and the values are echoed on the standard output stream, as shown at (8). Note the exceptions that are specified in the throws clause of the main() method.

Example 11.4  Demonstrating Readers and Writers, and Character Encoding

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class CharEncodingDemo {
  public static void main(String[] args)
                     throws IOException, NumberFormatException {

    // Character encoding.                                                                    (1)
    FileOutputStream outputFile = new FileOutputStream("info.txt");
    OutputStreamWriter writer = new OutputStreamWriter(outputFile, "8859_1");
    BufferedWriter bufferedWriter1 = new BufferedWriter(writer);
    PrintWriter printWriter = new PrintWriter(bufferedWriter1, true);
    System.out.println("Writing using encoding: " + writer.getEncoding());

    // Print Java primitive values, one on each line.                         (2)
    printWriter.println(true);
    printWriter.println('A'),
    printWriter.println(Byte.MAX_VALUE);
    printWriter.println(Short.MIN_VALUE);
    printWriter.println(Integer.MAX_VALUE);
    printWriter.println(Long.MIN_VALUE);
    printWriter.println(Float.MAX_VALUE);
    printWriter.println(Math.PI);

    // Close the writer, which also closes the underlying stream      (3)
    printWriter.flush();
    printWriter.close();

    // Create a BufferedReader which uses 8859_1 character encoding   (4)
    FileInputStream inputFile = new FileInputStream("info.txt");
    InputStreamReader reader = new InputStreamReader(inputFile, "8859_1");
    BufferedReader bufferedReader = new BufferedReader(reader);
    System.out.println("Reading using encoding: " + reader.getEncoding());

    // Read the (exact number of) Java primitive values                              (5)
    // in the same order they were written out, one on each line
    boolean   v = bufferedReader.readLine().equals("true")? true : false;
    char          c = bufferedReader.readLine().charAt(0);
    byte          b = (byte) Integer.parseInt(bufferedReader.readLine());
    short        s = (short) Integer.parseInt(bufferedReader.readLine());
    int             i = Integer.parseInt(bufferedReader.readLine());
    long          l = Long.parseLong(bufferedReader.readLine());
    float         f = Float.parseFloat(bufferedReader.readLine());
    double    d = Double.parseDouble(bufferedReader.readLine());

    // Check for end of stream:                                                                     (6)
    String line = bufferedReader.readLine();
    if (line != null ) {
      System.out.println("More input: " + line);
    } else {
      System.out.println("End of stream");
    }

    // Close the reader, which also closes the underlying stream              (7)
    bufferedReader.close();

    // Write the values read on the terminal                                                  (8)
    System.out.println("Values read:");
    System.out.println(v);
    System.out.println(c);
    System.out.println(b);
    System.out.println(s);
    System.out.println(i);
    System.out.println(l);
    System.out.println(f);
    System.out.println(d);
  }
}

Output from the program:

Writing using encoding: ISO8859_1
Reading using encoding: ISO8859_1
End of stream
Values read:
true
A
127
-32768
2147483647
-9223372036854775808
3.4028235E38
3.141592653589793

The Standard Input, Output, and Error Streams

The standard output stream (usually the display) is represented by the PrintStream object System.out. The standard input stream (usually the keyboard) is represented by the InputStream object System.in. In other words, it is a byte input stream. The standard error stream (also usually the display) is represented by System.err which is another object of the PrintStream class. The PrintStream class offers print() methods which act as corresponding print() methods from the PrintWriter class. These methods can be used to write output to System.out and System.err. In other words, both System.out and System.err act like PrintWriter, but in addition they have write() methods for writing bytes.

In order to read characters typed by the user, the Console class is recommended (see the next section).

Comparison of Byte Streams and Character Streams

It is instructive to see which byte streams correspond to which character streams. Table 11.7 shows the correspondence between byte and character streams. Note that not all classes have a corresponding counterpart.

Table 11.7  Correspondence Between Selected Byte and Character Streams

Correspondence Between Selected Byte and Character Streams

11.5 The Console Class

A console is a unique character-based device associated with a JVM. Whether a JVM has a console depends on the platform, and also on the manner in which the JVM is invoked. When the JVM is started from a command line, and the standard input and output streams have not been redirected, the console will normally correspond to the keyboard and the display (Figure 11.8). In any case, the console will be represented by an instance of the class Console. This Console instance is obtained by calling the static method console() of the System class. If there is no console associated with the JVM, the null value is returned by this method.

Figure 11.8  Keyboard and Display as Console

Keyboard and Display as Console

// Obtaining the console:
Console console = System.console();

if (console == null) {
  System.err.println("No console available.");
  return;
}
// Continue ...

For creating dialog for console-based applications, the Console class provides the following functionality:

• Prompt and read a line of character-based response.

String username = console.readLine("Enter the user name (%d chars): ", 4);

The readLine() method first prints the formatted prompt on the console, and then returns the characters typed at the console when the line is terminated by the ENTER key.

• Prompt and read passwords without echoing the characters on the console.

char[] password;
do {
password = console.readPassword("Enter password (min. %d chars): ", 6);
} while (password.length < 6);

The readPassword() method first prints the formatted prompt, and returns the password characters typed by the user in an array of char when the line is terminated by the ENTER key. The password characters are not echoed on the display.

Since a password is sensitive data, the recommended practice is to have it stored in memory only as long as it is necessary and to zero-fill the char array as soon as possible in order to overwrite the password characters.

• Print formatted strings to the console.

The Console class provides the format() and the printf() methods for this purpose. Using these methods and creating formatted strings are covered in Section 12.7, p. 593.

Note that the console only returns character-based input. For reading other types of values from the standard input stream, the Scanner class (p. 571) can be considered.

The Console class provides methods for formatted prompting and reading from the console, and obtaining the reader associated with it.

String readLine()
String readLine(String format, Object... args)

The first method reads a single line of text from the console. The second method prints a formatted prompt first, then reads a single line of text from the console. The prompt is constructed by formatting the specified args according to the specified format.

char[] readPassword()
char[] readPassword(String format, Object... args)

The first method reads a password or a password phrase from the console with echoing disabled. The second method does the same, but first prints a formatted prompt.

Reader reader()

This retrieves the unique Reader object associated with this console.

The Console class provides the following methods for writing formatted strings to the console, and obtaining the writer associated with it:

Console format(String format, Object... args)
Console printf(String format, Object... args)

These methods write a formatted string to this console’s output stream using the specified format string and arguments, according to the default locale.

PrintWriter writer()

The method retrieves the unique PrintWriter object associated with this console.

void flush()

This method flushes the console and forces any buffered output to be written immediately.

Example 11.5 illustrates a typical use of the Console class to change the password of a user. A password file can first be generated by running the program in the class MakePasswordFile. Each user name (String) and the hash value (int) of the corresponding password are stored on a single line in a text file, separated by a space. At the start of the program, this information is read into a map declared at (1) by the method readPWStore() declared at (9). This method uses a BufferedReader chained to a FileReader to read the lines in the text file. The login name and the password hash values are extracted using a Scanner and put into the password map.

At the end of the program, the updated password map is written back to the file at (6) by the method writePWStore() declared at (10). This method uses a PrintWriter chained to a FileWriter to write the information. It uses the printf() method of the PrintWriter to format the login/password information on each line that is written to the file.

The console is obtained at (2). The login name and the current password are read at (4) by calling the readLine() and the readPassword() methods, respectively:

...
login = console.readLine("Enter your login: ");
oldPassword = console.readPassword("Enter your current password: ");
...

User verification is done by the verifyPassword() method at (7). The char array is first converted to a string by calling the String.copyValueOf() method. The hash value of this string is compared with the hash code of the password for the user looked up in the password map.

The code at (5) implements the procedure for changing the password. The user is asked to submit the new password, and then asked to confirm it. Note the password characters are not echoed. The respective char arrays returned with this input are compared for equality by the static method equals() in the java.util.Arrays class, that compares two arrays. The password is changed by the changePassword() method at (8). This puts a new entry in the password map, whose value is the hash value of the new password.

The char arrays with the passwords are zero-filled by calling the Arrays.fill() method when they are no longer needed.

Example 11.5 Changing Passwords

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

/** Class to create a password file */
public final class MakePasswordFile {

  public static void main (String[] args) throws IOException {
    Map<String, Integer> pwStore = new TreeMap<String, Integer>();
    pwStore.put("tom", "123".hashCode());
    pwStore.put("dick", "456".hashCode());
    pwStore.put("harry", "789".hashCode());

    PrintWriter destination = new PrintWriter(new FileWriter("pws.txt"));
    Set<Map.Entry<String, Integer>> pwSet = pwStore.entrySet();
    for (Map.Entry<String, Integer> entry : pwSet) {
      // Format: login password

      destination.printf("%s %s%n", entry.getKey(), entry.getValue());
    }
    destination.flush();
    destination.close();
  }
}

import java.io.BufferedReader;
import java.io.Console;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.TreeMap;

/** Class to change the password of a user */
public class ChangePassword {

  // Map for storing login/password info.                            (1)
  private static Map<String, Integer> pwStore;

  public static void main (String[] args) throws IOException  {

    // Obtain the console:                                           (2)
    Console console = System.console();
    if (console == null) {
      System.err.println("No console available.");
      return;
    }

    // Read the login/password info from a file:                     (3)
    readPWStore();

    // Verify user:                                                  (4)
    String login;
    char[] oldPassword;
    do {
      login = console.readLine("Enter your login: ");
      oldPassword = console.readPassword("Enter your current password: ");
    } while (login.length() == 0 || oldPassword.length == 0 ||
             !verifyPassword(login, oldPassword));
    Arrays.fill(oldPassword, '0'),

    // Changing the password:                                        (5)
    boolean noMatch = false;
    do {
      // Read the new password and its confirmation:
      char[] newPasswordSelected
             = console.readPassword("Enter your new password: ");
      char[] newPasswordConfirmed

             = console.readPassword("Confirm your new password: ");

      // Compare the supplied passwords:
      noMatch = newPasswordSelected.length == 0  ||
                newPasswordConfirmed.length == 0 ||
                !Arrays.equals(newPasswordSelected, newPasswordConfirmed);
      if (noMatch) {
        console.format("Passwords don't match. Please try again.%n");
      } else {
        changePassword(login, newPasswordSelected);
        console.format("Password changed for %s.%n", login);
      }
      // Zero-fill the password arrays:
      Arrays.fill(newPasswordSelected, '0'),
      Arrays.fill(newPasswordConfirmed, '0'),
    } while (noMatch);

    // Save the login/password info to a file:                       (6)
    writePWStore();
  }

  /** Verifies the password. */                                   // (7)
  private static boolean verifyPassword(String login, char[] password) {
    Integer suppliedPassword = String.copyValueOf(password).hashCode();
    Integer storedPassword = pwStore.get(login);
    return storedPassword != null && storedPassword.equals(suppliedPassword);
  }

  /** Changes the password for the user. */                       // (8)
  private static void changePassword(String login, char[] password) {
    Integer newPassword = String.copyValueOf(password).hashCode();
    pwStore.put(login, newPassword);
  }

  /** Reads login/password from a file */                         // (9)
  private static void readPWStore() throws IOException {
    pwStore = new TreeMap<String, Integer>();
    BufferedReader source = new BufferedReader(new FileReader("pws.txt"));
    while (true) {
      String txtLine = source.readLine();
      if (txtLine == null) break;             // EOF?
      Scanner scanner = new Scanner(txtLine);
      // Format: <login string> <password int hash value>
      String login = scanner.next();
      Integer password = scanner.nextInt();
      pwStore.put(login, password);
    }
    source.close();
  }

  /** Writes login/password to a file */                          // (10)
  private static void writePWStore() throws IOException {
    PrintWriter destination = new PrintWriter(new FileWriter("pws.txt"));
    Set<Map.Entry<String, Integer>> pwSet = pwStore.entrySet();
    for (Map.Entry<String, Integer> entry : pwSet) {

      // Format: <login string> <password int hash value>
      destination.printf("%s %s%n", entry.getKey(), entry.getValue());
    }
    destination.close();
  }
}

Running the program:

>java ChangePassword
Enter your login: tom
Enter your current password:
Enter your new password:
Confirm your new password:
Password changed for tom

Review Questions

Review Questions

11.13 Which of these are valid parameter types for the write() methods of the Writer class?

Select the three correct answers.

(a) String

(b) char

(c) char[]

(d) int

11.14 What is the default encoding for an OutputStreamWriter?

Select the one correct answer.

(a) 8859_1

(b) UTF8

(c) Unicode

(d) The default is system-dependent.

(e) The default is not system-dependent, but is none of the above.

11.15 Which of these integer types do not have their own print() method in the PrintWriter class?

Select the one correct answer.

(a) byte

(b) char

(c) int

(d) long

(e) All have their own print() method.

11.16 How can one access the standard error stream?

Select the one correct answer.

(a) It is accessed via a member of the System.err class.

(b) It is accessed via the static variable named out in the System class.

(c) It is accessed via the static variable named err in the System class.

(d) It is accessed via the static variable named err in the Runtime class.

(e) It is returned by a method in the System class.

11.17 How can we programmatically guarantee that a call to a print() method of the PrintWriter class was successful or not?

Select the one correct answer.

(a) Check if the return value from the call is -1.

(b) Check if the return value from the call is null.

(c) Catch the IOException that is thrown when an I/O error occurs.

(d) Call the checkError() method of the PrinterWriter class immediately after the print() method call returns to see if an IOException was thrown.

11.18 Given the following program:

import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;

public class MoreEndings {
  public static void main(String[] args) {
    try {
      FileInputStream fis = new FileInputStream("seq.txt");
      InputStreamReader isr = new InputStreamReader(fis);
      int i = isr.read();
      while (i != -1) {
        System.out.print((char)i + "|");
        i = isr.read();
      }
    } catch (FileNotFoundException fnf) {
      System.out.println("File not found");
    } catch (EOFException eofe) {
      System.out.println("End of stream");
    } catch (IOException ioe) {
      System.out.println("Input error");
    }
  }
}

Assume that the file "seq.txt" exists in the current directory, has the required access permissions, and contains the string "Hello".

Which statement about the program is true?

Select the one correct answer.

(a) The program will not compile because a certain unchecked exception is not caught.

(b) The program will compile and print H|e|l|l|o|Input error.

(c) The program will compile and print H|e|l|l|o|End of stream.

(d) The program will compile, print H|e|l|l|o|, and then terminate normally.

(e) The program will compile, print H|e|l|l|o|, and then block in order to read from the file.

(f) The program will compile, print H|e|l|l|o|, and terminate because of an uncaught exception.

11.19 Which code, when inserted at (1), will result in the program compiling and running without errors?

import java.io.*;

public class MakeLines {
  public static void main(String[] args) {
    try {
      String fileName = "greetings.txt";
      // (1) INSERT CODE HERE ...
      writeGreetings(stream);
      stream.close();
    } catch (IOException ioe) {
      System.out.println("I/O error");
    }
  }

  private static void writeGreetings(Writer writer) {
    try {
      BufferedWriter bw = new BufferedWriter(writer);
      bw.write("Hello");
      bw.newLine();
      bw.write("Howdy");
      bw.newLine();
      bw.flush();
    } catch (IOException ioe) {
      System.out.println("I/O error");
    }
  }
}

Select the three correct answers.

(a) FileOutputStream fos = new FileOutputStream(fileName);

(b) OutputStreamWriter stream = new OutputStreamWriter(fos);

(c) FileOutputStream fos = new FileOutputStream(fileName);

(d) InputStreamWriter stream = new InputStreamWriter(fos);

(e) FileOutputStream stream = new FileOutputStream(fileName);

(f) PrintWriter stream = new PrintWriter(fileName);

(g) FileWriter stream = new FileWriter(fileName);

11.20 Given the following program:

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.FileNotFoundException;

import java.io.FileReader;
import java.io.IOException;

public class NoEndings {
  public static void main(String[] args) {
    try {
      FileReader fr = new FileReader("greetings.txt");
      BufferedReader br = new BufferedReader(fr);
      System.out.print(br.readLine() + "|");
      System.out.print(br.readLine() + "|");
      System.out.print(br.readLine() + "|");
    } catch (EOFException eofe) {
      System.out.println("End of stream");
    } catch (IOException ioe) {
      System.out.println("Input error");
    }
  }
}

Assume that the file "greeting.txt" exists in the current directory, has the required access permissions, and contains the following two lines of text:

Hello
Howdy

Which statement is true about the program?

Select the one correct answer.

(a) The program will not compile because the FileNotFoundException is not caught.

(b) The program will compile, print Hello|Howdy|null|, and then terminate normally.

(c) The program will compile and print Hello|Howdy|Input error.

(d) The program will compile and print Hello|Howdy|End of stream.

(e) The program will compile, print Hello|Howdy|, and then block in order to read from the file.

(f) The program will compile, print Hello|Howdy|, and terminate because of an uncaught exception.

11.21 Given the following program:

import java.io.Console;

public class ConsoleInput {

  public static void main(String[] args){
    Console console = System.console();
    if (console == null) {
      System.err.println("No console available.");
      return;
    }
    String username = console.readLine("Enter user name (%d chars): ", 4);
    char[] password = console.readPassword("Enter password (%d chars): ", 4);

    System.out.println("Username: " + username);

    System.out.println("Password: " + String.valueOf(password));
  }
}

Assume that the user types the strings "java dude" and "fort knox" when prompted for the user name and the password, respectively.

Which statement about the program is true?

Select the one correct answer.

(a) The program will print:

         Username: java
         Password: fort knox

(b) The program will print:

         Username: java dude                  
          Password: fort

(c) The program will print:

         Username: java dude
         Password: fort knox

(d) The program will print:

         Username: java
         Password: fort

11.6 Object Serialization

Object serialization allows an object to be transformed into a sequence of bytes that can later be re-created (deserialized) into the original object. After deserialization, the object has the same state as it had when it was serialized, barring any data members that were not serializable. This mechanism is generally known as persistence. Java provides this facility through the ObjectInput and ObjectOutput interfaces, which allow the reading and writing of objects from and to streams. These two interfaces extend the DataInput and DataOutput interfaces, respectively (see Figure 11.1, p. 476).

The ObjectOutputStream class and the ObjectInputStream class implement the ObjectOutput interface and the ObjectInput interface, respectively, providing methods to write and read binary representation of objects as well as Java primitive values. Figure 11.9 gives an overview of how these classes can be chained to underlying streams and some selected methods they provide. The figure does not show the methods inherited from the abstract OutputStream and InputStream superclasses.

Figure 11.9 Object Stream Chaining

Object Stream Chaining

The read and write methods in the two classes can throw an IOException, and the read methods will throw an EOFException if the end of the stream has been reached.

The ObjectOutputStream Class

The class ObjectOutputStream can write objects to any stream that is a subclass of the OutputStream, e.g., to a file or a network connection (socket). An Object OutputStream must be chained to an OutputStream using the following constructor:

ObjectOutputStream(OutputStream out) throws IOException

For example, in order to store objects in a file and thus provide persistent storage for objects, an ObjectOutputStream can be chained to a FileOutputStream:

FileOutputStream outputFile = new FileOutputStream("obj-storage.dat");
ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);

Objects can be written to the stream using the writeObject() method of the ObjectOutputStream class:

final void writeObject(Object obj) throws IOException

The writeObject() method can be used to write any object to a stream, including strings and arrays, as long as the object implements the java.io.Serializable interface, which is a marker interface with no methods. The String class, the primitive wrapper classes and all array types implement the Serializable interface. A serializable object can be any compound object containing references to other objects, and all constituent objects that are serializable are serialized recursively when the compound object is written out. This is true even if there are cyclic references between the objects. Each object is written out only once during serialization. The following information is included when an object is serialized:

• the class information needed to reconstruct the object.

the values of all serializable non-transient and non-static members, including those that are inherited.

An exception of the type java.io.NotSerializableException is thrown if a non-serializable object is encountered during the serialization process. Note also that objects of subclasses that extend a serializable class are always serializable.

The ObjectInputStream Class

An ObjectInputStream is used to restore (deserialize) objects that have previously been serialized using an ObjectOutputStream. An ObjectInputStream must be chained to an InputStream, using the following constructor:

ObjectInputStream(InputStream in)
        throws IOException, StreamCorruptedException

For example, in order to restore objects from a file, an ObjectInputStream can be chained to a FileInputStream:

FileInputStream inputFile = new FileInputStream("obj-storage.dat");
ObjectInputStream inputStream = new ObjectInputStream(inputFile);

The method readObject() of the ObjectInputStream class is used to read an object from the stream:

final Object readObject()
        throws OptionalDataException, ClassNotFoundException, IOException

Note that the reference returned is of type Object regardless of the actual type of the retrieved object, and can be cast to the desired type. Objects and values must be read in the same order as when they were serialized.

Serializable, non-transient data members of an object, including those data members that are inherited, are restored to the values they had at the time of serialization. For compound objects containing references to other objects, the constituent objects are read to re-create the whole object structure. In order to deserialize objects, the appropriate classes must be available at runtime. Note that new objects are created during deserialization, so that no existing objects are overwritten.

The class ObjectSerializationDemo in Example 11.6 serializes some objects in the writeData() method at (1), and then deserializes them in the readData() method at(2). The readData() method also writes the data to the standard output stream.

The writeData() method writes the following values to the output stream: an array of strings (strArray), a long value (num), an array of int values (intArray), and lastly a String object (commonStr) which is shared with the array of strings, strArray. However, this shared String object is actually only serialized once. Duplication is automatically avoided when the same object is serialized several times. Note that the array elements and the characters in a String object are not written out explicitly one by one. It is enough to pass the object reference in the writeObject() method call. The method also recursively goes through the array of strings, strArray, serializing each String object in the array.

The method readData() deserializes the data in the order in which it was written. An explicit cast is needed to convert the reference of a deserialized object to a subtype. Note that new objects are created by the readObject() method, and that an object created during the deserialization process has the same state as the object that was serialized.

Example 11.6 Object Serialization

//Reading and Writing Objects
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;

public class ObjectSerializationDemo {
  void writeData() {                                    // (1)
    try {
      // Set up the output stream:
      FileOutputStream outputFile = new FileOutputStream("obj-storage.dat");
      ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);

      // Write data:
      String[] strArray = {"Seven", "Eight", "Six"};
      long num = 2008;
      int[] intArray = {1, 3, 1949};
      String commonStr = strArray[2];                  // "Six"
      outputStream.writeObject(strArray);
      outputStream.writeLong(num);
      outputStream.writeObject(intArray);
      outputStream.writeObject(commonStr);

      // Flush and close the output stream:
      outputStream.flush();
      outputStream.close();
    } catch (FileNotFoundException e) {
      System.err.println("File not found: " + e);
    } catch (IOException e) {
      System.err.println("Write error: " + e);
    }
  }

  void readData() {                                     // (2)
    try {
      // Set up the input stream:
      FileInputStream inputFile = new FileInputStream("obj-storage.dat");
      ObjectInputStream inputStream = new ObjectInputStream(inputFile);

      // Read the data:
      String[] strArray = (String[]) inputStream.readObject();
      long num = inputStream.readLong();
      int[] intArray = (int[]) inputStream.readObject();
      String commonStr = (String) inputStream.readObject();

      // Write data to the standard output stream:
      System.out.println(Arrays.toString(strArray));
      System.out.println(Arrays.toString(intArray));
      System.out.println(commonStr);

      // Close the stream:
      inputStream.close();
    } catch (FileNotFoundException e) {
      System.err.println("File not found: " + e);
    } catch (EOFException e) {
      System.err.println("End of stream: " + e);
    } catch (IOException e) {
      System.err.println("Read error: " + e);
    } catch (ClassNotFoundException e) {
      System.err.println("Class not found: " + e);
    }
  }

  public static void main(String[] args) {
    ObjectSerializationDemo demo = new ObjectSerializationDemo();
    demo.writeData();
    demo.readData();
  }
}

Output from the program:

[Seven, Eight, Six]
[1, 3, 1949]
Six

Example 11.7 illustrates some salient aspects of serialization. The setup comprises the classes Wheel and Unicycle, and their client class SerialClient. The class Unicycle has a field of type Wheel, and the class Wheel has a field of type int. The class SerialClient provides two methods, writeData() and readData(), declared at (4) and (5), respectively. The writeData() method serializes a unicycle with a wheel of size 65 to a file. The readData() method deserializes the bytes on the file. The state of the objects is printed to the standard output stream before serialization, and so is the state of the object created by deserialization.

If we run the program with the following declarations for the Wheel and the Unicycle classes, where both classes are serializable:

class Wheel implements Serializable {                               // (1)
  private int wheelSize;
  ...
}

class Unicycle implements Serializable {                            // (2)
  private Wheel wheel;                                              // (3)
  ...
}

we get the following output, showing that both serialization and deserialization were successful:

Before writing: Unicycle with wheel size: 65
After reading: Unicycle with wheel size: 65

Example 11.7 Non-Serializable Objects

import java.io.Serializable;

//public class Wheel implements Serializable {                        // (1)
public class Wheel {                                              // (1a)
  private int wheelSize;

  Wheel(int ws) { wheelSize = ws; }

  public String toString() { return "wheel size: " + wheelSize; }
}
____________________________________________________
import java.io.Serializable;

public class Unicycle implements Serializable {                     // (2)
  private Wheel wheel;                                              // (3)
//transient private Wheel wheel;                                    // (3a)

  Unicycle (Wheel wheel) { this.wheel = wheel; }

  public String toString() { return "Unicycle with " + wheel; }
}
____________________________________________________
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerialClient {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    SerialClient demo = new SerialClient();
    demo.writeData();
    demo.readData();
  }

  void writeData() throws IOException {                            // (4)
    // Set up the output stream:
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);

    // Write the data:
    Wheel wheel = new Wheel(65);
    Unicycle uc = new Unicycle(wheel);
    System.out.println("Before writing: " + uc);
    outputStream.writeObject(uc);

    // Close the stream:
    outputStream.flush();
    outputStream.close();
  }

  void readData() throws IOException, ClassNotFoundException {     // (5)
    // Set up the input streams:
    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);

    // Read data.
    Unicycle uc = (Unicycle) inputStream.readObject();

    // Write data on standard output stream.
    System.out.println("After reading: " + uc);

    // Close the stream.
    inputStream.close();
  }
}

If we make the wheel field of the Unicycle class transient, (3a):

class Wheel implements Serializable {                               // (1)
  private int wheelSize;
  ...
}

class Unicycle implements Serializable {                            // (2)
  transient private Wheel wheel;                                    // (3a)
  ...
}

we get the following output, showing that the wheel field of the Unicycle object was not serialized:

Before writing: Unicycle with wheel size: 65
After reading: Unicycle with null

As noted earlier, static fields are not serialized, as these are not part of the state of an object.

If the class Wheel in Example 11.7 is not serializable, (1a):

class Wheel {                                                       // (1a)
  private int wheelSize;
  ...
}

class Unicycle implements Serializable {                            // (2)
  private Wheel wheel;                                              // (3)
  ...
}

we get the following output when we run the program, i.e., a Unicycle object cannot be serialized because its constituent Wheel object is not serializable:

>java SerialClient
Before writing: Unicycle with wheel size: 65
Exception in thread "main" java.io.NotSerializableException: Wheel
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1156)
  at java.io.ObjectOutputStream.defaultWriteFields(ObjectOutputStream.java:1509)
  at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1474)
  at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1392)
  at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1150)
  at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:326)
  at SerialClient.writeData(SerialClient.java:25)
  at SerialClient.main(SerialClient.java:12)

Customizing Object Serialization

As we have seen, the class of the object must implement the Serializable interface if we want the object to be serialized. If this object is a compound object, then all its constituent objects must also be serializable, and so on.

It is not always possible for a client to declare that a class is Serializable. It might be declared final, and therefore not extendable. The client might not have access to the code, or extending this class with a serializable subclass might not be an option. Java provides a customizable solution for serializing objects in such cases.

The basic idea behind the scheme is to use default serialization as much as possible, and provide “hooks” in the code for the serialization mechanism to call specific methods to deal with objects or values that should not or cannot be serialized by the default methods of the object streams.

Customizing serialization is illustrated in Example 11.8. The serializable class Unicycle would like to use the Wheel class, but this class is not serializable. If the wheel field in the Unicycle class is declared to be transient, it will be ignored by the default serialization procedure. This is not a viable option, as the unicycle will be missing the wheel size when a serialized unicycle is deserialized, as was illustrated in Example 11.7.

Any serializable object has the option of customizing its own serialization if it implements the following pair of methods:

private void writeObject(ObjectOutputStream) throws IOException;
private void readObject(ObjectInputStream)
                        throws IOException, ClassNotFoundException;

These methods are not part of any interface. Although private, these methods can be called by the JVM. The first method is called on the object when its serialization starts. The serialization procedure uses the reference value of the object passed in the ObjectOutputStream.writeObject() method to call the first method on this object. The second method is called on the object created when the deserialization procedure is initiated by the call to the ObjectInputStream.readObject() method.

Customizing serialization for objects of the class Unicycle in Example 11.8 is achieved by the methods at (3b) and (3c). Note that the field wheel is declared transient at (3a) and excluded by the normal serialization process.

In the method writeObject() at (3b), the pertinent lines of code are the following:

oos.defaultWriteObject();
oos.writeInt(wheel.getWheelSize());

The call to the defaultWriteObject() method of the ObjectOutputStream does what its name implies: normal serialization of the current object. The second line of code does the customization: it writes the binary int value of the wheel size to the ObjectOutputStream. Code for customization can be called both before and after the call to the defaultWriteObject() method, as long as the same order is used during deserialization.

In the method readObject() at (3c), the pertinent lines of code are the following:

ois.defaultReadObject();
int wheelSize = ois.readInt();
wheel = new Wheel(wheelSize);

The call to the defaultReadObject() method of the ObjectInputStream does what its name implies: normal deserialization of the current object. The second line of code reads the binary int value of the wheel size from the ObjectInputStream. The third line of code creates a Wheel object passing this value in the constructor call, and assigns its reference value to the wheel field of the current object. Again, code for customization can be called both before and after the call to the defaultReadObject() method, as long as it is in correspondence with the customization code in the writeObject() method.

The client class SerialClient in Example 11.8 is the same as the one in Example 11.7. The output from the program confirms that the object state prior to serialization is identical to the object state after deserialization.

Example 11.8 Customized Serialization

public class Wheel {                                               // (1a)
  private int wheelSize;

  Wheel(int ws) { wheelSize = ws; }

  int getWheelSize() { return wheelSize; }

  public String toString() { return "wheel size: " + wheelSize; }
}
______________________________________________________

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Unicycle implements Serializable {                    // (2)
  transient private Wheel wheel;                                   // (3a)

  Unicycle(Wheel wheel) { this.wheel = wheel; }

  public String toString() { return "Unicycle with " + wheel; }

  private void writeObject(ObjectOutputStream oos) {               // (3b)
    try {
      oos.defaultWriteObject();
      oos.writeInt(wheel.getWheelSize());
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void readObject(ObjectInputStream ois) {                 // (3c)
    try {
      ois.defaultReadObject();
      int wheelSize = ois.readInt();
      wheel = new Wheel(wheelSize);
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }
}
______________________________________________________
public class SerialClient { // Same as in Example 11.7 }

Output from the program:

Before writing: Unicycle with wheel size: 65
After reading: Unicycle with wheel size: 65

Serialization and Inheritance

The inheritance hierarchy of an object also determines what its state will be after it is deserialized. An object will have the same state at deserialization as it had at the time it was serialized if all its superclasses are also serializable. This is because the normal object creation procedure using constructors is not run during deserialization (see Section 9.11, p. 416, on constructing the initial object state).

However, if any superclass of an object is not serializable, then the normal creation procedure using constructors is run, starting at the first non-serializable superclass, all the way up to the Object class. This means that the state at deserialization might not be the same as at the time the object was serialized, because superconstructors run during deserialization may have initialized the object’s state.

Example 11.9 illustrates how inheritance affects serialization. The Student class is a subclass of the Person class. Whether the superclass Person is serializable or not has implications for serializing objects of the Student subclass, in particular, when their byte representation is deserialized.

The following code in the method writeData() declared at (1) in the class Serial-Inheritance serializes a Student object:

Student student = new Student("Pendu", 1007);
System.out.println("Before writing: " + student);
outputStream.writeObject(student);

The corresponding code for deserialization is in the method readData() declared at (2) in the class SerialInheritance:

Student student = (Student) inputStream.readObject();
System.out.println("After reading: " + student);

We get the following output from the program in Example 11.9 when it is run with (1a) in the Person class and the Student class, i.e., when the superclass is serializable and so is the subclass, by virtue of inheritance. The results show that the object state prior to serialization is identical to the object state after deserialization. In this case, no superclass constructors were run during deserialization.

Before writing: Student state(Pendu, 1007)
After reading: Student state(Pendu, 1007)

However, this is not the case when the superclass Person is not serializable. We get the following output from the program in Example 11.9 when it is run with (1b) in the Person class and the Student class, i.e. when only the subclass is serializable, but not the superclass. The output shows that the object state prior to serialization is not identical to the object state after deserialization.

Before writing: Student state(Pendu, 1007)
After reading: Student state(null, 1007)

During deserialization, the default constructor of the Person superclass at (2) is run. As we can see from the declaration of the Person class in Example 11.9, this default constructor does not initialize the name field, which remains initialized with the default value for reference types, i.e., null.

Example 11.9 Serialization and Inheritance

// A superclass
// public class Person implements Serializable {               // (1a)
public class Person  {                                                           // (1b)
  private String name;

  Person() {}                                                                            // (2)
  Person(String name) { this.name = name; }

  public String getName() { return name; }
}
______________________________________________________
import java.io.Serializable;

//public class Student extends Person {                                         // (1a)
public class Student extends Person implements Serializable {  // (1b)

  private long studNum;

  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }

  public String toString() {
    return "Student state(" + getName() + ", " + studNum + ")";
  }
}
______________________________________________________
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class SerialInheritance {
  public static void main(String[] args)
                     throws IOException, ClassNotFoundException {
    SerialInheritance demo = new SerialInheritance();
    demo.writeData();
    demo.readData();
  }

  void writeData() throws IOException {                                           // (1)
    // Set up the output stream:
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);

    // Write the data:
    Student student = new Student("Pendu", 1007);
    System.out.println("Before writing: " + student);
    outputStream.writeObject(student);

    // Close the stream:
    outputStream.flush();
    outputStream.close();
  }

  void readData() throws IOException, ClassNotFoundException {   // (2)
    // Set up the input stream:
    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);

    // Read data.
    Student student = (Student) inputStream.readObject();

    // Write data on standard output stream.
    System.out.println("After reading: " + student);

    // Close the stream.
    inputStream.close();
  }
}

Review Questions

Review Questions

11.22  How many methods are defined in the Serializable interface?

           Select the one correct answer.

(a) None

(b) One

(c) Two

(d) Three

(e) None of the above.

11.23   Which of the following best describes the data written by an ObjectOutputStream?

           Select the one correct answer.

(a) Bytes and other Java primitive types.

(b) Object hierarchies.

(c) Object hierarchies and Java primitive types.

(d) Single objects.

(e) Single objects and Java primitive types.

11.24   Given the following code:

public class Person  {
  protected String name;
  Person() { }
  Person(String name) { this.name = name; }
}
____________________________________________________
import java.io.Serializable;
public class Student extends Person implements Serializable {
  private long studNum;
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }
  public String toString() { return "(" + name + ", " + studNum + ")"; }
}
____________________________________________________
import java.io.*;
public class RQ800_10 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    Student stud1 = new Student("Aesop", 100);
    System.out.print(stud1);
    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);
    Student stud2 = (Student) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100)(Aesop, 100).

(d) It prints (Aesop, 100)(null, 100).

(e) It prints (Aesop, 100)( , 100).

11.25   Given the following code:

public class Person  {
  protected String name;
  Person() { this.name = "NoName"; }
  Person(String name) { this.name = name; }
}
____________________________________________________
import java.io.Serializable;
public class Student extends Person implements Serializable {
  private long studNum;
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }
  public String toString() { return "(" + name + ", " + studNum + ")"; }
}
____________________________________________________
import java.io.*;
public class RQ800_20 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    Student stud1 = new Student("Aesop", 100);
    System.out.print(stud1);

    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);
    Student stud2 = (Student) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100)(Aesop, 100).

(d) It prints (Aesop, 100)(null, 100).

(e) It prints (Aesop, 100)(NoName, 100).

11.26   Given the following code:

import java.io.Serializable;
public class Person implements Serializable {
  protected String name;
  Person() { this.name = "NoName"; }
  Person(String name) { this.name = name; }
}
____________________________________________________
public class Student extends Person {
  private long studNum;
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }
  public String toString() { return "(" + name + ", " + studNum + ")"; }
}
____________________________________________________
import java.io.*;
public class RQ800_30 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    Student stud1 = new Student("Aesop", 100);
    System.out.print(stud1);
    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);

    Student stud2 = (Student) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100)(Aesop, 100).

(d) It prints (Aesop, 100)(null, 100).

(e) It prints (Aesop, 100)(NoName, 100).

11.27   Given the following code:

import java.io.Serializable;
public class Person implements Serializable {
  protected transient String name;
  Person(String name) { this.name = name; }
}
____________________________________________________
public class Student extends Person {
  private long studNum;
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }
  public String toString() { return "(" + name + ", " + studNum + ")"; }
}
____________________________________________________
import java.io.*;
public class RQ800_40 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    Student stud1 = new Student("Aesop", 100);
    System.out.print(stud1);
    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);
    Student stud2 = (Student) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100)(Aesop, 100).

(d) It prints (Aesop, 100)(null, 100).

(e) It prints (Aesop, 100)(NoName, 100).

11.28   Given the following code:

import java.io.Serializable;
public class Person implements Serializable {
  protected transient String name;
  Person() { this.name = "NoName"; }
  Person(String name) { this.name = name; }
}
____________________________________________________
import java.io.*;
public class Student extends Person {
  private long studNum;
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }

  public String toString() { return "(" + name + ", " + studNum + ")"; }

  private void writeObject(ObjectOutputStream oos) throws IOException {
      oos.defaultWriteObject();
      oos.writeObject("NewName");
  }

  private void readObject(ObjectInputStream ois)
               throws IOException, ClassNotFoundException {
      ois.defaultReadObject();
      name = (String) ois.readObject();
  }
}
____________________________________________________
import java.io.*;
public class RQ800_50 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    Student stud1 = new Student("Aesop", 100);
    System.out.print(stud1);
    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);

    Student stud2 = (Student) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100)(Aesop, 100).

(d) It prints (Aesop, 100)(NewName, 100).

(e) It prints (Aesop, 100)(NoName, 100).

11.29   Given the following code:

public class Person {
  protected transient String name;
  Person() { this.name = "NoName"; }
  Person(String name) { this.name = name; }
}
____________________________________________________
public class Student extends Person {
  protected long studNum;
  Student() { }
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
  }
}
____________________________________________________
import java.io.*;

public class GraduateStudent extends Student implements Serializable {
  private int year;
  GraduateStudent(String name, long studNum, int year) {
    super(name, studNum);
    this.year = year;
  }

  public String toString() {
    return "(" + name + ", " + studNum + ", " + year  + ")";
  }

  private void readObject(ObjectInputStream ois)
               throws IOException, ClassNotFoundException {
      ois.defaultReadObject();
      name = "NewName";
      studNum = 200;
      year =2;
  }
}
____________________________________________________

import java.io.*;
public class RQ800_70 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    GraduateStudent stud1 = new GraduateStudent("Aesop", 100, 1);
    System.out.print(stud1);
    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);
    GraduateStudent stud2 = (GraduateStudent) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100, 1)(Aesop, 100, 1).

(d) It prints (Aesop, 100, 1)(NewName, 0, 1).

(e) It prints (Aesop, 100, 1)(NewName, 200, 2).

11.30   Given the following code:

import java.io.Serializable;
public class Person implements Serializable {
  protected transient String name;
  Person(String name) { this.name = name; }
}
____________________________________________________
public class Student extends Person {
  private static int numOfStudents;
  private long studNum;
  Student(String name, long studNum) {
    super(name);
    this.studNum = studNum;
    ++numOfStudents;
  }
  public String toString() {
    return "(" + name + ", " + studNum + ", " + numOfStudents + ")";
  }
}
_____________________________________________________
import java.io.*;
public class RQ800_80 {

  public static void main(String args[])
                     throws IOException, ClassNotFoundException {
    FileOutputStream outputFile = new FileOutputStream("storage.dat");
    ObjectOutputStream outputStream = new ObjectOutputStream(outputFile);
    Student stud1 = new Student("Aesop", 100);
    System.out.print(stud1);
    outputStream.writeObject(stud1);
    outputStream.flush();
    outputStream.close();

    Student student = new Student("Mowgli", 300);

    FileInputStream inputFile = new FileInputStream("storage.dat");
    ObjectInputStream inputStream = new ObjectInputStream(inputFile);
    Student stud2 = (Student) inputStream.readObject();
    System.out.println(stud2);
    inputStream.close();
  }
}

Which statement about the program is true?

Select the one correct answer.

(a) It fails to compile.

(b) It compiles, but throws an exception at runtime.

(c) It prints (Aesop, 100, 1)(Aesop, 100, 1).

(d) It prints (Aesop, 100, 1)(null, 100, 2).

(e) It prints (Aesop, 100, 1)(null, 100, 1).

Chapter Summary

Chapter Summary

The following information was included in this chapter:

• Discussion of the File class, which provides an interface to the host file system.

• Byte streams, as represented by the InputStream and OutputStream classes.

• File streams, as represented by the FileInputStream and FileOutputStream classes.

• Reading and writing binary files using the DataInputStream and DataOutputStream classes.

• Character streams, as represented by the Reader and Writer classes.

• Usage of character encodings, including Unicode and UTF8, by the InputStreamReader and OutputStreamWriter classes.

• Reading and writing text files.

• Buffered character streams, as represented by the BufferedReader and Buffered Writer classes.

• Standard input, output, and error streams represented by the fields System.in, System.out and System.err, respectively.

• Object serialization: reading and writing objects.

Programming Exercise

Programming Exercise

11.1   Write a program that reads text from a source using one encoding, and writes the text to a destination using another encoding. The program should have four optional arguments:

• The first argument, if present, should specify the encoding of the source. The default source encoding should be "8859_1".

• The second argument, if present, should specify the encoding of the destination. The default destination encoding should be "UTF8".

• The third argument, if present, should specify a source file. If no argument is given, the standard input should be used.

• The fourth argument, if present, should specify a destination file. If no argument is given, the standard output should be used.

Use buffering, and read and write 512 bytes at a time to make the program efficient.

Errors should be written to the standard error stream.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.7.224