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

1. Объектно-ориентированное программирование.

Основные понятия

В ООП такое описание, чертеж, схема или шаблон называется классом, из


которого при выполнении программы создается объект.

1)Сейчас мы применили принцип в ООП — выделение наиболее важных


характеристик и информации об объекте. Этот принцип называется абстракцией.

Теперь с помощью абстракции мы можем выделить в этой иерархии объектов общую


информацию: общий абстрактный тип объектов — телефон, общую характеристику
телефона — год его создания, и общий интерфейс — все телефоны способны
принимать и посылать вызовы.

2) Для исключения подобного вмешательства в конструкцию и работу объекта в ООП


используют принцип инкапсуляции – еще один базовый принцип ООП, при котором
атрибуты и поведение объекта объединяются в одном классе, внутренняя
реализация объекта скрывается от пользователя, а для работы с объектом
предоставляется открытый интерфейс.

Private — наиболее строгий модификатор доступа. Он ограничивает видимость


данных и методов пределами одного класса.

Поля и методы, обозначенные модификатором доступа protected, будут видны:

 в пределах всех классов, находящихся в том же пакете, что и наш;


 в пределах всех классов-наследников нашего класса.

Запомнить, что он делает, легко. По сути, default = protected-наследование :)

Части кода, помеченные модификатором public, предназначаются для конечного


пользователя.

3) В программировании наследование заключается в использовании уже


существующих классов для описания новых.
4) Принцип в ООП, когда программа может использовать объекты с одинаковым
интерфейсом без информации о внутреннем устройстве объекта, называется
полиморфизмом.

В итоге, чтобы стиль вашей программы соответствовал концепции ООП и


принципам ООП java следуйте следующим советам:

 выделяйте главные характеристики объекта;


 выделяйте общие свойства и поведение и используйте наследование при
создании объектов;
 используйте абстрактные типы для описания объектов;
 старайтесь всегда скрывать методы и поля, относящиеся к внутренней
реализации класса.

2)Состав пакета java.lang. Класс Object и его методы.

Пакет java.lang является наиболее важным из всех пакетов, входящих в Java API, поскольку включает
классы, составляющие основу для всех других классов. Каждый класс в Java неявным образом
импортирует все классы данного пакета, поэтому данный пакет можно не импортировать.

В этой библиотеке включены

 Object - базовый класс


 Class - объект описания класса (поля, методы)
 Math - набор статических методов для математических вычислений
 Классы-обёртки для примитивных типов :
o Integer
o Character
o Boolean
o Void
 Классы для работы с текстом
o String
o StringBuffer
 Системные классы :
o ClassLoader
o SecurityManager
o System
o Runtime
o Process
 Потоки исполнения :
o Runnable
o Thread
o ThreadGroup
 Классы работы с исключительными ситуациями :
o Error
o Exception
o RuntimeException

Object
Основу пакета составляет класс Object, который является корневым в иерархии классов. Если при
описании класса родитель не указывается, то им считается класс Object. Все объекты, включая
массивы, наследуются от этого класса.

Класс Object включает методы, которые наследуются остальными классами Java.


Метод Описание
Object clone() Функция создания нового объекта, копии существующего
boolean equals(Object
Функция определения равенства текущего объекта другому
object)
Процедура завершения работы объекта; вызывается перед удалением
void finalize()
неиспользуемого объекта
Class<?> getClass() Функция определения класса объекта во время выполнения
int hashCode() Функция получения хэш-кода объекта
Процедура возобновления выполнения потока, который ожидает вызывающего
void notify()
объекта
Процедура возобновления выполнения всех потоков, которые ожидают
void notifyAll()
вызывающего объекта
String toString() Функция возвращает строку описания объекта
Остановка текущего потока до тех пор, пока другой поток не вызовет notify()
void wait()
или notifyAll метод для этого объекта
Остановка текущего потока на время или до тех пор, пока другой поток не
void wait(long ms)
вызовет notify() или notifyAll метод для этого объекта
void wait(long ms, int Остановка текущего потока на время или до тех пор, пока другой поток не
nano) вызовет notify() или notifyAll метод для этого объекта

Методы notify(), notifyAll(), wait() используются для поддержки многопоточности и являются


"финальными" (final), т.е. их нельзя переопределять в классах-наследниках. Также нельзя
переопределять и метод getClass.

3. Класс Number. Классы-оболочки. Автоупаковка и автораспаковка.

Абстрактный класс Number является суперклассом для классов Byte, Double, Float, Integer, Long и Short.


Подклассы числа должны обеспечить методы, чтобы преобразовывать представленную числовую величину
в byte, double, float, int, long и short.

Конструктор класса:
Number()

Методы класса:

 byte byteValue() - Возвращает величину определенного числа как byte


 abstract double doubleValue() - Возвращает величину определенного числа как double
 abstract float floatValue() - Возвращает величину определенного числа как float
 abstract int intValue() - Возвращает величину определенного числа как int
 abstract long longValue() - Возвращает величину определенного числа как long
 short shortValue() - Возвращает величину определенного числа как short

Обертка — это специальный класс, который хранит внутри себя значение


примитива. Но поскольку это именно класс, он может создавать свои экземпляры.
Они будут хранить внутри нужные значения примитивов, при этом будут являться
настоящими объектами. Названия классов-оберток очень похожи на названия
соответствующих примитивов, или полностью с ними совпадают. Поэтому запомнить
их будет очень легко.

Объекты классов оберток создаются так же, как и любые другие:


public static void main(String[] args) {

Integer i = new Integer(682);


Double d = new Double(2.33);

Boolean b = new Boolean(false);


}
Классы-обертки позволяют нивелировать недостатки, которые есть у примитивных
типов. Самый очевидный из них — примитивы не имеют методов. Например, у них
нет метода toString(), поэтому ты не сможешь, например, преобразовать число int
в строку. А вот с классом-оберткой Integer — запросто.

Но при этом переменной класса-обертки можно присваивать значение примитивного


типа. Этот процесс называется автоупаковкой (autoboxing). Точно так же
переменной примитивного типа можно присваивать объект класса-обертки. Этот
процесс называется автораспаковкой (autounboxing).

Примитивы:

 имеют преимущество в производительности

Обертки:

 Позволяют не нарушать принцип “все является объектом”, благодаря чему


числа, символы и булевы значения true/false не выпадают из этой концепции
 Расширяют возможности работы с этими значениями, предоставляя удобные
методы и поля
 Необходимы, когда какой-то метод может работать исключительно с объектами

4. Математические функции. Класс Math. Пакет java.math

Класс Math содержит методы, связанные с геометрией и тригонометрией и прочей математики.


Методы реализованы как static, поэтому можно сразу вызывать через Math.methodName() без
создания экземпляра класса.

В классе определены две константы типа double: E и PI.

Популярные методы для тригонометрических функций принимают параметр типа double,


выражающий угол в радианах.

 sin(double d)
 cos(double d)
 tan(double d)
 asin(double d)
 acos(double d)
 atan(double d)
 atan2(double y, double x)

Существуют также гиперболические функции: sinh(), cosh(), tanh().

Экспоненциальные функции: cbrt(), exp(), expm1(), log(), log10(), log1p(), pow(), scalb(), sqrt().

Из них хорошо знакомы возведение в степень - pow(2.0, 3.0) вернёт 8.0.


Также популярен метод для извлечения квадратного корня - sqrt(4.0). Если аргумент меньше нуля,
то возвращается NaN. Похожий метод cbrt() извлекает кубический корень. Если аргумент
отрицательный, то и возвращаемое значение будет отрицательным: -27.0-> -3.0.

Функции округления:

 abs() - возвращает абсолютное значение аргумента


 ceil() - возвращает наименьшее целое число, которое больше аргумента
 floor() - возвращает наибольшее целое число, которое меньше или равно аргументу
 max() - возвращает большее из двух чисел
 min() - возвращает меньшее из двух чисел
 nextAfter() - возвращает следующее значение после аргумента в заданном направлении
 nextUp() - возвращает следующее значение в положительном направлении
 rint() - возвращает ближайшее целое к аргументу
 round() - возвращает аргумент, округлённый вверх до ближайшего числа
 ulp() - возвращает дистанцию между значением и ближайшим большим значением

Другие методы

 copySign() - возвращает аргумент с тем же знаком, что у второго аргумента


 getExponent() - возвращает экспоненту
 IEEEremainder() - возвращает остаток от деления
 hypot() - возвращает длину гипотенузы
 random() - возвращает случайное число между 0 и 1 (единица в диапазон не входит)
 signum() - возвращает знак значения
 toDegrees() - преобразует радианы в градусы
 toRadians() - преобразует градусы в радианы

Обеспечивает классы для того, чтобы они выполнили целочисленную арифметику произвольной
точности (BigInteger) и десятичная система исчисления произвольной точности
(BigDecimal). BigInteger походит на примитивные целочисленные типы за исключением того,
что это обеспечивает произвольную точность, следовательно операции на BigIntegers не
переполняют или теряют точность. В дополнение к стандартным арифметическим
операциям, BigInteger обеспечивает арифметику в остаточных классах, вычисление GCD,
тестирование простоты чисел, главную генерацию, побитовую обработку, и несколько других
разных операций. BigDecimal обеспечивает произвольную точность подписанные десятичные
числа, подходящие для вычислений валюты и т.п.. BigDecimal дает пользовательский полный
контроль над округлением поведения, разрешая пользователю выбрать из исчерпывающего
набора восьми округляющихся режимов.

5. Работа со строками. Классы String. Классы StringBuilder и StringBuffer.

Строка представляет собой последовательность символов. Для работы со строками в Java определен класс
String, который предоставляет ряд методов для манипуляции строками. Физически объект String представляет
собой ссылку на область в памяти, в которой размещены символы.

Для создания новой строки мы можем использовать один из конструкторов класса String, либо напрямую
присвоить строку в двойных кавычках

Основные методы класса String

Основные операции со строками раскрывается через методы класса String, среди которых можно выделить
следующие:
 concat(): объединяет строки

 valueOf(): преобразует объект в строковый вид

 join(): соединяет строки с учетом разделителя

 сompare(): сравнивает две строки

 charAt(): возвращает символ строки по индексу

 getChars(): возвращает группу символов

 equals(): сравнивает строки с учетом регистра

 equalsIgnoreCase(): сравнивает строки без учета регистра

 regionMatches(): сравнивает подстроки в строках

 indexOf(): находит индекс первого вхождения подстроки в строку

 lastIndexOf(): находит индекс последнего вхождения подстроки в строку

 startsWith(): определяет, начинается ли строка с подстроки

 endsWith(): определяет, заканчивается ли строка на определенную подстроку

 replace(): заменяет в строке одну подстроку на другую

 trim(): удаляет начальные и конечные пробелы

 substring(): возвращает подстроку, начиная с определенного индекса до конца или до определенного


индекса

 toLowerCase(): переводит все символы строки в нижний регистр

 toUpperCase(): переводит все символы строки в верхний регистр

Объекты String являются неизменяемыми, поэтому все операции, которые изменяют строки, фактически
приводят к созданию новой строки, что сказывается на производительности приложения. Для решения этой
проблемы, чтобы работа со строками проходила с меньшими издержками в Java были добавлены
классы StringBuffer и StringBuilder. По сути они напоминает расширяемую строку, которую можно изменять
без ущерба для производительности.

Эти классы похожи, практически двойники, они имеют одинаковые конструкторы, одни и те же методы,
которые одинаково используются. Единственное их различие состоит в том, что
класс StringBuffer синхронизированный и потокобезопасный. То есть класс StringBuffer удобнее
использовать в многопоточных приложениях, где объект данного класса может меняться в различных
потоках. Если же речь о многопоточных приложениях не идет, то лучше использовать класс StringBuilder,
который не потокобезопасный, но при этом работает быстрее, чем StringBuffer в однопоточных
приложениях.

StringBuffer определяет четыре конструктора:

1 StringBuffer()
2 StringBuffer(int capacity)
StringBuffer(String str)
3
4 StringBuffer(CharSequence chars)

Аналогичные конструкторы определяет StringBuilder:

1 StringBuilder()
2 StringBuilder(int capacity)
3 StringBuilder(String str)
4 StringBuilder(CharSequence chars)

Метод charAt() возвращает символ в указанной позиции.


А setCharAt() изменяет символ в указанной позиции:

Метод append() присоединяет подстроку к строке

Метод insert() вставляет подстроку в указанную позицию

Reverse() используется для инвертирования строки

Метод delete() удаляет подстроку, используя указанные позиции.


Метод deleteCharAt() удаляет символ с указанной позиции
Replace() заменяет подстроку в указанной позиции другой

Классы StringBuilder и StringBuffer не переопределяют
методы equals() и hashCode(). Они используют реализацию класса Object:

6. Обработка исключительных ситуаций. Классы Error, Exception, RuntimeException.

Что такое исключение?

В мире программирования возникновение ошибок и непредвиденных ситуаций при


выполнении программы называют исключением. В программе исключения могут
возникать в результате неправильных действий пользователя, отсутствии необходимого
ресурса на диске, или потери соединения с сервером по сети. Причинами исключений при
выполнении программы также могут быть ошибки программирования или неправильное
использование API. В отличие от нашего мира, программа должна четко знать, как
поступать в такой ситуации. Для этого в Java предусмотрен механизм исключений.

Кратко о ключевых словах

Обработка исключений в Java основана на использовании в программе следующих


ключевых слов:

 try – определяет блок кода, в котором может произойти исключение;


 catch – определяет блок кода, в котором происходит обработка исключения;
 finally – определяет блок кода, который является необязательным, но при его
наличии выполняется в любом случае независимо от результатов выполнения
блока try.

Эти ключевые слова используются для создания в программном коде специальных


обрабатывающих конструкций: try{}catch, try{}catch{}finally, try{}finally{}.

 throw – используется для возбуждения исключения;


 throws – используется в сигнатуре методов для предупреждения, о том что метод
может выбросить исключение.

При возникновении ошибки в процессе выполнения программы исполняющая среда


JVM создает объект нужного типа из иерархии исключений Java – множества
возможных исключительных ситуаций, унаследованных от общего «предка» – класса
Throwable. Исключительные ситуации, возникающие в программе, можно
разделить на две группы:

1. Ситуации, при которых восстановление дальнейшей нормальной работы


программы невозможно
2. Восстановление возможно.

К первой группе относят ситуации, когда возникают исключения, унаследованные из класса Error.
Это ошибки, возникающие при выполнении программы в результате сбоя работы JVM,
переполнения памяти или сбоя системы. Обычно они свидетельствуют о серьезных проблемах,
устранить которые программными средствами невозможно. Такой вид исключений в Java
относится к неконтролируемым (unchecked) на стадии компиляции. К этой группе также относят
RuntimeException – исключения, наследники класса Exception, генерируемые JVM во время
выполнения программы. Часто причиной возникновения их являются ошибки программирования.
Эти исключения также являются неконтролируемыми (unchecked) на стадии компиляции, поэтому
написание кода по их обработке не является обязательным. Ко второй группе относят
исключительные ситуации, предвидимые еще на стадии написания программы, и для которых
должен быть написан код обработки. Такие исключения являются контролируемыми (checked).
Основная часть работы разработчика на Java при работе с исключениями – обработка таких
ситуаций.

7. Классы System и Runtime. Класс java.io.Console.

Класс Java System является одним из базовых и используется практически в любом проекте. Он


располагает множеством полезных методов для работы.
Класс java.lang.System является final, все поля и методы являются статическими (static),
поэтому мы не можем создать подкласс и переопределить его методы используя наследование.
Класс Java System не предоставляет каких-либо публичных конструкторов, поэтому мы не можем
создать экземпляр этого класса.
Пример работы с классом
Вызов метода из класса java.lang.System выглядит так: System.out.print()  — это один из самых
простых способов логгирования информации.

Arraycopy() - Класс System обеспечивает нативный метод для копирования данных из одного
массива в другой

Cодержит набор статических методов, применимых к среде, в которой выполняется приложение.


Многие из них присутствуют так же в классе :py:class::java.lang.Runtime

Экземпляр этого класса НЕ может быть получен.

Среди прочих полезных средств, предоставляемых этим классом, особо стоит отметить:

 потоки стандартных ввода и вывода


 поток для вывода ошибок
 доступ к внешне определенным свойствам
 возможность загрузки файлов и библиотек
 утилиту для быстрого копирования порций массивов
class  java.lang.System
runFinalizersOnExit(boolean value)
public static void

Выставляет, будет ли производиться вызов метода finalize() у всех объектов (у кого еще не
вызывался), когда выполнение программы будет окончено

currentTimeMillis()
public static native long

Возвращает текущее время. Это время представляется как количество миллисекунд,


прошедших с 1-го января 1970 года

getProperty(String key)
public static String

Возвращает значение свойства с названием key.

getProperties()
public static Properties

Возвращает объект :py:class::java.util.Properties, в котором содержатся значения всех


определенных системных свойств.

out
атрибут выходного потока

print(String str)
метод отправляет на выходной поток строку

println(String str)
метод отправляет на выходной поток строку, с символом новой строки

Runtime
объект позволяет взаимодействовать с окружением, в котором запущена Java программа

Каждому приложению Java сопоставляется экземпляр класса Runtime.

class  java.lang.Runtime

getRuntime()

Возвращает соответсвующий приложению Runtime

exit(int status)

public void

Осуществляет завершение программы с кодом завершения status (при использовании


этого метода особое внимание нужно уделить обработке исключений - выход будет
осуществлен моментально, и в конструкциях try-catch-finally управление в finally передано
не будет)

gc()

public native void

Сигнализирует сборщику мусора о необходимости запуска

freeMemory()

public native long

Возвращает количество свободной памяти. В некоторых случаях это количество может


быть увеличено, если вызвать у объекта Runtime метод gc()

totalMemory()

public native long

Возвращает суммарное количество памяти, выделенное Java машине. Это количество


может из изменяться даже в течении одного запуска, что зависит от реализации
платформы на которой запущена Java машина. Так-же, не стоит закладываться на объем
памяти, занимаемой одним определенным объектом - эта величина так же зависит от
реализации Java машины.

loadLibrary(String libname)

public void

Загружает библиотеку с указанным именем. Обычно загрузка библиотек производится


следующим образом: в классе, использующем native реализации методов, добавляется
статический инициализатор, например:
static {
System.loadLibrary("LibFile");
}

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

load(String filename)

public void

Подгружает файл с указанным названием в качестве библиотеки. В принципе, этот метод


работает так-же как и метод load(), только принимает в качестве параметра именно
название файла, а не библиотеки, тем самым позволяя загрузить любой файл с native
кодом.

runFinalization()

public void

Производит запуск выполнения методов finalize() у всех объектов, этого ожидающих

exec(String command)

public Process

В отдельном процессе запускает команду, представленную переданной строкой.


Возвращаемый объект :py:class::java.lang.Process может быть использован для управления
выполнением этого процесса.

Специально для работы с консолью в Java определен класс Console, который хранится в пакете java.io. Он
не получает консольный ввод-вывод сам по себе, а использует уже имеющиеся потоки System.in и
System.out. Но в то же время Console значительно упрощает ряд операций, связанных с консолью.

Для получения объекта консоли надо вызвать статический метод System.console():

1 Console console = System.console();

Основные методы класса Console:

 flush(): выводит на консоль все данные из буфера


 format(): выводит на консоль строку с использованием форматирования
 printf(): выводит на консоль строку с использованием форматирования (фактически то же самое,
что и предыдущий метод)
 String readLine(): считывает с консоли введенную пользователем строку
 char[] readPassword(): считывает с консоли введенную пользователем строку, при этом символы
строки не отображаются на консоли

8. Коллекции. Виды коллекций. Интерфейсы Set, List, Queue.

Для хранения наборов данных в Java предназначены массивы. Однако их не всегда удобно использовать,
прежде всего потому, что они имеют фиксированную длину. Эту проблему в Java решают коллекции. Однако
суть не только в гибких по размеру наборах объектов, но в и том, что классы коллекций реализуют
различные алгоритмы и структуры данных, например, такие как стек, очередь, дерево и ряд других.

Классы коллекций располагаются в пакете java.util, поэтому перед применением коллекций следует


подключить данный пакет.

Хотя в Java существует множество коллекций, но все они образуют стройную и логичную систему. Во-первых,
в основе всех коллекций лежит применение того или иного интерфейса, который определяет базовый
функционал. Среди этих интерфейсов можно выделить следующие:

 Collection: базовый интерфейс для всех коллекций и других интерфейсов коллекций

 Queue: наследует интерфейс Collection и представляет функционал для структур данных в виде
очереди

 Deque: наследует интерфейс Queue и представляет функционал для двунаправленных очередей

 List: наследует интерфейс Collection и представляет функциональность простых списков

 Set: также расширяет интерфейс Collection и используется для хранения множеств уникальных
объектов

 SortedSet: расширяет интерфейс Set для создания сортированных коллекций

 NavigableSet: расширяет интерфейс SortedSet для создания коллекций, в которых можно


осуществлять поиск по соответствию

 Map: предназначен для созданий структур данных в виде словаря, где каждый элемент имеет
определенный ключ и значение. В отличие от других интерфейсов коллекций не наследуется от
интерфейса Collection

9. Обход элементов коллекции. Интерфейс Iterator.

Одним из ключевых методов интерфейса Collection является метод Iterator<E> iterator(). Он


возвращает итератор - то есть объект, реализующий интерфейс Iterator.

Интерфейс Iterator имеет следующее определение:

1 public interface Iterator <E>{


2      
3     E next();
4     boolean hasNext();
5     void remove();
6 }

Реализация интерфейса предполагает, что с помощью вызова метода next() можно получить следующий


элемент. С помощью метода hasNext() можно узнать, есть ли следующий элемент, и не достигнут ли конец
коллекции. И если элементы еще имеются, то hasNext() вернет значение true.
Метод hasNext() следует вызывать перед методом next(), так как при достижении конца коллекции
метод next() выбрасывает исключение NoSuchElementException. И метод remove() удаляет
текущий элемент, который был получен последним вызовом next().
Интерфейс Iterator предоставляет ограниченный функционал. Гораздо больший набор методов предоставляет
другой итератор - интерфейс ListIterator. Данный итератор используется классами, реализующими
интерфейс List, то есть классами LinkedList, ArrayList и др.

Интерфейс ListIterator расширяет интерфейс Iterator и определяет ряд дополнительных методов:

 void add(E obj): вставляет объект obj перед элементом, который должен быть возвращен
следующим вызовом next()

 boolean hasNext(): возвращает true, если в коллекции имеется следующий элемент, иначе


возвращает false

 boolean hasPrevious(): возвращает true, если в коллекции имеется предыдущий элемент, иначе


возвращает false

 E next(): возвращает текущий элемент и переходит к следующему, если такого нет, то генерируется
исключение NoSuchElementException

 E previous(): возвращает текущий элемент и переходит к предыдущему, если такого нет, то


генерируется исключение NoSuchElementException

 int nextIndex(): возвращает индекс следующего элемента. Если такого нет, то возвращается размер
списка

 int previousIndex(): возвращает индекс предыдущего элемента. Если такого нет, то возвращается
число -1

 void remove(): удаляет текущий элемент из списка. Таким образом, этот метод должен быть вызван
после методов next() или previous(), иначе будет сгенерировано
исключение IlligalStateException

 void set(E obj): присваивает текущему элементу, выбранному вызовом


методов next() или previous(), ссылку на объект obj

10. Сортировка элементов коллекций. Интерфейсы Comparable и Comparator

