Title: Java IO classes case study in an OO library
1Java I/O classes case study in an OO library
- Flexible and somewhat slick, but a bit of a mess
2Java classes for doing i/o
- Includes file i/o, memory i/o, socket i/o,
inter-process (pipes), etc. - All stored in package java.io
- Excellent example of OO design
- Very general and scaleable
- Unfortunately, also obfuscates simple tasks.
- How to proceed
- Understand basic design
- Create some libraries to do common tasks
3InputStream/OutputStream
- Start by studying the java.io.InputStream and
java.io.OutputStream API - These are base class for performing all binary
i/o - Note that these classes are abstract each with a
single abstract method - abstract int read()
- abstract void write(int)
- Concrete subclasses must provide implementation
of read/write that can get/put a single byte
to/from the relevant source
4Concrete subclasses of InputStream/OutputStream
- Since InputStream/OutputStream are abstract, they
cannot be used to create objects (of course, they
can be used for typing). - A very common non-abstract subclass is
FileOutputStream/FileInputStream. - These can be used in a simple way to do the most
basic byte-based file io
5Example with FileInputStream
/ class example DataInput1.java / / assumes
each char is one byte -- dangerous import
java.io.FileInputStream public class
DataInput1 public static void main(String
args) throws Exception String file
args0 int input
FileInputStream fin new FileInputStream(file)
while ( (input fin.read()) ! -1)
System.out.print((char) input)
6Example with FileOutputStream
/ class example DataOutput1.java / / assumes
each char is a single byte / import
java.io.FileOutputStream public class
DataOutput1 public static void main(String
args) throws Exception String file
args0 String output "Hello World"
FileOutputStream fout new
FileOutputStream(file) char
outputAsChars output.toCharArray()
for (int i 0 i lt outputAsChars.length i)
fout.write(outputAsCharsi)
7Higher-level functionality
- FileInputStream and FileOuputStream allow you to
do pretty much any file i/o at a very low level. - However, this is too low-level for Java.
- Java provides many more libraries to read/write
higher-level constructs - characters
- Strings
- native datatypes
- arrays
- arbitrary objects (serialization)
8Decorator Pattern
- These capabilities are added using a design
called the Decorator Pattern. - InputStream/OutputStream instances are passed to
a wrapper or decorator class that uses them and
adds to their functionality. - For example, floating point numbers can be read
from a file by chaining together a
FileInputStream and another class that assembles
bytes into portable floating point.
9Purpose of Decorator
- Best way to think of this is as follows
- There are two important issues when constructing
an i/o library - Where the i/o is going (file, etc).
- How the data is represented (String, native type,
etc.) - Rather than create a class for each combination,
Decorator classes allow you to mix and match,
augment functionality of base classes. - This is a bit confusing but is very flexible.
- Decotators can also add other capabilities, such
as peek ahead, push back, write line number, etc.
10Decorator Pattern
11Java i/o Decorator Classes
- All Java i/o decorator classes inherit from
FilterInputStream and FilterOutputStream - Look at the api for these classes and note a few
things - They wrap instances of InputStream/OutputStream
respectively. - They inherit from InputStream/OutputStream
respectively - This is an odd inheritence hierarchy but is
necessary to ensure that the FilterStreams
support the same interface as the underlying
class.
12More on Filter Streams
- Easiest way to think of the filter streams as
wrapping an underlying class which they augment
the functionality of. - Consider the respective constructors
- FilterInputStream(InputStream in)
- FilterOutputStream(OutputStream out)
- In each case, the FilterStreams use an underlying
presumably simpler inputstream and augment its
functionality.
13Some FilterStream examples to clarify this
- Perhaps most common FilterInputStream is
DataInputStream. - Study the API and be sure you understand the
inheritance hierarchy - DataInputStream stores an InputStream and uses
this to do higher-level i/o - readInt, readDouble, etc.
- DataOutputStream is analogous
14Example of DataInputStream
/ DataInputStream2 example in course examples
/ import java.io.DataOutputStream import
java.io.FileOutputStream public class
DataOutput2 public static void main(String
args) throws Exception String file
args0 double data
1.1,1.2,1.3,1.4,1.5 DataOutputStream
dout new DataOutputStream (new
FileOutputStream(file)) for (int i 0
i lt data.length i)
dout.writeDouble(datai)
dout.close()
15Example of DataInputStream
/ DataOutput2 example in course examples
/ import java.io.DataInputStream import
java.io.FileInputStream import
java.io.EOFException public class DataInput2
public static void main(String args) throws
Exception String file args0
DataInputStream din new DataInputStream(new
FileInputStream(file)) double data
try while (true)
data din.readDouble()
System.out.println(data)
catch (EOFException eofe)
din.close()
16Other Decorators
- Another common set of decorator classes is
BufferedInputStream and BufferedOutputStream. - Note that java.util.Scanner is a new and simpler
alternative for a lot of high-level parsing
tasks. - These augment the functionality of the underlying
stream by providing system buffering for
higher-performance i/o - They also add support for the mark method.
- Examples on next slide (notice how these classes
can be multiply chained together in various ways.
17BufferedInputStream Example
import java.io. / public class DataInput3
public static void main(String args) throws
Exception String file args0
DataInputStream din new DataInputStream
(new BufferedInputStream (new
FileInputStream(file))) double data
/ need an exception to know when end of file
is hit / try while (true)
data din.readDouble()
System.out.println(data)
catch (EOFException eofe)
din.close()
18BufferedOutputStream example
import java.io.BufferedOutputStream import
java.io.DataOutputStream import
java.io.FileOutputStream public class
DataOutput3 public static void
main(String args) throws Exception
String file args0 double data
1.1,1.2,1.3,1.4,1.5 DataOutputStream
dout new DataOutputStream (new
BufferedOutputStream (new
FileOutputStream(file))) for (int i
0 i lt data.length i)
dout.writeDouble(datai)
dout.close()
19Other output streams
- FileOutputStream is probably the most common.
- However, note that we could replace
FileOutputStream with another Outputstream in
these examples. - In that case, the same decorated or undecorated
data would be sent to some other device. - Good example of this is thread communicatoin,
memory i/o, and socket i/o (using Socket class). - I strongly encourage you to familiarize yourself
with these classes.
20Character-based i/o
- Reader and Writer classes
21Reader/Writer
- Java maintains a second class hierarchy for
performing higher-level character-based i/o. - The two base classes in this case are
- java.io.Reader
- java.io.Writer
- Study the API for these classes.
- Very similar to InputStream/OutputStream
- Here Ill show how to do some common i/o tasks as
examples
22 FileWriter Example
/ example Writer1.java in course examples / /
using a simple FileWriter for String-based i/o
/ import java.io.FileWriter public class
Writer1 public static void main(String
args) throws Exception String file
args0 String output "Hello World!"
FileWriter fw new FileWriter(file)
fw.write(output) fw.close()
23Reading lines from stdin
import java.io.BufferedReader import
java.io.InputStreamReader public class Reader1
public static void main(String args) throws
Exception / convert System.in, which is
an InputStream, to a Reader by
wrapping in InputStreamReader, then
wrap everything in BufferedReader /
String input BufferedReader bin new
BufferedReader (new
InputStreamReader (System.in))
while ( (input bin.readLine()) ! null)
System.out.println("you typed "
input)
converts an InputStream to a Reader
24Reading by line from file
import java.io.BufferedReader /Reader2.java
/ import java.io.InputStreamReader import
java.io.FileInputStream public class Reader2
public static void main(String args) throws
Exception / convert a FileInputStream,
which is an InputStream, to a Reader
by wrapping in InputStreamReader, then
wrap everything in BufferedReader and call
the readLine method to get a line at a time
/ String input String
file args0 BufferedReader bin new
BufferedReader (new
InputStreamReader (new
FileInputStream(file))) while ( (input
bin.readLine()) ! null)
System.out.println(input)
25Exercise
- Study the jdk API for GZIPOutputStream and
GZIPInputStream. Write a program that reads and
writes gzip files.
26Serialization
- Objects can be written to streams also. This
process is known as serialization. - This is a huge convenience compared with having
to marshal and unmarshal ivs. - But the issue is even deeper how are methods
represented, objects that contain objects as
ivs, etc. - Java takes care of all of this with a very nice
serialization interface.
27Serialization classes
- Relevant classes
- java.io.ObjectInputStream
- java.io.ObjectOutputStream
- Note that these required an underlying
Input/OutputStream to do their work. - For a class to be serializable, it also must
implement the Serializable interface (no
methods). - Finally, a class-scope variable can be declared
as transient, meaning that it is ignored during
serialization.
28Serialization Example
/ simple example of Serialization -- writing an
object directly to an OutputStream without
having to marshal and unmarshal / import
java.io. public class Serialization public
static void main(String args) throws
Exception String flag args0 String
file args1 Currency c new
Currency("US Dollar", "USD, 10, 5)
Currency d if (flag.equals("-w"))
ObjectOutputStream out
new ObjectOutputStream(new FileOutputStream(new
File(file))) out.writeObject(c)
else if (flag.equals("-r"))
ObjectInputStream in new
ObjectInputStream(new FileInputStream(new
File(file))) System.out.println("Read
ing serialized object") d
(Currency) in.readObject()
29Related Topics
- java.io.File class
- Very nice. Many methods for portably manipulating
files - java.io.Socket class
- Provides Input/OutputStreams for communication
across ports of different computers - PrintWriter class (e.g. println method)
- Writing zip files, jar files, etc.
- java rmi Remote Method Invocation
- DOs on top of serialization
30Suggested Readings
- Eckels detailed section on i/o
- Patterns in Java, A Catalog of Reusable Design
Patterns Illustratred with UML, Mark Grand, Wiley
Press. - Design Patterns, Elements of Reusable
Object-Oriented Software, Gamma et al.
31Java Exceptions
32Intro to Exceptions
- What are exceptions?
- Events that occur during the execution of a
program that interrupt the normal flow of
control. - One technique for handling Exceptions is to use
return statements in method calls. - This is fine, but java provides a much more
general and flexible formalism that forces
programmers to consider exceptional cases.
33Exception Class hierarchy
Object
- must handle
- may handle
- too serious to catch
Throwable
Error
Exception
RuntimeException
many
NullPointerException
IndexOutOfBounds
34Exception Handling Basics
- Three parts to Exception handling
- 1. claiming exception
- 2. throwing exception
- 3. catching exception
- A method has the option of throwing one or more
exceptions when specified conditions occur. This
exception must be claimed by the method. Another
method calling this method must either catch or
rethrow the exception. (unless it is a
RuntimeException)
35Claiming Exceptions
- Method declaration must specify every exception
that the method potentially throws - MethodDeclaration throws Exception1,
Exception2, ..., ExceptionN - Exceptions themselves are concrete subclasses of
Throwable and must be defined and locatable in
regular way.
36Throwing Exception
- To throw an Exception, use the throw keyword
followed by an instance of the Exception class - void foo() throws SomeException
- if (whatever) ...
- else throw new SomeException(...)
- Well talk about passing data via the Exception
constructor soon. - Note that if a method foo has a throw clause
within it, that the Exception that is thrown (or
one of its superclasses) must be claimed after
the signature.
37Catching Exceptions
- The third piece of the picture is catching
exceptions. - This is what you will do with most commonly,
since many of javas library methods are defined
to throw one or more runtime exception. - Catching exceptions
- When a method is called that throws and Exception
e.g SomeException, it must be called in a
try-catch block - try
- foo()
-
- catch(SomeException se)...
38Catching Exceptions, cont.
- Note that if a method throws an Exception that is
NOT a RuntimeException, you must do one of two
things - try-catch it (often called handling it)
- rethrow it
- In the latter case, responsibility then moves up
the calling chain to handle it, and so on all the
way up to main.
39More on try-catch
The general form of the try-catch structure is
try / any number of lines of code
that call any number of methods with any
thrown Exceptions / catch(Exception1
e1) / do anything you want here
e.g. change value and try again. print
error and quit print stacktrace
/ catch (Exception2 e2) / any number
of exceptions can be handled ... /
40Example1
import java.io. public class Exception1
public static void main(String args)
InputStream f try f new
FileInputStream("foo.txt")
catch(FileNotFoundException fnfe)
System.out.println(fnfe.getMessage())
41Example2
import java.io. public class Exception2
public static void main(String args)
InputStream fin try fin new
FileInputStream("foo.txt") int input
fin.read() catch(FileNotFoundExce
ption fnfe) System.out.println(fnfe.g
etMessage()) catch(IOException
ioe) System.out.println(ioe.getMessag
e())
42import java.io. public class Exception2
public static void main(String args)
InputStream fin try fin new
FileInputStream("foo.txt") int input
fin.read()
catch(FileNotFoundException fnfe)
System.out.println(fnfe.getMessage())
catch(IOException ioe)
System.out.println(ioe.getMessage())
43Recommendations
- Do not use Exceptions to handle normal conditions
in the program that can be checked with if
statements. For example - to find the end of an array
- to check if an object is null
- See other commented examples in course notes.
44Creating your own Exceptions
- You can follow this procedure exactly when
creating your own Exception. - Create a class that subclasses Exception (or
RuntimeException). - You may also add functionality so that a relevant
message is stored when the error is thrown, and
any other customized functionality you choose. - See Exception5.java example
45Overriding Object Methods
46The Object Class
- Every java class has Object as its superclass and
thus inherits the Object methods. - Object is a non-abstract class
- Many Object methods, however, have
implementations that arent particularly useful
in general - In most cases it is a good idea to override these
methods with more useful versions. - In other cases it is required if you want your
objects to correctly work with other class
libraries.
47Some Object class methods
- Object methods of interest
- clone
- equals
- hashcode
- toString
- finalize
- Other object methods
- getClass
- wait, notify, notifyAll (relevant for threaded
programming)
48Clone method
- Recall that the operator simply copies Object
references. e.g., - gtgt Student s1 new Student(Smith, Jim, 3.13)
- gtgt Student s2 s1
- gtgt s1.setGPA(3.2)
- gtgt System.out.println(s2.getGPA())
- 3.2
- What if we want to actually make a copy of an
Object? - Most elegant way is to use the clone() method
inherited from Object. - Student s2 (Student) s1.clone()
49Subtleties of clone() method
- First, note that the clone method is protected in
the Object class. - This means that it is protected for subclasses as
well. - Hence, it cannot be called from within an Object
of another class and package. - To use the clone method, you must override in
your subclass and upgrade visibility to public.
50More subtleties of clone
- Also, any class that uses clone must implement
the Cloneable interface. - This is a bit different from other interfaces
that weve seen. - There are no methods rather, it is used just as
a marker of your intent. - The method that needs to be implemented is
inherited from Object.
51More clone() issues
- Finally, clone throws a CloneNotSupportedException
. - This is thrown if your class is not marked
Cloneable. - This is all a little odd but you must handle this
in subclass.
52Steps for cloning
- To reiterate, if you would like objects of class
C to support cloning, do the following - implement the Cloneable interface
- override the clone method with public access
privileges - call super.clone()
- Handle CloneNotSupported Exception.
- This will get you default cloning, but more
subtleties still lurk.
53Shallow Copies
- We havent yet said what the default clone()
method does. - By default, clone makes a shallow copy of all
ivs in a class. - Shallow copy means that all native datatype ivs
are copied in regular way, but ivs that are
objects are not recursed upon that is,
references are copied. - This is not what you typically want.
- Must override clone explicitly clone object ivs!
54Immutable Objects
- A special class of Objects are called immutable
because their state cannot be changed once set. - Common examples are String, Integer, etc.
- Immutable object simplify programming in certain
instances, such as when writing thread safe code. - They also simplify cloning, since an object that
cannot be changed doesnt really need to be
deep-copied. - See ShallowCopy2.java in course examples
55Deep Copies
- For deep copies that recurse through the object
ivs, you have to do some more work. - super.clone() is first called to clone the first
level of ivs. - Returned cloned objects object fields are then
accessed one by one and clone method is called
for each. - See DeepClone.java example
56Additional clone() properties
- Note that the following are typical, but not
strictly required - x.clone() ! x
- x.clone().getClass() x.getClass()
- x.clone().equals(x)
- Finally, though no one really cares, Object does
not support clone()
57toString() method
- The Object method
- String toString()
- is intended to return a readable textual
representation of the object upon which it is
called. This is great for debugging! - Best way to think of this is using a print
statement. If we execute - System.out.println(someObject)
- we would like to see some meaningful info
about someObject, such as values of ivs,
etc.
58default toString()
- By default toString() prints total garbage that
no one is interested in - getClass().getName() '_at_'
Integer.toHexString(hashCode()) - By convention, print simple formatted list of
field names and values (or some important
subset). - The intent is not to overformat.
- Typically used for debugging.
- Always override toString()!
59equals() method
- Recall that boolean method compares when
applied to object compares references. - That is, two object are the same if the point to
the same memory. - Since java does not support operator overloading,
you cannot change this operator. - However, the equals method of the Object class
gives you a chance to more meaningful compare
objects of a given class.
60equals method, cont
- By default, equals(Object o) does exactly what
the operator does compare object references. - To override, simply override method with version
that does more meaningful test, ie compares ivs
and returns true if equal, false otherwise. - See Equals.java example in course notes.
61equals subtleties
- As with any method that you override, to do so
properly you must obey contracts that go beyond
interface matching. - With equals, the extra conditions that must be
met are discussed on the next slide
62equals contract
- It is reflexive for any reference value x,
x.equals(x) should return true. - It is symmetric for any reference values x and
y, x.equals(y) should return true if and only if
y.equals(x) returns true. - It is transitive for any reference values x, y,
and z, if x.equals(y) returns true and
y.equals(z) returns true, then x.equals(z) should
return true. - It is consistent for any reference values x and
y, multiple invocations of x.equals(y)
consistently return true or consistently return
false, provided no information used in equals
comparisons on the object is modified. - For any non-null reference value x,
x.equals(null) should return false.
63hashcode() method
- Java provides all objects with the ability to
generate a hash code. - By default, the hashing algorithm is typically
based on an integer representation of the java
address. - This method is supported for use with
java.util.Hashtable - Will discuss Hashtable in detail during
Collections discussion.
64Rules for overriding hashcode
- Whenever invoked on the same object more than
once, the hashCode method must return the same
integer, provided no information used in equals
comparisons on the object is modified. - If two objects are equal according to the
equals(Object) method, then calling the hashCode
method on each of the two objects must produce
the same integer result. - It is not required that if two objects are
unequal according to the equals(java.lang.Object)
method, then calling the hashCode method on each
of the two objects must produce distinct integer
results. However, the programmer should be aware
that producing distinct integer results for
unequal objects may improve the performance of
hashtables.
65finalize() method
- Called as final step when Object is no longer
used, just before garbage collection - Object version does nothing
- Since java has automatic garbage collection,
finalize() does not need to be overridden reclaim
memory. - Can be used to reclaim other resources close
streams, database connections, threads. - However, it is strongly recommended not to rely
on this for scarce resources. - Be explicit and create own dispose method.