Exceptions and Advanced File I/O II
- Creating Exception Classes
The @exception
Tag in Documentation Comments- Binary Files
- Writing and Reading Strings
- Appending Data to Binary Files
- Random Access Files
- The File Pointer
- Object Serialization
Creating Exception Classes
We can create our own exception classes by deriving them from the
Exception
class or one of its derived classes.Book example:
In this example, some of the exceptions that can affect a bank account are:
- A negative starting balance is passed to the constructor.
- A negative interest rate is passed to the constructor.
- A negative number is passed to the deposit method.
- A negative number is passed to the withdraw method.
- The amount passed to the withdraw method exceeds the account’s balance.
We can create exceptions that represent each of these error conditions.
Example code:
public class Program { public static void main(String[] args) { System.out.println("Calling myMethod..."); try { myMethod(); } catch (MyException e) { System.err.println(e.getMessage()); } System.out.println("Method main is done."); } private static void myMethod() throws MyException { System.out.println("Calling produceError..."); try { produceError(); } catch (StringIndexOutOfBoundsException e) { throw new MyException("Index out of bounds."); } System.out.println("myMethod is done."); } private static void produceError() throws StringIndexOutOfBoundsException { String str = "abc"; System.out.println(str.charAt(3)); System.out.println("peoduceError is done."); } } class MyException extends Exception { public MyException(String message) { super("My Exception: " + message); } }
The @exception
Tag in Documentation Comments
General format:
@exception ExceptionName Description
The following rules apply
The
@exception
tag in a method’s documentation comment must appear after the general description of the method.The description can span several lines. It ends at the end of the documentation comment (the
*/
symbol) or at the beginning of another tag.
Example code:
/** * Returns the character at the specified index. An index * ranges from <code>0</code> to <code>length() - 1</code>. * * @param index The index of the desired character. * @return The desired character. * @exception StringIndexOutOfRangeException * Thrown if the index is not in the range <code>0</code> * to <code>length()-1</code>. */ public char charAt(int index) { ... }
Binary Files
A file that contains binary data is often called a binary file.
Storing numeric data in its binary format is more efficient than storing it as text.
There are some types of data that should be stored only in raw binary format.
Binary files cannot be viewed in a text editor such as Notepad or TextWrangler.
To write data to a binary file, you generally create objects from the following classes:
FileOutputStream
— allows us to open a file for writing binary data. It provides only basic functionality for writing bytes to the file.DataOutputStream
— allows us to write data of any primitive type or String objects to a binary file. It cannot directly access a file. It is used in conjunction with aFileOutputStream
object that has a connection to a file.
A
DataOutputStream
object is wrapped around aFileOutputStream
object to write data to a binary file.If the file that you are opening with the
FileOutputStream
object already exists, it will be erased and an empty file by the same name will be created.These statements create a binary file. Closing the
DataOutputStream
also closes theFileOutputStream
.FileOutputStream stream = new FileOutputStream("myFile.dat") DataOutputStream outputFile = new DataOutputStream(stream);
Book example:
To open a binary file for input, we wrap a
DataInputStream
object around aFileInputStream
object.These two statements create a stream that can read binary files. Closing the
DataInputStream
also closes theFileInputStream
.FileInputStream stream = new FileInputStream("myFile.dat") DataInputStream inputFile = new DataInputStream(stream);
The
FileInputStream
constructor will throw aFileNotFoundException
if the file named by the string argument cannot be found.Book example:
Example code:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Random; public class Program { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream( new FileOutputStream("data.foo")); try { Random r = new Random(); for (int i = 0; i < 10; i++) { out.writeDouble(r.nextDouble()); out.writeInt(r.nextInt(100)); } } finally { out.close(); } DataInputStream in = new DataInputStream( new FileInputStream("data.foo")); try { for (int i = 0; i < 10; i++) { System.out.println(in.readDouble()); System.out.println(in.readInt()); } } finally { in.close(); } } }
Writing and Reading Strings
To write a string to a binary file, use the
DataOutputStream
class’swriteUTF
method.This method writes its
String
argument in a format known as UTF-8 encoding.Just before writing the string, this method writes a two-byte integer indicating the number of bytes that the string occupies.
Then, it writes the string’s characters in Unicode.
UTF stands for Unicode Text Format.
The
DataInputStream
class’sreadUTF
method reads from the file.The
readUTF
method will correctly read a string only when the string was written with thewriteUTF
method.Book example:
Example code:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; public class Program { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream( new FileOutputStream("data.foo")); try { out.writeUTF("Jade Cheng"); out.writeUTF("\u6210 \u7389"); } finally { out.close(); } DataInputStream in = new DataInputStream( new FileInputStream("data.foo")); try { System.out.println(in.readUTF()); System.out.println(in.readUTF()); } finally { in.close(); } } }
Appending Data to Binary Files
The
FileOutputStream
constructor takes an optional second argument which must be aboolean
value.If the argument is
true
, the file will not be erased if it exists; new data will be written to the end of the file.If the argument is
false
, the file will be erased if it already exists.Example code:
import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.util.Date; public class Program { public static void main(String[] args) throws IOException { DataOutputStream out = new DataOutputStream( new FileOutputStream("data.foo", true)); try { out.writeUTF("The time is " + new Date()); } finally { out.close(); } DataInputStream in = new DataInputStream( new FileInputStream("data.foo")); try { while (in.available() > 0) System.out.println(in.readUTF()); } finally { in.close(); } } }
Random Access Files
Text files and the aforementioned binary files use sequential file access.
With sequential access:
The first time data is read from the file, the data will be read from its beginning.
As the reading continues, the file’s read position advances sequentially through the file’s contents.
Sequential file access is useful in many circumstances.
If the file is very large, locating data buried deep inside it can take a long time.
Java allows a program to perform random file access.
In random file access a program may immediately jump to any location in the file.
To create and work with random access files in Java, we use the
RandomAccessFile
class.RandomAccessFile(String fileName, String mode)
fileName
: the name of the file.mode
: a string indicating the mode in which the file will be accessed.- “r”: open for reading.
- “rw”: open for reading and writing.
RandomAccessFile
class allows us to move around a file and read from it or write to it as we please.We can replace existing parts of a file too. This is not possible with the
FileInputStream
orFileOutputStream
.Example code:
import java.io.DataInputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.RandomAccessFile; public class Program { public static void main(String[] args) throws IOException { RandomAccessFile f = new RandomAccessFile("data.foo", "rw"); try { f.writeUTF("Mario ate a zucchini and grew taller."); f.seek(14); f.write(new byte[] { 'm', 'u', 's', 'h', 'r', 'o', 'o', 'm' }); } finally { f.close(); } DataInputStream in = new DataInputStream( new FileInputStream("data.foo")); try { System.out.println(in.readUTF()); } finally { in.close(); } } }
The File Pointer
The
RandomAccessFile
class treats a file as an array of bytes.The file pointer
Internally, the
RandomAccessFile
class keeps a long integer value known as the file pointer.The file pointer holds the byte number of a location in the file.
When a file is first opened, the file pointer is set to 0.
Reading from the file
When an item is read from the file, it is read from the byte that the file pointer points to.
Reading also causes the file pointer to advance to the byte just beyond the item that was read.
If another item is immediately read, the reading will begin at that point in the file.
An
EOFException
is thrown when a read causes the file pointer to go beyond the size of the file.
Writing to the file
Writing also takes place at the location pointed to by the file pointer.
If the file pointer points to the end of the file, data will be written to the end of the file.
If the file pointer holds the number of a byte within the file, at a location where data is already stores, a write will overwrite the data at that point.
Seeking within the file
The
RandomAccessFile
class lets you move the file pointer.This allows data to be read and written at any byte location in the file.
The
seek
method is used to move the file pointer.f.seek(long position)
Book example:
Example code:
Also refer to the previous example.
Object Serialization
Overview
If an object contains other types of objects as fields, saving its contents can be complicated.
Java allows you to serialize objects, which is a simpler way of saving objects to a file.
When an object is serialized, it is converted into a series of bytes that contain the object’s data.
If the object is set up properly, even the other objects that it might contain as fields are automatically serialized.
The resulting set of bytes can be saved to a file for later retrieval.
The
Serializable
interfaceFor an object to be serialized, its class must implement the
Serializable
interface.The
Serializable
interface has no methods or fields.It is used only to let the Java compiler know that objects of the class might be serialized.
If a class contains objects of other classes as fields, those classes must also implement the
Serializable
interface, in order to be serialized.Book example:
Serializing an object
To write a serialized object to a file, we use an
ObjectOutputStream
object.To write the bytes to a file, an output stream object is needed.
To serialize an object and write it to the file, the
ObjectOutputStream
class’swriteObject
method is used.The
writeObject
method throws anIOException
if an error occurs.Book example:
Deserializing an object
The process of reading a serialized object’s bytes and constructing an object from them is known as deserialization.
To deserialize an object
ObjectInputStream
object is used in conjunction with aFileInputStream
object.To read a serialized object from the file, the
ObjectInputStream
class’sreadObject
method is used.The
readObject
method returns the deserialized object.- Note: we must cast the return value to the desired class type.
The
readObject
method throws a number of different exceptions if an error occurs.Book example:
Example code:
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; public class Program { public static void main(String args[]) throws IOException, ClassNotFoundException { ObjectOutputStream out = new ObjectOutputStream( new FileOutputStream("data.foo")); try { MySeriablizableData obj1 = new MySeriablizableData("foo"); System.out.println("obj1: " + obj1); out.writeObject(obj1); } finally { out.close(); } ObjectInputStream in = new ObjectInputStream( new FileInputStream("data.foo")); try { MySeriablizableData obj2 = (MySeriablizableData) in.readObject(); System.out.println("obj2: " + obj2); } finally { in.close(); } } } class MySeriablizableData implements Serializable { ArrayList<Integer> list = new ArrayList<Integer>(); String name; public MySeriablizableData(String name) { System.out.println("Constructor is executing..."); this.name = name; for (int i = 0; i < 10; i++) this.list.add(Integer.valueOf(i)); } @Override public String toString() { return "name=[" + this.name + "]; list=" + this.list; } }