JavaRush /Курсы /Java Core /BufferedInputStream

BufferedInputStream

Java Core
8 уровень , 6 лекция
Открыта

— Привет, Амиго! Сегодня я расскажу тебе немного интересных вещей про класс BufferedInputStream, но начнем мы с «обертки» и «мешка сахара».

— Это что еще за «обертка» и «мешок сахара»?

— Это метафоры. Слушай. Итак…

Паттерн проектирования «Обёртка» (Wrapper или Decorator) – это довольно простой и удобный механизм расширения функциональности объектов.

BufferedInputStream - 1

Пусть у нас есть класс Cat с двумя методами getName и setName:

Код на Java Описание
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;
    }
}
Класс Кот(Cat) имеет два метода: getName & setName
public static void main(String[] args) {
    Cat cat = new Cat("Васька");

    printName(cat);
}

public static void printName(Сat cat) {
    System.out.println(cat.getName());
}
Пример использования.

В консоль будет выведена строка «Васька».

Допустим нам нужно перехватить вызов методов у объекта cat и, возможно, внести туда небольшие изменения. Для этого нам понадобится обернуть его в свой класс-обертку.

Если мы хотим «обернуть» вызовы методов какого-то объекта своим кодом, то нам нужно:

1) Создать свой класс-обертку и унаследоваться от класса/интерфейса, для которого делаем обертку.

2) Передать оборачиваемый объект в конструктор нашего класса.

3) Переопределить все методы в нашем новом классе, и вызвать в них методы оборачиваемого объекта.

4) Внести свои изменения «по вкусу»: менять результаты вызовов, параметры или делать что-то еще.

В примере ниже мы перехватываем вызов метода getName у объекта cat и немного меняем его результат.

Код на Java Описание
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;
    }
}
Класс Кот(Cat) содержит два метода – получить имя и установить имя.
class CatWrapper extends Cat {
    private Cat original;
    public CatWrapper (Cat cat) {
        super(cat.getName());
        this.original = cat;
    }

    public String getName() {
        return "Кот по имени " + original.getName();
    }

    public void setName(String name) {
        original.setName(name);
    }
}
Класс-обертка. Класс не хранит никаких данных, кроме ссылки на оригинальный объект.
Класс в состоянии «пробрасывать» вызовы оригинальному объекту (setName), переданному ему в конструкторе.А также «перехватывать» эти вызовы и модифицировать их параметры и результаты.
public static void main(String[] args) {
     Cat cat = new Cat("Васька ");
    Cat catWrap = new CatWrapper (cat);
    printName(catWrap);
}

public static void printName(Cat named) {
    System.out.println(named.getName());
}
Пример использования.

В консоль будет выведена строка
«Кот по имени Васька».

Т.е. мы тихонечко подменяем каждый оригинальный объект на объект-обертку, в который уже передаем ссылку на оригинальный объект. Все вызовы методов у обертки идут к оригинальному объекту, и все работает как часы.

— Мне понравилось. Решение несложное и функциональное.

— Еще я расскажу тебе про «мешок сахара», но это не паттерн, а метафора. Метафора к слову буфер и буферизация. Что же такое буферизация и зачем она нужна?

BufferedInputStream - 2

Допустим, сегодня очередь Риши готовить, а ты ему помогаешь. Риши еще нет, а я хочу выпить чай и прошу тебя принести мне ложечку сахара. Ты пошел в подвал, там стоит мешок с сахаром. Ты можешь принести мне целый мешок, но мешок мне не нужен. Мне нужна только одна ложка. Тогда ты, как хороший робот, набрал одну ложку и принес мне. Я добавила ее в чай, но все равно не очень сладко. И я попросила у тебя еще одну. Ты опять сходил в подвал и принес еще ложку. Потом пришла Элли, и я попросила тебя принести сахара для нее… Это все слишком долго и неэффективно.

Пришел Риша, посмотрел на все это и попросил тебя принести ему полную сахарницу сахара. Потом я и Элли стали просить сахар у Риши. Он просто давал его нам из сахарницы, и все.

То, что произошло после появления Риши называется буферизацией, а сахарница – это буфер. Благодаря буферизации «клиенты» могут читать данные из буфера маленькими порциями, а буфер, чтобы сэкономить время и силы, читает их из источника большими порциями.

— Классный пример, Ким. Я все понял. Просьба ложки сахара – это аналог чтения из потока одного байта.

— Да. Класс BufferedInputStream – классический представитель обертки-буфера. Он – класс-обертка над InputStream. При чтении данных из него, он читает их из оригинального InputStream’а большими порциями в буфер, а потом отдает из буфера потихоньку.

— Отлично. Все понятно. А буферы для записи бывают?

— Да, конечно.

— А можно пример?

— Представь себе мусорное ведро. Вместо того, чтобы каждый раз ходить выбрасывать мусор на улице в дезинтегратор, ты просто выкидываешь его в мусорное ведро. А Скрафи раз в две недели выносит его на улицу. Классический буфер.

