undefined

BufferedInputStream

Java Core
Level 8 , Lesson 6
Available

"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."

Comments (17)
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION
Andrei Level  20
14 January 2021
Man this was a sweet lesson but could've come a lot earlier, especially the part about Buffer and Buffering! 🤓😁
Onur Bal Level  27, Istanbul, Turkey
22 September 2020
In the constructor of CatWrapper

public CatWrapper (Cat cat)
 {
  super(cat.getName());
  this.original = cat;
 }
What is the purpose of calling the Cat class's constructor, when we already have a cat instance which we will be assigning to "original"? I suppose it means that our CatWrapper class now has a "private String name" field, but there seems to be no point since we never access it.
yehuda b Level  23, Beersheba, Israel
19 August 2020
I wonder if the CatWrapper constructor's call to super(cat.getName()), is only there because, being that Cat doesn't have a default constructor, when defining CatWrapper's original field through CatWrapper's constructor, you have to call the parent class's constructor. Which makes me wonder why not name the CatWrapper object cat.getName() + " 's Wrapper "?
Fadi Alsaidi Level  26, Carrollton, TX, USA
9 March 2020
that first picture up there is making me very uncomfortable.
Robert Constantinescu Level  25, Bucharest, Romania
30 November 2019
Nice one !
Jason Level  26, Rancho Cucamonga, United States
6 October 2019
System.out.println(named.getName()); anyone have any idea where "named" came from in this line of code? feel like this is another important detail not covered by CodeGym but presented by them which seems like a common theme.
Krisztian Level  24, Budapest, Hungary
9 July 2019
"The «wrapper» (or «decorator») design pattern is ... for extending object functionality without using inheritance." a couple lines later: "If we want to "wrap" ..., then we need to: 1) Create our own wrapper class and inherit from the same class/interface as the object to be wrapped." I can recognize a fraud if I see one.
Ewerton Level  30, Belo Horizonte, Brasil
3 July 2019
I hope the next exercises doesn't involve overriding dozens of methods again...
David Lavigne Level  19, Lynnwood, United States
8 April 2019
I think the captions on the bottom image should be Writing data ....