Вы находитесь на странице: 1из 20

• Вопрос 1

Сетевое взаимодействие - клиент-серверная архитектура. Основные протоколы. Их


сходства и отличия
Ответ:
Клиент-серверная архитектура — шаблон проектирования, основа для создания веб-
приложений. Данная архитектура состоит из трех компонентов:

1. Клиент — из названия становится понятно, что это пользователь сервиса (веб-


приложения), который обращается к серверу для получения какой-то информации.
2. Сервер — место, где располагается твое веб-приложение или его серверная часть. Он
владеет необходимой информацией о пользователях или может ее запрашивать. Также
при обращении клиента сервер возвращает ему запрашиваемую информацию.
3. Сеть — все просто: обеспечивает обмен информацией между клиентом и сервером.
Сервер может обрабатывать огромное количество запросов от разных юзеров. То есть
клиентов может быть много, а если им нужно обменяться информацией между собой, делать
это придется через сервер. Таким образом, сервер получает еще одну дополнительную
функцию — контроль трафика. Если речь идет о созданном нами многопользовательском чате,
весь программный код будет состоять из двух модулей:

• клиентского — содержит графический интерфейс для авторизации,


отправки/получения сообщений;
• серверного — веб-приложение, которое размещается на сервере и принимает
сообщения от пользователей, обрабатывает их, а потом отправляет адресатам.
Пример: Когда мы хотим посмотреть полезную (или не очень полезную) информацию в
интернете, мы открываем браузер, в строке поиска вводим запрос, и в ответ получаем
информацию от поисковика. В этой цепочке браузер — это наш клиент. Он отправляет
запрос с информацией о том, что мы ищем, серверу. Сервер обрабатывает запрос, находит
наиболее релевантные результаты, упаковывает их в понятный для браузера (клиента)
формат и отправляет назад.
Важно: понятие сервер — не о конкретном компьютере, а о взаимоотношениях абонентов
сети.
Протокол — это по сути правила обмена информацией, которые описывают каким
образом обмениваются информацией взаимодействующие стороны.
В нашей лабораторной мы использовали протоколы Транспортного уровня — TCP и UDP.
И TCP, и UDP используют для определения протокола верхнего уровня число, называемое
портом.
А основное их отличие в том TCP - протокол с
гарантированной доставкой пакетов, UDP - нет. TCP
— «гарантированный» транспортный механизм с
предварительным установлением соединения,
предоставляющий приложению надёжный поток
данных, дающий уверенность в безошибочности
получаемых данных, перезапрашивающий данные в
случае потери и устраняющий дублирование
данных. TCP позволяет регулировать нагрузку на
сеть, а также уменьшать время ожидания данных
при передаче на большие расстояния. Более того,
TCP гарантирует, что полученные данные были
отправлены точно в такой же последовательности. В
этом его главное отличие от UDP.
UDP протокол передачи датаграмм без установления
соединения. Также его называют протоколом
«ненадёжной» передачи, в смысле невозможности
удостовериться в доставке сообщения адресату, а также возможного перемешивания
пакетов. В приложениях, требующих гарантированной передачи данных, используется
протокол TCP. UDP обычно используется в таких приложениях, как потоковое видео и
компьютерные игры, где допускается потеря пакетов, а повторный запрос затруднён или не
оправдан, либо в приложениях вида запрос-ответ (например, запросы к DNS), где создание
соединения занимает больше ресурсов, чем повторная отправка.

• Вопрос 2
Протокол TCP. Классы Socket и Serversocket
Ответ:
TCP - это транспортный протокол передачи данных. Чтобы передать пакеты, ему нужно
заранее установить соединение с сетью. После передачи пакета, протокол будет требовать
отчета о передаче, успешно или не успешно. Контролирует нагруженность сети. Поэтому этот
протокол считается более безопасным, надежным.

