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

BufferedInputStream

Открыта

— Привет, Амиго! Сегодня я расскажу тебе немного интересных вещей про класс 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(Cat 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() – это вынести мусор немедленно. Можно использовать перед приходом гостей.

Комментарии (336)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Grigoryvvv
Уровень 28
Expert
позавчера, 07:53
15.10.2026 / 28 уровень 1. Понятия Обёртка (Wrapper) и Декоратор (Decorator). Обёртка (Wrapper) — это приём проектирования, при котором один объект оборачивает другой объект и предоставляет доступ к нему через себя. Обёртка позволяет работать с объектом через дополнительный слой кода, не изменяя исходный класс. Декоратор (Decorator) — это паттерн проектирования, который позволяет динамически добавлять объекту новые обязанности или изменять его поведение, не изменяя исходный класс. Как они связаны между собой? Обёртка — общее понятие, описывающее способ взаимодействия с объектом через другой объект. Декоратор — конкретный паттерн проектирования, использующий принцип обёртки для расширения поведения объекта. Тем самым декоратор один из видов обертки. Три основных паттерна использующих принцип Обёртки: Паттерн Главная цель Декоратор Добавить или изменить поведение Адаптер Согласовать несовместимые интерфейсы Прокси Контролировать доступ к объекту Мы здесь рассматриваем конкретный паттерн - Декоратор. 2. Классический вариант Декоратора — через интерфейс. В классическом варианте декоратор реализует тот же интерфейс, что и оригинальный объект. Очень хорошо данный вариант показан в данном видео : Открыть с VPN Рекомендую параллельно с просмотром видео набирать тот же код у себя в IntelliJ IDEA.
Grigoryvvv
Уровень 28
Expert
позавчера, 07:54
3. Вариант Декоратора из лекции, через наследование - без использования интерфейса. В лекции показан вариант обёртки Декоратор, который использует наследование от конкретного класса Cat, а не отдельный интерфейс. Такой способ допускается. В реальных Java-проектах чаще ориентируются на классический подход: общий интерфейс + декоратор, который хранит ссылку на объект этого интерфейса. Но это не жёсткое правило: всё зависит от существующей архитектуры и задачи. Недостатки Декоратора из лекции: - Обёртка привязана к конкретному классу Cat; - В Java нельзя наследоваться от нескольких классов; - Если у базового класса много методов, легко получить лишнее или неудобное наследуемое поведение. - Приходится прописывать в конструкторе оборачивающего объекта вызов конструктора родительского класса. Это рабочий, но немного избыточный вариант: поле name родительской части обёртки почти не используется. 4. Зачем вообще нужно прописывать super(cat.getName()) в конструкторе оборачивающего класса? Как мы помним из лекции "Создание объекта. Открыть", при создании любого объекта в его конструкторе по умолчанию вызывается конструктор класса родителя. Если в классе-родителе конструктор имеет входящие аргументы, то мы должны в конструкторе класса-потомка явно прописать вызов конструктора класса-родителя с входящими аргументами. super(cat.getName()) — это вызов конструктора родителя, а не команда создания нового объекта При вызове конструктора родителя в конструкторе потомка, выполняется код конструктора родителя, но новый объект класса-родителя не создается. Вместо этого у объекта класса-потомка создается своя родительская часть в которой сохраняются все поля класса родителя и эти поля в родительской части не связаны с каким то реальным объектом класса родителя.
Grigoryvvv
Уровень 28
Expert
позавчера, 11:34
Это сделано, потому что мы можем создать Объект потомок без создания класса Родителя. И где-то эти поля нужно хранить. Если мы создали объект класса-родителя, то при создании объекта класса-потомка у него так-же создастся своя родительская часть и в неё сохранятся поля из объекта класса-родителя. Самое интересное, что поля родительской части объекта потомка не связаны и не имеют ссылок на поля объекта родителя. Разберем на конкретном примере:
public class Solution {
    public static void main(String[] args) {
        Pet pet = new Pet(10);                 // создали объект pet
        Cat cat = new Cat(pet);               // создали объект cat
        System.out.println("Pet: age=" + pet.age);
        System.out.println("Cat: age=" + cat.age);
        System.out.println("");

        pet.age = 1;                          // изменили поле age объекта pet
        System.out.println("Pet: age=" + pet.age);
        System.out.println("Cat: age=" + cat.age);
    }

    static class Pet {
        int age;
        Pet(int age) {
            this.age = age;
        }
    }

    static class Cat extends Pet {
        Cat(Pet pet) {                       // вызвали конструктор класса Cat
            super(pet.age);      // вызвали конструктор родителя
        }                      // в объекте cat создалась его родительская часть
    }                          // и в неё сохранилось своё поле age
}


Вывод в консоль:
Pet: age=10
Cat: age=10

Pet: age=1        // поле объекта родителя получило новое значение
Cat: age=10       // поле age родительской части объекта cat не получило новое
                  // значение, т.к. не связано с полем age объекта pet
Вот такой важный нюанс! В лекции вызов super(cat.getName()) так-же создаст в родительской части потомка поле String name, которое в последующем мы не используем в коде.
20 марта, 12:00
А, вот оно как! Кажется понял, зачем нужно наследование. Без наследования мы не сможем использовать полиморфизм и выдавать объект обёртки за объект оригинального класса.
Anonymous #3585174
Уровень 33
3 сентября 2025, 07:41
Like
Савелий
Уровень 3
9 июня 2025, 15:18
💡 Вопрос: Хранит ли 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, 14:33
Вот тут про метод flush наконец-то стало понятно!
{Java_Shark}
Уровень 36
26 ноября 2024, 05:15
++
10 марта 2025, 05:25
++Java_Shark
Vitalii
Уровень 26
Expert
30 марта 2025, 15:58
++
bayel
Уровень 32
20 ноября 2024, 17:01
Краткое объяснение, почему вызывается 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Работает в Автосервис
25 января 2025, 11:34
для чего вызывать супер я не понял. ведь в конcтрукторе обертки мы присваиваем внутреннему полю original принимаемый объект и потом пользуемся только original
Леонид
Уровень 1
14 августа 2024, 15:16
BufferedInputStream не заменяет InputStream, а служит в качестве расширения для улучшения производительности. Вот как это работает и когда использовать BufferedInputStream: Что такое InputStream? InputStream — это базовый абстрактный класс в Java, который представляет поток ввода байтов. Он определяет методы для чтения байтов из потока. Что такое BufferedInputStream? BufferedInputStream — это подкласс InputStream, который добавляет функциональность буферизации к стандартному потоку ввода. Он создает внутренний буфер для хранения данных, что делает операции чтения более эффективными. Когда использовать BufferedInputStream? При чтении больших объемов данных: Если вам нужно читать данные из файла или другого потока ввода, использование BufferedInputStream может значительно улучшить производительность, особенно если данные читаются по одному байту за раз. Для частого доступа к данным: Если вам нужно часто читать данные, буферизация позволяет уменьшить количество обращений к исходному потоку и снизить накладные расходы на ввод-вывод.
G L
Уровень 38
22 августа 2024, 15:41
Ну вот и чатгпт подьехал) А как вы тут оказались на первом уровне?
Леонид
Уровень 1
16 сентября 2024, 11:19
Сбросил уровень :)
Алексей
Уровень 4
14 октября 2024, 22:16
во дают люди! я наоборот поднимал!))
Глеб
Уровень 30
25 июля 2024, 11:52
Все кого смутило наследование от Кота и вызывания конструктора для создания неиспользуемого объекта Кот при вызове конструктора суперкласса - super(cat.getName()); В данной задаче можно использовать принципы Агрегации или Композиции (оба являются частными случаями Ассоциации) Так же, эта тема обёрток подводит нас к такому патерну проектирования как Декортаор, который позволяет динамически добавлять объектам новую функциональность, оборачивая их в полезные «обёртки». Подробнее - Тут.
SomeBody098веник в бане
20 июня 2024, 16:29
ок