В своей работе программисты часто сталкиваются с тем, что надо что-то сортировать -
например, покупая товары в интернет-магазине Вы можете сортировать их
по цене, популярности и т.д.
Но для того, чтобы что-то отсортировать, нам нужно сравнивать объекты по каким-то
правилам. Тут, казалось бы, все просто - мы можем сортировать числа, да и в сортировке
по алфавиту нет ничего сложного. Да, с такими данными все легко. Но как нам сравнить
два объекта класса Car? По цене, пробегу, лошадиным силам или дате выпуска? А может
по количеству владельцев?
Интерфейс Comparable
С английского "Comparable" переводится как "сравнимый". Имплементируя этот
интерфейс мы как бы говорим "Эй, теперь объекты этого класса можно сравнивать между
собой! И я знаю, как это сделать!" А до этого было нельзя 🙂
Так как выглядит интерфейс Comparable? Очень просто - в нем находится всего один
метод:
1 public interface Comparable<T> {
2     public int compareTo(T o);
3}
Как видите, если мы реализуем этот интерфейс нам придется определить только один
метод - compareTo(T o). С английского "compareTo" переводится как "сравнить
с". Именно этот метод буде использоваться во всяких сортировках.
Вы могли заметить, что метод compareTo(T o) возвращает int. Он возвращает:
  ноль, если два объекта равны;
 число >0, если первый объект (на котором вызывается метод)
больше, чем второй (который передается в качестве параметра);
 число <0, если первый объект меньше второго.

Итак, нестандартная сортировка. Допустим, мы все согласны что логичнее всего


сравнивать дома поплощади. Ну а если их нужно отсортировать, например, по
цене?
Для этой цели мы можем создать отдельный класс, который реализует
интерфейс Comparator.
Например, у нас уже есть класс House. Давайте создадим отдельный класс,
которые будут выполнять функцию сравнения - PriceComparator:
1
2
3
public class PriceComparator implements Comparator<House> {
4
 
5
    public int compare(House h1, House h2) {
6
        if (h1.price == h2.price) {
7
            return 0;
8
        }
9
        if (h1.price > h2.price) {
1
            return 1;
0
        }
1
        else {
1
            return -1;
1
        }
2
    }
1
}
3
1
4
 Обратите внимение: мы указываем тип объектов, которые хотим сравнивать
(House) в скобках после слова "Comparator".

11. Интерфейс Set, его варианты и реализации.


Интерфейс Set определяет множество (набор).
Он расширяет Collection и определяет поведение коллекций, не
допускающих дублирования элементов. Таким образом,
метод add() возвращает false, если делается попытка добавить
дублированный элемент в набор. 
Он не определяет никаких собственных дополнительных методов. 
Интерфейс Set заботится об уникальности хранимых объектов,
уникальность определятся реализацией метода equals().
 HashSet хранит элементы в произвольном порядке, но зато быстро ищет.
Подходит, если порядок Вам не важен, но важна скорость. Более того, для
оптимизации поиска, HashSet будет хранить элементы так, как ему удобно.
 LinkedHashSet будет хранить элементы в порядке добавления, но зато
работает медленнее.
 TreeSet хранит элементы отсортированными.

Операции с множествами
1. add() - добавляет элемент в множество
2. remove() - удаляет элемент из множества
3. contains() - определяет, есть ли элемент в множестве
4. size() - возвращает размер множества
5. clear() - удаляет все элементы из коллекции
6. isEmpty() - возвращает true если множество пустое, и false если там есть хотя бы 1 элемент

12. Интерфейс List и его реализации. Особенности класса Vector.

Интерфейс List из пакета java.utii, расширяющий интерфейс collection , описывает методы работы с


упорядоченными коллекциями. Иногда их называют последовательностями (sequence ). Элементы такой
коллекции пронумерованы, начиная от нуля, к ним можно обратиться по индексу. В отличие от
коллекции Set элементы коллекции List могут повторяться.

Класс vector — одна из реализаций интерфейса List .

Интерфейс List добавляет к методам интерфейса Collection методы, использующие индекс index элемента:

void add(int index, object obj) — вставляет элемент obj в позицию index ; старые элементы, начиная с
позиции index , сдвигаются, их индексы увеличиваются на единицу;

boolean addAll(int index, Collection coll) — вставляет все элементы коллекции coil ;

object get(int index) — возвращает элемент, находящийся в позиции index ;

int indexOf(Object obj) — возвращает индекс первого появления элемента obj в коллекции;

int lastindexOf (object obj) — возвращает индекс последнего появления элемента obj в коллекции;
Listiterator listiterator () — возвращает итератор коллекции;

Listiterator listiterator (int index) — возвращает итератор конца коллекцииот позиции   index ;

object set (int index, object obj) — заменяет элемент, находящийся в позиции index , элементом obj ;

List subListUnt from, int to) — возвращает часть коллекции от позиции from включительно до позиции to
исключительно.

Major Differences between ArrayList and Vector:


1. Synchronization : Vector is synchronized, which means only one thread at a time can access
the code, while arrayList is not synchronized, which means multiple threads can work on arrayList
at the same time. For example, if one thread is performing an add operation, then there can be
another thread performing a remove operation in a multithreading environment.
If multiple threads access arrayList concurrently, then we must synchronize the block of the code
which modifies the list structurally, or alternatively allow simple element modifications. Structural
modification means addition or deletion of element(s) from the list. Setting the value of an existing
element is not a structural modification.
2. Performance: ArrayList is faster, since it is non-synchronized, while vector operations give
slower performance since they are synchronized (thread-safe). If one thread works on a vector, it
has acquired a lock on it, which forces any other thread wanting to work on it to have to wait until
the lock is released.
3. Data Growth: ArrayList and Vector both grow and shrink dynamically to maintain optimal use
of storage – but the way they resize is different. ArrayList increments 50% of the current array
size if the number of elements exceeds its capacity, while vector increments 100% – essentially
doubling the current array size.
4. Traversal: Vector can use both Enumeration and Iterator for traversing over elements of vector
while ArrayList can only use Iterator for traversing.

13. Интерфейс Map, его варианты и реализации

В контейнерах Map (отображение) хранятся два объекта: ключ и связанное с ним значение. Иногда


используют термин "ассоциативный массив" или "словарь".

Map позволяет искать объекты по ключу. Объект, ассоциированный с ключом, называется


значением. И ключи, и значения являются объектами. Ключи могут быть уникальными, а значения
могут дублироваться. Некоторые отображения допускают пустые ключи и пустые значения.

Классы для карт:


 AbstractMap - абстрактный класс, реализующий большую часть интерфейса Map
 EnumMap - расширяет класс AbstractMap для использования с ключами перечислимого
типа enum
 HashMap - структура данных для хранения связанных вместе пар "ключ-значение",
применяется для использования хеш-таблицы
 TreeMap - для использования дерева, т.е. отображение с отсортированными ключами
 WeakHashMap - для использования хеш-таблицы со слабыми ключами, отображение со
значениями, которые могут удаляться сборщиком мусора, если они больше не
используются
 LinkedHashMap - отображение с запоминанием порядка, в котором добавлялись элементы,
разрешает перебор в порядке вставки
 IdentityHashMap - использует проверку ссылочной эквивалентности при сравнении
документов, отображение с ключами, сравниваемыми с помощью операции == вместо
метода equals()

Метод toString() выводит содержимое в виде фигурных скобок, где ключи и значения разделяются


знаком равенства. Ключи слева, значения справа.

Отображения не поддерживают реализацию интерфейса Iterable, поэтому нельзя перебрать карту


через цикл for в форме for-each.

Интерфейс Map соотносит уникальные ключи со значениями. Ключ - это объект, который вы


используете для последующего извлечения данных. Задавая ключ и значение, вы можете помещать
значения в объект отображения. После того как это значение сохранено, вы можете получить его
по ключу.

Методы:

 void clear() - удаляет все пары "ключ-значение" из вызывающего отображения


 boolean containsKey(Object k) - возвращает значение true, если вызывающее отображение
содержит ключ k. В противном случае возвращает false
 boolean containsValue(Object v) - возвращает значение true, если вызывающее отображение
содержит значение v. В противном случае возвращает false
 Set<Map.Entry<K, V>> entrySet() - возвращает набор, содержащий все значения
отображения. Набор содержит объекты интерфейса Map.Entry. Т.е. метод представляет
отображение в виде набора
 boolean equals(Object o) - возвращает значение true, если параметр o - это отображение,
содержащее одинаковые значения. В противном случае возвращает false
 V get(Object k) - возвращает значение, ассоциированное с ключом k. Возвращает
значение null, если ключ не найден.
 int hashCode() - возвращает хеш-код вызывающего отображения
 boolean isEmpty() - возвращает значение true, если вызывающее отображение пусто. В
противном случае возвращает false
 Set<K> keySet() - возвращает набор, содержащий ключи вызывающего отображения. Метод
представляет ключи вызывающего отображения в виде набора
 V put(K k, V v) - помещает элемент в вызывающее отображение, переписывая любое
предшествующее значение, ассоциированное с ключом. Возвращает null, если ключ ранее
не существовал. В противном случае возвращается предыдущее значение, связанное с
ключом.
 void putAll(Map<? extends K, ? extends V> m) - помещает все значения из m в отображение
 V remove(Object k) - удаляет элемент, ключ которого равен k
 int size() - возвращает количество пар "ключ-значение" в отображении
 Collection<V> values() - возвращает коллекцию, содержащую значения отображения.
14. Классы Collections и Arrays

Для хранения наборов данных в Java предназначены массивы. Однако их не всегда удобно использовать,
прежде всего потому, что они имеют фиксированную длину. Эту проблему в Java решают коллекции. Однако
суть не только в гибких по размеру наборах объектов, но в и том, что классы коллекций реализуют
различные алгоритмы и структуры данных, например, такие как стек, очередь, дерево и ряд других.

Классы коллекций располагаются в пакете java.util, поэтому перед применением коллекций следует


подключить данный пакет.

Хотя в Java существует множество коллекций, но все они образуют стройную и логичную систему. Во-первых,
в основе всех коллекций лежит применение того или иного интерфейса, который определяет базовый
функционал. Среди этих интерфейсов можно выделить следующие:

 Collection: базовый интерфейс для всех коллекций и других интерфейсов коллекций

 Queue: наследует интерфейс Collection и представляет функционал для структур данных в виде
очереди

 Deque: наследует интерфейс Queue и представляет функционал для двунаправленных очередей

 List: наследует интерфейс Collection и представляет функциональность простых списков

 Set: также расширяет интерфейс Collection и используется для хранения множеств уникальных
объектов

 SortedSet: расширяет интерфейс Set для создания сортированных коллекций

 NavigableSet: расширяет интерфейс SortedSet для создания коллекций, в которых можно


осуществлять поиск по соответствию

 Map: предназначен для созданий структур данных в виде словаря, где каждый элемент имеет
определенный ключ и значение. В отличие от других интерфейсов коллекций не наследуется от
интерфейса Collection

В пакете java.util находится класс Arrays, который содержит методы

манипулирования содержимым массива, а именно для поиска, заполнения,

сравнения, преобразования в коллекцию и прочие:

 int binarySearch(параметры) – перегруженный метод организации бинарного

поиска значения в массивах примитивных и объектных типов. Возвращает

позицию первого совпадения;

 void fill(параметры) – перегруженный метод для заполнения массивов

значениями различных типов и примитивами;

 void sort(параметры) – перегруженный метод сортировки массива или его части с

использованием интерфейса Comparator и без него;


 static <T> T[] copyOf(T[] original, int newLength) –заполняет массив

определенной длины, отбрасывая элементы или заполняя null при

необходимости;

 static <T> T[] copyOfRange(T[] original, int from, int to) – копирует заданную

область массива в новый массив;

 <T> List<T> asList(T… a) – метод, копирующий элементы массива в объект

типа List<T>.

15. Обощенные и параметризованные типы. Создание параметризованных классов

Обобщения или generics (обобщенные типы и методы) позволяют нам уйти от


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

Допустим, мы определяем класс для представления банковского счета. К примеру, он


мог бы выглядеть следующим образом. Класс Account имеет два поля: id - уникальный
идентификатор счета и sum - сумма на счете.

В данном случае идентификатор задан как целочисленное значение, например, 1, 2, 3,


4 и так далее. Однако также нередко для идентификатора используются и строковые
значения. И числовые, и строковые значения имеют свои плюсы и минусы. И на момент
написания класса мы можем точно не знать, что лучше выбрать для хранения
идентификатора - строки или числа. Либо, возможно, этот класс будет использоваться
другими разработчиками, которые могут иметь свое мнение по данной проблеме.
Например, в качестве типа id они захотят исподльзовать какой-то свой класс. С
помощью буквы T в определении класса class Account<T> мы указываем, что данный
тип T будет использоваться этим классом. Параметр T в угловых скобках
называется универсальным параметром, так как вместо него можно подставить
любой тип. При этом пока мы не знаем, какой именно это будет тип: String, int или
какой-то другой. Причем буква T выбрана условна, это может и любая другая буква или
набор символов.

Про дженерики - ???

16. Работа с параметризованными методами. Ограничение типа сверху или снизу

<T extends class> - сверху ограничение

<T super class> - снизу ограничение

17. Потоки ввода-вывода в Java. Байтовые и символьные потоки.

Отличительной чертой многих языков программирования является работа с файлами и


потоками. В Java основной функционал работы с потоками сосредоточен в классах из
пакета java.io.

Ключевым понятием здесь является понятие потока. Хотя понятие "поток" в


программировании довольно перегружено и может обозначать множество различных
концепций. В данном случае применительно к работе с файлами и вводом-выводом мы
будем говорить о потоке (stream), как об абстракции, которая используется для чтения
или записи информации (файлов, сокетов, текста консоли и т.д.).
Поток связан с реальным физическим устройством с помощью системы ввода-вывода
Java. У нас может быть определен поток, который связан с файлом и через который мы
можем вести чтение или запись файла. Это также может быть поток, связанный с
сетевым сокетом, с помощью которого можно получить или отправить данные в сети.
Все эти задачи: чтение и запись различных файлов, обмен информацией по сети, ввод-
ввывод в консоли мы будем решать в Java с помощью потоков.

Объект, из которого можно считать данные, называется потоком ввода, а объект, в


который можно записывать данные, - потоком вывода. Например, если надо считать
содержание файла, то применяется поток ввода, а если надо записать в файл - то
поток вывода.

В основе всех классов, управляющих потоками байтов, находятся два абстрактных


класса: InputStream (представляющий потоки ввода)
и OutputStream (представляющий потоки вывода)

Но поскольку работать с байтами не очень удобно, то для работы с потоками символов


были добавлены абстрактные классы Reader (для чтения потоков символов)
и Writer (для записи потоков символов).

Потоки байтов

Класс InputStream

Класс InputStream является базовым для всех классов, управляющих байтовыми потоками ввода. Рассмотрим
его основные методы:

 int available(): возвращает количество байтов, доступных для чтения в потоке

 void close(): закрывает поток

 int read(): возвращает целочисленное представление следующего байта в потоке. Когда в


потоке не останется доступных для чтения байтов, данный метод возвратит число -1

 int read(byte[] buffer): считывает байты из потока в массив buffer. После чтения
возвращает число считанных байтов. Если ни одного байта не было считано, то возвращается число
-1

 int read(byte[] buffer, int offset, int length): считывает некоторое количество
байтов, равное length, из потока в массив buffer. При этом считанные байты помещаются в массиве,
начиная со смещения offset, то есть с элемента buffer[offset]. Метод возвращает число
успешно прочитанных байтов.

 long skip(long number): пропускает в потоке при чтении некоторое количество байт, которое
равно number

Класс OutputStream

Класс OutputStream является базовым классом для всех классов, которые работают с бинарными потоками
записи. Свою функциональность он реализует через следующие методы:

 void close(): закрывает поток

 void flush(): очищает буфер вывода, записывая все его содержимое


 void write(int b): записывает в выходной поток один байт, который представлен
целочисленным параметром b

 void write(byte[] buffer): записывает в выходной поток массив байтов buffer.

 void write(byte[] buffer, int offset, int length): записывает в выходной поток
некоторое число байтов, равное length, из массива buffer, начиная со смещения offset, то
есть с элемента buffer[offset].

Абстрактные классы Reader и Writer

Абстрактный класс Reader предоставляет функционал для чтения текстовой информации. Рассмотрим его
основные методы:

 absract void close(): закрывает поток ввода

 int read(): возвращает целочисленное представление следующего символа в потоке. Если таких
символов нет, и достигнут конец файла, то возвращается число -1

 int read(char[] buffer): считывает в массив buffer из потока символы, количество которых
равно длине массива buffer. Возвращает количество успешно считанных символов. При достижении
конца файла возвращает -1

 int read(CharBuffer buffer): считывает в объект CharBuffer из потока символы.


Возвращает количество успешно считанных символов. При достижении конца файла возвращает -1

 absract int read(char[] buffer, int offset, int count): считывает в массив
buffer, начиная со смещения offset, из потока символы, количество которых равно count

 long skip(long count): пропускает количество символов, равное count. Возвращает число
успешно пропущенных символов

Класс Writer определяет функционал для всех символьных потоков вывода. Его основные методы:

 Writer append(char c): добавляет в конец выходного потока символ c. Возвращает объект
Writer

 Writer append(CharSequence chars): добавляет в конец выходного потока набор символов


chars. Возвращает объект Writer

 abstract void close(): закрывает поток

 abstract void flush(): очищает буферы потока

 void write(int c): записывает в поток один символ, который имеет целочисленное
представление

 void write(char[] buffer): записывает в поток массив символов

 absract void write(char[] buffer, int off, int len) : записывает в поток только
несколько символов из массива buffer. Причем количество символов равно len, а отбор символов из
массива начинается с индекса off

 void write(String str): записывает в поток строку


 void write(String str, int off, int len): записывает в поток из строки некоторое
количество символов, которое равно len, причем отбор символов из строки начинается с индекса off

18. Новый пакет ввода-вывода. Буферы и каналы

Основные отличия между Java IO и Java NIO

IO NIO

Потокоориентированный Буфер-ориентированный

Блокирующий (синхронный) ввод/вывод Неблокирующий (асинхронный) ввод/вывод

Селекторы

Потокоориентированный и буфер-ориентированный ввод/вывод

Основное отличие между двумя подходами к организации ввода/вывода в том, что Java IO
является потокоориентированным, а Java NIO – буфер-ориентированным. Разберем подробней.

Потокоориентированный ввод/вывод подразумевает чтение/запись из потока/в поток одного или


нескольких байт в единицу времени поочередно. Данная информация нигде не кэшируются. Таким
образом, невозможно произвольно двигаться по потоку данных вперед или назад. Если вы хотите
произвести подобные манипуляции, вам придется сначала кэшировать данные в буфере.

Подход, на котором основан Java NIO немного отличается. Данные считываются в буфер для
последующей обработки. Вы можете двигаться по буферу вперед и назад. Это дает немного
больше гибкости при обработке данных. В то же время, вам необходимо проверять содержит ли
буфер необходимый для корректной обработки объем данных. Также необходимо следить, чтобы
при чтении данных в буфер вы не уничтожили ещё не обработанные данные, находящиеся в
буфере.
Блокирующий и неблокирующий ввод/вывод

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

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

Каналы (channels)

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

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

Селекторы

Селекторы в Java NIO позволяют одному потоку выполнения мониторить несколько каналов ввода.
Вы можете зарегистрировать несколько каналов с селектором, а потом использовать один поток
выполнения для обслуживания каналов, имеющих доступные для обработки данные, или для
выбора каналов, готовых для записи.
Чтобы лучше понять концепцию и выгоду от применения селекторов, давайте абстрагируемся от
программирования и представим себе железнодорожный вокзал. Вариант без селектора: есть три
железнодорожных пути (каналы), на каждый из них в любой момент времени может прибыть поезд
(данные из буфера), на каждом пути постоянно ожидает сотрудник вокзала (поток выполнения),
задача которого – обслуживание прибывшего поезда. В результате трое сотрудников постоянно
находятся на вокзале даже если там вообще нет поездов. Вариант с селектором: ситуация та же,
но для каждой платформы есть индикатор, сигнализирующий сотруднику вокзала (поток
выполнения) о прибытии поезда. Таким образом на вокзале достаточно присутствия одного
сотрудника.

Влияние Java NIO и Java IO на дизайн приложения

Выбор между Java NIO и Java IO может на следующие аспекты дизайна вашего приложения:
1. API обращений к классам ввода/вывода;
2. Обработка данных;
3. Количество потоков выполнения, использованных для обработки данных.

API обращений к классам ввода/вывода

Естественно, использование Java NIO серьезно отличается от использования Java IO. Так как,
вместо чтения данных байт за байтом с использованием, например InputStream, данные для
начала должны быть считаны в буфер и браться для обработки уже оттуда.

Java NIO позволяет управлять несколькими каналами (сетевыми соединениями или файлами)
используя минимальное число потоков выполнения. Однако ценой такого подхода является более
сложный, чем при использовании блокирующих потоков, парсинг данных.

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

19. Работа с файлами в Java. Классы java.io.File и java.nio.Path.

Класс File, определенный в пакете java.io, не работает напрямую с потоками. Его задачей является
управление информацией о файлах и каталогах. Хотя на уровне операционной системы файлы и каталоги
отличаются, но в Java они описываются одним классом File.

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

1 File(String путь_к_каталогу)
2 File(String путь_к_каталогу, String имя_файла)
3 File(File каталог, String имя_файла)

Например:

1 // создаем объект File для каталога


2 File dir1 = new File("C://SomeDir");
3 // создаем объекты для файлов, которые находятся в каталоге
4 File file1 = new File("C://SomeDir", "Hello.txt");
5 File file2 = new File(dir1, "Hello2.txt");

Класс File имеет ряд методов, которые позволяют управлять файлами и каталогами. Рассмотрим некоторые из
них:

 boolean createNewFile(): создает новый файл по пути, который передан в конструктор. В случае
удачного создания возвращает true, иначе false
 boolean delete(): удаляет каталог или файл по пути, который передан в конструктор. При удачном
удалении возвращает true.
 boolean exists(): проверяет, существует ли по указанному в конструкторе пути файл или каталог. И
если файл или каталог существует, то возвращает true, иначе возвращает false
 String getAbsolutePath(): возвращает абсолютный путь для пути, переданного в конструктор
объекта
 String getName(): возвращает краткое имя файла или каталога
 String getParent(): возвращает имя родительского каталога
 boolean isDirectory(): возвращает значение true, если по указанному пути располагается каталог
 boolean isFile(): возвращает значение true, если по указанному пути находится файл
 boolean isHidden(): возвращает значение true, если каталог или файл являются скрытыми
 long length(): возвращает размер файла в байтах
 long lastModified(): возвращает время последнего изменения файла или каталога. Значение
представляет количество миллисекунд, прошедших с начала эпохи Unix
 String[] list(): возвращает массив файлов и подкаталогов, которые находятся в определенном
каталоге
 File[] listFiles(): возвращает массив файлов и подкаталогов, которые находятся в определенном
каталоге
 boolean mkdir(): создает новый каталог и при удачном создании возвращает значение true
 boolean renameTo(File dest): переименовывает файл или каталог

Paths
Paths — это совсем простой класс с единственным статическим методом
get(). Его создали исключительно для того, чтобы из переданной строки или
URI получить объект типа Path. Другой функциональности у него нет.

Path
Path, по большому счету, — это переработанный аналог класса File. Работать с ним
значительно проще, чем с File. Во-первых, из него убрали многие утилитные
(статические) методы, и перенесли их в класс Files. Во-вторых, в Path были
упорядочены возвращаемые значения методов. В классе File методы возвращали то
String, то boolean, то File — разобраться было непросто. Например, был метод
getParent(), который возвращал родительский путь для текущего файла в виде
строки. Но при этом был метод getParentFile(), который возвращал то же самое, но
в виде объекта File! Это явно избыточно. Поэтому в интерфейсе Path метод
getParent() и другие методы работы с файлами возвращают просто объект Path.
Никакой кучи вариантов — все легко и просто. Какие же полезные методы есть у
Path? Вот некоторые из них и примеры их работы:

 getFileName() — возвращает имя файла из пути;

 getParent() — возвращает «родительскую» директорию по отношению к