• Сокет. Оно обозначает точку, через которую происходит соединение. Проще говоря,
сокет соединяет в сети две программы. Класс Socket реализует идею сокета. Через его
каналы ввода/вывода будут общаться клиент с сервером.
Объявляется этот класс на стороне клиента, а сервер воссоздаёт его, получая сигнал на
подключение. Так происходит общение в сети.
Socket(String имя_хоста, int порт) throws UnknownHostException, IOException
Socket(InetAddress IP-адрес, int порт) throws UnknownHostException
«имя_хоста» — подразумевает под собой определённый узел сети, ip-адрес. Если класс сокета
не смог преобразовать его в реальный, существующий, адрес, то сгенерируется исключение
UnknownHostException.
Порт — есть порт. Если в качестве номера порта будет указан 0, то система сама выделит
свободный порт. Также при потере соединения может произойти исключение IOException.
Следует отметить тип адреса во втором конструкторе — InetAddress. Он приходит на помощь,
например, когда нужно указать в качестве адреса доменное имя. Так же когда под доменом
подразумевается несколько ip-адресов, то с помощью InetAddress можно получить их массив.
Тем не менее с ip он работает тоже. Так же можно получить имя хоста, массив байт
составляющих ip адрес и т.д. Мы немного затронем его далее, но за полными сведениями
придется пройти к официальной документации.
При инициализации объекта типа Socket, клиент, которому тот принадлежит, объявляет в сети,
что хочет соединиться с сервером по определённому адресу и номеру порта. Ниже
представлены самые часто используемые методы класса Socket:
InetAddress getInetAddress() – возвращает объект содержащий данные о сокете. В
случае если сокет не подключен – null
int getPort() – возвращает порт по которому происходит соединение с сервером
int getLocalPort() – возвращает порт к которому привязан сокет. Дело в том, что
«общаться» клиент и сервер могут по одному порту, а порты, к которым они привязаны –
могут быть совершенно другие
boolean isConnected() – возвращает true, если соединение установлено
void connect(SocketAddress адрес) – указывает новое соединение
boolean isClosed() – возвращает true, если сокет закрыт
boolean isBound() - возвращает true, если сокет действительно привязан к адресу
Класс Socket реализует интерфейс AutoCloseable, поэтому его можно использовать в
конструкции try-with-resources. Тем не менее закрыть сокет также можно классическим
образом, с помощью close().

• Допустим мы объявили, в виде класса Socket, на стороне клиента запрос на соединение.


Как сервер разгадает наше желание? Для это сервер имеет такой класс как ServerSocket,
и метод accept() в нём. При объявлении ServerSocket не нужно указывать адрес
соединения, потому что общение происходит на машине сервера. Только при
многоканальном хосте нужно указать к какому ip привязан сокет сервера.
• Tак как предоставлять программе больше ресурсов чем ей необходимо - и затратное и
не разумное дело, поэтому в конструкторе ServerSocket вам предлагают объявить
максимум соединений, принимаемых сервером при работе. Если оно не указано, то
умолчанию это число будет считаться равным 50. Да, по идее можно предположить,
что ServerSocket это такой же сокет, только для сервера. Но он играет совершенно
иную роль нежели класс Socket. Он нужен только на этапе создания соединения.
Создав объект типа ServerSocket необходимо выяснить, что с сервером кто-то хочет
соединиться. Тут подключается метод accept(). Искомый ждёт пока кто-либо не захочет
подсоединится к нему, и когда это происходит возвращает объект типа Socket, то есть
воссозданный клиентский сокет. И вот когда сокет клиента создан на стороне сервера,
можно начинать двухстороннее общение. Создать объект типа Socket на стороне
клиента и воссоздать его с помощью ServerSocket на стороне сервера – вот
необходимый минимум для соединения.
• Вопрос 3
Протокол UDP. Классы DatagramSocket и DatagramPacket
Ответ:
UDP (User Datagram Protocol) не устанавливает виртуального соединения и не гарантирует
доставку данных. Отправитель просто посылает пакеты по указанному адресу; если отосланная
информация была повреждена или вообще не дошла, отправитель об этом даже не узнает.
Однако достоинством UDP является высокая скорость передачи данных. Данный протокол
часто используется при трансляции аудио- и видеосигналов, где потеря небольшого количества
данных не может привести к серьезным искажениям всей информации.
По протоколу UDP данные передаются пакетами. Пакетом в этом случае UDP является объект
класса DatagramPacket. Этот класс содержит в себе передаваемые данные, представленные в
виде массива байт. Класс DatagramPacket обеспечивает доставку пакетов без установления
соединения Обслуживание. Каждое сообщение маршрутизируется с одной машины на другую
на основе информации, содержащейся в этом пакете. Многочисленные пакеты отправляются
из одного хост для другого, который может идти по другому маршруту и может прибыть в
случайном порядке.

