Java Streams Overview, Part I
Want to the basics of Java streams?
Join the DZone community and get the full member experience.
Join For FreeFor a long time, I had a gap in my knowledge of Java streams. On a basic level, I could use them, but I did not a deep understanding of them. So, I decided to do a quick overview of Java streams.
In this article, I will deal with many everything from the fundamentals to chaining and clean-up.
Hope this helps you broaden your knowledge of Java streams.
What Are Streams?
A stream is an ordered sequence of data that...
- Provides a common I/O model
- Abstracts details from an underlying source or destination
Whether you use streams to take data from memory, storage, or your network, it will hide the implementation details from you. The details are abstracted away, so in every situation, you can look at it as an ordered sequence of data.

- Stream types are uni-directional
This means that if you create an instance of a Java stream, you decide whether you would like to write to it or read from it. You can’t do both at the given time on a single stream.

We can divide the streams into two categories:
- Byte streams – Interacts as binary data
- Text streams – Interacts as unicode characters
The general interaction is the same for both Java stream types
Reading With Streams
As we mentioned, each stream is used either to read from or write to.
Firstly, let us see how we can read data from Java streams.

The base class to read binary data in Java is the InputStream
. And the base class to read text data is called the Reader
class.
Both classes almost have the same two methods:
-
int read()
-
int read(byte/char[] buff)
Notice that in both scenarios, they give back an Integer
value. These are interpreted values. An integer is a 32-bit container, so it will work in both cases.
The difference between the two:
- The
InputStream
works with bytes, which are 8-bits. - The
Reader
works with unicode characters, which are 16-bits.
Read Bytes With InputStream
InputStream input = // create input stream
int result;
while(result = input.read() >= 0) //Indicates the end-of-stream with a return value of -1
byte byteVal = (byte)result;
// do something with byteVal
};
Read Text with Reader
Reader reader = // create reader
int result;
while(result = reader.read() >= 0)//Indicates the end-of-stream with a return value of -1
char charVal = (char)result;
// do something with charVal
};
Note that if you would like to retrieve the value, you simply need to cast the result to the appropriate type — in this case, byte
or char
.
Writing With Streams
To write data, there are two base classes similar to the read streams.
-
OutputStream
(for bytes) -
Writer
(for text)

write
methods with the
void return
type.
Writing Bytes With OutputStream
To write with OutputStream
, you can pass a single byte the write
method, or you can pass a byte array as well.
OutputStream output // create output stream;
Byte byteVal = 100;
output.write(byteVal);
byte[] byteBuff = {0, 10, 20};
output.write(byteBuff);
Writing Characters With Writer
To write with the Writer
class, you can pass a simple character, character array, or String to its write
method.
Writer writer // create output stream;
char charVal = 'c';
writer.write(charVal);
char[] charArray = {'c', 'h', 'a', 'r'};
writer.write(charArray);
String stringVal = "String";
writer.write(stringVal);
As you can see, you need much less code to write than to read.
Common Java Stream Classes
Above, I wrote about the base stream classes. Now, let us go a bit deeper and talk about the different implementations.
Common Input/OutputStream Derived Classes

-
ByteArrayInputStream
/ByteArrayOutputStream
– Enables us to create a stream over a byte array -
PipedInputStream
PipedOutputStream
– This is much like a producer-consumer concept. A piped output stream can be connected to a piped input stream to create a communications pipe. The piped output stream is the sending end of the pipe. Typically, data is written to aPipedOutputStream
object by one thread and data is read from the connectedPipedInputStream
by some other thread. -
FileInputStream
/FileOutputStream
– These allow us to create streams over files.
Common Reader/Writer Derived Classes

-
CharArrayReader
/Writer
– Allows creating streams over characters -
StringReader
/Writer
– Allows creating stream over Strings -
PipedReader
/Writer
– Allows creating a stream in a Producer/Consumer relationship over text. Similarly to thePipedOutput
/InputStream
-
InputStreamReader
/Writer
– Allows us to create a stream over anInput
/OutputStream
-
FileReader
/Writer
– These are delivered from the least mentioned above. It allows us to make a stream over text files.
Stream Errors and Clean-Up
So far, we looked at the general features of streams, but we have not considered all the realities of working with them.
Stream Realities

Let us see the two main groups here.
Clean-Up
Problems
- Streams are backed by physical storage, which often exists outside the Java runtime, like files or network connections.
- Hence, Java may not reliably clean up, so we need to do our own reliable clean-up. We need to close the Streams when we are done with them.
Solutions
- Streams implement the
Closeable
interface, which implements one single close method. So, this is our responsibility to call it.
Let us see a simple solution:
Reader reader;
try{
reader = // create output stream;
// do something with reader;
}catch (IOException e) {
//handle exception
}finally {
if(reader != null)
reader.close();
}
The problem with the above example is that you always need to implement it. Usually, we use Streams frequently, so it should be done automatically. Let us see how we can achieve it.
Automating Clean-Up
-
AutoClosable
interface- One method: close
- The base interface of the
Closable
interface, so every Stream supports it. - Provides support for try-with-resources
Try-With-Resources
- Automates the clean-up of one or more resources
- A “resource” is any type that implements
AutoClosable
- A “resource” is any type that implements
- Syntax similar to traditional try statement
- Optionally includes catch block(s)
- Handle try body
- Handle close method call
Working With Try-With-Resources
Here, I provided a simple example of how we can use the automatic close of streams with try-with-resources block.
I will use it through a FileInputStream
. We will talk about this specific stream later.
try(FileInputStream input = new FileInputStream("file1.txt")) {
int data = input.read();
while(data != -1){
System.out.print((char) data);
data = input.read();
}
}
With the above approach, you do not need to investigate further work to close your streams.
Summary
In this article, we talked about the fundamentals of Java streams, what are they, how they work, and how you can use them. In my next article, I will dig a bit deeper, and I will write about more advanced Stream topics like chaining, buffered streams, and how to use file systems with it. So stay tuned!
See more about streams in the official Java documentation here.
If you enjoyed this article and want to learn more about Java Streams, check out this collection of tutorials and articles on all things Java Streams.
Published at DZone with permission of Zoltan Raffai, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
Grow Your Skills With Low-Code Automation Tools
-
Core Knowledge-Based Learning: Tips for Programmers To Stay Up-To-Date With Technology and Learn Faster
-
Hiding Data in Cassandra
-
What Is JHipster?
Comments