текущему пути (то есть ту директорию, которая находится выше по дереву
каталогов);

 getRoot() — возвращает «корневую» директорию; то есть ту, которая


находится на вершине дерева каталогов;

 startsWith(), endsWith() — проверяют, начинается/заканчивается ли путь с


переданного пути

 boolean isAbsolute() — возвращает true, если текущий путь является


абсолютным

 Path normalize() — «нормализует» текущий путь, удаляя из него ненужные


элементы. Ты, возможно, знаешь, что в популярных операционных системах
при обозначении путей часто используются символы “.” (“текущая директория”)
и “..” (родительская директория). Например: “./Pictures/dog.jpg” обозначает,
что в той директории, в которой мы сейчас находимся, есть папка Pictures, а в
ней — файл “dog.jpg”.Так вот. Если в твоей программе появился путь,
использующий “.” или “..”, метод normalize() позволит удалить их и получить
путь, в котором они не будут содержаться

 Path relativize() — вычисляет относительный путь между текущим и


переданным путем.

20. Сериализация объектов. Интерфейс Serializable.

Сериализация представляет процесс записи состояния объекта в поток, соответственно процесс извлечения


или восстановления состояния объекта из потока называется десериализацией. Сериализация очень
удобна, когда идет работа со сложными объектами.
Интерфейс Serializable

Сразу надо сказать, что сериализовать можно только те объекты, которые реализуют интерфейс Serializable.
Этот интерфейс не определяет никаких методов, просто он служит указателем системе, что объект,
реализующий его, может быть сериализован.

Сериализация. Класс ObjectOutputStream

Для сериализации объектов в поток используется класс ObjectOutputStream. Он записывает данные в


поток.

Для создания объекта ObjectOutputStream в конструктор передается поток, в который производится запись:

1 ObjectOutputStream(OutputStream out)

Для записи данных ObjectOutputStream использует ряд методов, среди которых можно выделить следующие:

 void close(): закрывает поток

 void flush(): очищает буфер и сбрасывает его содержимое в выходной поток

 void write(byte[] buf): записывает в поток массив байтов

 void write(int val): записывает в поток один младший байт из val

 void writeBoolean(boolean val): записывает в поток значение boolean

 void writeByte(int val): записывает в поток один младший байт из val

 void writeChar(int val): записывает в поток значение типа char, представленное целочисленным
значением

 void writeDouble(double val): записывает в поток значение типа double

 void writeFloat(float val): записывает в поток значение типа float

 void writeInt(int val): записывает целочисленное значение int

 void writeLong(long val): записывает значение типа long

 void writeShort(int val): записывает значение типа short

 void writeUTF(String str): записывает в поток строку в кодировке UTF-8

 void writeObject(Object obj): записывает в поток отдельный объект

Эти методы охватывают весь спектр данных, которые можно сериализовать.

Десериализация. Класс ObjectInputStream

Класс ObjectInputStream отвечает за обратный процесс - чтение ранее сериализованных данных из потока. В
конструкторе он принимает ссылку на поток ввода:
1 ObjectInputStream(InputStream in)

Функционал ObjectInputStream сосредоточен в методах, предназначенных для чтения различных типов


данных. Рассмотрим основные методы этого класса:

 void close(): закрывает поток

 int skipBytes(int len): пропускает при чтении несколько байт, количество которых равно len

 int available(): возвращает количество байт, доступных для чтения

 int read(): считывает из потока один байт и возвращает его целочисленное представление

 boolean readBoolean(): считывает из потока одно значение boolean

 byte readByte(): считывает из потока один байт

 char readChar(): считывает из потока один символ char

 double readDouble(): считывает значение типа double

 float readFloat(): считывает из потока значение типа float

 int readInt(): считывает целочисленное значение int

 long readLong(): считывает значение типа long

 short readShort(): считывает значение типа short

 String readUTF(): считывает строку в кодировке UTF-8

 Object readObject(): считывает из потока объект

21. Библиотеки графического интерфейса. Особенности и различия

AWT была первой попыткой Sun создать графический интерфейс для


Java. Они пошли легким путем и просто сделали прослойку на Java,
которая вызывает методы из библиотек, написанных на С.
Библиотечные методы создают и используют графические
компоненты операционной среды. С одной стороны, это хорошо, так
как программа на Java похожа на остальные программы в рамках
данной ОС. Но с другой стороны, нет никакой гарантии, что различия
в размерах компонентов и шрифтах не испортят внешний вид
программы при запуске ее на другой платформе. Кроме того, чтобы
обеспечить мультиплатформенность, пришлось унифицировать
интерфейсы вызовов компонентов, из-за чего их функциональность
получилась немного урезанной. Да и набор компонентов получился
довольно небольшой. К примеру, в AWT нет таблиц, а в кнопках не
поддерживается отображение иконок.
Использованные ресурсы AWT старается освобождать автоматически.
Это немного усложняет архитектуру и влияет на производительность.
Освоить AWT довольно просто, но написать что-то сложное будет
несколько затруднительно. Сейчас ее используют разве что для
апплетов.

Достоинства:
 часть JDK;
 скорость работы;
 графические компоненты похожи на стандартные.
Недостатки:
 использование нативных компонентов налагает ограничения на
использование их свойств. Некоторые компоненты могут вообще не
работать на «неродных» платформах;
 некоторые свойства, такие как иконки и всплывающие
подсказки, в AWT вообще отсутствуют;
 стандартных компонентов AWT очень немного, программисту
приходится реализовывать много кастомных;
 программа выглядит по-разному на разных платформах (может
быть кривоватой).
заключение:
В настоящее время AWT используется крайне редко — в основном в
старых проектах и апплетах. Oracle припрятал обучалки и всячески
поощряет переход на Swing. Оно и понятно, прямой доступ к
компонентам оси может стать серьезной дырой в безопасности.

Вслед за AWT Sun разработала набор графических компонентов под


названием Swing. Компоненты Swing полностью написаны на Java.
Для отрисовки используется 2D, что принесло с собой сразу
несколько преимуществ. Набор стандартных компонентов
значительно превосходит AWT по разнообразию и
функциональности. Стало легко создавать новые компоненты,
наследуясь от существующих и рисуя все, что душе угодно. Стала
возможной поддержка различных стилей и скинов. Вместе с тем
скорость работы первых версий Swing оставляла желать лучшего.
Некорректно написанная программа и вовсе могла повесить винду
намертво.

Тем не менее благодаря простоте использования, богатой


документации и гибкости компонентов Swing стал, пожалуй, самым
популярным графическим фреймворком в Java. На его базе
появилось много расширений, таких как SwingX, JGoodies, которые
значительно упрощают создание сложных пользовательских
интерфейсов. Практически все популярные среды программирования
Java включают графические редакторы для Swing-форм. Поэтому
разобраться и начать использовать Swing не составит особого труда.

Достоинства:
 часть JDK, не нужно ставить дополнительных библиотек;
 по Swing гораздо больше книжек и ответов на форумах. Все
проблемы, особенно у начинающих, гуглу досконально известны;
 встроенный редактор форм почти во всех средах разработки;
 на базе свинга есть много расширений типа SwingX;
 поддержка различных стилей (Look and feel).
Недостатки:
 окно с множеством компонентов начинает подтормаживать;
 работа с менеджерами компоновки может стать настоящим
кошмаром в сложных интерфейсах.
Заключение:
Swing жил, Swing жив, Swing будет жить. Хотя Oracle и старается
продвигать JavaFX, на сегодняшний день Swing остается самым
популярным фреймворком для создания пользовательских
интерфейсов на Java.

Standard Widget Toolkit


SWT
SWT был разработан в компании IBM в те времена, когда Swing еще
был медленным, и сделано это было в основном для продвижения
среды программирования Eclipse. SWT, как и AWT, использует
компоненты операционной системы, но для каждой платформы у него
созданы свои интерфейсы взаимодействия. Так что для каждой
новой системы тебе придется поставлять отдельную JAR-библиотеку с
подходящей версией SWT. Это позволило более полно использовать
существующие функции компонентов на каждой оси. Недостающие
функции и компоненты были реализованы с помощью 2D, как в
Swing. У SWT есть много приверженцев, но, положа руку на сердце,
нельзя не согласиться, что получилось не так все просто, как
хотелось бы. Новичку придется затратить на изучение SWT намного
больше времени, чем на знакомство с тем же Swing. Кроме того, SWT
возлагает задачу освобождения ресурсов на программиста, в связи с
чем ему нужно быть особенно внимательным при написании кода,
чтобы случайное исключение не привело к утечкам памяти.

Достоинства:
 использует компоненты операционной системы — скорость
выше;
 Eclipse предоставляет визуальный редактор форм;
 обширная документация и множество примеров;
 возможно использование AWT- и Swing-компонентов.
Недостатки:
 для каждой платформы необходимо поставлять отдельную
библиотеку;
 нужно все время следить за использованием ресурсов и
вовремя их освобождать;
 сложная архитектура, навевающая суицидальные мысли после
тщетных попыток реализовать кастомный интерфейс.
Заключение:
Видно, что в IBM старались. Но получилось уж очень на любителя…

JavaFX
JavaFX можно без преувеличения назвать прорывом. Для отрисовки
используется графический конвейер, что значительно ускоряет
работу приложения. Набор встроенных компонентов обширен, есть
даже отдельные компоненты для отрисовки графиков. Реализована
поддержка мультимедийного контента, множества эффектов
отображения, анимации и даже мультитач. Внешний вид всех
компонентов можно легко изменить с помощью CSS-стилей. И самое
прекрасное — в JavaFX входит набор утилит, которые позволяют
сделать родной инсталлятор для самых популярных платформ: exe
или msi для Windows, deb или rpm для Linux, dmg для Mac. На сайте
Oracle можно найти подробную документацию и огромное количество
готовых примеров. Это превращает программирование с JavaFX в
легкое и приятное занятие.

Достоинства:
 быстрая работа за счет графического конвейера;
 множество различных компонентов;
 поддержка стилей;
 утилиты для создания установщика программы;
 приложение можно запускать как десктопное и в браузере как
часть страницы.
Недостатки:
 фреймворк еще разрабатывается, поэтому случаются и падения
и некоторые глюки;
 JavaFX пока не получил широкого распространения.
Заключение:
Хорошая работа, Oracle. Фреймворк оставляет только позитивные
впечатления. Разобраться несложно, методы и интерфейсы выглядят
логичными. Хочется пользоваться снова и снова!

22. Библиотека Swing. Особенности.


Создатели новой библиотеки пользовательского интерфейса Swing не стали «изобретать велосипед» и
в качестве основы для своей библиотеки выбрали AWT. Конечно, речь не шла об использовании
конкретных тяжеловесных компонентов AWT (представленных классами Button, Label и им подобными).
Нужную степень гибкости и управляемости обеспечивали только легковесные компоненты. На
диаграмме наследования представлена связь между AWT и Swing.

Для создания графического интерфейса приложения необходимо использовать специальные


компоненты библиотеки Swing, называемые контейнерами высшего уровня (top level containers). Они
представляют собой окна операционной системы, в которых размещаются компоненты
пользовательского интерфейса. К контейнерам высшего уровня относятся окна JFrame и JWindow,
диалоговое окно JDialog, а также апплет JApplet (который не является окном, но тоже предназначен
для вывода интерфейса в браузере, запускающем этот апплет). Контейнеры высшего уровня Swing
представляют собой тяжеловесные компоненты и являются исключением из общего правила. Все
остальные компоненты Swing являются легковесными.

Конструктор JFrame() без параметров создает пустое окно. Конструктор JFrame(String title) создает


пустое окно с заголовком title. Чтобы создать простейшую программу с пустым окном необходимо
использовать следующие методы :

 setSize(int width, int height) - определение размеров окна;


 setDefaultCloseOperation(int operation) - определение действия при завершении программы;
 setVisible(boolean visible) - сделать окно видимым.

Если не определить размеры окна, то оно будет иметь нулевую высоту независимо от того, что в нем
находится. Размеры окна включают не только «рабочую» область, но и границы и строку заголовка.

Метод setDefaultCloseOperation определяет действие, которое необходимо выполнить при "выходе из


программы". Для этого следует в качестве параметра operation передать константу EXIT_ON_CLOSE,
описанную в классе JFrame.

По умолчанию окно создается невидимым. Чтобы отобразить окно на экране вызывается метод
setVisible с параметром true. Если вызвать его с параметром false, окно станет невидимым

Корневая панель JRootPane


Каждый раз, как только создается контейнер высшего уровня, будь то обычное окно, диалоговое окно
или апплет, в конструкторе этого контейнера создается корневая панель JRootPane. Контейнеры
высшего уровня Swing следят за тем, чтобы другие компоненты не смогли "пробраться" за пределы
JRootPane.

Корневая палель JRootPane добавляет в контейнеры свойство "глубины", обеспечивая возможность не


только размещать компоненты один над другим, но и при необходимости менять их местами,
увеличивать или уменьшать глубину расположения компонентов. Такая возможность необходима при
создании многодокументного приложения Swing, у которого окна представляют легковесные
компоненты, располагающиеся друг над другом, а также выпадающими (контекстными) меню и
всплывающими подсказками.

Многослойная панель JLayeredPane


В основании корневой панели (контейнера) лежит так называемая многослойная
панель JLayeredPane, занимающая все доступное пространство контейнера. Именно в этой панели
располагаются все остальные части корневой панели, в том числе и все компоненты пользовательского
интерфейса.

JLayeredPane используется для добавления в контейнер свойства глубины (depth). To есть,


многослойная панель позволяет организовать в контейнере третье измерение, вдоль которого
располагаются слои (layers) компонента. В обычном контейнере расположение компонента
определяется прямоугольником, который показывает, какую часть контейнера занимает компонент.
При добавлении компонента в многослойную панель необходимо указать не только прямоугольник,
занимаемый компонентом, но и слой, в котором он будет располагаться. Слой в многослойной панели
определяется целым числом. Чем больше определяющее слой число, тем выше слой находится.

Первый добавленный в контейнер компонент оказывается выше компонентов, добавленных позже.


Чаще всего разработчик не имеет дело с позициями компонентов. При добавлении компонентов их
положение меняются автоматически. Тем не менее многослойная панель позволяет менять позиции
компонентов динамически, уже после их добавления в контейнер.

Возможности многослойной панели широко используются некоторыми компонентами Swing. Особенно


они важны для многодокументных приложений, всплывающих подсказок и меню.
Многодокументные Swing приложения задействуют специальный контейнер JDesktopPane («рабочий
стол»), унаследованный от JLayeredPane, в котором располагаются внутренние окна Swing. Самые
важные функции многодокументного приложения — расположение «активного» окна над другими,
сворачивание окон, их перетаскивание — обеспечиваются механизмами многослойной панели.
Основное преимущество от использования многослойной панели для всплывающих подсказок и меню
— это ускорение их работы. Вместо создания для каждой подсказки или меню нового тяжеловесного
окна, располагающегося над компонентом, в котором возник запрос на вывод подсказки или
меню, Swing создает быстрый легковесный компонент. Этот компонент размещается в достаточно
высоком слое многослойной панели выше в стопке всех остальных компонентов и используется для
вывода подсказки или меню.

Многослойная панель позволяет организовать неограниченное количество слоев.


Структура JLayeredPane включает несколько стандартных слоев, которые и используются всеми
компонентами Swing, что позволяет обеспечить правильную работу всех механизмов многослойной
панели. Стандартные слои JLayeredPane представлены на следующем рисунке.

Default

Слой Default используется для размещения всех обычных компонентов, которые добавляются в
контейнер. В этом слое располагаются внутренние окна многодокументных приложений.

Palette

Слой Palette предназначен для размещения окон с набором инструментов, которые обычно
перекрывают остальные элементы интерфейса. Создавать такие окна позволяет панель JDesktopPane,
которая размещает их в этом слое.

Modal

Слой Modal планировался для размещения легковесных модальных диалоговых окон. Однако такие
диалоговые окна пока не реализованы, так что этот слой в Swing в настоящее время не используется.

Popup

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

Drag
Самый верхний слой. Предназначен для операций перетаскивания (drag and drop), которые должны
быть хорошо видны в интерфейсе программы.

Панель содержимого ContentPane


Панель содержимого ContentPane - это следующая часть корневой панели, которая используется для
размещения компонентов пользовательского интерфейса программы. ContentPane занимает большую
часть пространства многослойной панели (за исключением места, занимаемого строкой меню). Чтобы
панель содержимого не закрывала добавляемые впоследствии в окно компоненты, многослойная
панель размещает ее в специальном очень низком слое с названием FRAME_CONTENT_LAYER, с
номером -30000.

Обратиться к панели содержимого можно методом getContentPane() класса JFrame. С помощью


метода add(Component component) можно добавить на нее любой элемент управления.
Заменить ContentPane любой другой панелью типа JPanel можно методом setContentPane()

Пример добавления кнопки в панель содержимого

Прозрачная панель JOptionPane


Прозрачная панель JOptionPane размещается корневой панелью выше всех элементов многослойной
панели. За размещением JOptionPane следит корневая панель, которая размещает прозрачную панель
выше многослойной панели, причем так, чтобы она полностью закрывала всю область окна, включая и
область, занятую строкой меню.

JOptionPane используется в приложениях достаточно редко, поэтому по умолчанию корневая панель


делает ее невидимой, что позволяет уменьшить нагрузку на систему рисования. Следует иметь в виду,
что если вы делаете прозрачную панель видимой, нужно быть уверенным в том, что она прозрачна (ее
свойство opaque равно false), поскольку в противном случае она закроет все остальные элементы
корневой панели, и остальной интерфейс будет невидим.

В каких случаях можно использовать прозрачную панель JOptionPane? С ее помощью можно


определять функции приложения, для реализации которых «с нуля» понадобились бы серьезные
усилия. Прозрачную панель можно приспособить под автоматизированное тестирование
пользовательского интерфейса. Синтезируемые в ней события позволяют отслеживать промежуточные
отладочные результаты. Иногда такой подход гораздо эффективнее ручного тестирования.

Строка меню JMenuBar


Одной из важных особенностей использования корневой панели JRootPane в Swing, является
необходимость размещения в окне строки меню JMenuBar. Серьезное приложение нельзя построить
без какого-либо меню для получения доступа к функциям программы. Библиотека Swing предоставляет
прекрасные возможности для создания удобных меню JMenuBar, которые также являются
легковесными компонентами.

Строка меню JMenuBar размещается в многослойной панели в специальном слое


FRAME_CONTENT_LAYER и занимает небольшое пространство в верхней части окна. По размерам в
длину строка меню равна размеру окна. Ширина строки меню зависит от содержащихся в ней
компонентов.

23. Библиотека SWT. Особенности

SWT библиотека представляет собой кросс-платформенную оболочку для графических библиотек


конкретных операционных систем. SWT разработана с использованием стандартного языка Java и
получает доступ к специфичным библиотекам различных ОС через JNI (Java Native Interface), который
используется для доступа к родным визуальным компонентам операционной системы.

Библиотека SWT является библиотекой с открытым исходным кодом и предназначена для разработки


графических интерфейсов приложений на языке Java. Лицензируется SWT под Eclipse Public License,
предназначенной для открытого ПО.
SWT позволяет получать привычный внешний нтерфейс программы в соответствующей операционной
системе и является альтернативой библиотекам AWT и Swing. Java-приложение с
использованием SWT более эффективна по сравнению с AWT и Swing. Но при этом появляется
зависимость от операционной системы и оборудования.

Библиотека SWT использует основные widgets (виджеты) операционной системы. Во многих IDE


такой подход к разработке является слишком низкоуровненным. Дополнительно совместно с SWT
используется библиотека JFace, предоставляя java приложениям с библиотекой SWT многочисленные
сервисы.

Виджет widget

С точки зрения программного приложения widget («виджет») - это элемент графического интерфейса,


имеющий стандартный внешний вид и выполняющий стандартные действия. Под виджет'ом
подразумеваются окно (диалоговое, модальное), кнопка (стандартная, радиокнопка, флаговая), список
(иерархический, раскрывающийся) и т.д.

«Виджет» также используется для представления класса вспомогательных мини-программ в виде


графических модулей, которые размещаются в рабочем пространстве родительской программы и
служат для украшения пользовательского интерфейса, развлечения, решения отдельных рабочих
задач или быстрого получения информации из интернета без помощи веб-браузера.

Библиотека JFace

Библиотека JFace расширяет возможности SWT. Одна из основных важных особенностей JFace


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

JFace — это набор java-классов, реализующих наиболее общие задачи построения GUI. С точки зрения
разработки java-приложения JFace представляет собой дополнительный программный слой над SWT,
который реализует шаблон Model-View-Controller. JFace реализует следующие возможности:

 представление «Viewer» классов, отвечающих за отображение и реализующих задачи по


заполнению, сортировке, фильтрации;
 представление «Action» классов, позволяющих определять "поведение" отдельных widget'ов,
таких как пункты меню, кнопки, списки и т.п.;
 представление регистров, содержащих шрифты и изображения;
 представление набора стандартных диалоговых окон и виджетов для взаимодействия с
пользователем.

Основная цель использования JFace связана с освобождением разработчика от рутинных операций по


созданию пользовательского интерфейса, позволяя ему сосредоточиться на бизнес-логике
приложения.

Основной задачей группы разработчиков Eclipse было сокрытие реализации компонентов


графического интерфейса построенных на основе библиотеки SWT и по возможности максимальное
использование библиотеки JFace как более высокоуровневой и простой в использовании.

Библиотека SWT не зависит от JFace, но JFace использует SWT. Тем не менее, IDE Eclipse построена


с использованием обеих библиотек, но в некоторых случаях SWT используется напрямую в обход
JFace.

Используемая в SWT терминология

 Widget – основной компонент графического интерфейса SWT (сродни компоненту в пакете Java
AWT и JComponent в Swing). Widget является абстрактным классом.
 Composite (композит) – это элемент управления, который может заключать в себе другие
элементы. Аналог контейнеру в пакете Java AWT и JPanel пакета Swing.
 Object (объект) – это родительский класс других элементов (которые могут и не быть
композитами), например, таких как, список или таблица. Объект является абстрактным
классом.

24. Библиотека JavaFX. Особенности


JavaFX можно без преувеличения назвать прорывом. Для отрисовки
используется графический конвейер, что значительно ускоряет
работу приложения. Набор встроенных компонентов обширен, есть
даже отдельные компоненты для отрисовки графиков. Реализована
поддержка мультимедийного контента, множества эффектов
отображения, анимации и даже мультитач. Внешний вид всех
компонентов можно легко изменить с помощью CSS-стилей. И самое
прекрасное — в JavaFX входит набор утилит, которые позволяют
сделать родной инсталлятор для самых популярных платформ: exe
или msi для Windows, deb или rpm для Linux, dmg для Mac. На сайте
Oracle можно найти подробную документацию и огромное количество
готовых примеров. Это превращает программирование с JavaFX в
легкое и приятное занятие.

Достоинства:
 быстрая работа за счет графического конвейера;
 множество различных компонентов;
 поддержка стилей;
 утилиты для создания установщика программы;
 приложение можно запускать как десктопное и в браузере как
часть страницы.
Недостатки:
 фреймворк еще разрабатывается, поэтому случаются и падения
и некоторые глюки;
 JavaFX пока не получил широкого распространения.
Заключение:
Хорошая работа, Oracle. Фреймворк оставляет только позитивные
впечатления. Разобраться несложно, методы и интерфейсы выглядят
логичными. Хочется пользоваться снова и снова!

25. Компоненты графического интерфейса. Класс Component.

Графическая библиотека AWT предлагает более двадцати готовых компонентов. Они