Метод Описание
публичная пустая отправка (пакет Он используется для отправки пакета UDP
DatagramPacket)броски IOException на указанный порт рядом с пакетом.
публичный синхронизированный void Метод receive ожидает получения пакета из
receive(DatagramPacket пакет) выбрасывает порта, указанного пакетом и возвращает
исключение IOException результат.
публичная пустота закрыть() Он закрывает гнездо датаграммы.
public int getLocalPort() Он возвращает номер порта на локальном
хосте, к которому этот сокет привязан.
публичные синхронизированные пустые Он установил SO_TIMEOUT с указанным
setSOTimeout (int)броски SocketException временем ожидания в миллисекунды.
public synchronized int getSOTimeout () Он извлекает настройку для SO_TIMEOUT.
броски SocketException Он возвращает 0, это означает, что опция
отключена(т. е. тайм-аут бесконечности).
public getLocalAddress() Он получает локальный адрес, на который
находится сокет связанный.
public InetAddress getInetAddress() Если сокет подключен, то адрес является
возвращенный. В противном случае
возвращается значение null.
public int getPort() Он возвращает номер порта, к которому
подключен сокет соединенный.
public boolean isConnected() Он возвращает true, если сокет подключен к
a сервер.
public boolean isBound() Он возвращает true, если сокет привязан к
адресу.

Класс DatagramSocket может выступать в роли клиента и сервера, то есть он способен


получать и отправлять пакеты. Отправить пакет можно с помощью метода
send(DatagramPacket pac), для получения пакета используется метод receive(DatagramPacket
pac).
Этот класс представляет собой сокет для отправки и приема пакета дейтаграмм. Гнездо
датаграммы обеспечивает отправляющие или принимающие стороны для пакета без
установления соединения. Каждый пакет отправляется или получается на розетку. Многие
пакеты отправляются с одной машины на другую и возможно маршрутизируется по-разному в
любом порядке. DatagramSocket определяет множество методов.

Конструктор Описание
DatagramSocket () выбрасывает исключение Он создает экземпляр сокета datagram и
SocketException связывает его с любой неиспользуемый
номер порта на локальном компьютере.
DatagramSocket (int port)выбрасывает Он создает сокет дейтаграммы и связывает
исключение SocketException его с указанный порт на локальном хост-
компьютере.
DatagramSocket (int port, Он создает сокет дейтаграммы, привязанный
int,InetAddress)броски SockeEexception к указанному порт и InetAddress.
DatagramSocket (адрес SocketAddress)броски Он строит DatagramSocket, привязанный к
SocketException указанному SocketAddress.

• Вопрос 4
Отличия блокирующего и неблокирующего ввода-ввывода, их приемущества и
недостатки. Работа с сетевыми каналами
Ответ:

IO NIO
Потокоориентированный Буфер-ориентированный
Блокирующий (синхронный) ввод/вывод Неблокирующий (асинхронный)
ввод/вывод
Селекторы

Блокирующий и неблокирующий ввод/вывод

Потоки ввода/вывода (streams) в Java IO являются блокирующими. Это значит, что когда в
потоке выполнения (tread) вызывается read() или write() метод любого класса из пакета
java.io.*, происходит блокировка до тех пор, пока данные не будут считаны или записаны.
Поток выполнения в данный момент не может делать ничего другого.

Неблокирующий режим Java NIO позволяет запрашивать считанные данные из канала


(channel) и получать только то, что доступно на данный момент, или вообще ничего, если
доступных данных пока нет. Вместо того, чтобы оставаться заблокированным пока данные не
станут доступными для считывания, поток выполнения может заняться чем-то другим.
Тоже самое справедливо и для неблокирующего вывода. Поток выполнения может запросить
запись в канал некоторых данных, но не дожидаться при этом пока они не будут полностью
записаны.

