Learn Java Programming
4.0 Input Output programming
The following are the objectives of this module
- What is a stream – understand the Java abstraction for IO
- Understand difference between byte streams and character streams
- FileInputStreams – how to read input and write output – how End-of-file EOF is identified
- Why do we need Buffered Streams – what is BlockBased IO – how interrupts are caused – how optimization is acheived using buffered streams – Why it is necessary to flush a buffered stream
- Why do we need ByteArrayStreams – where will it be used
- Why do we need DataStreams – what abstraction is provided by DataStreams
- What is UTF
- Difference between FileInputStream and FileReader
- The abstraction is provided by FileReader and FileWriter
- What is object serialization – how to persist the state of an object
- What is the decorator pattern and how is it used in the Java IO classes
I/O Programming in Java
We know that Java is a platform independent language :
There are a number of input and output devices in the real world – For example
Input devices:
- Keyboard
- Mouse
- Network connection
Output devices
- Screen
- Printer
- File System (Memory)
- Network connection
We know different input and output devices behave differently in different operating systems:
For example, the screen display device driver for a Windows system is different than a driver for the Linux systems.
But Java holds the promise of “Write Once – Run Anywhere” – Well, How is this possible?
Java provides us with a beautiful abstraction called “STREAMS”
There are two types of Streams
- Input Streams
- Output Streams
Java encourages us to program in terms of the Streams – the underlying difference between the Operating System and the device is handled by the Java Virtual Machine.
So we the programmer is left to focus only on the application and not the machine level differences. Thus we have a program that will print the same output on a Windows machine and also on a Linux machine – Predictable Behavior on all operating systems
Java keeps its promise – “Write once, Run Anywhere”
Stream:
A Stream is an abstraction representing an Input or an Output device.
An abstraction is like a black box – What is inside a black box (we do not know for sure – it is best left to itself.) Similarly Java Streams is an abstraction.
Java handles all the idiosyncrasies and finesse of the various input and output devices, freeing the programmer to concentrate on the real application.
FileStreams
Our first encounter with the Java IO classes is the FileStream.
A FileInputStream represents an abstraction used to read a file
A FileOutputStream represents an abstraction to write to a file
If we want to read a file, we need to know the filename.
FileInputStream fin = new FileInputStream(“abc.txt”);
Assuming we have a file called “abc.txt” in our current folder, and we have read permissions to read the file, the above statement opens a stream to the file and gives us a handle, represented by the variable fin.
We can now read the data from the file one byte at a time. The read() method returns a int value denoting the byte.
int i = fin.read();
We can continue reading the bytes until the End-Of-File has been reached. We know that the End-Of-File has been reached when the read() method returns -1
do {
i = fin.read();
} while (i != -1);
Important Note:
While using Java IO, we generally have to access other resources, say File or Memory. These resources are generally owned by the Operating System. Our program will request access to these resources, as in the above example, we saw how we got a reference to the file using the statement
FileInputStream fin = new FileInputStream(“abc.txt”);
Once we acquire a handle to the device, we need to release it once we are done working on it. Also in the event of something unexpected happening in our program, we need to anyhow release the handle to the device from our program.
IO programming makes extensive use of exception handling.
The general format of a Exception Handling block is as follows:
//-- define the resource variable
try {
//-- acquire handle to the resource
} catch (Exception e) {
//-- do exception handling here
} finally {
//-- release the handle to the device
}
When we follow this strategy, we can be definitely sure of releasing the handle back to the operating system.
Here is a sample program, to display the contents of a file on your console:
In this program, we will be passing in the name of the file as a command line parameter.
Remember the method
public static void main(String[] args) {
}
args is a String array to which is passed the command line arguments
Filename: DisplayFile.java
import java.io.FileInputStream;
import java.io.IOException;
public class DisplayFile {
public static void main(String args[]) {
if (args.length != 1) {
System.out.println("Usage is :");
System.out.println("java DisplayFile ");
System.out.println("<Name of file to display> ");
return;
}
FileInputStream fin = null;
int i = 0;
try {
fin = new FileInputStream( args[0]);
do {
i = fin.read();
if (i != -1) System.out.print( (char) i);
} while (i != -1);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fin != null) fin.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
To invoke the file, you would have to type the following at the command prompt
C:> java DisplayFile C:/abc.txt
The text C:\abc.txt is what is passed in the String array – args
If you look at the code, we are reading the value of args[0]
FileInputStream fin = null;
try {
fin = new FileInputStream( args[0]);
}
Once we have the file handle in the variable “fin”, we can start reading the bytes from the file using the read() method.
int i = 0;
i = fin.read();
If you notice we have declared i to be an int type – we keep on reading bytes until we come to the End-Of-File
End-Of-File is denoted by -1
[You may be wondering what if my file has a -1? Will that confuse with the End-Of-File marker which is -1
The answer is No. The -1 in your file would be read as 2 bytes [first the – value and the the 1 value]
So -1 in your text file is different from End-Of-File marker]
Important: We have assumed that we have a file called abc.txt in the location C: drive of Windows.
You would need to enter the full path in your operating system of choice. If the file is not found, the Java Virtual Machine would throw an exception that the file is not found as shown in the above figure
Now let us create a file on the C: drive on the Windows operating system. Let us now try to read that file programmatically from our Java program. The abc.txt file on the C: drive of the Windows operating system would look like this. We created a sample file with the following text:
Welcome to Easy Java Programming!
You are now viewing the file C:\abc.txt using a program.
You are learning IO programming and you are making good progress!
Have a good day!
You will notice that this file resides on the Windows C: drive and is given the name abc.txt
Here are the command line parameters we would pass to the Java program to read the file. Now execute the command line argument,
java DisplayFile C:\abc.txt
The contents of the file we saw in Notepad are printed on the console.
Now that we know how to read the contents of a file, let us see how we can now write to a file.
What we will do is now, read from one file and copy to another file.
Let us modify the previous program to take two input parameters – the name of the file to copy and the file to which we should copy.
Filename: CopyFile.java
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class CopyFile {
public static void main(String args[]) {
if (args.length != 2) {
System.out.println("Usage is :");
System.out.println("java CopyFile ");
System.out.println("<Name of file to copy> ");
System.out.println("<Name of new file which is the copy> ");
return;
}
FileInputStream fin = null;
FileOutputStream fout = null;
int i = 0;
try {
fin = new FileInputStream( args[0]);
fout = new FileOutputStream( args[1]);
do {
i = fin.read();
if(i!= -1) fout.write(i);
} while ( i != -1);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fin != null) fin.close();
if (fout != null) fout.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
To write to a file, we use the FileOutputStream:
FileOutputStream fout = null;
...
try {
out = new FileOutputStream( args[1]);
}
...
Once we have the file handle (and assuming we have write permissions to the location where we are writing to), we write the byte one at a time to the new destination.
do {
i = fin.read();
if(i!= -1) fout.write(i);
} while ( i != -1);
until the End-Of_File is reached.
Now let us execute the program;
Okay, so let us give the proper command line arguments – the program expects us to send two arguments now — the name of the file to copy and the destination file.
Let us copy C:\abc.txt to a new file C:\copy.txt
And we verify that the file C:\copy.txt indeed is created.
Write Once, Run Anywhere!
The above program will execute not only on Windows Operating System, but on most of the operating systems.
It does not matter if the File System Organization is a FAT32 based (standing for File Allocation Table format) used on the Windows Operating Systems, or if it is NTFS (Network File System) used on the Unix and Linux based operating systems.
Java’s promise of Write Once, Run Anywhere is indeed true!
ByteArrayStreams
We now turn our special attention to Byte array input and output. And the reason is very obvious.
In network data transfer, data is always transmitted as an array of bytes. Java provides us with methods that can read and write data in the form of an array of bytes.
Let us look at a short example where we can write the contents of a byte array into a file:
Filename: ByteArrayStreams.java
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.io.IOException;
public class ByteArrayStreams {
public static void main(String args[]) {
String str = "The quick brown fox jumped over the lazy dog";
byte[] byteArray = str.getBytes();
//-- If the input source is a byte array, then wrap the
//-- byteArray into a ByteArrayInputStream
//-- and use the read method to read the byte into your program
ByteArrayInputStream bain = new ByteArrayInputStream(byteArray);
//-- In this example, learn just how to create a ByteArrayInputStream
OutputStream out = null;
try {
//-- Create a byte array output stream
ByteArrayOutputStream baos = new ByteArrayOutputStream();
//-- Copy the contents of the byteArray to the
//-- ByteArrayOutputStream
baos.write(byteArray);
//-- Now the byteArrayOutputStream is ready for operations
//-- Create a new file for output
out = new FileOutputStream("test.txt");
//-- Now copy the contents of the byte array output
//-- stream to any other output stream
baos.writeTo(out);
//-- Notice We did not write one byte at a time
//-- But we used just one method to transfer the
//-- contents of the byteArray to the FileOutputStream
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (out != null) out.close();
} catch(IOException ioe) {
ioe.printStackTrace();
}
}
}
}
BufferedStreams
Let us take a minute to understand how IO programming works on the Operating System.
Whenever a program requests a resource from the Operating System, like reading a File or Printing to the printer, the program suspends its current operation and gives control to the operating system, through an Interrupt. Once the external application or the device performs the necessary operation, the control is returned back to the program which was earlier interrupted.
Whenever an input or output request happens from a program, the process has to suspend its execution and handover state to the Operating System. The Operating System then reads data from a location (memory or socket or file) and makes it available in the heap memory.
If the operation involves writing data from the heap memory to some external device like a printer of a file system, then the Operating System is invoked which performs the actual operation to writing the data to the appropriate location.
This transfer of control to and fro between the process and the Operating System causes a strain on the processor thereby reducing its resources favailable or other concurrent processes.
To overcome this issue of too many reads and writes (and thereby the strain on the operating system) Java provides an abstraction in the form of BufferedStreams. BufferedStreams are Block based IO (and not byte based)
Whenever an input output operation is performed on a BufferedStream, data is read or written to in blocks of 512 bytes.
Let us look at an example to understand better:
Filename: BufferedStreams.java
import java.io.*;
/*
* This program is similiar to CopyFile.java -
* but uses a BufferedStream wrapper
* under the underlying FileInputStream and FileOutputStreams
*
*/
public class BufferedStreams {
public static void main (String args[]) {
if (args.length != 2) {
System.out.println("Usage is :");
System.out.println("java BufferedStreams ");
System.out.println("<Name of file to copy> ");
System.out.println("<Name of new file> ");
return;
}
FileInputStream fin = null;
FileOutputStream fout = null;
int i = 0;
try {
fin = new FileInputStream( args[0]);
fout = new FileOutputStream( args[1]);
BufferedInputStream bis = new BufferedInputStream(fin);
BufferedOutputStream bos = new BufferedOutputStream(fout);
do {
i = bis.read();
if (i != -1) bos.write(i);
} while ( i != -1);
bos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fin != null) fin.close();
if (fout != null) fout.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
In the above example, we have wrapped the underlying FileInputStream and FileOutputStream into a BufferedInputStream and a BufferedOutputStream.
Whenever a read happens on a Buffered Stream, a block of data (around 512 bytes) is read ahead – causing the next byte read() to fetch from an internal buffer and not from an OS interrupt.
FileInputStream fin = new FileInputStream( args[0]);
FileOutputStream fout = new FileOutputStream( args[1]);
BufferedInputStream bis = new BufferedInputStream(fin);
BufferedOutputStream bos = new BufferedOutputStream(fout);
do {
i = bis.read();
if (i != -1) bos.write(i);
} while ( i != -1);
bos.flush();
Similarly when writing to the stream, when using BufferedStream, the data is not written to the file system for every write() call. Instead it is written to an internal buffer, and once the data length crosses the BLOCK size, it is written to the file system.
Using BufferedInputStream and BufferedOutputStream we have an optimized physical read and writes that make use of the Operating System resources.
Remember, when using BufferedOutputStream, we need to flush() the remaining bytes (may still not be 512 yet) to the stream.
The flush() method performs a physical write of the remaining bytes in the internal buffer.
Rule:
Always remember to use the BufferedInputStream and BufferedOutputStream for File based Input-Output programming.
DataStreams
To deal with the differences in representation of primitive data-types across platforms, Java provides us the abstraction called DataStreams.
For example, an int value is composed of 2 bytes in one operating system, say Windows, could be a different representation in another operating system like Linux.
Java does not want us programmers to worry about the differences in the storage patterns among Operating Systems. Our program that reads an int value from a file on Windows should also work similiarly (reading a int value from a file) in Linux.
Just because Windows and Linux store int values (primitive values) differently does not mean that our program should behave differently on different Operating Systems. Our goal is Write-Once, Run Anywhere, isn’t it?!
The sample program shown below will let us read and write primitive data in a platform (operating system and hardware) independent manner.
FileName: DataStreams.java
import java.io.*;
/*
* A DataInputStream allows an application to read
* primitive java datatype from the underlying
* input stream in a machine independent way
* An application uses DataOutputStream to write data that
* can be later read from a DataInputStream
*/
public class DataStreams {
public static void main(String args[]) throws Exception {
FileInputStream fin = null;
FileOutputStream fos = null;
DataInputStream dis = null;
DataOutputStream dos = null;
try {
//-- Create a FileOutputStream and wrap over
//-- the DataOutputStream
fos = new FileOutputStream("test2.txt");
dos = new DataOutputStream(fos);
//-- Write the primitives to the filesystem
dos.writeShort(0);
dos.writeInt(5000);
dos.writeBoolean(true);
dos.writeUTF("MyName");
dos.flush();
//-- Now read the file - Open FileInputStream
//-- and wrap over DataInputStream
fin = new FileInputStream("test2.txt");
dis = new DataInputStream(fin);
//-- Read the primitive values from the DataInputStream
int shortValue = dis.readShort();
int intValue = dis.readInt();
boolean boolValue = dis.readBoolean();
String utfValue = dis.readUTF();
System.out.println("Read the following values as short ["
+ shortValue
+ "] int [" + intValue
+ "] boolean [" + boolValue
+ "] utf [" + utfValue + "]");
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (dis != null) dis.close();
if (dos != null) dos.close();
if (fin != null) fin.close();
if (fout != null) fout.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
In the above example, we create a file called test2.txt and write the primitive data to the file. Then we read the primitive data from the file.
To read and write primitives, we must DataInputStream and DataOutputStream.
Simply wrap the FileInputStream and FileOutputStream over the DataInputStream and DataOutputStream respectively to get the corresponding DataStreams.
FileOutputStream fos = new FileOutputStream("test2.txt");
DataOutputStream dos = new DataOutputStream(fos);
UTF – Unicode Text Format
We know that ASCII data is represented as 1 byte.
In some Eurpoean and Indian languages, we need atleast 2 bytes to represent all the characters of the language. And in some eastern asian languages like Chinese and Japanese, we need atleast 3 bytes to represent the language alphabets.
So depending on the regional settings of the host system, the Operating System either allocates 1, 2 or 3 bytes to represent one character data.
When we use UTF encoding for Strings – Java uses the operating system defaults and uses either 1 or 2 or 3 bytes to represent character data – thereby freeing the programmer from having to worry about the storage and retrieval details.
FileReader and FileWriter – Character based Input Output Programming
We have seen so far that Java IO classes are all about providing an abstraction using Streams that will take care of the underlying platform differences between various Operating System, so that a Java program that is written once, will execute on all the different platforms.
When it comes to reading and writing text files, the main difference between different Operating Systems is the way in which the NewLine character is represented.
In some Operating Systems, the newLine character is represented as a LineFeed (Ox10) and in some Operating Systems it is represented as CtrlFeed (Ox13) and in some Operating Systems as a combination of LineFeed and CtrlFeed (0x10 0x13)
To overcome the difference in the LineFeed implementation difference between different Operating Systems, Java allows us programmers to use the FileWriter and the FileReader when working with Textfiles.
These methods have convenience methods
readLine()
writeLine()
that will read and write one line of text respectively
FileName: FileReader.java
import java.io.*;
public class FileReaderDemo {
public static void main(String args[]) {
try {
FileReader fr = new FileReader("FileReaderDemo.java");
BufferedReader br = new BufferedReader(fr);
String str = null;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (br != null) br.close();
if (fr != null) fr.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
In the above example, we read the file called FileReaderDemo.java (the same file as the source file)
This file is wrapped in a BufferedReader for optimimum read operation (Block based IO)
The statement
while ((str = br.readLine()) != null) {
System.out.println(str);
}
reads one line at a time and prints the output to the console.
FileName: FileWriteDemo.java
import java.io.*;
public class FileWriterDemo {
public static void main(String args[]) {
try {
FileWriter fw= new FileWriter("fwdemo.txt");
BufferedWriter bw = new BufferedWriter(fw);
String str = "The quick brown fox jumped"
+ "over the lazy dog in a java program";
bw.write( str, 0, str.length());
bw.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bw != null) bw.close();
if (fw != null) fw.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
Object Serialization
In Java, we know we always deal with Objects – this is because we solve real world problems by modeling the real world.
We also know that an object has STATE (The type is the class which the object belongs to and the STATE is the value of the attributes of the object)
If we have Student objects, each object has a different state.
Suppose if we want to store the object and retrieve it later in our programs, Java allows us to do this using ObjectStreams.
Object Streams are used to store and read object information as a whole.. The process of storing object data is called Object Serialization.
FileName: SerializationDemo.java
import java.io.*;
public class SerializationExample {
public static void main(String args[]) {
Student s1 = new Student();
s1.setName("Jack"); s1.setAge(18);
s1.setAdmissionNumber(123);
s1.setCourse("Management Studies");
Student s2 = new Student();
s2.setName("Jill"); s2.setAge(18);
s2.setAdmissionNumber(456);
s2.setCourse("Computer Science");
//----------- Serialization -----------//
FileOutputStream fos = null;
ObjectOutputStream oos = null;
try {
fos = new FileOutputStream("objectstore2.ser");
oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.writeObject(s2);
oos.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (oos != null) oos.close();
if (fos != null) fos.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
//---------- De Serialization ----------//
FileInputStream fis = null;
ObjectInputStream oin = null;
try {
fis = new FileInputStream("objectstore2.ser");
oin = new ObjectInputStream(fis);
Student s3 = (Student) oin.readObject();
Student s4 = (Student) oin.readObject();
System.out.println("-Deserialized object-- ");
System.out.println(s3);
System.out.println(s4);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (oin != null) oin.close();
if (fis != null) fis.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
}
Here we create two new student objects s1 and s2. We set values for the object and store them in a file.
For this we create a FileOutputStream and wrap that around an ObjectOutputStream
Java gives us very convenient methods to write object state to a file using the writeObject() method.
To read the object, we create a FileInputStream and wrap it around an ObjectOutputStream.
The read() method on the ObjectOutputStream returns us an Object, which we cast it into a Student object.
We verify that the student object is read correctly by printing the object state.
System.out.println(s3);
System.out.println(s4);
Passing object to System.out.println() method calls the toString() method on the object and hence we verify that the object has been read correctly from the file.
Let us try executing the program:
We get a warning that the Student object is not serializable – Let us take a look at our Student class
FileName: Student.java
public class Student {
private String name;
private int age;
private int admissionNumber;
private String course;
//-- Getters and Setters
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAdmissionNumber(int admissionNumber) {
this.admissionNumber = admissionNumber;
}
public int getAdmissionNumber() {
return this.admissionNumber;
}
public void setCourse(String course) {
this.course = course;
}
public String getCourse() {
return this.course;
}
//-- Methods for the behaviour
public void attendClass() {
//-- write code here
}
public String takeNotes() {
//-- write code here
return null;
}
public boolean writeExams() {
//-- write code here
return true;
}
public String toString() {
return this.name + " " + this.age + " "
+ this.admissionNumber;
}
public boolean equals(Object obj) {
//-- first check reference
//-- if reference are the same,
//-- then just return true
if (this == obj) return true;
//-- Now check for instance
//-- if the object being compared is not a Student,
//-- simply return false
if( !(obj instanceof Student) ) return false;
//-- if we have reached so far,
//-- we can safely say obj is of Type Student
final Student thatStudent = (Student) obj;
return (this.admissionNumber
== thatStudent.getAdmissionNumber());
}
public int hashCode() {
return this.admissionNumber * 19;
}
}
If we want to make our objects serializable, then we need to implement an interface in Java called the java.io.Serializable interface.
Let us modify the Student.java class to add this interface – everything else in the Student class remains the same.
FileName: Student.java
public class Student implements Serializable {
private String name;
private int age;
private int admissionNumber;
private String course;
//-- Getters and Setters
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void setAge(int age) {
this.age = age;
}
public int getAge() {
return this.age;
}
public void setAdmissionNumber(int admissionNumber) {
this.admissionNumber = admissionNumber;
}
public int getAdmissionNumber() {
return this.admissionNumber;
}
public void setCourse(String course) {
this.course = course;
}
public String getCourse() {
return this.course;
}
//-- Methods for the behaviour
public void attendClass() {
//-- write code here
}
public String takeNotes() {
//-- write code here
return null;
}
public boolean writeExams() {
//-- write code here
return true;
}
public String toString() {
return this.name + " " + this.age + " "
+ this.admissionNumber;
}
public boolean equals(Object obj) {
//-- first check reference
//-- if reference are the same,
//-- then just return true
if (this == obj) return true;
//-- Now check for instance
//-- if the object being compared is not a Student,
//-- simply return false
if( !(obj instanceof Student) ) return false;
//-- if we have reached so far,
//-- we can safely say obj is of Type Student
final Student thatStudent = (Student) obj;
return (this.admissionNumber
== thatStudent.getAdmissionNumber());
}
public int hashCode() {
return this.admissionNumber * 19;
}
}
So let us go ahead and make this change – Modify the Student.java to implement Serializable.
Now let us compile the code and run the example again
Summary:
Input Output programming is heavily dependent on the Operating System – and varies from platform to platform. Java makes it easier for us programmers to write-once and run anywhere.
The Java language gives us methods and classes that provide an abstraction over the different platform implementations and limitations. We have to use these methods and the underlying JVM for each platform and operating system, will take care of the necessary operating system to device communication.
The Stream is an abstraction that represents a flow of data – and we have an InputStream and an OutputStream abstraction.
The different Streams in Java and the abstractions they provide are summarized below:
Streams | Abstraction | Watch-outs! | ||||
FileStreams | ||||||
|
To read and write files | |||||
ByteStreams | ||||||
|
To read and write array of bytes. In Network communications, we generally have to deal with array of bytes | |||||
BufferedStreams | ||||||
|
Optimization of IO processing – block based IO – read ahead and internal buffer write from file system | Need to flush() the BufferedOutputStream | ||||
DataStreams | ||||||
|
To read and write primitive data – also allows for UTF operations | |||||
CharacterStreams | ||||||
|
To read and write text files – provides abstraction over the newLine character represenation. – BufferedFileReader and BufferedFileWriter provide optimization using block based IO | |||||
Serialization – Object storage and retrieval | ||||||
|
To read and write object data, preferably to a file |
Object to be serialized – need to implement the java.io.Serializable marker interface (Marker interface is an interface without any body!) |
The Decorator pattern in the java.io packages
Since all the Java stream implementations implement the java.io.InputStream and the java.io.OutputStream interfaces, it is possible to decorate an existing stream reference into another stream.
For example, A FileInputStream can be converted into a ByteArrayStream or a DataStream or a BufferedStream by simply wrapping the underlying stream
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("filename"));
DataInputStream dis = new DataInputStream(new FileInputStream("filename"));