показаны на рис. 8.2. Наиболее часто используются подклассы класса Component: классы
Button, Canvas, Checkbox, Choice, Container, Label, List, Scrollbar, TextArea, TextField,
Panel, ScrollPane, Window, Dialog, FileDialog, Frame.

Еще одна группа компонентов — это компоненты меню — классы Menuitem, MenuBar,
Menu, PopupMenu, CheckboxMenuItem. Мы рассмотрим ИХ В главе 13.

Забегая вперед, для каждого компонента перечислим события, которые в нем происходят.
Обработку событий мы разберем в главе 12.

Начнем изучать эти компоненты от простых компонентов к сложным и от наиболее часто


используемых к применяемым реже. Но сначала посмотрим на то общее, что есть во всех
этих компонентах, на сам класс component.
Класс Component

Класс component — центр библиотеки AWT — очень велик и обладает большими


возможностями. В нем пять статических констант, определяющих размещение
компонента внутри пространства, выделенного для компонента в содержащем его
контейнере: BOTTOM_ALIGNMENT, CENTER_ALIGNMENT,

LEFT_ALIGNMENT, RIGHT_ALIGNMENT, TOP_ALIGNMENT, И ОКОЛО СОТНИ


МеТОДОВ.

Большинство методов— это методы доступа getxxx(), isxxx(), setxxx(). Изучать их нет
смысла, надо просто посмотреть, как они используются в подклассах.

Конструктор класса недоступен — он защищенный (protected), потому, что класс


component абстрактный, он не может использоваться сам по себе, применяются только его
подклассы.

Компонент всегда занимает прямоугольную область со сторонами, параллельными


сторонам экрана и в каждый момент времени имеет определенные размеры, измеряемые в
пикселах, которые можно узнать методом getsizeo, возвращающим объект класса
Dimension, или целочисленными методами getHeighto и getwidtho, возвращающими
высоту и ширину прямоугольника. Новый размер компонента можно установить из
программы методами setSize(Dimension d) или setSize(int width, int height), если это
допускает менеджер размещения контейнера, содержащего компонент.

У компонента есть предпочтительный размер, при котором компонент выглядит наиболее


пропорционально. Его можно получить методом getPreferredSizef) В виде объекта
Dimension.

Компонент обладает минимальным и максимальным размерами. Их возвращают методы


getMinimumSize() И getMaximumSize () В виде объекта Dimension.

В компоненте есть система координат. Ее начало — точка с координатами (0, 0) —


находится в левом верхнем углу компонента, ось Ох идет вправо, ось Оу — вниз,
координатные точки расположены между пикселами.

В компоненте хранятся координаты его левого верхнего угла в системе координат


объемлющего контейнера. Их можно узнать методами getLocation (), а изменить —
методами setLocationO, переместив компонент в контейнере, если это позволит менеджер
размещения компонентов.

Можно выяснить сразу и положение, и размер прямоугольной области компонента


методом getBounds (), возвращающим объект класса Rectangle, и изменить разом и
положение, и размер компонента методами setBounds (), если это позволит сделать
менеджер размещения.

Компонент может быть недоступен для действий пользователя, тогда он выделяется на


экране обычно светло-серым цветом. Доступность компонента можно проверить
логическим методом isEnabiedo, а изменить— методом setEnabled(boolean enable).
Для многих компонентов определяется графический контекст — объект класса Graphics,
— который управляется методом paint (), описанным в предыдущей главе, и который
можно получить методом getGraphics ().

В контексте есть текущий цвет и цвет фона — объекты класса color. Цвет фона можно
получить методом getBackground{), а изменить— методом setBackground(Color color).
Текущий цвет можно получить методом getForeground(), а изменить — методом
setForeground(Color color).

В контексте есть шрифт — объект класса Font, возвращаемый методом

getFont() И изменяемый Методом setFont(Font font) .

В компоненте определяется локаль — объект класса Locale. Его можно получить методом
getLocale(), изменить — методом setLocale(Locale locale).

В компоненте существует курсор, показывающий положение мыши, — объект класса


Cursor. Его можно получить методом getcursor (), изменяется форма курсора в "тяжелых"
компонентах с помощью метода setcursor(Cursor cursor). Остановимся на этом классе
подробнее.

26. Размещение компонентов в контейнерах. Менеджеры компоновки

Менеджеры расположения Layout


Менеджер расположения (layout manager) определяет, каким образом на форме будут располагаться
компоненты. Независимо от платформы, виртуальной машины, разрешения и размеров экрана
менеджер расположения гарантирует, что компоненты будут иметь предпочтительный или близкий к
нему размер и располагаться в том порядке, который был указан программистом при создании
программы.

На странице представлено описание следующих менеджеров расположения и примеров :

 Полярное расположение BorderLayout
 Последовательное расположение FlowLayout
 Табличное расположение GridLayout
 Менеджер расположения GridBagLayout
 Менеджер расположения CardLayout
 Менеджер расположения BoxLayout
 Менеджер расположения GroupLayout
 Менеджер расположения SpringLayout
 Пример диалогового окна авторизации
 Пример собственного менеджера расположения

Исходные коды примеров различных менеджеров расположения, рассмотренных на странице


можно скачать здесь (13.4 Кб).

В Swing менеджер расположения играет еще большую роль, чем обычно. Он позволяет не только
сгладить различия между операционными системами, но к тому же дает возможность с легкостью
менять внешний вид приложения, не заботясь о том, как при этом изменяются размеры и
расположение компонентов.

Поддержка менеджеров расположения встроена в базовый класс контейнеров java.awt.Container. Все


компоненты библиотеки Swing унаследованы от базового класса JComponent, который, в свою очередь,
унаследован от класса Container. Таким образом, для любого компонента Swing можно установить
менеджер расположения или узнать, какой менеджер им используется в данный момент. Для этого
предназначены методы setLayout() и getLayout().

Конечно, изменять расположение желательно только в контейнерах, которые предназначены для


размещения в них компонентов пользовательского интерфейса, то есть в панелях (JPanel) и окнах
(унаследованных от класса Window). Не стоит менять расположение в кнопках или флажках, хотя
такая возможность имеется. В стандартной библиотеке Java существует несколько готовых менеджеров
расположения, и с их помощью можно реализовать абсолютно любое расположение.

Вызов менджера расположения, revalidate

Контейнер окна вызывает методы менеджера расположения каждый раз при изменении своих
размеров или при первом появлении на экране. Кроме этого, можно программно запросить менеджер
расположения заново расположить компоненты в контейнере: для этого служит так называемая
проверка корректности (валидация) контейнера и содержащихся в нем компонентов. Проверка
корректности очень полезна, если интерфейс приложения меняется динамически. Например, если
динамически меняются размеры компонентов (во время выполнения программы изменяется текст
надписи или количество столбцов таблицы — все это приведет к изменению размеров компонентов).
После изменений компонентам может не хватать прежних размеров или наоборот, прежний размер
будет для них слишком велик. Для этого и предназначена проверка корректности. Выполнить проверку
корректности для любого компонента Swing, будь это контейнер или отдельный компонент, позволяет
метод revalidate(), определенный в базовом классе библиотеки JComponent.

Менеджер расположения размещает добавляемые в контейнер компоненты в некотором порядке


согласно реализованного в нем алгоритма, и определяет их некоторый размер. При этом он обычно
учитывает определенные свойства компонентов :

 Предпочтительный размер. Размер, который идеально подходит компоненту. По умолчанию


все размеры компонентов устанавливаются текущим менеджером внешнего вида и поведения
(look and feel manager), но можно их изменить. Предпочтительный размер можно изменить с
помощью метода setPrefferedSize().
 Минимальный размер. Параметр определения минимального размера компонента. После
достижения минимального размера всех компонентов контейнер больше не сможет уменьшить
свой размер. Минимальный размер также можно изменить, для этого предназначен метод
setMinimumSize().
 Максимальный размер. Параметр определения максимального размера компонента при
увеличении размеров контейнера. Например, максимальный размер текстового поля JTextField
не ограничен. Чаще всего оно должно сохранять свой предпочтительный размер. Это можно
исправить с помощью метода setMaximumSize(), устанавливающего максимальный размер.
Большая часть менеджеров расположения игнорирует максимальный размер, работая в
основном с предпочтительным и минимальным размерами.
 Выравнивание по осям X и Y. Данные параметры нужны только менеджеру BoxLayout,
причем для него они играют важнейшую роль.
 Границы контейнера. Эти параметры контейнера определяют размеры отступов от границ
контейнера. Значения границ контейнера позволяет получить метод getInsets(). Иногда
менеджеру расположения приходится их учитывать.

Описание бизнес-логики работы менеджера расположения

Логика работы менеджера расположения происходит следующим образом : он ждет прихода сигнала от
контейнера окна, требующего расположить в нем компоненты. Этому соответствует вызов метода
layoutContainer() интерфейса LayoutManager. В методе layoutContainer() и происходит основная работа
по расположению компонентов в контейнере.

Менеджер расположения учитывает различные размеры и свойства компонентов и контейнера,


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

Менеджер расположения VerticalLayout реализует интерфейс LayoutManager. Самым важным методом


является layoutContainer(), который определяет расположения всех компонентов, содержащиеся в
контейнере. Алгоритм функционирования менеджера расположения не сложный : размещение
компонентов по вертикали, отделяя их друг от друга расстоянием в 5 пикселов; все компоненты имеют
предпочтительный размер. Чтобы реализовать алгоритм выполняются следующие шаги :

1. Получение массива компонентов - метод контейнера getComponents().


2. Организация цикла перебора компонентов.
3. Определение предпочтительного размера для каждого из компонентов - метод
getPreferredSize().
4. Определение позиции компонента на экране методом setBounds().
5. Смещение текущей вертикальной координаты Y, увеличением ее значения на высоту
очередного компонента с учетом «декоративного» расстояния в 5 пикселов.

Таким образом, менеджер VerticalLayout разместит все компоненты контейнера друг над другом на
расстоянии в 5 пикселов. Следует обратить внимание, что компоненты отделены и от левой границы
контейнера.

Метод addLayoutComponent() предназначен для добавления компонентов в список менеджера


расположения, а метод removeLayoutComponent() для удаления компонентов из списка.
addLayoutComponent() позволяет ассоциировать с компонентом строку, которая может быть
использована менеджером расположения как рекомендация относительно того, где именно необходимо
разместить данный компонент. Наш пример менеджера расположения в отличие от BorderLayout не
поддерживает подобных рекомендаций.

Две функции менеджера (предпочтительный preferredLayoutSize() и минимальный


minimumLayoutSize()) сообщают размеры контейнера. Для нашего простого менеджера расположения
минимальный и предпочтительный размеры контейнера совпадают, их вычисляет метод
calculateBestSize(). Для вычисления оптимального размера контейнера в цикле выполняется поиск
компонента с самой большой длиной. К найденной длине добавляется 5 пикселов в качестве отступа от
левой границы контейнера и получаем оптимальную длину контейнера. Для высоты вычисления чуть
сложнее : необходимо сложить высоты всех находящихся в контейнере компонентов, а также
прибавлять к ним расстояние в 5 пикселов между всеми компонентами. Полученная сумма и является
оптимальной высотой контейнера.

Полярное расположение BorderLayout


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

Менеджер расположения BorderLayout иммет отличия от остальных. Чтобы добавить с его помощью


компонент в методе add() необходимо использовать дополнительный параметр, который определяет
область контейнера для размещения компонента. В таблице перечислены допустимые значения этого
параметра.

Последовательное расположение FlowLayout


Менеджер последовательного расположение FlowLayout размещает компоненты в контейнере слева
направо, сверху вниз. При полном заполнении компонентами строки контейнера FlowLayout переходит
на следующую строку вниз. Данное расположение устанавливается по умолчанию в панелях JPanel.
Основным свойством FlowLayout является определение предпочтительного размера компонентов.
Например, размер метки с текстом JLabel соответствует размеру текста. Рассмотрим простой пример
FlowLayout

Табличное расположение GridLayout


Менеджер расположения GridLayout представляет контейнер в виде таблицы с ячейками одинакового
размера. Количество строк и столбцов можно указать в конструкторе. Имеется возможность задать
произвольное количество либо строк, либо столбцов, но не одновременно. Все ячейки таблицы имеют
одинаковый размер, равный размеру самого большого компонента, находящегося в таблице.

Менеджер расположения GridBagLayout


Менеджер расположения GridBagLayout подобно табличному менеджеру устанавливает компоненты в
таблицу. Но он дает возможность определять для компонентов разную ширину и высоту колонок и
строк таблицы. Фактически GridBagLayout позволяет получить абсолютно любое расположение
компонентов.

Менеджер расположения CardLayout


Менеджер CardLayout можно использовать для создания так называемых вкладок (tabs), выбирая
которые будут избранно открываться разные панели, занимающие одно и то же место в интерфейсе
окна. В библиотеке Swing данную задачу можно решить с использованием класса JTabbedPane,
организовывающего управление панелями с вкладками.

Менеджер расположения BoxLayout


Менеджер расположения BoxLayout позволяет управлять размещением компонентов либо в
вертикальном, либо в горизонтальном направлениях и управлять пространством между компонентами,
используя вставки. Для размещения компонентов в вертикальной плоскости необходимо конструктору
передать константу BoxLayout.Y_AXIS, для размещения в горизонтальной - BoxLayout.X_AXIS.

Менеджер расположения GroupLayout


Менеджер расположения компонентов GroupLayout появился только в Java 6. Он раскладывает
компоненты по группам. Группы имеют горизонтальное и вертикальное направление и могут быть
параллельными и последовательными. В последовательной группе у каждого следующего компонента
координата вдоль оси на единицу больше (имеется в виду координата в сетке), в параллельной –
компоненты имеют одну и ту же координату.

Менеджер расположения SpringLayout


Несмотря на универсальность менеджера расположения SpringLayout, действие его весьма
специфично и не похоже на действие ни одного из уже рассмотренных выше менеджеров
расположения.

С каждым компонентом ассоциируется особый информационный объект SpringLayout, который


позволяет задать расстояние (в пикселах) между парой границ различных компонентов. Границ у
компонента четыре — это его северная, восточная, западная и южная стороны. Можно задавать
расстояние и между границами одного и того же компонента: к примеру, задав расстояние между
северной и южной сторонами одного компонента, вы укажете его высоту. По умолчанию все
компоненты имеют предпочтительный размер, однако SpringLayout учитывает и два остальных
размера, не делая компоненты меньше минимального и больше максимального размеров.

На первом этапе работы менеджера SpringLayout все компоненты находятся в начале координат


контейнера и имеют предпочтительный размер. Чтобы разместить их по нужным позициям, обычно
проводят следующие действия: первый компонент отделяют некоторым расстоянием от границы
контейнера, второй отделяют от первого расстоянием между нужными границами, далее размещают
третий компонент и т.д.

27. Обработка событий графического интерфейса.

Обработка любого события (нажатие кнопки, щелчок мышью и др.) состоит в связывании события
с методом, его обрабатывающим. Принцип обработки событий, начиная с Java 2, базируется на
модели делегирования событий. В этой модели имеется блок прослушивания события
(EventListener), который ждет поступления события определенного типа от источника, после чего
обрабатывает его и возвращает управление. Источник – это объект, который генерирует событие,
если изменяется его внутреннее состояние, например, изменился размер, изменилось значение
поля, произведен щелчок мыши по форме или выбор значения из списка. После генерации объект-
событие пересылается для обработки зарегистрированному в источнике блоку прослушивания как
параметр его методов – обработчиков событий.

Блоки прослушивания Listener представляют собой объекты классов, реализующих интерфейсы


прослушивания событий, определенных в пакете java.awt.event. Соответствующие методы,
объявленные в используемых интерфейсах, необходимо явно реализовать при создании
собственных классов прослушивания. Эти методы и являются обработчиками события.
Передаваемый источником блоку прослушивания объект-событие является аргументом
обработчика события. Объект класса – блока прослушивания события необходимо
зарегистрировать в источнике методом

источник.addСобытиеListener(объект_прослушиватель);

После этого объект-прослушиватель (Listener) будет реагировать именно на данное событие и


вызывать метод «обработчик события». Такая логика обработки событий позволяет легко
отделить интерфейсную часть приложения от функциональной, что считается необходимым при
проектировании современных приложений. Удалить слушателя определенного события можно с
помощью методаremoveСобытиеListener().

Источником событий могут являться элементы управления: кнопки


(JButton,JCheckbox, JRadioButton), списки, кнопки-меню. События могут генерироваться
фреймами и апплетами, как mouse- и key-события. События генерируются окнами при развертке,
сворачивании, выходе из окна. Каждый класс-источник определяет один или несколько
методов addСобытиеListener() или наследует эти методы

Когда событие происходит, все зарегистрированные блоки прослушивания уведомляются и


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

Каждый интерфейс, включаемый в блок прослушивания, наследуется от


интерфейсаEventListener и предназначен для обработки определенного типа событий. При этом
он содержит один или несколько методов, которые всегда принимают объект события в качестве
единственного параметра и вызываются в определенных ситуациях. В таблице приведены
некоторые интерфейсы и их методы, которые должны быть реализованы в классе прослушивания
событий, реализующем соответствующий интерфейс:

Интерфейсы Обработчики события

ActionListener actionPerformed(ActionEvent e)

AdjustmentListener adjustmentValueChanged(AdjustmentEvent e)

ComponentListener componentResized(ComponentEvent e)

componentMoved(ComponentEvent e)

componentShown(ComponentEvent e)

componentHidden(ComponentEvent e)

ContainerListener componentAdded(ContainerEvent
e)componentRemoved(
ContainerEvent e)

FocusListener focusGained(FocusEvent e)focusLost(FocusEvent e)


ItemListener itemStateChanged(ItemEvent e)
KeyListener keyPressed(KeyEvent e)keyReleased(KeyEvent e)
keyTyped(KeyEvent e)

MouseListener mouseClicked(MouseEvent
e)mousePressed(MouseEvent e)
mouseReleased(MouseEvent e)

mouseEntered(MouseEvent e)

mouseExited(MouseEvent e)

MouseMotionListene mouseDragged(MouseEvent
r e)mouseMoved(MouseEvent e)
TextListener textValueChanged(TextEvent e)
WindowListener windowOpened(WindowEvent
e)windowClosing(WindowEvent e)
windowClosed(WindowEvent e)

windowIconified(WindowEvent e)

windowDeiconified(WindowEvent e)

windowActivated(WindowEvent e)

Событие, которое генерируется в случае возникновения определенной ситуации и затем


передается зарегистрированному блоку прослушивания для обработки, – это объект класса
событий. В корне иерархии классов событий находится суперклассEventObject из пакета java.util.
Этот класс содержит два метода: getSource(), возвращающий источник событий, и toString(),
возвращающий строчный эквивалент события. Абстрактный класс AWTEvent из
пакета java.awt является суперклассом всех AWT-событий, связанных с компонентами.
Метод getID() определяет тип события, возникающего вследствие действий пользователя в
визуальном приложении. Ниже приведены некоторые из классов событий, производных
от AWTEvent, и расположенные в пакете java.awt.event:

ActionEvent – генерируется: при нажатии кнопки; двойном щелчке клавишей мыши по элементам
списка; при выборе пункта меню;

AdjustmentEvent – генерируется при изменении полосы прокрутки;

ComponentEvent – генерируется, если компонент скрыт, перемещен, изменен в размере или


становится видимым;

FocusEvent – генерируется, если компонент получает или теряет фокус ввода;


TextEvent – генерируется при изменении текстового поля;

ItemEvent – генерируется при выборе элемента из списка.

Класс InputEvent является абстрактным суперклассом событий ввода (для клавиатуры или мыши).


События ввода с клавиатуры обрабатывает класс KeyEvent, события мыши – MouseEvent.

Чтобы реализовать методы-обработчики событий, связанных с клавиатурой, необходимо


определить три метода, объявленные в интерфейсе KeyListener. При нажатии клавиши
генерируется событие со значением KEY_PRESSED. Это приводит к запросу обработчика
событий keyPressed(). Когда клавиша отпускается, генерируется событие со
значением KEY_RELEASED и выполняется обработчик keyReleased(). Если нажатием клавиши
сгенерирован символ, то посылается уведомление о событии со значением KEY_TYPED и
вызывается обработчик keyTyped().

28. Многопоточные программы. Класс Thread и интерфейс Runnable

По сути, многопоточность была придумана, чтобы решить две главные задачи:

1. Одновременно выполнять несколько действий.

В примере выше разные потоки (т.е. члены семьи) параллельно выполняли


несколько действий: мыли посуду, ходили в магазин, складывали вещи.

Можно привести и более «программистский» пример. Представь, что у тебя


есть программа с пользовательским интерфейсом. При нажатии кнопки
«Продолжить» внутри программы должны произойти какие-то вычисления, а
пользователь должен увидеть следующий экран интерфейса. Если эти
действия осуществляются последовательно, после нажатия кнопки
«Продолжить» программа просто зависнет. Пользователь будет видеть все тот
же экран с кнопкой «Продолжить», пока все внутренние вычисления не будут
выполнены, и программа не дойдет до части, где начнется отрисовка
интерфейса.

Ускорить вычисления.

Тут все намного проще. Если наш процессор имеет несколько ядер, а большинство
процессоров сейчас многоядерные, список наших задач могут параллельно решать
несколько ядер. Очевидно, что если нам нужно решить 1000 задач и каждая из них
решается за секунду, одно ядро справится со списком за 1000 секунд, два ядра — за
500 секунд, три — за 333 с небольшим секунды и так далее.

На примере с книгами ты увидел, что многопоточность решает достаточно важные


задачи, и ее использование ускоряет работу наших программ. Во многих случаях — в
разы. Но многопоточность недаром считается сложной темой. Ведь при
неправильном использовании она создает проблемы вместо того, чтобы решать их.
Говоря «создавать проблемы» я не имею в виду что-то абстрактное. Есть две
конкретные проблемы, которые может вызвать использование многопоточности —
взаимная блокировка (deadlock) и состояние гонки (race condition). Deadlock —
ситуация, при которой несколько потоков находятся в состоянии ожидания ресурсов,
занятых друг другом, и ни один из них не может продолжать выполнение. Представь,
что поток-1 работает с каким-то Объектом-1, а поток-2 работает с Объектом-2. При
этом программа написана так:
1. Поток-1 перестанет работать с Объектом-1 и переключится на Объект-2, как
только Поток-2 перестанет работать с Объектом 2 и переключится на Объект-
1.
2. Поток-2 перестанет работать с Объектом-2 и переключится на Объект-1, как
только Поток-1 перестанет работать с Объектом 1 и переключится на Объект-
2.

Даже не обладая глубокими знаниями в многопоточности ты легко поймешь, что


ничего из этого не получится. Потоки никогда не поменяются местами и будут
ждать друг друга вечно. Ошибка кажется очевидной, но на самом деле это не так.
Допустить ее в программе можно запросто.

Состояние гонки — ошибка проектирования многопоточной системы или приложения,


при которой работа системы или приложения зависит от того, в каком порядке
выполняются части кода.

В русской терминологии за термином Thread укрепился перевод "Поток". Хотя это слово


также можно перевести как "Нить". Иногда в зарубежных учебных материалах понятие
потока объясняется именно на нитях. Продолжим логический ряд - там где нити , там и
клубок. А где клубок, там и кот. Сразу видно, что у переводчиков не было котов. Так и
возникла путаница. Тем более что существуют другие потоки под термином Stream.
Переводчики, вообще странный народ.

Когда запускается любое приложение, то начинает выполняться поток ,


называемый главным потоком (main). От него порождаются дочерние потоки. Главный
поток, как правило, является последним потоком, завершающим выполнение программы .