Таким образом неблокирующий режим Java NIO позволяет использовать один поток
выполнения для решения нескольких задач вместо пустого прожигания времени на ожидание в
заблокированном состояний. Наиболее частой практикой является использование
сэкономленного времени работы потока выполнения на обслуживание операций ввода/вывода
в другом или других каналах.

• С помощью каналов вы можете выполнить асинхронную операцию закрытия на канале


и нить, блокированная на этом канале примет AsynchronousCloseException.

Как следует из названия, канал используется как среднее значение потока данных от
одного конца к другому. Здесь в канале Java NIO действуют одинаково между буфером
и объектом на другом конце, другими словами, канал используется для чтения данных
в буфер, а также для записи данных из буфера.
В отличие от потоков, которые используются в традиционных каналах Java IO, они
являются двухсторонними, т.е. могут считывать и записывать. Канал Java NIO
поддерживает асинхронный поток данных как в режиме блокировки, так и в режиме
без блокировки.

• Вопрос 5
Классы SocketChannel и DatagramChannel
Ответ:

• Канал DatagramChannel был представлен, чтобы позволить разработчикам


создавать высокопроизводительные приложения для потоковой передачи
данных, которые отправляют и получают датаграммы с помощью протокола,
называемого UDP. DatagramChannel – канал дейтаграмм может считывать и
записывать данные по сети через UDP (протокол пользовательских дейтаграмм).
Объект DataGramchannel может быть создан с использованием заводских методов.
• В реальных приложениях желательно предоставить пользователям возможность прервать слишком
затянувшийся процесс установления соединения с помощью сокета. Однако если поток блокирован,
поскольку не получает ответа от сокета, вы не можете разблокировать его, вызвав метод interrupt().Для
прерывания операции с сокетом используется класс SocketChannel, предоставляемый пакетом java.nio.
SocketChannel – канал SocketChannel может считывать и записывать данные по сети
через TCP (протокол управления передачей). Он также использует фабричные методы
для создания нового объекта. С каналом не связываются потоки. Вместо этого он предоставляет
методы read() и write(), использующие объекты Buffer. Данные методы объявлены в интерфейсах
ReadableByteChannel и WriteableByteChannel.

Если вы не хотите работать с буферами, можете использовать для чтения из SocketChannel объект
Scanner.

• Вопрос 6
Передача данных по сети. Сериализация Объектов
Ответ:

Простейший сервер и клиент:


Этот пример покажет простейшее использование серверного и клиентского сокета. Все,
что делает сервер, это ожидает соединения, затем использует сокет, полученный при
соединении, для создания InputStream'а и OutputStream'а. Они конвертируются в Reader
и Writer, которые оборачиваются в BufferedReader и PrintWriter. После этого все, что
будет прочитано из BufferedReader'а будет переправлено в PrintWriter, пока не будет
получена строка "END", означающая, что пришло время закрыть соединение.
• Клиент создает соединение с сервером, затем создает OutputStream и создает
некоторую обертку, как и в сервере. Строки текста посылаются через
полученный PrintWriter. Клиент также создает InputStream (опять таки, с
соответствующей конвертацией и оберткой), чтобы слушать, что говорит сервер
(который, в данном случае, просто отсылает слова назад).
• И сервер, и клиент используют одинаковый номер порта, а клиент использует
адрес локальной заглушки для соединения с сервером на этой же самой машине,
так что вы не можете провести тест по сети. (Для некоторых конфигураций вам
может понадобиться сетевое соединения для работы программы даже, если вы не
используете сетевую коммуникацию.)
* Класс Socket реализует идею сокета. Через его каналы ввода/вывода будут общаться
клиент с сервером:
Объявляется этот класс на стороне клиента, а сервер воссоздаёт его, получая сигнал на
подключение. Так происходит общение в сети. Для начала вот возможные конструкторы
класса Socket:

«имя_хоста» — подразумевает под собой определённый узел сети, ip-адрес. Если класс
сокета не смог преобразовать его в реальный, существующий, адрес, то сгенерируется
исключение UnknownHostException.
Порт — есть порт. Если в качестве номера порта будет указан 0, то система сама выделит
свободный порт. Также при потере соединения может произойти исключение
IOException.
Следует отметить тип адреса во втором конструкторе — InetAddress. Он приходит на
помощь, например, когда нужно указать в качестве адреса доменное имя. Так же когда
под доменом подразумевается несколько ip-адресов, то с помощью InetAddress можно
получить их массив. Тем не менее с ip он работает тоже. Так же можно получить имя
хоста, массив байт составляющих ip адрес и т.д.
Класс Socket реализует интерфейс AutoCloseable, поэтому его можно использовать в
конструкции try-with-resources. Тем не менее закрыть сокет также можно классическим
образом, с помощью close().
• Сериализация объекта представляет процесс перевода какой-либо структуры
данных в последовательность битов. Обратной к операции сериализации
является операция десериализации, т.е. восстановление начального состояния
структуры данных из битовой последовательности. Существует два способа
сериализации объекта: стандартная сериализация java.io.Serializable и
«расширенная» сериализация java.io.Externalizable.
• Дополнительным бонусом ко всему является сохранение
кроссплатформенности. Не важно какая у вас операционная система,
сериализация переводит объект в поток байтов, который может быть
восстановлен на любой ОС. Если вам необходимо передать объект по сети, вы
можете сериализовать объект, сохранить его в файл и передать по сети
получателю. Он сможет восстановить полученный объект. Так же сериализация
позволяет осуществлять удаленный вызов методов (Java RMI), которые
находятся на разных машинах с, возможно, разными операционными
системами, и работать с ними так, словно они находятся на машине
вызывающего java-процесса.

• Вопрос 7
Интерфейс Serializable. Объектный граф, сериализация и десериализация полей и
методов
Ответ:

Интерфейс java.io.Serializable
При использовании Serializable применяется стандартный алгоритм сериализации,
который с помощью рефлексии (Reflection API) выполняет
• запись в поток метаданных о классе, ассоциированном с объектом (имя класса,
идентификатор SerialVersionUID, идентификаторы полей класса),
• рекурсивную запись в поток описания суперклассов до класса java.lang.Object
(не включительно),
• запись примитивных значений полей сериализуемого экземпляра, начиная с
полей самого верхнего суперкласса,
• рекурсивную запись объектов, которые являются полями сериализуемого
объекта.
При этом ранее сериализованные объекты повторно не сериализуются, что позволяет
алгоритму корректно работать с циклическими ссылками.
Для выполнения десериализации под объект выделяется память, после чего его поля
заполняются значениями из потока. Конструктор объекта при этом не вызывается.
Однако при десериализации будет вызван конструктор без параметров родительского
несериализуемого класса, а его отсутствие повлечёт ошибку десериализации.
• Реализовать механизм сериализации довольно просто. Необходимо, чтобы ваш
класс реализовывал интерфейс Serializable. Это интерфейс — идентификатор,
который не имеет методов, но он указывает jvm, что объекты этого класса
могут быть сериализованы. Так как механизм сериализации связан с базовой
системой ввода/вывода и переводит объект в поток байтов, для его
выполнения необходимо создать выходной поток OutputStream, упаковать его в
ObjectOutputStream и вызвать метод writeObject(). Для восстановления объекта
нужно упаковать InputStream в ObjectInputStream и вызвать метод
readObject().
В процессе сериализации вместе с сериализуемым объектом сохраняется его
граф объектов. Т.е. все связанные с этим объектом, объекты других классов
так же будут сериализованы вместе с ним.
Идентификатор версии есть у любого класса, который имплементирует интерфейс
Serializable. Он вычисляется по содержимому класса — полям, порядку объявления,
методам. И если мы поменяем в нашем классе тип поля и/или количество полей,
идентификатор версии моментально изменится. serialVersionUID тоже
записывается при сериализации класса. Когда мы пытаемся провести
десериализацию, то есть восстановить объект из набора байт, значение
serialVersionUID сравнивается со значением serialVersionUID класса в нашей
программе. Если значения не совпадают, будет выброшено исключение
java.io.InvalidClassException. Мы увидим пример этого ниже. Чтобы избежать таких
ситуаций, мы просто вручную задаем для нашего класса этот идентификатор
версии. В нашем случае он будет равен просто 1 (можешь подставить любое другое
понравившееся число).
Интерфейс java.io.Externalizable
При реализации интерфейса Externalizable вызывается пользовательская логика
сериализации. Способ сериализации и десериализации описывается в методах
writeExternal и readExternal. Во время десериализации вызывается конструктор без
параметров, а потом уже на созданном объекте вызывается метод readExternal.
Расширенный алгоритм сериализации может быть использован, если данные содержат
«конфиденциальную» информацию. В этом случае имеет смысл шифровать
сериализуемые данные и дешифровать их при десерилизации, что требует реализацию
собственного алгоритма.
• Для интерфейса Externalizable будет вызван метод получения конструктора
getExternalizableConstructor(), внутри которого мы через Reflection попробуем
получить конструктор по умолчанию класса, для которого мы восстанавливаем
объект. Если нам не удается его найти, или он не public, то мы получаем
исключение. Обойти эту ситуацию можно следующим образом: не создавать
явно никакого конструктора в классе и заполнять поля с помощью сеттеров и
получать значение геттерами. Тогда при компиляции класса будет создан
конструктор по умолчанию, который будет доступен для
getExternalizableConstructor(). Для Serializable метод getSerializableConstructor()
получает конструктор класса Object и от него ищет нужный класс, если не
найдет, то получим исключение ClassNotFoundException. Выходит, что
ключевое различие между Serializable и Externalizable в том, что первому не
нужен конструктор для создания восстановления объекта. Он просто полностью
восстановится из байтов. Для второго при восстановлении сначала будет создан
объект с помощью конструктора в точке объявления, а затем в него будут
записаны значения его полей из байтов, полученных при сериализации. Лично
мне больше нравится первый способ, он гораздо проще. Причем, даже если нам
нужно все таки задать поведение сериализации, мы можем не использовать
Externalizable, а так же реализовать Serializable, добавив (не переопределив) в
него методы writeObject() и readObject(). Но для того, чтобы они «работали»
нужно точно соблюсти их сигнатуру.
• Граф объектов — это набор объектов, которые будут сериализованы
автоматически, если объект, содержащий ссылку на них, сериализован.
Другими словами, мы можем сказать, что, когда мы сериализуем какой-либо
объект и если он содержит какую-либо другую ссылку на объект, JVM
сериализует объект, а также ссылки на его объекты.
Модификатор поля transient
Использование при описании поля класса модификатора transient позволяет
исключить указанное поле из сериализации. Это бывает полезно для секретных
(пароль) или не особо важных данных. Если, например, при описании объекта Person
включить следующее поле address
transient public String address;
то в результате сериализации и десериализации адрес объекта принимает значение по
умолчанию или будет null.
Модификатор transient действует только на стандартный механизм сериализации
Serializable. При использовании Externalizable никто не мешает сериализовать это поле,
равно как и использовать его для определения других полей.
Модификатор поля static
При стандартной сериализации поля, имеющие модификатор static, не сериализуются.
Соответственно, после десериализации это поле значения не меняет. При
использовании реализации Externalizable сериализовать и десериализовать статическое
поле можно, но не рекомендуется этого делать, т.к. это может сопровождаться
трудноуловимыми ошибками.
Модификатор поля final
Поля с модификатором final сериализуются как и обычные. За одним исключением –
их невозможно десериализовать при использовании Externalizable, поскольку final-
поля должны быть инициализированы в конструкторе, а после этого в readExternal
изменить значение этого поля будет невозможно. Соответственно, если необходимо
сериализовать объект с final-полем неоходимо использовать только стандартную
сериализацию.
Класс ObjectOutputStream
Для сериализации объектов в поток используется класс ObjectOutputStream. Он
записывает данные в поток.
Для создания объекта ObjectOutputStream в конструктор передается поток, в который
производится запись:
1 ObjectOutputStream(OutputStream out)
Для записи данных ObjectOutputStream использует ряд методов, среди которых можно
выделить следующие:
Десериализация. Класс ObjectInputStream
Класс ObjectInputStream отвечает за обратный процесс - чтение ранее сериализованных
данных из потока. В конструкторе он принимает ссылку на поток ввода:
1 ObjectInputStream(InputStream in)
Функционал ObjectInputStream сосредоточен в методах, предназначенных для чтения
различных типов данных. Рассмотрим основные методы этого класса:
• Вопрос 8
Java Stream API. Создание конвейеров. Промежуточные и терминальные операции.
Ответ:

