"Hello, Amigo! Today I'll tell you a few interesting things about the BufferedInputStream class, but let's start with «wrappers» and a «bag of sugar»."

"What do you mean by «wrapper» and «bag of sugar»?"

"These are metaphors. Listen. So…"

The «wrapper» (or «decorator») design pattern is a fairly simple and convenient mechanism for extending object functionality without using inheritance.

BufferedInputStream - 1

Suppose we have a Cat class with two methods: getName and setName:

Java code Description
class Cat
{
 private String name;
 public Cat(String name)
 {
  this.name = name;
 }
 public String getName()
 {
  return this.name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}
The Cat class has two methods: getName and setName
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");

 printName(cat);
}

public static void printName(Cat cat)
{
 System.out.println(cat.getName());
}
An example of how it might be used.

«Oscar» will be displayed on the console.

Suppose we need to intercept a method call on a cat object and perhaps make some small changes. For this, we need to wrap it in its own wrapper class.

If we want to "wrap" our own code around the method calls on some object, then we need to:

1) Create our own wrapper class and inherit from the same class/interface as the object to be wrapped.

2) Pass the object to be wrapped to our class's constructor.

3) Override all methods in our new class. Invoke the wrapped object's methods inside each of the overridden methods.

4) Make whatever changes you want: change what the method calls do, change their parameters, and/or do something else.

In the example below, we intercept calls to a Cat object's getName method and slightly change its return value.

Java code Description
class Cat
{
 private String name;
 public Cat(String name)
 {
  this.name = name;
 }
 public String getName()
 {
  return this.name;
 }
 public void setName(String name)
 {
  this.name = name;
 }
}
The Cat class contains two methods: getName and setName.
class CatWrapper extends Cat
{
 private Cat original;
 public CatWrapper (Cat cat)
 {
  super(cat.getName());
  this.original = cat;
 }

 public String getName()
 {
  return "A cat named " + original.getName();
 }

 public void setName(String name)
 {
  original.setName(name);
 }
}
The wrapper class. The class doesn't store any data except a reference to the original object.
The class is able to "throw" calls to the original object (setName) passed to the constructor. It can also "catch" these calls and modify their parameters and/or results.
public static void main(String[] args)
{
 Cat cat = new Cat("Oscar");
 Cat catWrap = new CatWrapper (cat);
 printName(catWrap);
}

public static void printName(Cat named)
{
 System.out.println(named.getName());
}
An example of how it might be used.

«A cat named Oscar».
will be displayed on the console

In other words, we quietly replace each original object with a wrapper object, which receives a link to the original object. All method calls on the wrapper are forwarded to the original object, and everything runs like clockwork.

"I like it. The solution is simple and functional."

"I'll also tell you about a «bag of sugar». This is a metaphor rather than a design pattern. A metaphor for the word buffer and buffering. What is buffering and why do we need it?"

BufferedInputStream - 2

Let's say that today it's Rishi's turn to cook, and you're helping him. Rishi isn't here yet, but I want to drink tea. I ask you to bring me a spoonful of sugar. You go to the basement and find a bag of sugar. You can bring me the entire bag, but I don't need the bag. I only need one spoonful. Then, like a good robot, you take one spoonful and bring it to me. I add it to the tea, but it still isn't sweet enough. And I ask you to bring me one more. You again go to the basement and bring another spoonful. Then Ellie comes along, and I ask you to bring sugar for her... All this takes too long and is inefficient.

Rishi comes, sees all this, and asks you to bring him a sugar bowl full of sugar. Then Ellie and I start asking Rishi for sugar. He simply serves it to us from the sugar bowl, and that's all.

What happened after Rishi showed up is called buffering: the sugar bowl is a buffer. Thanks to buffering, "clients" can read data from a buffer in small portions, while the buffer, in order to save time and effort, reads them from the source in large portions.

"That's a cool example, Kim. I understand perfectly. The request for a spoonful of sugar is like reading one byte from a stream."

"Exactly. The BufferedInputStream class is a classical example of a buffered wrapper. It wraps the InputStream class. It reads data from the original InputStream in large blocks into a buffer, and then pulls it out of the buffer piece-by-piece as we read from it."

"Very good. It's all clear. Are there buffers for writing?"

"Oh, sure."

"Maybe an example?"

"Imagine a trash can. Instead of going outside to put trash into an incinerator every time, you just throw it into the trash can. Then Bubba takes the can outside once every two weeks. A classic buffer."

"How interesting! And a lot clearer than a bag of sugar, by the way."

"And the flush() method is like taking out the garbage right away. You can use it before guests arrive."