Несмотря на то, что главный поток создаётся автоматически, им можно управлять через
объект класса Thread. Для этого нужно вызвать метод currentThread(), после чего можно
управлять потоком.

Класс Thread содержит несколько методов для управления потоками.

 getName() - получить имя потока


 getPriority() - получить приоритет потока
 isAlive() - определить, выполняется ли поток
 join() - ожидать завершение потока
 run() - запуск потока. В нём пишите свой код
 sleep() - приостановить поток на заданное время
 start() - запустить поток

Создать собственный поток не сложно. Достаточно наследоваться от


класса Thread.

Объявим внутри нашего класса внутренний класс и вызовем его по щелчку,


вызвав метод start().
public class MyThread extends Thread {
public void run() {
Log.d(TAG, "Mой поток запущен...");
}
}

public void onClick(View view) {


MyThread myThread = new MyThread();
myThread.start();
}

Есть более сложный вариант создания потока. Для создания нового потока нужно
реализовать интерфейс Runnable. Вы можете создать поток из любого объекта,
реализующего интерфейс Runnable и объявить метод run().

Внутри метода run() вы размещаете код для нового потока. Этот поток завершится, когда
метод вернёт управление.

Когда вы объявите новый класс с интерфейсом Runnable, вам нужно использовать


конструктор:

Thread(Runnable объект_потока, String имя_потока)

В первом параметре указывается экземпляр класса, реализующего интерфейс . Он


определяет, где начнётся выполнение потока. Во втором параметре передаётся имя
потока.

После создания нового потока, его нужно запустить с помощью метода start(), который,
по сути, выполняет вызов метода run().

Внутри конструктора MyRunnable() мы создаём новый объект класса Thread

thread = new Thread(this, "Поток для примера");

В первом параметре использовался объект this, что означает желание вызвать


метод run() этого объекта. Далее вызывается метод start(), в результате чего
запускается выполнение потока, начиная с метода run(). В свою очередь метод
запускает цикл для нашего потока. После вызова метода start(),
конструктор MyRunnable() возвращает управление приложению. Когда главный поток
продолжает свою работу, он входит в свой цикл. После этого оба потока выполняются
параллельно.

29. Состояние потока. Синхронизация потока. Модификатор synchronized.

Можно запускать несколько потоков, а не только второй поток в дополнение к первому .


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

Метод может иметь модификатор syncronized. Когда поток находится внутри


синхронизированного метода, все другие потоки, которые пытаются вызвать его в том же
экземпляре, должны ожидать. Это позволяет исключить путаницу, когда несколько
потоков пытаются вызвать метод.

syncronized void meow(String msg);

Кроме того, ключевое слово syncronized можно использовать в качестве оператора. Вы


можете заключить в блок syncronized вызовы методов какого-нибудь класса:

syncronized(объект) {
// операторы, требующие синхронизации
}

Looper
Поток имеет в своём составе сущности Looper, Handler, MessageQueue.

Каждый поток имеет один уникальный Looper и может иметь много Handler.

Считайте Looper вспомогательным объектом потока, который управляет им. Он


обрабатывает входящие сообщения, а также даёт указание потоку завершиться в нужный
момент.

Поток получает свой Looper и MessageQueue через метод Looper.prepare() после


запуска. Looper.prepare() идентифицирует вызывающий потк,
создаёт Looper и MessageQueue и связывает поток с ними в хранилище ThreadLocal.
Метод Looper.loop() следует вызывать для запуска Looper. Завершить его работу можно
через метод looper.quit().

Состояние Java потока


На диаграмме ниже показаны различные состояния потока в Java. Обратите внимание, что мы
можем создать поток в Java и запустить его, но как состояние потока меняется от Runnable в
Running и в Blocked зависит от реализации системного планировщика потоков (Thread scheduler)
и в Java нет полного контроля над этим процессом.
1. Состояние потока: New
Когда мы создаем новый объект класса Thread, используя оператор new, то поток находится в
состоянии New. В этом состоянии поток еще не работает.

2. Состояние потока: Runnable


Когда мы вызываем метод start() созданного объекта Thread, его состояние изменяется
на Runnable и управление потоком передается Планировщику потоков (Thread scheduler). Ли
запустить эту нить мгновенно или сохранить его в работоспособный пула потоков перед
запуском, это зависит от реализации ОС в планировщик потоков.

3. Состояние потока: Running


Когда поток будет запущен, его состояние изменится на Running. Планировщик потоков выбирает
один поток из своего общего пула потоков и изменяет его состояние на Running. Сразу после
этого процессор начинает выполнение этого потока. Во время выполнения состояние потока
также может изменится на Runnable, Dead или Blocked.

4. Состояние потока: Blocked или Waiting


Поток может ждать другой поток для завершения своей работы, например, ждать освобождения
ресурсов или ввода-вывода. В этом случае его состояние изменяется на Waiting. После того, как
ожидание потока закончилось, его состояние изменяется на Runnable и он
возвращается общий пул потоков.

5. Состояние потока: Dead


После того, как поток завершает выполнение, его состояние изменяется на Dead, то есть он
отработал свое и уже не нужен.
Вот и все, что нужно знать о состояниях потока в Java и работе планировщика потоков (Thread
scheduler)

30. Взаимодействие потоков. Методы wait() и notify().

Иногда при взаимодействии потоков встает вопрос о извещении одних потоков о действиях других.
Например, действия одного потока зависят от результата действий другого потока, и надо как-то известить
один поток, что второй поток произвел некую работу. И для подобных ситуаций у класса Object определено
ряд методов:

 wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор,
пока другой поток не вызовет метод notify()
 notify(): продолжает работу потока, у которого ранее был вызван метод wait()

 notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait()

Все эти методы вызываются только из синхронизированного контекста - синхронизированного блока или
метода.

public class Program {


  
    public static void main(String[] args) {
          
        Store store=new Store();
        Producer producer = new Producer(store);
        Consumer consumer = new Consumer(store);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}
// Класс Магазин, хранящий произведенные товары
class Store{
   private int product=0;
   public synchronized void get() {
      while (product<1) {
         try {
            wait();
         }
         catch (InterruptedException e) {
         }
      }
      product--;
      System.out.println("Покупатель купил 1 товар");
      System.out.println("Товаров на складе: " + product);
      notify();
   }
   public synchronized void put() {
       while (product>=3) {
         try {
            wait();
         }
         catch (InterruptedException e) {
         }
      }
      product++;
      System.out.println("Производитель добавил 1 товар");
      System.out.println("Товаров на складе: " + product);
      notify();
   }
}
// класс Производитель
class Producer implements Runnable{
  
    Store store;
    Producer(Store store){
       this.store=store;
    }
    public void run(){
        for (int i = 1; i < 6; i++) {
            store.put();
        }
    }
}
// Класс Потребитель
class Consumer implements Runnable{
      
     Store store;
    Consumer(Store store){
       this.store=store;
    }
    public void run(){
        for (int i = 1; i < 6; i++) {
            store.get();
        }
    }
}

Итак, здесь определен класс магазина, потребителя и покупателя. Производитель в методе run() добавляет


в объект Store с помощью его метода put() 6 товаров. Потребитель в методе run() в цикле обращается к
методу get объекта Store для получения этих товаров. Оба метода Store - put и get являются
синхронизированными.

Для отслеживания наличия товаров в классе Store проверяем значение переменной product. По умолчанию
товара нет, поэтому переменная равна 0. Метод get() - получение товара должен срабатывать только при
наличии хотя бы одного товара. Поэтому в методе get проверяем, отсутствует ли товар:

1 while (product<1)

Если товар отсутсвует, вызывается метод wait(). Этот метод освобождает монитор объекта Store и
блокирует выполнение метода get, пока для этого же монитора не будет вызван метод notify().

Когда в методе put() добавляется товар и вызывается notify(), то метод get() получает монитор и


выходит из конструкции while (product<1), так как товар добавлен. Затем имитируется получение
покупателем товара. Для этого выводится сообщение, и уменьшается значение product: product--. И в
конце вызов метода notify() дает сигнал методу put() продолжить работу.

В методе put() работает похожая логика, только теперь метод put() должен срабатывать, если в магазине


не более трех товаров. Поэтому в цикле проверяется наличие товара, и если товар уже есть, то освобождаем
монитор с помощью wait() и ждем вызова notify() в методе get().

Таким образом, с помощью wait() в методе get() мы ожидаем, когда производитель добавит новый


продукт. А после добавления вызываем notify(), как бы говоря, что магазин теперь снова пуст, и можно
еще добавлять.

А в методе put() с помощью wait() мы ожидаем освобождения места на складе. После того, как место
освободится, добавляем товар и через notify() уведомляем покупателя о том, что он может забирать
товар.

31. Пакет java.util.concurrent. Интерфейс Lock и его реализации

Наверное у многих возникало чувство некоторого хаоса при беглом взгляде на java.util.concurrent.
В одном пакете намешаны разные классы с совершенно разным функционалом, что несколько
затрудняет понимание что к чему относится и как это работает. Поэтому, можно схематично
поделить классы и интерфейсы по функциональному признаку, а затем пробежаться по
реализации конкретных частей.
Concurrent Collections — набор коллекций, более эффективно работающие в многопоточной
среде нежели стандартные универсальные коллекции из java.util пакета. Вместо базового
враппера Collections.synchronizedList с блокированием доступа ко всей коллекции используются
блокировки по сегментам данных или же оптимизируется работа для параллельного чтения
данных по wait-free алгоритмам.

Queues — неблокирующие и блокирующие очереди с поддержкой многопоточности.


Неблокирующие очереди заточены на скорость и работу без блокирования потоков. Блокирующие
очереди используются, когда нужно «притормозить» потоки «Producer» или «Consumer», если не
выполнены какие-либо условия, например, очередь пуста или перепонена, или же нет свободного
«Consumer»'a.

Synchronizers — вспомогательные утилиты для синхронизации потоков. Представляют собой


мощное оружие в «параллельных» вычислениях.

Executors — содержит в себе отличные фрейморки для создания пулов потоков, планирования
работы асинхронных задач с получением результатов.

Locks — представляет собой альтернативные и более гибкие механизмы синхронизации потоков


по сравнению с базовыми synchronized, wait, notify, notifyAll.

Atomics — классы с поддержкой атомарных операций над примитивами и ссылками.

Locks:

Интерфейс Lock — это абстракция, допускающая выполнение блокировок, которые


реализуются как классы Java, а не как возможность языка (объекта). Это расширяет
возможности применения Lock, которые могут иметь различные алгоритмы
планирования. Блокировка Lock является инструментом для того, чтобы управлять
доступом к совместно используемому ресурсу паралельными потоками.
Condition     — Интерфейс, который описывает альтернативные методы
стандарным wait/notify/notifyAll. Объект с условием чаще всего получается из локов через
метод lock.newCondition(). Тем самым можно получить несколько комплектов wait/notify
для одного объекта.

Lock     — Базовый интерфейс из lock framework, предоставляющий более


гибкий подход по ограничению доступа к ресурсам/блокам нежели при использовании
synchronized. Так, при использовании нескольких локов, порядок их освобождения может
быть произвольный. Плюс имеется возможность пойти по альтернативному сценарию,
если лок уже кем то захвачен.

ReentrantLock     — Лок на вхождение. Только один поток может зайти в


защищенный блок. Класс поддерживает «честную» (fair) и «нечестную» (non-fair)
разблокировку потоков. При «честной» разблокировке соблюдается порядок
освобождения потоков, вызывающих lock(). При «нечестной» разблокировке порядок
освобождения потоков не гарантируется, но, как бонус, такая разблокировка работает
быстрее. По умолчанию, используется «нечестная» разблокировка.

ReadWriteLock     — Дополнительный интерфейс для создания read/write


локов. Такие локи необычайно полезны, когда в системе много операций чтения и мало
операций записи.

ReentrantReadWriteLock     — Очень часто используется в многопоточных


сервисах и кешах, показывая очень хороший прирост производительности по сравнению с
блоками synchronized. По сути, класс работает в 2-х взаимоисключающих режимах: много
reader'ов читают данные в параллель и когда только 1 writer пишет данные.

ReentrantReadWriteLock.ReadLock     — Read lock для reader'ов, получаемый


через readWriteLock.readLock().
ReentrantReadWriteLock.WriteLock     — Write lock для writer'ов, получаемый
через readWriteLock.writeLock().

LockSupport     — Предназначен для построения классов с локами. Содержит


методы для парковки потоков вместо устаревших методов Thread.suspend() и
Thread.resume().

32. Атомарные типы данных

Пакет java.util.concurrent.atomic содержит девять классов для выполнения атомарных операций.


Операция называется атомарной, если её можно безопасно выполнять при параллельных вычислениях
в нескольких потоках, не используя при этом ни блокировок, ни синхронизацию synchronized. Прежде,
чем перейти к рассмотрению атомарных классов, рассмотрим выполнение наипростейших операций
инкремента и декремента целочисленных значений.

С точки зрения программиста операции инкремента (i++, ++i) и декремента (i--, --i) выглядят
наглядно и компактно. Но, с точки зрения JVM (виртуальной машины Java) данные операции не
являются атомарными, поскольку требуют выполнения нескольких действительно атомарных
операции: чтение текущего значения, выполнение инкремента/декремента и запись полученного
результата. При работе в многопоточной среде операции инкремента и декремента могут стать
источником ошибок. Т.е. в многопоточной среде простые с виду операции инкремента и декремента
требуют использование синхронизации и блокировки. Но блокировки содержат массу недостатков, и
для простейших операций инкремента/декремента являются тяжеловесными. Выполнение блокировки
связано со средствами операционной системы и несёт в себе опасность приостановки с
невозможностью дальнейшего возобновления потока, а также опасность взаимоблокировки или
инверсии приоритетов (priority inversion). Кроме этого, появляются дополнительные расходы на
переключение потоков. Но можно ли обойтись без блокировок? В ряде случаев можно!

Блокировка подразумевает пессимистический подход, разрешая только одному потоку выполнять


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

Описание атомарного класса AtomicLong

Рассмотрим принцип действия механизма оптимистической блокировки на примере атомарного


класса AtomicLong, исходный код которого представлен ниже. В этом классе переменная value
объявлена с модификатором volatile, т.е. её значение могут поменять разные потоки одновременно.
Модификатор volatile гарантирует выполнение отношения happens-before, что ведет к тому, что
измененное значение этой переменной увидят все потоки.

Каждый атомарный класс включает метод compareAndSet, представляющий механизм оптимистичной


блокировки и позволяющий изменить значение value только в том случае, если оно равно ожидаемому
значению (т.е. current). Если значение value было изменено в другом потоке, то оно не будет равно
ожидаемому значению. Следовательно, метод compareAndSet вернет значение false, что приведет к
новой итерации цикла while в методе getAndAdd. Таким образом, в очередном цикле в переменную
current будет считано обновленное значение value, после чего будет выполнено сложение и новая
попытка записи получившегося значения (т.е. next). Переменные current и next - локальные, и,
следовательно, у каждого потока свои экземпляры этих переменных.

Список атомарных классов


Атомарные классы пакета java.util.concurrent.atomic можно разделить на 4 группы :

• AtomicBoolean Atomic-классы для boolean, integer, long и ссылок на объекты.


• AtomicInteger Классы этой группы содержат метод compareAndSet,
• AtomicLong принимающий 2 аргумента : предполагаемое текущее и новое
• AtomicReference значения. Метод устанавливает объекту новое значение, если
текущее равно предполагаемому, и возвращает true. Если текущее
значение изменилось, то метод вернет false и новое значение не
будет установлено.
Кроме этого, классы имеют метод getAndSet, который безусловно
устанавливает новое значение и возвращает старое.
Классы AtomicInteger и AtomicLong имеют также методы
инкремента/декремента/добавления нового значения.

• AtomicIntegerArray
Atomic-классы для массивов integer, long и ссылок на объекты.
• AtomicLongArray
Элементы массивов могут быть изменены атомарно.
• AtomicReferenceArray

Atomic-классы для обновления полей по их именам с


• AtomicIntegerFieldUpdater использованием reflection.
• AtomicLongFieldUpdater Смещения полей для CAS операций определяется в конструкторе и
• AtomicReferenceFieldUpdater кэшируются. Сильного падения производительности из-
за reflection не наблюдается.

Atomic-классы для реализации некоторых алгоритмов, (точнее


сказать, уход от проблем при реализации алгоритмов).
• AtomicStampedReference Класс AtomicStampedReference получает в качестве параметров
• AtomicMarkableReference ссылку на объект и int значение.
Класс AtomicMarkableReference получает в качестве параметров
ссылку на объект и битовый флаг (true/false).

Полная документация по атомарным классам на английском языке представлена на оффициальном


сайте Oracle. Наиболее часто используемые классы (не трудно догадаться) сосредоточены в первой
группе.

Производительность атомарных классов

Согласно множеству источников неблокирующие алгоритмы в большинстве случаев более


масштабируемы и намного производительнее, чем блокировки. Это связано с тем, что операции CAS
реализованы на уровне машинных инструкций, а блокировки тяжеловесны и используют приостановку
и возобновление потоков, переключение контекста и т.д. Тем не менее, блокировки демонстрируют
лучший результат только при очень «высокой конкуренции», что в реальной жизни встречается не так
часто.

Основной недостаток неблокирующих алгоритмов связан со сложностью их реализации по сравнению с


блокировками. Особенно это касается ситуаций, когда необходимо контролировать состояние не
одного поля, а нескольких.

33. Шаблоны проектирования. Структурные шаблоны.

Паттерны проектирования - это готовые к использованию решения часто


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

Типы паттернов:

 порождающие
 структурные
 поведенческие
Структурные:

 Adapter (Адаптер) - это конвертер между двумя несовместимыми объектами.


Используя паттерн адаптера, мы можем объединить два несовместимых
интерфейса.
 Composite (Компоновщик) - использует один класс для представления
древовидной структуры.
 Proxy (Заместитель) - представляет функциональность другого класса.
 Flyweight (Легковес) - вместо создания большого количества похожих
объектов, объекты используются повторно.
 Facade (Фасад) - беспечивает простой интерфейс для клиента, и клиент
использует интерфейс для взаимодействия с системой.
 Bridge (Мост) - делает конкретные классы независимыми от классов
реализации интерфейса.
 Decorator (Декоратор) - добавляет новые функциональные возможности
существующего объекта без привязки его структуры.

https://refactoring.guru/ru/design-patterns/catalog

34. Шаблоны проектирования. Порождающие шаблоны

Порождающие:

 Singleton (Одиночка) - ограничивает создание одного экземпляра класса,


обеспечивает доступ к его единственному объекту.
 Factory (Фабрика) - используется, когда у нас есть суперкласс с несколькими
подклассами и на основе ввода, нам нужно вернуть один из подкласса.
 Abstract Factory (Абстрактная фабрика) - используем супер фабрику для
создания фабрики, затем используем созданную фабрику для создания
объектов.
 Builder (Строитель) - используется для создания сложного объекта с
использованием простых объектов. Постепенно он создает больший объект от
малого и простого объекта.
 Prototype (Прототип) - помогает создать дублированный объект с лучшей
производительностью, вместо нового создается возвращаемый клон
существующего объекта.

35. Шаблоны проектирования. Поведенческие шаблоны

Поведенческие:

 Template Method (Шаблонный метод) - определяющий основу алгоритма и


позволяющий наследникам переопределять некоторые шаги алгоритма, не
изменяя его структуру в целом.
 Mediator (Посредник) - предоставляет класс посредника, который
обрабатывает все коммуникации между различными классами.
 Chain of Responsibility (Цепочка обязанностей) - позволяет избежать жесткой
зависимости отправителя запроса от его получателя, при этом запрос может
быть обработан несколькими объектами.
 Observer (Наблюдатель) - позволяет одним обьектам следить и реагировать
на события, происходящие в других объектах.
 Strategy (Стратегия) - алгоритм стратегии может быть изменен во время
выполнения программы.
 Command (Команда) - интерфейс команды объявляет метод для выполнения
определенного действия.
 State (Состояние) - объект может изменять свое поведение в зависимости от
его состояния.
 Visitor (Посетитель) - используется для упрощения операций над
группировками связанных объектов.
 Interpreter (Интерпретатор) - определяет грамматику простого языка для
проблемной области.
 Iterator (Итератор) - последовательно осуществляет доступ к элементам
объекта коллекции, не зная его основного представления.
 Memento (Хранитель) - используется для хранения состояния объекта, позже
это состояние можно восстановить.

36. Сетевое взаимодействие. Основные протоколы, их сходства и отличия.

Как я уже неоднократно писал, современный мир программирования невозможно себе


представить без взаимодействия программ. И в подавляющем большинстве случаев это
взаимодействие осуществляется через сеть.
Я е хотел бы глубоко погружаться в область устройства сети, но думаю, что несколько важных
понятий все-таки надо проговорить.

Протоколы сетевого взаимодействия

Протокол — это по сути правила обмена информацией, которые описывают каким образом
обмениваются информацией взаимодействующие стороны. Если вспомнить достаточно
распространенную фразу “дипломатический протокол”, то суть та же — вы в определенных
случаях должны говорить фразы из определенного набора слов, фраз и другая сторона делает то
же самое. В ИТ-сфере все очень похоже -вы посылаете определенные байты и ждете в ответ
определенные байты. Этот обмен и есть протокол. Если он соблюдается обеими сторонами, то
они смогут о чем-нибудь договориться.

Если рассматривать полную сетевую модель OSI (Open System Interconnection — взаимодействие
открытых систем), то прикладного программиста на Java затрагивают в основном протоколы
Прикладного уровня — HTTP, FTP, SMTP, SNMP и протоколы Транспортного уровня — TCP и UDP.
(там еще есть парочка, но они крайне редко встречаются)

В этой статье я хочу поговорить именно о транспортном уровне, а точнее о протоколе TCP —
Transmission Control Protocol (Протокол Управления Передачей). Именно этот протокол является
основой для очень широкого круга задач — подключения к базам данных, работа через Интернет,
web-сервисы. Это очень важный протокол и на мой взгляд, крайне важно знать инструменты,
которые позволяют с ним работать. Java имеет вполне зрелый инструментарий для этой работы и
мы с ним сейчас будем знакомиться.

Что касается протокола UDP, то он тоже важен и нужен, но в моей практике он встречается реже.
Хотя конечно же многое зависит от того, какую задачу вы решаете. Были у меня проекты, где мы
работали с UDP достаточно плотно.

UDP – User Datagram Protocol (протокол пользовательских датаграмм)

TCP – Transmission Control Protocol (протокол управления передачей)

Оба протокола – из стека TCP/IP (транспортный уровень). Решают задачи


доставки сообщений.

UDP
Простой транспортный протокол.

По сути, берёт порцию данных, снабжает её коротким служебным заголовком и


передаёт всё это протоколу IP для дальнейшей передачи. Факт получения или
пропажи данных никак не отслеживается.

Плюсы Минусы
Быстрее передаёт данные, так как не
выполняет процедуры установления Не гарантирует доставки данных
соединения между узлами
Сообщения имеют ограниченную длину
Позволяет вести широковещательную (65 528 байт). Значит, много данных
рассылку (в рамках локальной сети) нужно передавать несколькими
сообщениями
Имеет короткий служебный заголовок
 
(8 байт)

TCP
Основная задача – надёжная доставка данных.

Ориентирован на соединение: два приложения перед обменом данными должны


выполнить определенные вспомогательные действия,
называемые установлением соединения.

Следствие: в обмене данными всегда участвуют две оконечные точки.


Организовать широковещательную рассылку при помощи протокола TCP нельзя!

Для обеспечения надёжности:

1. Разбивает передаваемые данные на сегменты оптимальнойдлины,


которые приёмник собирается в правильном порядке.
2. При пересылке сегмента использует таймер для ожидания подтверждения
от принимающей стороны (квитанция). Если по истечении определенного
времени квитанция не приходит, выполняется повторная передача сегмента .
3. После получения сегмента данных принимающая сторона проверяет
контрольную сумму сегмента.

Плюсы Минусы
Обеспечивает надёжную доставку
Работает медленнее UDP
информации
  Нет широковещательной рассылки
Медленнее «стартует» (установление
 
соединения)

Использование UDP и TCP


37. Протокол TCP. Классы Socket и ServerSocket.

Сокеты по протоколу ТСР/IP служат для реализации надежных двунаправленных,


постоянных, двухточечных, потоковых соединений между хостами в Интернете.
Сокет может служить для подключения системы ввода-вывода в Java к другим программам,
которые могут находиться как на локальной машине, таки на любой другой машине в Интернете.

В Java поддерживаются две разновидности сокетов по протоколу ТСР /IP: один - для серверов,
другой - для клиентов.

Класс ServerSocket служит "приемником", ожидая подключения клиентов прежде, чем


предпринять какие-нибудь действия. Иными словами, класс ServerSocket предназначен для
серверов, тогда как класс Socket - для клиентов.
Он служит для подключения к серверным сокетам и инициирования обмена данными по сетевому
протоколу. Клиентские сокеты чаще всего применяются в прикладных программах на Java.

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


Выявить это соединение нельзя никакими методами или конструкторами. Ниже перечислены два
конструктора класса Socket, предназначенные для создания клиентских сокетов.

В классе Socket определяется ряд методов экземпляра. Например, объект типа Socket может
быть просмотрен в любой момент для извлечения сведений о связанных с ним адресе и порте.
Для этого применяются методы, перечисленные ниже.
 InetAddress getInetAddress() - возвращает объект типа InetAddress, связанный с
объектом типа Socket. Если же сокет не подключен, возвращается значение null
 int getPort() - возвращает удаленный порт, к которому привязан вызывающий
объект типа Socket. Если же сокет не привязан, возвращается нулевое значение
 int getLocalPort() - возвращает локальный порт, к которому привязан вызывающий
объект типа Socket. Если же сокет не привязан, возвращается значение -1
Для доступа к потокам ввода-вывода, связанным с классом Socket, можно воспользоваться
методами getInputStream() и getOuptutStream(), перечисленными ниже.
Каждый из этих методов может сгенерировать исключение типа IOException, если сокет
оказался недействительным из-за потери соединения.
Эти потоки ввода-вывода используются для передачи и приема данных таким же образом, как и
потоки ввода-вывода.

