[Stripped down versions of code below is here.]
0. Assignment: Please read chapters 11 on I/O and 12 on RTTI (Run-time Type Identification). In chapter 11, please pay special attention to the following sections: "Input and output", "Adding attributes and useful interfaces", "Readers and Writers", "Typical uses of I/O streams", "Standard I/O", and "Object serialization". I'm not going to assign anything from chapter 9: I suggest you consult this chapter as a reference when you're looking for containers for aggregate data.
1. Containers and RTTI (first pass)
// First a couple of skeletal classes--so we can put objects of different
// types into a Collection
public class Dog extends Canine {
public void bark() {
System.out.println("Bark");
}
}
public class Wolf extends Canine {
public void howl() {
System.out.println("Howl");
}
}
// Ok, here's our testbed
import java.util.*;
public class RTTI {
public static void main(String[] args) {
// Make an instance of a concrete class implementing Collection, and
// then, like the man says, upcast and do all subsequent operations
// in terms of the Collection
Collection c = new ArrayList();
// Throw a dog and a wolf in there
c.add(new Dog());
c.add(new Wolf());
// Get an iterator
Iterator it = c.iterator();
// Ok, at this point we're all set up to inspect the Collection. We
// know that the first element is really a Dog. I give five alternative
// ways of proceeding from this point (by alternative I mean: assume
// that in each case the call to next() is the first one that's
// made to the iterator); some won't compile; some will compile
// but blow up at run-time; some run ok but aren't at all general;
// one--the one using instanceof--is definitely better than the others.
//
// First try: next() wants to give us an Object, so we'll point a
// reference variable of type Object at it, and call bark() on that.
// But the compiler doesn't let us get away with this: no bark() in Object
// Object o = it.next();
// o.bark();
//
// Second try: next() wants to give us an Object, but we know there's a
// Dog in there, so we'll point a ref variable of type Dog at what next()
// gives us. No go: compiler says type incompatibility between Dog and
// Object
// Dog d = it.next();
// d.bark();
//
// Third try: get rid of the type incompatibility by explicitly casting
// the returned Object to Dog. This works, and the dog barks.
// Dog d = (Dog) it.next();
// d.bark();
//
// Fourth try: we know there's a Dog there in the first slot, but what
// happens if we try casting to Wolf? As it turns out this compiles, but
// at run-time Java discovers it's just a Dog in Wolf's clothing, and
// throws a class cast exception
// Wolf w = (Wolf) it.next();
// w.howl();
//
// Fifth try: so casting to Dog works, but what if we've got a mix of
// Dog and Wolf and we don't know or don't want to keep track of where they
// are? Obviously we need something more flexible, and instanceof lets us
// ask an object what it is and react accordingly
//
// Loop through the iterator; for each object, parse its type by queries
// with instanceof; when you get a match, cast and do something specific
// to the type. Notice that instanceof observes the grand principle of
// "is-a": it returns true for an object when matching against the object's
// immediate class or any superclass of it.
while(it.hasNext()) {
Object o = it.next();
// if
if (o instanceof Dog) {
Dog d = (Dog) o;
d.bark();
} else if (o instanceof Wolf) {
Wolf w = (Wolf) o;
w.howl();
}
}
}
}
2. Java I/O
...
DataInputStream dis = new DataInputStream(new FileInputStream("index.html"));
// Copier.java
//
// The I/O-related classes are in the java.io package, so we have to do an
// import
import java.io.*;
public class Copier {
public static void main(String[] args) {
String inFile, outFile;
FileInputStream fis;
FileOutputStream fos;
int nextByteValue;
// Code has to be wrapped in a try-catch because the I/O classes we're
// using will throw exceptions e.g. on physical I/O error
try {
// There are two I/O-related sections in the program--the first fetches
// the input and output filenames from the user and the second does the
// actual file copying--and I take the opportunity to exemplify two
// different ways of handling I/O.
//
// First thing we need are the filenames, and just for fun we'll get them
// interactively from the user. Java conveniently hooks up keyboard input
// to System.in for us; however, System.in is an InputStream object, so
// if we left it alone we'd have to read in a bunch of bytes and construct
// the filename from them--too painful to think about. We could turn it
// into a DataInputStream object, and use readLine() in that class,
// unfortunately--as you'll read in Eckel--it's broken. So instead we turn
// System.in into a 1.1-style Reader class (using the converter class
// InputStreamReader), and then (because Reader just lets us deal with data
// a char at a time) turn that into a BufferedReader, which has a nice
// (working) readLine(), and we use that to fetch the filenames with a
// single method invocation.
BufferedReader bIn = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Enter input filename:");
inFile = bIn.readLine();
System.out.println("Enter output filename:");
outFile = bIn.readLine();
// Clean up
bIn.close();
// Ok, now we have the names, and we can proceed with the copying. First,
// set up input and output streams attached to the files. FileInputStream
// and FileOutputStream derive from InputStream and OutputStream
// respectively (InputStream and OutputStream have numerous children,
// corresponding to the different types of data sources and sinks). Tell
// the FIS and FOS constructors what files you're talking about by
// handing them String parameters.
fis = new FileInputStream(inFile);
fos = new FileOutputStream(outFile);
// Now we're ready to copy. Call read() repeatedly on the input source
// to fetch the next byte value. read() returns an int, whose value
// will be either in the range 0-255 (interpreted as the value of the
// next byte in the source) or -1 (signalling end of file). So we loop
// on read() waiting for a result of -1 to terminate. Inside the loop,
// that is for each byte read, write() to the output stream.
while ((nextByteValue = fis.read()) != -1)
fos.write(nextByteValue);
// Clean up
fis.close();
fos.close();
}
catch (FileNotFoundException e) {
System.out.println("Couldn't get files.");
}
catch (IOException e) {
System.out.println("Bad i/o happened.");
}
}
}
// Person.java
//
// Start by defining classes to hold the addressbook data--Person and
// Address. These are of course very skeletal. The only reason for
// separating them is to show further on that when an object is
// serialized, the objects to which it refers are themselves
// serialized.
//
import java.io.*;
// If you're going to serialize objects of a class, you have to
// specially tag the class definition. As you can see, you do this
// by saying that you're implementing the Serializable interface.
// In this case there are no extra methods to implement, it's
// really just a hint to the compiler that, yes, you really do
// want to serialize.
public class Person implements Serializable {
public String name;
public Address addr;
// the constructor inits the name and calls the constructor for
// the subobject
public Person(String n, String str, String city, String zip) {
name = n;
addr = new Address(str, city, zip);
}
// lame toString() for printing
public String toString() {
return name + " " + addr.zip;
}
}
// Address.java
import java.io.*;
public class Address implements Serializable {
String street;
String city;
String zip;
public Address(String st, String city, String zipcode) {
street = st;
this.city = city; // oops, name conflict, disambiguate with "this"
zip = zipcode;
}
}
// Then here's the main program. The load-enter-show-save pieces are
// representative of the kind of functionality you'd expect, though
// of course not just simply sequenced as I have it here in main().
// ABook.java
import java.io.*;
import java.util.*;
public class ABook {
// We'll just hard-code the name of the file holding the objects
private String theFile = "out";
// When the program's running, store the data in an ArrayList.
// However, "upcast" the ArrayList to a Collection. All references
// to the data store, inside the program, will be to "col" and will
// involve operations defined in the Collection interface. If at
// any time we want to change the implementation in favor of any
// other class implementing the Collection interface, it's only the
// following line that has to be changed.
private Collection col = new ArrayList();
// Make an instance of the class, then: read in the objects from
// disk; let the user enter more data; when they're done show
// what we have; finally write back to disk
public static void main(String[] args) {
IOPlay5 iop = new IOPlay5();
iop.load();
iop.enter();
iop.show();
iop.save();
}
// Deserialize the data from the file in which we're storing it
public void load() {
try {
// Get a FileInputStream by handing in the name of the datafile as a
// String. The FileInputStream itself doesn't know anything about
// objects, would only lets us read in a bunch of bytes, so we wrap
// it in an ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(theFile));
// Now read in the objects. Repeatedly do readObject() on the
// ObjectInputStream, and for each object you see, add() it to our data
// store. Notice that the reads are done inside an infinite
// loop ("while (true)..."). In this case we want an exception
// of a specific kind to happen: as long as there are still more objects
// in the datafile, each call to readObject() returns one of them.
// When the datafile's depleted, readObject() throws an EOFException
// (EOF = end-of-file; notice that here, and also in readInt() etc. in
// DataInputStream, we get an exception throw at EOF, but that in the case
// of read() in InputStream, as we saw in the file copier, EOF is flagged
// by a special return value, -1). So we just let the thing loop for as long as
// it feels like it, and wait for the exception to happen.
try {
while (true)
col.add(ois.readObject());
}
// Ok, if we get here, it looks like all the data was read, so we'll
// just print a message and politely close down the stream
catch (EOFException eofe) { System.out.println("Data read."); }
ois.close();
}
// On the other hand if we were to reach either of these catch
// clauses we'd know something's gone seriously wrong, and we might
// be tempted to do something drastic like shut the program down.
catch (ClassNotFoundException cnfe) { cnfe.printStackTrace(); }
catch (IOException ioe) { ioe.printStackTrace(); }
}
// Let the user enter new addressbook data
//
// The implementation here is pretty straightforward. As in the case of
// fetching the filenames in the file copier above, we're getting keyboard
// input, so for the sake of convenience we wrap System.in in a BufferedReader,
// and then do repeated calls to readLine() into String variables for the
// information we need for addressbook entries. When we've got everything
// we need, create a new Person instance and add() it to the ArrayList holding
// our data
public void enter() {
try {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
System.out.println("Another? (Y/N)");
while ("Y".equals(br.readLine())) {
System.out.println("Name?");
String name = br.readLine();
System.out.println("Street?");
String street = br.readLine();
System.out.println("City?");
String city = br.readLine();
System.out.println("Zip?");
String zipcode = br.readLine();
Person p = new Person(name, street, city, zipcode);
col.add(p);
System.out.println("Another? (Y/N)");
}
br.close();
}
catch (Exception e) { System.out.println("Died in enter()"); }
}
// Show the contents of the addressbook. Derive an iterator for
// the in-memory data store and walk through it
public void show() {
Iterator it = col.iterator();
while (it.hasNext()) { System.out.println(it.next()); }
}
// Write the in-memory data store to disk. Again generate an iterator on
// the data store. Get a FOS for the output file and turn it into an OOS
// (mirroring what we did when we read objects in from the file. Iterate and,
// for each object seen do a writeObject() on it.
public void save() {
Iterator it = col.iterator();
try {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(theFile));
while (it.hasNext())
oos.writeObject(it.next());
oos.close();
}
catch (IOException e) { System.out.println("Died in save()"); }
}
}