BufferedInputStream - 3

— Как интересно. И гораздо понятнее, кстати, чем с мешком сахара.

— А метод flush() – это вынести мусор немедленно. Можно использовать перед приходом гостей.

Комментарии (338)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Anonymous #3585174 Уровень 33
3 сентября 2025
Like
Савелий Уровень 66
9 июня 2025
💡 Вопрос: Хранит ли CatWrapper какие-либо данные кроме original? Формально — да, потому что наследуется от Cat, и при этом вызывает super(cat.getName()), а значит: У объекта CatWrapper есть поля родительского класса Cat. При вызове конструктора super(cat.getName()) — поле name в классе Cat заполняется значением cat.getName(). Это поле является частью объекта CatWrapper, потому что наследование в Java означает, что все поля и методы родителя — это часть объекта-наследника. 🤯 Значит ли это, что CatWrapper дублирует данные? Да, потенциально. Например, если Cat имеет поле name, то: original.getName() и super.getName() могут возвращать разные значения. setName(name) влияет только на original, а не на поле name, унаследованное от Cat. 📌 Вывод: Фраза "Класс не хранит никаких данных, кроме ссылки на оригинальный объект" — не совсем точна. Правильнее было бы сказать: "Класс логически не использует свои собственные поля, унаследованные от Cat, кроме ссылки на original. Все операции делегируются оригинальному объекту."
9 июня 2025
Вот тут про метод flush наконец-то стало понятно!
{Java_Shark} Уровень 36
26 ноября 2024
++
Денис Кокшаров Уровень 32
10 марта 2025
++Java_Shark
Vitalii Уровень 32 Expert
30 марта 2025
++
bayel Уровень 32
20 ноября 2024
Краткое объяснение, почему вызывается super(cat.getName()); 👇

class CatWrapper extends Cat
{
    //private String name; "так как мы наследуемся от Cat, то у нас скрытно появляются его поля

    private Cat original;
    public CatWrapper (Cat cat)
    {
        super(cat.getName()); //тут не создается новый кот, а инициализируется поле name
        this.original = cat;
    }
Когда вы наследуетесь от класса, объект наследника уже содержит все поля и методы базового класса. Эти поля "скрыты" внутри объекта наследника, но они являются его частью.
CodeMashine Уровень 33
25 января 2025
для чего вызывать супер я не понял. ведь в конcтрукторе обертки мы присваиваем внутреннему полю original принимаемый объект и потом пользуемся только original
Леонид Уровень 12
14 августа 2024
BufferedInputStream не заменяет InputStream, а служит в качестве расширения для улучшения производительности. Вот как это работает и когда использовать BufferedInputStream: Что такое InputStream? InputStream — это базовый абстрактный класс в Java, который представляет поток ввода байтов. Он определяет методы для чтения байтов из потока. Что такое BufferedInputStream? BufferedInputStream — это подкласс InputStream, который добавляет функциональность буферизации к стандартному потоку ввода. Он создает внутренний буфер для хранения данных, что делает операции чтения более эффективными. Когда использовать BufferedInputStream? При чтении больших объемов данных: Если вам нужно читать данные из файла или другого потока ввода, использование BufferedInputStream может значительно улучшить производительность, особенно если данные читаются по одному байту за раз. Для частого доступа к данным: Если вам нужно часто читать данные, буферизация позволяет уменьшить количество обращений к исходному потоку и снизить накладные расходы на ввод-вывод.
G L Уровень 38
22 августа 2024
Ну вот и чатгпт подьехал) А как вы тут оказались на первом уровне?
Леонид Уровень 12
16 сентября 2024
Сбросил уровень :)
Алексей Уровень 7
14 октября 2024
во дают люди! я наоборот поднимал!))
Глеб Уровень 30
25 июля 2024
Все кого смутило наследование от Кота и вызывания конструктора для создания неиспользуемого объекта Кот при вызове конструктора суперкласса - super(cat.getName()); В данной задаче можно использовать принципы Агрегации или Композиции (оба являются частными случаями Ассоциации) Так же, эта тема обёрток подводит нас к такому патерну проектирования как Декортаор, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки». Подробнее - Тут.
SomeBody098 Уровень 51
20 июня 2024
ок
mkhlv Уровень 29
6 мая 2024
Получается, любой наследник = обертка?
ARTIsshoque Уровень 28
12 июня 2024
Нет. Обёртка содержит объект родительского класса в качестве поля и вызывает методы у него. В примере из лекции это объект original типа Cat. Обычно же наследники вызывают методы родительского КЛАССА, а не объекта этого класса.
Long_byte Уровень 43
9 марта 2024
как я понял класс обертка дает новый функционал к существующему объекту переопределяя его методы?
Novikova Natalia Уровень 32
28 марта 2024
похоже на то.
Dmitry Уровень 34
21 июля 2024
Wrapper обычно предназначен для изменения интерфейса или упрощения работы с объектом, тогда как Decorator предназначен для добавления нового поведения или функциональности к объекту.