Stream API — это новый способ работать со структурами данных в функциональном


стиле. Stream (поток) API (описание способов, которыми одна компьютерная
программа может взаимодействовать с другой программой) — это по своей сути поток
данных. Сам термин "поток" довольно размыт в программировании в целом и в Java в
частности.
С Stream API позволило программистам писать существенно короче то, что раньше
занимало много строк кода, а именно — упростить работу с наборами данных, в
частности, упростить операции фильтрации, сортировки и другие манипуляции с
данными. Если у вас промежуточных операций нет, часто можно и нужно обойтись без
стрима, иначе код будет сложнее чем без потока

Как говорилось выше, Stream API позволяет сократить количество строк кода.
Пример без потока:

Возможные способы создания Stream:


• Пустой стрим: Stream.empty()
• Стрим из List: list.stream()
• Стрим из Map: map.entrySet().stream()
• Стрим из массива: Arrays.stream(array)
• Стрим из указанных элементов: Stream.of("1", "2", "3")
Далее, есть такое понятие как операторы (по сути методы класса Stream) Операторы
можно разделить на две группы:
• Промежуточные (“intermediate”, ещё называют “lazy”) — обрабатывают
поступающие элементы и возвращают стрим. Промежуточных операторов в
цепочке обработки элементов может быть много.
• Терминальные (“terminal”, ещё называют “eager”) — обрабатывают элементы и
завершают работу стрима, так что терминальный оператор в цепочке может
быть только один.

Что здесь происходит:


• 1 — создаём список list;
• 2-11 — заполняем его тестовыми данными;
• 12 — создаём обьект Stream;
• 13 — метод filter (фильтр) — промежуточный оператор, x приравнивается к
одному элементу коллекции для перебора (как при for each) и после -> мы
указываем как фильтруется наша коллекция и так как это промежуточный
оператор, отфильтрованная коллекция идёт дальше в метод forEach который в
свою очередь является терминальным (конечным) аналогом перебора for each
(Выражение System.out::println сокращенно от: x-> System.out.println(x)),
которое в свою очередь проходит по всем элементам переданной ему коллекции
и выводит её)
Обработка не начнётся до тех пор, пока не будет вызван терминальный оператор.
list.stream().filter(s -> s > 5) (не возьмёт ни единого элемента из списка);
Экземпляр, стрима нельзя использовать более одного раза =(
Поэтому каждый раз новый:

промежуточных операторов вызванных на одном стриме может быть множество, в


то время терминальный оператор только один:
stream.filter(x-> x.toString().length() == 3).map(x -> x + " - the length of the letters is
three").forEach(x -> System.out.println(x));
промежуточные операторы:
• filter(Predicate predicate) фильтрует стрим, пропуская только те элементы, что
проходят по условию (Predicate встроенный функциональный интерфейс,
добавленный в Java SE 8 в пакет java.util.function. Проверяет значение на “true”
и “false”);
• map(Function mapper) даёт возможность создать функцию с помощью которой
мы будем изменять каждый элемент и пропускать его дальше (Функциональный
интерфейс Function<T,R> представляет функцию перехода от объекта типа T к
объекту типа R)
• flatMap(Function<T, Stream<R>> mapper) — как и в случае с map, служат для
преобразования в примитивный стрим.
[stream1,stream2,stream3,stream4] => stream:

В то время когда map преобразует в список потоков (точнее <Stream> потоков)


[stream1,stream2,stream3,stream4] =>Stream.of(stream1,stream2,stream3,stream4):

Ещё одно отличие в сравнении с map, можно преобразовать один элемент в ноль, один
или множество других.
Для того, чтобы один элемент преобразовать в ноль элементов, нужно вернуть null,
либо пустой стрим. Чтобы преобразовать в один элемент, нужно вернуть стрим из
одного элемента, например, через Stream.of(x). Для возвращения нескольких
элементов, можно любыми способами создать стрим с этими элементами.
Тот же метод flatMap, но для Double, Integer и Long:
• flatMapToDouble(Function mapper)
• flatMapToInt(Function mapper)
• flatMapToLong(Function mapper)
IntStream.range(0,x) – выдаёт на поток элементов с 0 (включительно) по x (не
включительно);
limit(long maxSize) – ограничивает стрим по количеству элементов
skip(long n) – пропускаем n элементов
sorted()
sorted(Comparator comparator) – сортирует стрим (сортировка как у TreeMap)
distinct() — проверяет стрим на уникальность элементов(убирает повторы элементов);
dropWhile(Predicate predicate) — пропускает элементы которые удовлетворяют
условию (появился в 9 java, Функциональный интерфейс Predicate<T> проверяет
соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true.
В качестве параметра лямбда-выражение принимает объект типа
Терминальные операторы:
forEach(Consumer action) – аналог for each (Consumer<T> выполняет некоторое
действие над объектом типа T, при этом ничего не возвращая);
count() – возвращает количество елементов стрима:
System.out.println(stream.count());
collect(Collector collector) – метод собирает все элементы в список, множество или
другую коллекцию, сгруппировывает элементы по какому-нибудь критерию,
объединяет всё в строку и т.д.:
List<String> list = Stream.of(“One”, “Two”, “Three”).collect(Collectors.toList());
collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) — тот же, что и
collect(collector), только параметры разбиты для удобства (supplier поставляет новые
объекты (контейнеры), например new ArrayList(), accumulator добавляет элемент в
контейнер, combiner объединяет части стрима воедино);
reduce(T identity, BinaryOperator accumulator) — преобразовывает все элементы стрима
в один объект(посчитать сумму всех элементов, либо найти минимальный элемент),
cперва берётся объект identity и первый элемент стрима, применяется функция
accumulator и identity становится её результатом. Затем всё продолжается для
остальных элементов.
reduce(BinaryOperator accumulator) — такой же метод как и выше но отсутствует
начальный identity, им служит первый элемент стрима
Optional min(Comparator comparator)
Optional max(Comparator comparator) ищет минимальный/максимальный элемент,
основываясь на переданном компараторе;
findFirst() – вытаскивает первый элемент стрима
allMatch(Predicate predicate) — возвращает true, если все элементы стрима
удовлетворяют условию. Если встречается какой-либо элемент, для которого результат
вызова функции-предиката будет false, то оператор перестаёт просматривать элементы
и возвращает false
anyMatch(Predicate predicate) — вернет true, если хотя бы один элемент стрима
удовлетворяет условию predicate
некоторые методы Collectors:
toList() — собирает элементы в List
toSet() — cобирает элементы в множество
counting() — Подсчитывает количество элементов
joining()
joining(CharSequence delimiter)
joining(CharSequence delimiter, CharSequence prefix, CharSequence suffix) — cобирает
элементы в одну строку. Дополнительно можно указать разделитель, а также префикс
и суффикс для всей последовательности
summingInt(ToIntFunction mapper)
summingLong(ToLongFunction mapper)
summingDouble(ToDoubleFunction mapper) — коллектор, который преобразовывает
объекты в int/long/double и подсчитывает сумму.

• Вопрос 9
Шаблоны проектирования. Decorator, Iterator, Factory method, Command, Flyweight,
Interpreter, Singleton, Strategy, Adapter, Facade, Proxy.
Ответ:
Паттерны бывают разные, т.к. решают разные проблемы. Обычно выделяют следующие
категории:

• Порождающие
Эти паттерны решают проблемы обеспечения гибкости создания объектов
• Структурные
Эти паттерны решают проблемы эффективного построения связей между объектами

• Поведенческие
Эти паттерны решают проблемы эффективного взаимодействия между объектами