 InputStream getInputStream() throws IOException - возвращает объект типа


InetAddress, связанный с вызывающим сокетом
 OutputStream getOutputStream() throws IOException - возвращает объект типа
OutputStream, связанный с вызывающим сокетом
Имеется и ряд других методов, в том числе метод connect(), позволяющий указать новое
соединение; метод isConnected(), возвращающий логическое значение true, если сокет
подключен к серверу; метод isBound(), возвращающий логическое значение true, если сокет
привязан к адресу; а также метод isClosed(), возвращающий логическое значение true, если
сокет закрыт.
Чтобы закрыть сокет, достаточно вызвать метод close(). Закрытие сокета приводит также к
закрытию связанных с ним потоков ввода-вывода.
38. Протокол UDP. Классы DatagramSocket и DatagramPacket.

UDP (User Datagram Protocol) не устанавливает виртуального соединения и не

гарантирует доставку данных. Отправитель просто посылает пакеты по указанному

адресу; если отосланная информация была повреждена или вообще не дошла,

отправитель об этом даже не узнает. Однако достоинством UDP является высокая

скорость передачи данных. Данный протокол часто используется при трансляции аудио -

и видеосигналов, где потеря небольшого количества данных не может привести к

серьезным искажениям всей информации.

По протоколу UDP данные передаются пакетами. Пакетом в этом случае UDP является

объект класса DatagramPacket. Этот класс содержит в себе передаваемые данные,

представленные в виде массива байт. Конструкторы класса:

DatagramPacket(byte[] buf, int length)

DatagramPacket(byte[] buf, int length,

InetAddress address, int port)

DatagramPacket(byte[] buf, int offset, int length)

DatagramPacket(byte[] buf, int offset, int length,

InetAddress address, int port)

DatagramPacket(byte[] buf, int offset, int length,

SocketAddress address)

DatagramPacket(byte[] buf, int length,

SocketAddress address)

Для посылки дейтаграмм отправитель и получатель создают сокеты дейта-граммного


типа. В Java их представляет класс DatagramSocket. В классе три конструктора:

 DatagramSocket () — создаваемый сокет присоединяется к любому свободному


порту на локальной машине;
 DatagramSocket (int port) — создаваемый сокет присоединяется к порту port на
локальной машине;
 DatagramSocket(int port, InetAddress addr) — создаваемый СОКСТ при соединяется
к порту port; аргумент addr — один из адресов локальной машины.

Класс содержит массу методов доступа к параметрам сокета и, кроме того, методы
отправки и приема дейтаграмм:

 send(DatagramPacket pack) — отправляет дейтаграмму, упакованную в пакет pack;


 receive (DatagramPacket pack) — дожидается получения дейтаграммы и заносит ее в
пакет pack.

При обмене дейтаграммами соединение обычно не устанавливается, дейтаграммы


посылаются наудачу, в расчете на то, что получатель ожидает их. Но можно установить
соединение методом

connect(InetAddress addr, int port)


При этом устанавливается только одностороннее соединение с хостом по адресу addr и
номером порта port — или на отправку или на прием дейтаграмм. Потом соединение
можно разорвать методом

disconnect()

При посылке дейтаграммы по протоколу JJDP сначала создается сообщение в виде


массива байтов, например,

String mes = "This is the sending message."; 

byte[] data = mes.getBytes();

Потом записывается адрес — объект класса inetAddress, например: 

InetAddress addr = InetAddress.getByName (host);

Затем сообщение упаковывается в пакет — объект класса DatagramPacket. При этом


указывается массив данных, его длина, адрес и номер порта:

DatagramPacket pack = new DatagramPacket(data, data.length, addr, port)

Далее создается дейтаграммный сокет

DatagramSocket ds = new DatagramSocket()

и дейтаграмма отправляется

ds.send(pack)

После посылки всех дейтаграмм сокет закрывается, не дожидаясь какой-либо реакции со


стороны получателя:

ds.close ()

39. Взаимодействие с базами данных. Протокол JDBC. Основные элементы.

Для хранения данных мы можем использовать различные базы данных - Oracle, MS SQL Server,
MySQL, Postgres и т.д. Все эти системы упраления базами данных имеют свои особенности.
Главное, что их объединяет это взаимодействие с хранилищем данных посредством команд SQL.
И чтобы определить единый механизм взаимодействия с этими СУБД в Java еще начиная с 1996
был введен специальный прикладной интерфейс API, который называется JDBC.

То есть если мы хотим в приложении на языке Java взаимодействовать с базой данных, то


необходимо использовать функциональные возможности JDBC. Данный API входит в состав Java
(на текущий момент это версия JDBC 4.3), в частности, для работы с JDBC в программе Java
достаточно подключить пакет java.sql. Для работы в Java EE есть аналогичный пакет javax.sql,
который расширяет возможности JDBC.

Однако не все базы данных могут поддерживаться через JDBC. Для работы с определенной СУБД
также необходим специальный драйвер. Каждый разработчик определенной СУБД обычно
предоставляет свой драйвер для работы с JDBC. То есть если мы хотим работать с MySQL, то нам
потребуется специальный драйвер для работы именно MySQL. Как правило, большиство
драйверов доступны в свободном доступе на сайтах соответствующих СУБД. Обычно они
представляют JAR-файлы. И преимущество JDBC как раз и состоит в том, что мы абстрагируемся
от строения конкретной базы данных, а используем унифицированный интерфейс, который един
для всех.

Для взаимодействия с базой данных через JDBC используются запросы SQL. В то же время
возможности SQL для работы с каждой конкретной СУБД могут отличаться. Например, в MS SQL Server это T-
SQL, в Oracle - это PL/SQL. Но в целом эти разновидности языка SQL не сильно отличаются.

Особенности запуска программы

На процесс компиляции необходимость работы с БД никак не сказывается, но влияет на процесс запуска


программы. При запуске программы в командной строке необходимо указать путь к JAR-файлу драйвера
после параметра -classpath.

java -classpath путь_к_файлу_драйвера:путь_к_классу_программы главный_класс_программы

Например, в папке C:\Java располагаются файл программы - Program.java, скомпилированный класс Program
и файл драйвер, допустим, MySQL - mysql-connector-java-8.0.11.jar. Для выполнения класса Program мы
можем использовать следующую команду:

java -classpath c:\Java\mysql-connector-java-8.0.11.jar;c:\Java Program

Если C:\Java является текущим каталогом, то мы можем сократить команду:


java -classpath mysql-connector-java-8.0.11.jar;. Program

В принципе мы можем и не использовать параметр -classpath, и запустить програму на выполнение обычным


способом с помощью команды "java Program". Но в этом случае путь к драйверу должен быть добавлен в
переменную Path.

40. Создание соединения с базой данных. Класс DriverManager. Интерфейс


DataSource

Класс DriverManager является уровнем управления JDBC, отслеживает все доступные драйверы и


управляет установлением соединений между БД и соответствующим драйвером.

DriverManager.getConnection

Устанавливать соединения с БД можно сразу после регистрации драйвера JDBC. Для этого следует
вызвать метод DriverManager.getConnection, которому передаются параметры соединения с
БД. DriverManager опрашивает каждый зарегистрированный драйвер с целью определения, какой из
них может установить данное соединение. Может оказаться, что установить соединение согласно
параметрам URL могут более одного драйвера JDBC. В этом случае важен порядок, в котором
происходит этот опрос, так как DriverManager будет использовать первый драйвер, откликнувшийся на
URL.

Мост JDBC-ODBC-Bridge
Получить доступ к серверу базы данных можно с использованием моста JDBC - ODBC. Программа
взаимодействия между драйвером JDBC и ODBC была разработана фирмой JavaSoft в сотрудничестве с
InterSolv. Данная "связка" реализована в виде класса JdbcOdbc.class (для платформы Windows
JdbcOdbc.dll).

При использовании JDBC - ODBC необходимо принимать во внимание, что помимо JdbcOdbc-библиотек
должны существовать специальные драйвера (библиотеки), которые реализуют непосредственный
доступ к базам данных через стандартный интерфейс ODBC. Как правило эти библиотеки описываются
в файле ODBC.INI.

На внутреннем уровне JDBC-ODBC-Bridge преобразует методы Java в вызовы ODBC и тем самым


позволяет использовать любые существующие драйверы ODBC, которых к настоящему времени
накоплено в изобилии. Однако, чаще всего, все-таки используется механизм ODBC благодаря его
универсальности и доступности.

Особенности использования JDBC-ODBC

JDBC DriverManager является "хребтом" JDBC-архитектуры, и его основная функция очень проста -


соединить Java-программу и соответствующий JDBC драйвер и затем "выйти из игры". Структура
драйвера ODBC была взята в качестве основы JDBC из-за его популярности среди независимых
поставщиков программного обеспечения и пользователей. Но может возникнуть законный вопрос - а
зачем вообще нужен JDBC? не легче ли было организовать интерфейсный доступ к ODBC-драйверам
непосредственно из Java? Путь через JDBC-ODBC-Bridge, как ни странно, может оказаться гораздо
короче. С чем это связано:

 ODBC основан на C-интерфейсе и его нельзя использовать непосредственно из Java. Вызов из


Java C-кода нарушает целостную концепцию Java и пробивает брешь в защите.
 Так как Java не имеет указателей, а ODBC их использует, то перенос ODBC C-API в Java-API
нежелателен.
 Java-API необходим, чтобы добиться абсолютно чистых Java решений. Когда ODBC
используется, то ODBC-драйвер и ODBC менеджер должны быть инсталлированы на каждой
клиентской машине. В то же время, JDBC драйвер написан полностью на Java и может быть
легко переносим на любые платформы.

DataSource:
В Установлении Соединения Вы изучили, как получить соединение,
используя DriverManager class. Этот раздел показывает Вам, как использовать
a DataSource объект получить соединение с Вашим источником данных, который является
привилегированным путем.

Объекты, которые инстанцируют классы, которые реализуют DataSource представьте


определенный DBMS или некоторый другой источник данных, такой как файл.
A DataSource объект представляет определенный DBMS или некоторый другой источник
данных, такой как файл. Если компания будет использовать больше чем один источник данных, то
она развернет отдельное DataSource объект для каждого из них. DataSource интерфейс
реализуется поставщиком драйвера. Это может быть реализовано тремя различными способами:

 Основное DataSource реализация производит стандарт Connection объекты, которые


не объединяются в пул или используются в распределенной транзакции.
 A DataSource реализация, которая поддерживает объединение в пул соединения,
производит Connection объекты, которые участвуют в объединении в пул соединения,
то есть, соединения, которые могут быть переработаны.
 A DataSource реализация, которая поддерживает распределенные транзакции,
производит Connection объекты, которые могут использоваться в распределенной
транзакции, то есть, транзакция что доступы два или больше сервера DBMS.

DriverManager.

1. препятствует производительности приложения, поскольку соединения


создаются/закрываются в классах Java.
2. не поддерживает объединение пулов.

DataSource

1. повышает производительность приложений, поскольку соединения не


создаются/закрываются внутри класса, они управляются сервером приложений и
могут быть извлечены во время выполнения.
2. он предоставляет средство создания пула соединений
3. полезно для корпоративных приложений

41. Создание запросов. Интерфейсы Statement, PreparedStatement, CallableStatement

 Statement
Этот интерфейс используется для доступа к БД для общих целей. Он
крайне полезен, когда мы используем статические SQL – выражения во
время работы программы. Этот интерфейс не принимает никаких
параметров.
 PreparedStatement
Этот интерфейс используется в случае, когда мы планируем использовать
SQL – выражения множество раз. Он принимает параметры во время
работы программы.
 CallableStatement
Этот интерфейс становится полезным вслучае, когда мы хотим получить
досутп к различным процедурам БД. Он также может прнимать параметры
во время работы программы.

Создание экземпляра Statement


Прежде, чем мы сможем использовать экземпляр Statement для выполнения
SQL – запросов, нам необходимо создать такой экземпляр. ДЛя этого
используется метод Connection.createStatement(). В коде это выглядит таким
образом:

try {
statement =connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
} finally {
/*Do some job...*/
}

После этго мы можем использовать наш экземпляр statement для выполнения


SQL – запросов.

Для этой цели интерфейс Statement имеет три метода, которые реализуются
каждой конкретной реализацией JDBC драйвера:

 boolean execute (String SQL)


Этот метод возвращает логическое значение true, если объект ResultSet
может быть получен. В противном случае он вовращает false. Он
используется для выполнения DDL SQL – запросов ил в случаях, когда мы
используем динамический SQL.
 int executeUpdate (String SQL)
Этот метода возвращает количесство столбцов в таблице, на которое
повлиял наш SQL – запрос. Мы используем этот метод для выполнения SQL
– запросов, когда хотим получить количество задействованных столбцов ,
например количество данных по определённому запросу.
 ResultSet executeQuery (String SQL)
Этот мтеод возвращает нам экземпляр ResultSet. Мы используем этот
метод в случаях, когда мы рассчитываем получить множество объектов в
результате выполнения нашего SQL – запроса. Например, при получении
списка элементов, которые удовлетворяют опредлённым условиям.

Закрытие экземпляра Statement

Когда мы закрываем наше соединение (Connection) для сохранения результатов


в БД мы таким же образом закрываем и экpемпляр Statement.

Для этого мы испольузем метод close().

Рассмотрим, как это выглядит в нашем коде:

Connection connection = null;


Statement statement = null;

Class.forName(JDBC_DRIVER);
connection = DriverManager.getConnection(DATABASE_URL, USER, PASSWORD);

try {
statement = connection.createStatement();
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (statement != null) {
statement.close();
}
}

Создание экземпляра PreparedStatement

PreparedStatement наследует интерфейс Statement, что даёт нам опредёлнные


преимущества перед обычным Statement. В частности, мы получаем большую
гибкость при динамичской поддержке аргументов.

Вот как выглядит создание экземпляра PreparedStatement на практике:

try {
String SQL = "Update developers SET salary WHERE specialty = ?";
preparedStatement = connection.prepareStatement(SQL);
}catch (SQLException e){
e.printStackTrace();
}finally {
/*do some job...*/
}

Все параметры, которые отмечены символом ? называются маркерами


параметра. Это означает, что они должны быть переданы через параметры
метода.

Каждый параметр ссылается на свой порядковый номер в сигнатуре метода . Т.е .


первый маркер находится на первом месте, второй – на втором и т.д. В отличие
от массивов, здесь отсчёт идёт с 1. Это связано с особенностями реляционной
модели, на которой и основана работа реляционных БД.

Для выполнения SQL – запросов используются методы с такими же названиями


(execute(), executeQuery, executeUpdate), которые несколько модифицированы.

Закрытие экземпляра PreparedStatement

Когда мы закрываем наше соединение (Connection) для сохранения результатов


в БД мы таким же образом закрываем и экземпляр PreparedStatement.

Для этого мы испольузем метод close().

Рассмотрим, как это выглядит в нашем коде:

try {
String SQL = "Update developers SET salary WHERE specialty = ?";
preparedStatement = connection.prepareStatement(SQL);
} catch (SQLException e) {
e.printStackTrace();
} finally {
if (preparedStatement != null) {
preparedStatement.close();
}
}

Создание экземпляра CallableStatement


Экземпляр CallableStatement используется для выполнения процедур,
непосредоственно в самой БД.

Рассмотрим пример, в котором нам необходимо выполнить такую процедуру в


MySQL:

DELIMITER $$
DROP PROCEDURE IF EXISTS `developers`.`getDeveloperName` $$
CREATE PROCEDURE PROSELYTE_TUTORIALS.`getDeveloperName`
(IN DEVELOPER_ID INT, OUT DEVELOPER_NAME VARCHAR(50))
BEGIN
SELECT first INTO DEVELOPER_NAME
FROM developers
WHERE id = DEVELOPER_ID;

END $$

DELIMITER ;

Существует три типа параметров: IN, OUT, INOUT. PreparedStatement использует


только IN, а CallableStatement, в свою очередь, использует все три.

Рассмотрим, что же это за параметры:

 IN
Параметр, значение которого известно в момент, когда создаётся запрос .
Мы назначем параметр IN с помощью метода типа setXXX().
 OUT
Параметр, значение которого возвращается SQL – запросом. Мы получаем
значения из  OUT с помощью методов типа getXXX().
 INOUT
Параметр, который использует входные и выходные значения. Мы
назначем параметр с помощью метода типа setXXX(), а получаем
значения, с помощью метода типа getXXX().

В коде, создание экземпляра CallableStatement выглядит следующим образом:

try {
String SQL = "{call getDeveloperName (?, ?)}";
callableStatement = connection.prepareCall(SQL);
}finally {
/* do some job */
}

Строка SQL представляет собой процедуру, с параметрами.

Схожим с PreparedStatement способом, мы, используя экземпляр


CallableStatement, должны установить значения параметров.

Когда мы используем параметры типа OUT и INOUT, нам


необходимозадействовать дополнительный метод registerOutParameter(). Этот
метод устанавливает тип данных JDBC в тип данных процедуры.

После того, как мы вызвали рпоцедуру, мы получаем значение из параметра


OUT с помощью соответствующего метода getXXX(). Этот метод преобразует
полученное значение из типа дыннх SQL в тип данных Java.
Закрытие экземпляра CallableStatement

Когда мы закрываем наше соединение (Connection) для сохранения результатов


в БД мы таким же образом закрываем и экземпляр Statement.

Для этого мы испольузем метод close().

Рассмотрим, как это выглядит в нашем коде:

try {
String SQL = "{call getDeveloperName (?, ?)}";
callableStatement = connection.prepareCall(SQL);
}finally {
if(callableStatement!=null){
callableStatement.close();
}
}

42. Обработка результатов запроса. Интерфейсы ResultSet и RowSet.

Базы данных всё таки о данных, а не о запросах. В JDBC данные, которые


возвращают запросы, представлены в виде объектов ResultSet.
ResultSet, в свою очередь, жёстко связан со Statement, который его породил и
существует только до момента закрытия этого самого Statement  или даже
раньше, до выполнения нового запроса в Statement.
Для доступа к данным интерфейс ResultSet реализует смесь шаблонов Итератор и
Курсор: внутри ResultSet есть указатель, который указывает на какую-либо строку
(или даже не на строку, а в никуда) в данных. Этот указатель можно передвигать
программно и запрашивать данные из столбцов текущей строки. По умолчанию
курсор ResultSet находится перед первой строкой набора данных.
Существует целых восемь методов, перемещающих курсор по ResultSet:
 next() — перемещает курсор на одну строку вперёд. Возвращает true, если
перемещение удалось и false, если курсор уже находится за последней строкой.
 previous() — очевидно, антоним next(). Перемещает курсорс на одну строку назад и
тоже возвращает true, если перемещение удалось и false, если курсор находится перед
первой строкой.
 first() и last() — перемещают курсор соответственно на первую и последнюю строку
набора данных. В случае, если набор данных пуст, возвращают false. В случае
успешного перемещения возвращают true.
 beforeFirst() и afterLast() — перемещают курсор на позицию перед первой строкой
или после последней строки.
 relative() — перемещает курсор на указанное число строк от текущей позиции.
 absolute() — перемещает курсор на указанное число строк от первой позиции.
Стоит отметить, что не все эти методы всегда работают.  ResultSet (а точнее
конкретная его реализация драйвером JDBC) может не поддерживать перемещение
кроме как вперёд. Такой ResultSet называется TYPE_FORWARD_ONLY. В случае,
если перемещение возможно, открытый ResultSet может следить за изменениями в
базе данных, произошедшими после его открытия или не следить. В первом случае
это будет TYPE_SCROLL_SENSITIVE ResultSet, во втором TYPE_SCROLL_INSENSITIVE.
Читать из ResultSet немного не интуитивно, но сравнительно просто: перемещаем
курсор в нужные строки и запрашиваем содержимое столбцов.
1 protected static void readResultSet(Connection db) throws SQLException {
2   System.out.println("Dumping ORDER_ITEMS table:");
3   try (Statement results = db.createStatement()) {
4     ResultSet rs =
5         results.executeQuery("SELECT * FROM ORDER_ITEMS");
6     while (rs.next()) {
7       System.out.println(
8         String.format(
9           "client=%d, order=%d, item=%d",
10           rs.getInt("CLIENT_ID"),
11           rs.getInt("ORDER_ID"),
12           rs.getInt(3)));
13      }
14   }
15 }
1 Dumping ORDER_ITEMS table:
2 client=1, order=1, item=1
3 client=1, order=1, item=2
4 client=1, order=1, item=3
5 client=1, order=1, item=4
6 client=1, order=1, item=5
7 client=2, order=2, item=1
8  
9 [skip]
10 client=3, order=3, item=4
11 client=3, order=3, item=5
Методы getType() возвращают объект нужного типа, с учётом типа в базе данных. К
столбцам можно обращаться как по имени, причем без учёта регистра, так и по
номеру, причём отсчёт столбцов начинается с единицы.

Запись в ResultSet
Да да, именно запись в ResultSet. JDBC позволяет не только читать данные
из ResultSet, но и записывать их обратно и эти изменения будут автоматически
переданы в базу. Конечно, такое поведение может быть несколько непривычно для
тех, кто работает с SQL базами данных, но JDBC рассчитан не только на SQL базы,
но и например, на какой-нибудь там FoxPro, в котором именно так данные и
обновляют (или обновляли, лет 20 назад).
Обновление данных производится не сложнее, чем чтение: выбираем строку, пишем
в столбцы и сохраняем.

1 protected static void updateResultSet(Connection db) throws SQLException {

2         try (Statement updatableResult = db.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,

3                 ResultSet.CONCUR_UPDATABLE)) {

4             ResultSet rs =

5                     updatableResult.executeQuery("SELECT * FROM ORDER_ITEMS");

6             rs.absolute(5);

7             rs.updateInt("CLIENT_ID", 2);

8             rs.updateRow();

9  

10             rs.moveToInsertRow();

11             rs.updateInt("CLIENT_ID", 1);

12             rs.updateInt("ORDER_ID", 1);

13             rs.updateInt("ITEM_ID", 10);

14             rs.insertRow();

15         }

16     }

1 Dumping ORDER_ITEMS table:


2 client=1, order=1, item=1

3 client=1, order=1, item=2

4 client=1, order=1, item=3

5 client=1, order=1, item=4

6 client=2, order=1, item=5

7 client=2, order=2, item=1

8 [skip]

9 client=3, order=3, item=5

10 client=1, order=1, item=10

Вторая часть кода из примера показывает, что строки можно не только обновлять но
и добавлять! Добавляются строки почти так же, как и обновляются: перемещаемся в
специальное место(ага, девятый метод позиционирования в ResultSet), обновляем
значения столбцов, вставляем строку.
Опять таки, не всякий ResultSet может обновлять данные, а только созданный
с CONCUR_UPDATABLE и только если драйвер JDBC этот режим поддерживает.

RowSet
RowSet расширяет ResultSet и делает его совместимым с концепцией JavaBean (то
есть с конструктором по умолчанию, сериализуемым и т.д.). Поскольку
интерфейс RowSet расширяет интерфейс ResultSet, весь вышеперечисленный
функционал, разумеется, остаётся доступным и в RowSet. Главными
отличиями RowSet от ResultSet является тот факт, что RowSet есть JavaBean, со
свойствами и нотификациями. Кроме того, RowSet можно строить напрямую из
соединения с базой, пропуская отдельно создание запроса.

Самая простая реализация RowSet — JdbcRowSet, обычно обёртка над ResultSet:


1   protected static void jdbcRowSet(Connection db) throws SQLException {

2     JdbcRowSet rs = new JdbcRowSetImpl(db);

3     rs.setCommand("SELECT * FROM ORDER_ITEMS");

4     rs.execute();

5  

6     rs.moveToInsertRow();

7     rs.updateInt("CLIENT_ID", 1);

8     rs.updateInt("ORDER_ID", 1);

9     rs.updateInt("ITEM_ID", 11);

10     rs.insertRow();

11  

12     rs.execute();

13     rs.beforeFirst();

14     System.out.println("Dumping ORDER_ITEMS table using RowSet:");

15     while (rs.next()) {
16       System.out.println(

17         String.format(

18            client=%d, order=%d, item=%d",

19            rs.getInt("CLIENT_ID"),

20            rs.getInt("ORDER_ID"),

21            rs.getInt("ITEM_ID")));

22     }

23 }

JdbcRowSet можно создать как в примере выше, а можно напрямую из ResultSet. Этот


тип RowSet, так же как и ResultSet, требует наличия соединения с базой для работы с
данными.

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

1 protected static CachedRowSet cachedRowSet(Connection db) throws SQLException {

2   try (Statement results = db.createStatement()) {

3     ResultSet rs =

4       results.executeQuery("SELECT * FROM ORDER_ITEMS");

5     CachedRowSet cs = new CachedRowSetImpl();

6     cs.populate(rs);

7  

8     return cs;

9   }

10 }

11  

12 public static void main(final String[] args) {

13   CachedRowSet cs = null;

14   try (Connection db = DriverManager.getConnection("jdbc:h2:mem:")) {

15     cs = cachedRowSet(db);

16   } catch (SQLException ex) {

17     System.out.println("Database connection failure: "

18       + ex.getMessage());

19   } catch (IOException ex) {

20     System.out.println("I/O error: "

21       + ex.getMessage());

22   }
23   try {

24     System.out.println("Dumping ORDER_ITEMS table without database connection:");

25     assert cs != null;

26     while(cs.next()) {

27       System.out.println(

28         String.format(

29           "client=%d, order=%d, item=%d",

30           cs.getInt("CLIENT_ID"),

31           cs.getInt("ORDER_ID"),

32           cs.getInt("ITEM_ID")));

33     }

34   } catch (SQLException ex) {

35     System.out.println("Database connection failure: "

36       + ex.getMessage());

37   }

38 }

CachedRowSet можно спокойно возвращать и передавать куда угодно, не опасаясь,


что его соединение с базой внезапно закроется и RowSet превратится в тыкву.
Такой тип RowSet называется disconnected,  как и  все нижеописанные RowSet.

JoinRowSet
Делает слияние таблиц в памяти. Конечно, с точки зрения эффективности выгоднее
делать join непосредственно на стороне базы, но не всякая база умеет join
(вспоминаем FoxPro опять, ага). Чтобы слить таблицы, вначале необходимо
получить две таблицы для слияния и потом добавить их в JoinRowSet, указав по
какому полю сливать их.
1 protected static void joinRowSet(Connection db) throws SQLException {

2   CachedRowSet orders = new CachedRowSetImpl();

3   CachedRowSet clients = new CachedRowSetImpl();

4   try (Statement results = db.createStatement()) {

5     ResultSet rs =

6       results.executeQuery("SELECT * FROM ORDER_ITEMS");

7     orders.populate(rs);

8   }

9   try (Statement results = db.createStatement()) {

10     ResultSet rs =

11       results.executeQuery("SELECT * FROM CLIENTS");

12     clients.populate(rs);
13   }

14   JoinRowSet jrs = new JoinRowSetImpl();

15   jrs.addRowSet(orders, "CLIENT_ID");

16   jrs.addRowSet(clients, "ID");

17  

18   System.out.println("Dumping client logins and their items:");

19   while (jrs.next()) {

20     System.out.println(

21       String.format(

22         "client=%s, order=%d",

23         jrs.getString("LOGIN"),

24         jrs.getInt("ORDER_ID")));

25   }

26 }

1 client=test, order=1

2 client=test, order=1

3 client=example, order=3

4 client=example, order=3

5 [skip]

После для слияния указывается для каждого участника слияния отдельно. Можно
использовать или название столбца (регистронезависимое) или номер (нумерация
начинается с единицы). Тип столбца в обоих таблицах должен совпадать.

FilteredRowSet
RowSet который умеет сам себя фильтровать. На первый вгляд кажется
бесполезной вещью (даже FoxPro умеет в условия), но на самом деле очень удобен,
так как фильтры в коде могут быть гораздо гибче, чем условия выборки в базе. Да и
фильтровать какой-нибудь постоянно висящий в памяти словарь становится
выгоднее, чем постоянно его перезапрашивать.

Создаётся и наполняется данными FilteredRowSet как обычно, а потом ему


методом setFilter() назначается объект класса, реализующего интерфейс Predicate.
Причём это не тот модный функциональный интерфейс Predicate, который
используется с лямбда выражениями и потоками, а собственный

кондовый Predicate из JDBC. Так что никаких лямбд 


1 private static class ClientFilter implements Predicate {

2   @Override

3   public boolean evaluate(RowSet rs) {

4     try {
5       return rs.getInt("CLIENT_ID") == 3;

6     } catch (SQLException e) {

7       return false;

8     }

9   }

10  

11   @Override

12   public boolean evaluate(Object value, int column) throws SQLException {

13     return !(column == 1 && !"3".equals(value));

14   }

15  

16   @Override

17   public boolean evaluate(Object value, String columnName) throws SQLException {

18     return !("CLIENT_ID".equals(columnName) && !"3".equals((String) value));

19   }

20 }

21  

22 protected static void filteredRowSet(Connection db) throws SQLException {

23   try (Statement results = db.createStatement()) {

24     ResultSet rs =

25       results.executeQuery("SELECT * FROM ORDER_ITEMS");

26     FilteredRowSet fs = new FilteredRowSetImpl();

27     fs.populate(rs);

28  

29     fs.setFilter(new ClientFilter());

30  

31     System.out.println("Dumping only 3rd client from ORDER_ITEMS table:");

32     while (fs.next()) {

33       System.out.println(

34         String.format(

35           "client=%d, order=%d, item=%d",

36           fs.getInt("CLIENT_ID"),

37           fs.getInt("ORDER_ID"),

38           fs.getInt("ITEM_ID")));

39       }

40     }
41   }

42 }

1 Dumping only 3rd client from ORDER_ITEMS table:

2 client=3, order=3, item=1

3 client=3, order=3, item=2

4 client=3, order=3, item=3

5 client=3, order=3, item=4

6 client=3, order=3, item=5

WebRowSet
WebRowSet умеет сам сохранять себя в XML и создавать себя из XML же. В том
году, когда его изобретали, это было удивительным достижением конечно, а сейчас
выглядит слегка архаично.

1 protected static void webRowSet(Connection db) throws SQLException, IOException {

2   try (Statement results = db.createStatement()) {

3     ResultSet rs =

4       results.executeQuery("SELECT * FROM ORDER_ITEMS");

5     WebRowSet ws = new WebRowSetImpl();

6     ws.populate(rs);

7  

8     ws.writeXml(System.out);

9   }

10 }

XML представление
Результат в XML настолько огромен и ужасен, что его приходится прятать под кат.

ResultSet vs RowSet
Что же выбрать? Оба интерфейса выглядят хорошо и сравнительно одинаково. Какой
из них использовать? Ответ мой любимый: «это зависит». С одной стороны,
ResultSet выглядит более низкоуровневым и неудобным. В RowSet можно и listeners
приделывать и в памяти сразу фильтровать и работать с данными в отсутствие базы
(и обновлять кстати можно тоже). Но. Цена этого удобства — память. ResultSet в
общем виде может не иметь доступа более чем к одной строке результатов и
обращаться к базе при каждом движении указателя. И это хорошо: во-первых можно
начинать работу с данными, когда они только начали поступать, не дожидаясь, пока
сформируется весь ответ. Во-вторых, если данных слишком много, а памяти
слишком мало, может не получиться их обработать. Я могу дать только такой совет:
если вам нужен функционал RowSet, используйте его. Если нет, выбирайте по
ситуации, что использовать.

43. Интернационализация. Локализация. Хранение локализованных ресурсов


Вопрос интернационализации пользовательского интерфейса - один из важных вопросов при
разработке приложения. Для этого недостаточно использовать Unicode и перевести на нужный язык
все сообщения пользовательского интерфейса. Интернационализация приложения означает нечто
большее, чем поддержка Unicode. Дата, время, денежные суммы и даже числа могут по-разному
представляться на различных языках.

Широкое распространение получили условные сокращения


терминов интернационализации и локализации приложений i18n и l10n, в которых цифра означает
количество символов между первой и последней позицией:

 i18n - интернационализация (internationalization);


 l10n - локализация (localization).

В отдельной литературе делают акцент на этих двух определениях, под которыми понимается :

1. Интернационализация - это процесс разработки приложения такой структуры, при которой


дополнение нового языка не требует перестройки и перекомпиляции (сборки) всего
приложения.
2. Локализация предполагает адаптацию интерфейса приложения под несколько языков.
Добавление нового языка может внести определенные сложности в локализацию интерфейса.

Java - первый язык программирования, в котором изначально были предусмотрены средства


интернационализации. Строки формируются из символов Unicode. Поддержка этого стандарта
кодирования позволяет создавать Java-приложения, обрабатывающие тексты на любом из
существующих в мире языков.

Большинство фреймворков, используемые в Java 2EE, поддерживает интернационализацию


приложений с использованием Java-технологии, существенно снижая трудозатраты при разработке
Web-приложения, "говорящего" на нескольких языках.

Региональные стандарты Locale


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

Существует ряд классов, которые выполняют форматирование, принимая во внимание указанные выше
различия. Для управления форматированием используется класс Locale.

Региональный стандарт Locale определяет язык. Кроме этого могут быть указаны географическое


расположение и вариант языка. Например, в США используется следующий региональный стандарт:

language=English, location=United States

В Германии региональный стандарт имеет вид :

language=German, location=Germany

В Швейцарии используются четыре официальных языка : немецкий, французский, итальянский и


ретороманский. Поэтому немецкие пользователи в Швейцарии, вероятно, захотят использовать
следующий региональный стандарт:

language=German, location=Switzerland

В данном случае текст, даты и числа будут форматироваться так же, как и для Германии, но денежные
суммы будут отображаться в швейцарских франках, а не в евро. Если задавать только язык,
например language=German, то особенности конкретной страны (например, формат представления
денежных единиц) не будут учтены.
Вариант языка используется довольно редко. Например, в настоящее время в норвежском языке
(производном от датского) определены два набора правил правописания (Bokmel) и новый (Nynorsk).
В этом случае, для задания традиционных правил орфографии используется параметр, определяющий
вариант:

language=Norwegian, location=Norway, variant=Bokmel

Для выражения языка и расположения в компактной и стандартной форме в Java используются коды,
определенные Международной организацией по стандартизации (ISO). Язык обозначается двумя
строчными буквами в соответствии со стандартом ISO-639, а страна ( расположение)- двумя
прописными буквами согласно стандарту ISO-3166.

Чтобы задать региональный стандарт, необходимо объединить код языка, код страны и вариант (если
он есть), а затем передать полученную строку в конструктор класса Locale.

Locale german = new Locale ("de");


Locale germanGermany = new Locale ("de", "DE");
Locale germanSwitzerland = new Locale ("de", "CH");
Locale norwegianNorwayBokmel = new Locale ("no","NO","B");

Для удобства пользователей в JDK предусмотрено несколько предопределенных объектов с


региональными настройками, а для некоторых языков также имеются объекты, позволяющие указать
язык без указания страны:

Предопределенные объекты с региональными Объекты, позволяющие указать язык без


установками указания страны

Locale.CHINA Locale.CHINESE

Locale.FRANCE Locale.FRENCH

Locale.GERMANY Locale.GERMAN

Locale.ITALY Locale.ITALIAN

Locale.JAPAN Locale.JAPANESE

Locale.US Locale.ENGLISH

Помимо вызова конструктора или выбора предопределенных объектов, существует еще два пути
получения объектов с региональными настройками. Статический
метод getDefault() класса Locale позволяет определить региональную настройку, которая используется
в операционной системе по-умолчанию. Изменить настройку по-умолчанию можно вызвав
метод setDefault (). Однако следует помнить, что данный метод воздействует только на Java-
программу, а не на операционную систему в целом.

Региональные настройки, getAvailableLocales

Метод getLocale() возвращает региональные настройки того компьютера, на котором он запущен. И


наконец, все зависимые от региональных настроек вспомогательные классы могут возвращать массив
поддерживаемых региональных стандартов. Например, приведенный ниже метод возвращает все
региональные настройки, поддерживаемые классом DateFormat.

Locale [] supportedLocales = DateFormat.getAvailableLocales();

Какие действия можно выполнять на основе полученных региональных настроек? Выбор невелик.
Единственными полезными методами класса Locale являются методы определения кодов языка и
страны. Наиболее важными из них является метод getDisplayName(), возвращающий строку с
описанием региональной настройки, которая содержит не какие-то двухбуквенные загадочные коды, а
вполне понятные пользователю обозначения

German (Switzerland)

Но данная строка отображается на используемом по умолчанию языке, что далеко не всегда бывает
удобно. Если пользователь выбрал немецкий язык интерфейса, то строку описания следует отобразить
именно на немецком языке, для чего можно передать в качестве параметра соответствующую
региональную настройку так, как представлено в следующих строках кода :

Locale loc = new Locale ("de", "CH");


System.out.println (loc.getDisplayName (Locale.GERMAN));

В результате выполнения этого кода описание региональной настройки будет выведено на указанном в
ней языке :

Deutsch (Schweiz)

Данный пример поясняет, зачем нужны объекты Locale. Передавая их методам, способным


реагировать на региональные настройки, можно отображать текст на языке, понятном пользователю.

Форматирование числовых значений NumberFormat


Ранее упоминалось, что в разных странах и регионах используются различные способы представления
чисел и денежных сумм. В пакете java.text содержатся классы, позволяющие форматировать числа и
выполнять разбор их строкового представления. Для форматирования чисел в соответствии с
конкретным региональным стандартом необходимо выполнить ряд действий:

1. Получить объект регионального стандарта, как было описано в предыдущем разделе.


2. Использовать фабричный метод для получения объекта форматирования.
3. Применить полученный объект форматирования для формирования числа или разбора его
строкового представления.

В качестве фабричных методов (factory method) используются статические методы getNumberInstance


(), getCurrencyInstance (), getPercentInstance () класса NumberFormat. Они получают в качестве
параметра объект Locale и возвращают объекты, предназначенные для форматирования чисел,
денежных сумм и значений, выраженных в процентах. Например, для отображения денежной суммы в
формате, принятом в Германии, можно использовать приведенный ниже фрагмент кода:

Locale loc = new Locale ("de", "DE");


NumberFormat currFmt;
currFmt = NumberFormat.getCurrencyInstance (loc);
double amt = 123456.78;
System.out.println (currFmt.format (amt));

В результате выполнения этого кода будет получена следующая строка:

123.456,78 €
 

Для обозначения евро здесь используется знак €, который располагается в конце строки. Кроме этого,
следует обратить внимание на символы, применяемые для обозначения дробной части и разделения
десятичных разрядов.

Для преобразования строки, записанной в соответствии с определенным региональным стандартом, в


число предусмотрен метод parse (), который выполняет синтаксический анализ строки с
автоматическим использованием заданного по умолчанию регионального стандарта. В приведенном
ниже примере показан способ преобразования строки, введеной пользователем в поле
редактирования, в число. Метод parse () способен преобразовывать числа, в которых в качестве
разделителя используется точка и запятая.

TextField inputField;
. . .
NumberFormat fmt = NumberFormat.getNumberInstance ();
// Получить объект форматирования для используемого
// по умолчанию регионального стандарта
Number input = fmt.parse (inputField.getText ().trim ());
double x = input.doubleValue ();

Метод parse () возвращает результат абстрактного типа Number. На самом деле возвращаемый объект


является экземпляром класса Long или Double, в зависимости от того, представляет исходная строка
целое число или число с плавающей точкой. Если это не важно, то для получения числового значения
достаточно использовать метод doubleValue() класса Number.

Для объектов типа  Number  не поддерживается автоматическое приведение к простым типам.


Необходимо явным образом вызывать метод  doubleValue ()  или  intValue ().

Если число представлено в некорректном формате, генерируется исключение ParseException.


Например, не допускается наличие символа пробела в начале строки, преобразуемой в число. (Для их
удаления следует использовать метод trim ()). Любые символы, которые располагаются в строке после
числа, лишь игнорируются и исключение в этом случае не возникает.

Очевидно, что классы, возвращаемые методами getXxxInstance (), являются экземплярами не


абстрактного класса NumberFormat, а одного из его подклассов. Фабричным методам известно лишь
то, как найти объект, представляющий определенный региональный стандарт.

Для получения списка поддерживаемых региональных стандартов можно использовать статистический


метод getAvailableLocales, возвращающий массив региональных стандартов, для которых существуют
объекты форматирования.

44. Форматирование локализованных числовых данных, текста, даты и времени.

Класс PropertyResourceBundle использует файлы настроек (properties) для хранения


текста. Эти файлы не являются частью кода Java и они могут содержать только
объекты String.
Класс ListResourceBundle использует список локализованных ресурсов, хранящихся в
классах Java.
Класс java.util.ResourceBundle  очень гибкий. Если вы сначала
использовали PropertyResourceBundle, чтобы хранить локализованные строки в
файлах properties, а позже решили использовать ListResourceBundle, то это не
отразится на коде. Например, следующий getBundle  получает ResourceBundle 
для Locale  независимо от способа хранения этого ResourceBundle.
Java
1 ResourceBundle introLabels = ResourceBundle.getBundle(
2                                  "ButtonLabel", currentLocale);
Метод getBundle  сначала ищет класс с указанным базовым именем, а затем, если его не
находит, ищет файл properties.
Класс ResourceBundle  содержит массив пар ключ-значение. Вы указываете ключ,
который должен быть String, когда вам нужно достать значение из ResourceBundle.

45. Пакет java.time. Классы для представления даты и времени.


Наиболее удобный и современный способ работы с датой и временем. Берёт своё начало
от библиотеки Joda-Time.
Есть два базовых способа представления времени. Один способ представляет время в
терминах человека, таких как год, месяц, день, час, минуты и секунды. Второй способ
представляет машинное время, измеряя время непрерывно с начала, называемого эпохой,
в наносекундах. Пакет Date-Time содержит большое количество классов, представляющий
дату и время. Некоторые классы в Date-Time API представляют машинное время,
некоторые человеческое.

Сначала определите, какие аспекты даты и времени вам нужны, затем выберите класс или
классы, которые подходят под ваши нужды.

Например, вы можете выбрать java.time.LocalDate для хранения даты рождения, так


как многие люди празднуют день рождения в тот же день, независимо от того, находятся
ли они в месте рождения или на другом конце Земли. Если вам нужно астрологическое
время, то вы можете использовать java.time.LocalDateTime, чтобы показать дату и
день рождения, либо java.time.ZonedDateTime, который дополнительно содержит
часовой пояс. Если вы создаёте временную отметку, то вероятнее всего вы захотите
использовать java.time.Instant, который позволяет сравнивать одну временную
отметку с другой.

java.time.DayOfWeek

Перечисление java.time.DayOfWeek состоит из семи констант, описывающих дни


недели. Целочисленные значения для констант начинаются с 1 (Понедельник) и
заканчиваются 7 (Воскресенье).
Список констант:

MONDAY  (понедельник, 1)
TUESDAY  (вторник, 2)
WEDNESDAY  (среда, 3)
THURSDAY  (четверг, 4)
FRIDAY  (пятница, 5)
SATURDAY  (суббота, 6)
SUNDAY  (воскресенье, 7)
Вы можете использовать
метод public String getDisplayName(TextStyle style, Locale locale)  для
получения названий дней недели в соответствии с региональными настройками
пользователя. Перечисление java.time.format.TextStyle  позволяет указать тип
строки: FULL, NARROW  (обычно одна буква), SHORT  (аббревиатура).
Пример:

Java

1 DayOfWeek dow = DayOfWeek.MONDAY;

2 Locale locale = Locale.getDefault();

3 System.out.println(dow.getDisplayName(TextStyle.FULL, locale));

4 System.out.println(dow.getDisplayName(TextStyle.NARROW, locale));

5 System.out.println(dow.getDisplayName(TextStyle.SHORT, locale));
java.time.Month
Перечисление java.time.Month  содержит константы для двенадцати месяцев,
пронумерованных от 1 до 12:
JANUARY  (январь, 1)
FEBRUARY  (февраль, 2)
MARCH  (март, 3)
APRIL  (апрель, 4)
MAY  (май, 5)
JUNE  (июнь, 6)
JULY  (июль, 7)
AUGUST  (август, 8)
SEPTEMBER  (сентябрь, 9)
OCTOBER  (октябрь, 10)
NOVEMBER  (ноябрь, 11)
DECEMBER  (декабрь, 12)
Перечисление java.time.Month  содержит несколько полезных методов. Например,
метод maxLength()  возвращает максимально возможное количество дней в месяце:
System.out.printf("%d%n", Month.FEBRUARY.maxLength());
Также есть метод public String getDisplayName(TextStyle style, Locale locale),
позволяющий получить текстовое название месяца в соответствии с указанной локалью:
Java

1 Month month = Month.AUGUST;

2 Locale locale = Locale.getDefault();

3 System.out.println(month.getDisplayName(TextStyle.FULL, locale));

4 System.out.println(month.getDisplayName(TextStyle.NARROW, locale));

5 System.out.println(month.getDisplayName(TextStyle.SHORT, locale));

java.time.LocalDate
Класс java.time.LocalDate хранит год, месяц и день. Он используется для хранения и
обработки даты без времени. Примеры создания:
Java

1 LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20);

2 LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));

В дополнение к обычным методам класс java.time.LocalDate  содержит методы для


получения информации о дате. Метод getDayOfWeek  возвращает день недели.
Например, следующий код вернёт MONDAY:
Java

1 DayOfWeek dotw = LocalDate.of(2012, Month.JULY, 9).getDayOfWeek();

Следующий пример использует TemporalAdjuster, чтобы следующую среду от


указанной даты:
Java

1 LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20);

2 TemporalAdjuster adj = TemporalAdjusters.next(DayOfWeek.WEDNESDAY);

3 LocalDate nextWed = date.with(adj);


4 System.out.printf("For the date of %s, the next Wednesday is %s.%n",

5                   date, nextWed);

java.time.YearMonth
Класс java.time.YearMonth представляет месяц с годом. Следующие примеры
используют YearMonth.lengthOfMonth(), чтобы определить количество дней в
конкретном годе и месяце:
Java

1 YearMonth date = YearMonth.now();

2 System.out.printf("%s: %d%n", date, date.lengthOfMonth());

4 YearMonth date2 = YearMonth.of(2010, Month.FEBRUARY);

5 System.out.printf("%s: %d%n", date2, date2.lengthOfMonth());

7 YearMonth date3 = YearMonth.of(2012, Month.FEBRUARY);

8 System.out.printf("%s: %d%n", date3, date3.lengthOfMonth());

Этот код выведет в консоль:

1 2013-06: 30

2 2010-02: 28

3 2012-02: 29

java.time.MonthDay
Класс java.time.MonthDay содержит день с месяцем. Следующий пример
использует MonthDay.isValidYear, чтобы определить, является ли 29 февраля
корректной датой для 2010 года. Этот вызов вернёт false, так как 2010 год не является
високосным.
Java

1 MonthDay date = MonthDay.of(Month.FEBRUARY, 29);

2 boolean validLeapYear = date.isValidYear(2010);

java.time.Year
Класс java.time.Year хранит год. Следующий пример использует метод Year.isLeap ,
чтобы определить, является ли год високосным. Этот вызов вернёт true, так как 2012 год
високосный.
Java

1 boolean validLeapYear = Year.of(2012).isLeap();

java.time.LocalTime
Класс java.time.LocalTime оперирует только временем. Он полезен  для хранения
времени открытия/закрытия магазина и т. д. Пример:
Java

1 LocalTime thisSec;


3 for (;;) {

4     thisSec = LocalTime.now();

6     // Предположим, что метод display уже есть

7     display(thisSec.getHour(), thisSec.getMinute(), thisSec.getSecond());

8}

Класс LocalTime  не сохраняет информацию о часовой поясе и летнем/зимнем времени.


java.time.LocalDateTime
Класс java.time.LocalDateTime хранит дату и время. Он является нечтом вроде
комбинации LocalDate  и LocalTime. В дополнение к методу now(), который есть у
каждого временного класса, класс LocalDateTime  содержит большое количество
методов of, которые позволяют создать экземпляры LocalDateTime. Метод from 
конвертирует экземпляр другого класса в LocalDateTime. Также есть методы для
добавления и вычитания часов, минут, дней и недель Пример:
Java

1 System.out.printf("now: %s%n", LocalDateTime.now());

2  

3 System.out.printf("Apr 15, 1994 @ 11:30am: %s%n",

4                   LocalDateTime.of(1994, Month.APRIL, 15, 11, 30));

5  

6 System.out.printf("now (from Instant): %s%n",

7                   LocalDateTime.ofInstant(Instant.now(), ZoneId.systemDefault()));

8  

9 System.out.printf("6 months from now: %s%n",

10                   LocalDateTime.now().plusMonths(6));

11  

12 System.out.printf("6 months ago: %s%n",

13                   LocalDateTime.now().minusMonths(6));

Этот код выведет в консоль:

1 now: 2013-07-24T17:13:59.985

2 Apr 15, 1994 @ 11:30am: 1994-04-15T11:30

3 now (from Instant): 2013-07-24T17:14:00.479

4 6 months from now: 2014-01-24T17:14:00.480

5 6 months ago: 2013-01-24T17:14:00.481

java.time.ZoneId и java.time.ZoneOffset
Часовой пояс — это участок земной поверхности, на котором используется одно и то же
стандартное время. Каждый часовой пояс определяется идентификатором, имеющим
формат регион/город (Asia/Tokyo), и смещением от Гринвича/UTC. Например, смещение
для Токио +9:00.
Date-Time API содержит два класса для указания часового пояса или смещения:

 java.time.ZoneId указывает идентификатор часового пояса и поставляет


правила для конвертирования java.time.Instant  в java.time.LocalDateTime.
 java.time.ZoneOffset указывает смещение часового пояса от
Гринвича/UTC.
Смещения от Гринвича/UTC обычно определяются в полных часах, но есть исключения.
Следующий код выводит в консоль все часовые пояса, которые используют смещение от
Гринвича/UTC, указанные не в полных часах.

java.time.ZonedDateTime

Класс java.time.ZonedDateTime можно рассматривать как


комбинацию java.time.LocalDateTime  и java.time.ZoneId. Он представляет собой
полную дату, время и часовой пояс.
Следующий код определяет время вылета из Сан-Франциско в Токио
как java.time.ZonedDateTime  в часовом поясе America/Los Angeles. Для получения
экземпляра java.time.ZonedDateTime, содержащего время прибытия в Токио после
650 минут полёта, используются методы withZoneSameInstant  и plusMinutes.
Метод ZoneRules.isDaylightSavings  определяет, используется ли летнее время по
прибытии в Токио.
Для форматированного вывода java.time.ZonedDateTime  используется
объект java.time.format.DateTimeFormatter.

java.time.OffsetDateTime

Класс java.time.OffsetDateTime можно рассматривать как


комбинацию java.time.LocalDateTime  и java.time.ZoneOffset . Он содержит
полную дату, время и смещение от Гринвича/UTC (+/-часы:минуты).
Следующий пример использует класс java.time.OffsetDateTime  и
метод TemporalAdjuster.lastDay, чтобы найти последний четверг в июле 2013 года.

java.time.OffsetTime

Класс java.time.OffsetTime можно рассматривать как


комбинацию java.time.LocalTime  и java.time.ZoneOffset . Он содержит время и
смещение от Гринвича/UTC (+/-часы:минуты).
java.time.Instant
Класс java.time.Instant — это один из самых основных классов Date-Time API,
который представляет наносекунды от 1 января 1970 года 00:00:00 по UTC.
Класс java.time.Instant  может содержать отрицательное значение, если он
представляет время до 1 января 1970 года.

46. Рефлексия. Классы Class, Field, Method, Constructor.

Рефлексия (от позднелат. reflexio — обращение назад) — это механизм


исследования данных о программе во время её выполнения. Рефлексия позволяет
исследовать информацию о полях, методах и конструкторах классов. Сам же
механизм рефлексии позволяет обрабатывать типы, отсутствующие при компиляции,
но появившиеся во время выполнения программы. Рефлексия и наличие логически
целостной модели выдачи информации об ошибках дает возможность создавать
корректный динамический код. Иначе говоря, понимание принципов работы
рефлексии в java открывает перед вами ряд удивительных возможностей. Вы
буквально можете жонглировать классами и их составляющими. Вот основной список
того, что позволяет рефлексия:

 Узнать/определить класс объекта;


 Получить информацию о модификаторах класса, полях, методах, константах,
конструкторах и суперклассах;
 Выяснить, какие методы принадлежат реализуемому
интерфейсу/интерфейсам;
 Создать экземпляр класса, причем имя класса неизвестно до момента
выполнения программы;
 Получить и установить значение поля объекта по имени;
 Вызвать метод объекта по имени.

Рефлексия используется практически во всех современных технологиях Java. Сложно


себе представить, могла бы Java, как платформа, достигнуть такого огромного
распространения без рефлексии. Скорее всего не смогла бы.

Попробуем добраться до private поля name класса MyClass:


public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //no getter =(
System.out.println(number + name);//output 0null
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
System.out.println(number + name);//output 0default
}
Разберем что тут сейчас произошло. В java есть замечательный класс Class. Он
представляет классы и интерфейсы в исполняемом приложении Java. Связь между
Class и ClassLoader мы затрагивать не будем, т.к. это не есть тема статьи. Далее,
чтобы получить поля этого класса нужно вызвать метод getFields(), этот метод
вернет нам все доступные поля класса. Нам это не подходит, так как наше поле
private, поэтому используем метод getDeclaredFields(), этот метод также
возвращает массив полей класса, но теперь и private и protected. В нашей
ситуации мы знаем имя поля, которое нас интересует, и можем использовать метод
getDeclaredField(String), где String — имя нужного поля. Примечание:
getFields() и getDeclaredFields() не возвращают поля класса-родителя! Отлично,
мы получили объект Field с ссылкой на наш name. Т.к. поле не было публичным
(public) следует дать доступ для работы с ним. Метод setAccessible(true)
разрешает нам дальнейшую работу. Теперь поле name полностью под нашим
контролем! Получить его значение можно вызовом get(Object) у объекта Field, где
Object — экземпляр нашего класса MyClass. Приводим к типу String и присваиваем
нашей переменной name. На тот случай если у нас вдруг не оказалось setter’a, для
установки нового значения полю name можно использовать метод set:
field.set(myClass, (String) "new value");
Поздравляю! Вы только что овладели базовым механизмом рефлексии и
смогли получить доступ к private полю! Обратите внимание на блок try/catch и
типы обрабатываемых исключений. IDE сама укажет на их обязательное присутствие,
но по их названию итак ясно зачем они здесь. Идем дальше! Как вы могли заметить,
наш MyClass уже имеет метод для вывода информации о данных класса:
private void printData(){
System.out.println(number + name);
}
Но этот программист и тут наследил. Метод находится под модификатором доступа
private, и нам пришлось самим каждый раз писать код вывода. Не порядок, где там
наша рефлексия?… Напишем вот такую функцию:
public static void printData(Object myClass){
try {
Method method = myClass.getClass().getDeclaredMethod("printData");
method.setAccessible(true);
method.invoke(myClass);
} catch (NoSuchMethodException | InvocationTargetException |
IllegalAccessException e) {
e.printStackTrace();
}
}
Здесь примерно такая же процедура как и с получением поля — получаем нужный
метод по имени и даем доступ к нему. И для вызова объекта Method используем
invoke(Оbject, Args), где Оbject — все также экземпляр класса MyClass. Args —
аргументы метода — наш таковых не имеет. Теперь для вывода информации мы
используем функцию printData:
public static void main(String[] args) {
MyClass myClass = new MyClass();
int number = myClass.getNumber();
String name = null; //?
printData(myClass); // outout 0default
try {
Field field = myClass.getClass().getDeclaredField("name");
field.setAccessible(true);
field.set(myClass, (String) "new value");
name = (String) field.get(myClass);
} catch (NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
printData(myClass);// output 0new value
}

Ура, теперь у нас есть доступ к приватному методу класса. Но что делать если у
метода все таки будут аргументы, и зачем тот закомментированный конструктор?
Всему свое время.

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

В классе class нет конструкторов, экземпляр этого класса создается исполняющей системой Java во время
загрузки класса и предоставляется методом getciass() класса objec

Класс Field предоставляет возможность:
 получить значение поля, его тип, имя а так же модификаторы поля
 получить список аннотаций, класс, в котором объявлено поле и другую
информацию
 установить новое значение в поле, даже если оно объявлено как private

Получение значения переменной


Для того, чтобы получить значение из класса Field существуют методы getByte(), getShort(), getInt(), getLong(),
getFloat(), getDouble(), getChar(), getBoolean() и get(). Как вы уже догадались, первые 8 методов существуют для
получения примитивов, а последний для получения объектов.

Класс Method
Класс Method предоставляет возможность:
 получить название метода, его модификаторы, тип возвращаемого значения и
входящих параметров
 получить аннотации метода, бросаемые исключения и другую информацию
 вызвать метод, даже приватный
 getAnnotations() возвращает массив аннотаций метода
 getAnnotation() возвращает аннотацию по типу
 getAnnotationsByType() возвращает массив аннотаций по типу. Метод был
добавлен в Java 8 вместе с @Repeatable аннотациями
 getParameterCount() возвращает количество входящих параметров
 getParameters() возвращает массив всех входящих параметров в виде
класса Parameter
 getParameterTypes() возвращает массив типов входящих параметров в виде
класса Class
 getGenericParameterTypes() возвращает массив дженерик входящих типов
параметров.
 getTypeParameters() возвращает массив дженериков входящих типов в виде
класса TypeVariable
 getParameterAnnotations() возвращает массив аннотаций входящих
параметров

Constructor - ???

47. Функциональные интерфейсы и λ-выражения. Пакет java.util.function.

Функциональный интерфейс в Java – это интерфейс, который содержит только 1


абстрактный метод. Основное назначение – использование в лямбда выражениях и
method reference.

Наличие 1 абстрактного метода - это единственное условие, таким образом


функциональный интерфейс может содержать так же default и static методы. К
функциональному интерфейсу можно добавить аннотацию @FunctionalInterface. Это
не обязательно, но при наличии данной аннотации код не скомпилируется, если будет
больше или меньше, чем 1 абстрактный метод. Рекомендуется добавлять
@FunctionalInterface. Это позволит использовать интерфейс в лямбда выражениях, не
остерегаясь того, что кто-то добавит в интерфейс новый абстрактный метод и он
перестанет быть функциональным. В Java есть встроенные функциональные
интерфейсы, размещенные в пакете java.util.function.

Среди новшеств, которые были привнесены в язык Java с выходом JDK 8, особняком стоят
лямбда-выражения. Лямбда представляет набор инструкций, которые можно выделить в
отдельную переменную и затем многократно вызвать в различных местах программы.

Основу лямбда-выражения составляет лямбда-оператор, который представляет стрелку ->.


Этот оператор разделяет лямбда-выражение на две части: левая часть содержит список
параметров выражения, а правая собственно представляет тело лямбда-выражения, где
выполняются все действия.

Лямбда-выражение не выполняется само по себе, а образует реализацию метода, определенного


в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен
содержать только один единственный метод без реализации.

Рассмотрим пример:

1 public class LambdaApp {


2  
3     public static void main(String[] args) {
4          
5         Operationable operation;
6         operation = (x,y)->x+y;
7          
8         int result = operation.calculate(10, 20);
9         System.out.println(result); //30
10     }  
11 }
12 interface Operationable{
13     int calculate(int x, int y);
14 }

В роли функционального интерфейса выступает интерфейс Operationable, в котором определен


один метод без реализации - метод calculate. Данный метод принимает два параметра - целых
числа, и возвращает некоторое целое число.

По факту лямбда-выражения являются в некотором роде сокращенной формой внутренних


анонимных классов, которые ранее применялись в Java. В частности, предыдущий пример мы
можем переписать следующим образом:

Чтобы объявить и использовать лямбда-выражение, основная программа разбивается на ряд этапов:

1. Определение ссылки на функциональный интерфейс:

1 Operationable operation;

2. Создание лямбда-выражения:

1 operation = (x,y)->x+y;
3. Причем параметры лямбда-выражения соответствуют параметрам единственного метода интерфейса
Operationable, а результат соответствует возвращаемому результату метода интерфейса. При этом
нам не надо использовать ключевое слово return для возврата результата из лямбда-выражения.
4. Так, в методе интерфейса оба параметра представляют тип int, значит, в теле лямбда-выражения
мы можем применить к ним сложение. Результат сложения также представляет тип int, объект
которого возвращается методом интерфейса.
5. Использование лямбда-выражения в виде вызова метода интерфейса:

1 int result = operation.calculate(10, 20);

6. Так как в лямбда-выражении определена операция сложения параметров, результатом метода будет
сумма чисел 10 и 20.

тложенное выполнение

Одним из ключевых моментов в использовании лямбд является отложенное выполнение (deferred execution).
То есть мы определяем в одном месте программы лямбда-выражение и затем можем его вызывать при
необходимости неопределенное количество раз в различных частях программы. Отложенное выполнение
может потребоваться, к примеру, в следующих случаях:

 Выполнение кода отдельном потоке

 Выполнение одного и того же кода несколько раз

 Выполнение кода в результате какого-то события

 Выполнение кода только в том случае, когда он действительно необходим и если он необходим

Передача параметров в лямбда-выражение

Параметры лямбда-выражения должны соответствовать по тип параметрам метода из функционального


интерфейса.

48. Конвейерная обработка данных. Пакет java.util.stream.

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

C чего, собственно, начать? С создания экземпляра Stream, который опирается на


нужную нам коллекцию, массив или метод их и откуда соответственно будут браться
данные:
List<String> list = new ArrayList<String>();
list.add("One");
list.add("Two");
list.add("Three");
list.add("Four");
list.add("Five");
list.add("Six");
list.add("Seven");
list.add("Eight");
list.add("Nine");
list.add("Ten");
Stream stream = list.stream();
Как говорилось выше, Stream API позволяет сократить количество строк кода. Пример
c потоком:
IntStream.of(50, 60, 70, 80, 90, 100, 110, 120).filter(x -> x < 90).map(x
-> x + 10)
.limit(3).forEach(System.out::print);
Пример без потока:
int[] arr = {50, 60, 70, 80, 90, 100, 110, 120
int count = 0;
for (int x : arr) {
if (x >= 90) continue;
x += 10;
count++;
if (count > 3) break;
System.out.print(x);
}
Возможные способы создания 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<String> list = new ArrayList<String>();
2.list.add("One");

11.list.add("Ten");
12.Stream stream = list.stream();
13.stream.filter(x-> x.toString().length() ==
3).forEach(System.out::println);
Что здесь происходит:

 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) (не возьмёт ни единого элемента из
списка);
 Экземпляр, стрима нельзя использовать более одного раза =( ;

Поэтому каждый раз новый:

list.stream().filter(x-> x.toString().length() ==
3).forEach(System.out::println);
list.stream().forEach(x -> System.out.println(x));

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


множество, в то время терминальный оператор только один:

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:
String[] array = {"Java", "Ruuuuussshhh"};
Stream<String> streamOfArray = Arrays.stream(array);
streamOfArray.map(s->s.split("")) //Преобразование слова в массив букв
.flatMap(Arrays::stream).distinct() //выравнивает каждый
сгенерированный поток в один поток
.collect(Collectors.toList()).forEach(System.out::println);
В то время когда map преобразует в список потоков (точнее <Stream> потоков)
[stream1,stream2,stream3,stream4]
=>Stream.of(stream1,stream2,stream3,stream4):
String[] array = {"Java", "Ruuuuussshhh"};
Stream<String> streamOfArray = Arrays.stream(array);
streamOfArray.map(s->s.split("")) //Преобразование слова в массив букв
.map(Arrays::stream).distinct() //Сделать массив в отдельный
поток
.collect(Collectors.toList()).forEach(System.out::println);
Ещё одно отличие в сравнении с map, можно преобразовать один элемент в ноль,
один или множество других. Для того, чтобы один элемент преобразовать в ноль
элементов, нужно вернуть null, либо пустой стрим. Чтобы преобразовать в один
элемент, нужно вернуть стрим из одного элемента, например, через Stream.of(x).
Для возвращения нескольких элементов, можно любыми способами создать стрим с
этими элементами. Тот же метод flatMap, но для Double, Integer и Long:

 flatMapToDouble(Function mapper)
 flatMapToInt(Function mapper)
 flatMapToLong(Function mapper)

И ещё пример для сравнения, flatMap:


Stream.of(2, 3, 0, 1, 3)
.flatMapToInt(x -> IntStream.range(0, x))
.forEach(System.out::print);// 010120012

 IntStream.range(0,x) – выдаёт на поток элементов с 0 (включительно) по x


(не включительно);

map:
Stream.of(2, 3, 0, 1, 3)
.map(x -> IntStream.range(0, x))
.forEach(System.out::print);//перечень стримов(потоков);

 limit(long maxSize) – ограничивает стрим по количеству элементов:


stream.limit(5).forEach(x -> System.out.println(x));

 skip(long n) – пропускаем n элементов:


stream.skip(3).forEach(x -> System.out.println(x));

 sorted()
 sorted(Comparator comparator) – сортирует стрим (сортировка как
у TreeMap):

stream.sorted().forEach(x -> System.out.println(x));

 distinct() — проверяет стрим на уникальность элементов(убирает повторы


элементов);

 dropWhile(Predicate predicate) — пропускает элементы которые


удовлетворяют условию (появился в 9 java, Функциональный
интерфейс Predicate<T> проверяет соблюдение некоторого условия. Если оно
соблюдается, то возвращается значение true. В качестве параметра лямбда-
выражение принимает объект типа T:
 Predicate<Integer> isPositive = x -> x > 0;
 System.out.println(isPositive.test(3)); // true
System.out.println(isPositive.test(-9)); // false

 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 становится её
результатом. Затем всё продолжается для остальных элементов.
int sum = Stream.of(1, 2, 3, 4, 5).reduce(10, (acc, x) -> acc +
x);//10 + 1 + 2 +3 + 4 = 20

 reduce(BinaryOperator accumulator) — такой же метод как и выше но


отсутсвует начальный identity, им служит первый элемент стрима

Optional min(Comparator comparator)


Optional max(Comparator comparator) ищет минимальный/максимальный
элемент, основываясь на переданном компараторе;

 findFirst() – вытаскивает первый элемент стрима:


Stream.of(1, 2, 3, 4, 9).findFirst();

 allMatch(Predicate predicate) — возвращает true, если все элементы


стрима удовлетворяют условию. Если встречается какой-либо элемент, для
которого результат вызова функции-предиката будет false, то оператор
перестаёт просматривать элементы и возвращает false:
Stream.of(1, 2, 3, 4, 9).allMatch(x -> x <= 7);//false

 anyMatch(Predicate predicate) — вернет true, если хотя бы один элемент


стрима удовлетворяет условию predicate:
Stream.of(1, 2, 3, 4, 9).anyMatch(x -> x >= 7);//true

 noneMatch(Predicate predicate) — вернёт true, если, пройдя все элементы


стрима, ни один не удовлетворил условию predicate:
Stream.of(1, 2, 3, 4, 9).noneMatch(x -> x >= 7);//false
И хотелось бы напоследок просмотреть некоторые методы Collectors:

 toList() — собирает элементы в List:


List<Integer> list = Stream.of(99, 2,
3).collect(Collectors.toList());

 toSet() — cобирает элементы в множество:


Set<Integer> set = Stream.of(99, 2, 3).collect(Collectors.toSet());

 counting() — Подсчитывает количество элементов:


Long count = Stream.of("1", "2", "3",
"4").collect(Collectors.counting());

 joining()

 joining(CharSequence delimiter)

 joining(CharSequence delimiter, CharSequence prefix, CharSequence


suffix) — cобирает элементы в одну строку. Дополнительно можно указать
разделитель, а также префикс и суффикс для всей последовательности:
 String a = Stream.of("s", "u" ,"p", "e",
"r").collect(Collectors.joining());
 System.out.println(a); // super

 String b = Stream.of("s", "u", "p", "e",
"r").collect(Collectors.joining("-"));
 System.out.println(b); // s-u-p-e-r

 String c = Stream.of("s", "u", "p", "e",
"r").collect(Collectors.joining(" -> ", "[ ", " ]"));
System.out.println(c); // [ s -> u -> p -> e -> r ]

 summingInt(ToIntFunction mapper)

 summingLong(ToLongFunction mapper)

 summingDouble(ToDoubleFunction mapper) — коллектор, который


преобразовывает объекты в int/long/double и подсчитывает сумму.

Оценить