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

Инструменты сайта


develop:java:in

Begin

ООП

В отличии от процедурных типов, в ООП на первом месте стоят данные, а уже после алгоритмы для их обработки

:!: Notes

Инкапсуляция - (сокрытие информации) объединение свойств с методами класса в одном пакете и сокрытие данных от другого.
Запрещение прямого доступа к полям класса из другого класса, состояние объекта меняется только через его методы

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

Объекты и объектные переменные не одно и тоже. Объект всегда сам по себе, переменные это лишь ссылки на него, присвоив такую переменную другой переменной, обе они будут ссылаться на один и тот же объект
Без инициализации, объектная переменная ничего не содержит и с-но нельзя вызывать никакие методы
Объекты можно свободно использовать напрямую, без ссылок (типа «var d = new Date().toString()»), но тогда только единожды, сборщик мусора чистит все потом автоматом
Операция new так же возвращает ссылку на объект, хранящийся в другом месте

Все объекты хранятся в динамической памяти, у переменных проводится параллель с ссылками в C++

Существует 3 типа отношений между классами:

  • Зависимость (методы одного выполняют какие либо д-я другого)
  • Агрегирование (содержит другие объекты, точнее ссылки на них)
  • Наследование

Date

Есть два класса с разным назначением
«Date()» представляет момент времени, для расчета/подсчета, в мс
«LocalDate()» создан для представления даты/времени в нужном формате и т.д., он содержит такие методы как getYear/Month() и т.д.

:!: Notes

Некоторые методы, в т.ч. «Date().plusDays() или String().toUpperCase()», не модифицируют исхрдный объект а возвращают новый

Классы

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

:!: Notes

Каждый класс может содержаться в отдельном файле, может и несколько быть в одном, обычно раздельно
Утилита «javac» компилирует указанный файл. Если есть связи, она автоматически скомпилирует связанные файлы, и обновит при необходимости, работает аналогично утилите «make» в линукс
В каждом файле должен быть один класс с модификатором «public» и методом «main()», как точкой входа в программу
Конструкторы вызываются только с оператором «new», всегда
Неявный аргумент «this» так же присутствует во всех методах, смысл остается прежним

:!: NullPointerExceptionNotes

Распространенная мозоль, возникает если обратится к методу объекта который содержит «null» вместо ссылки на объект
Есть пару спец методов, например Objects.requireNonNullElse(var, «Default value») или Objects.requireNonNull(var, «Исключение с данным сообщением»)

:!: Notes

«final» константа, указывает на то что поле не изменяемое в этом классе, константы можно делать открытыми, принцип инкапсуляции не нарушается
«static» статическое поле, т.е. поле класса а не объекта, существует в единственном экземпляре, всегда и доступно всем объектам класса
Есть понятие «блок инициализации», в описании класса, блок без заголовка, в нем можно инициализировать свойства создаваемого объекта, выполняется перед конструктором
Есть и статический блок инициализации, для статических полей с-но

:!: Пакеты. Notes

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

Параметры методов

:!: Notes

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

Рекомендации

:!: Notes

Всегда храните данные в переменных объявленных как private
Следует всеми способами избегать нарушения инкапсуляции

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

Не употребляйте в классе слишком много простых типов
Связанные между собой данные объединяйте в классы

Не для всех полей нужно создавать методы доступа и модификации
Существуют поля которые не требуют изменения после создания объекта

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

Осмысленные названия классов и методов
Имена классов обычно являются существительными и(или вместе с) прилагательными, методы в свою очередь глаголы

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

Классы, суперклассы и подклассы

:!: Наследование. Notes

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

Модификатором «final» можно определить класс конечным т.е. го наследование становится запрещено, при указании модификатора, его методы автоматически становятся тоже конечными, но не поля

Рекомендации:
Размещайте общие поля и операции в супер-классе

Старайтесь не пользоваться защищенными полями
Лучше пресекать возможность нарушения инкапсуляции

Используйте наследование для моделирования отношений «является»
Если подкласс по смыслу «не является» базовым, то наследование не оправдано, внесет только больше путаницы и проблем

Не применяйте наследование если не все методы имеет смысл наследовать

Переопределяя метод, не изменяйте его предполагаемое поведение

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

:!: Полиморфизм. Notes

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

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

:!: Абстрактные классы. Notes

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

Если класс содержит абстрактные методы то уже является абстрактным, модификатор «abstract» у класса можно указывать можно нет, можно указать даже если нет абстрактных методов, подклассы тоже будут абстрактными (непонятка, в книге сказано что «не перестанет быть абстрактным даже если определить все методы», как тогда создаются объекты ? оО хня какая-то)

Собсна объекты не создаются у абстрактных классов

:!: Еще раз про доступы. Модификаторы могут быть следующими:

  • private - ограничивает пределами класса
  • public - не ограничивает
  • protected - пределами класса и всеми подклассами
  • не указан - по умолчанию пределы пакета
:!: Списочный массив. ArrayList

Массив динамической длинны, для хранения объектов, объявляется одним из следующих образов:

ArrayList<ClassName> myVar = new ArrayList<ClassName>();
ArrayList<ClassName> myVar = new ArrayList<>();
var myVar = new ArrayList<ClassName>();
var myVar = new ArrayList<>();

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

Для упрощения можно задать размер сразу или позже, ф-ей «ensureCapacity()», по аналогии с векторами С++. По умолчанию он и так есть, какой то, просто при заполнении автоматически увеличивается на какую то величину
Использование этого размера здесь, не тоже самое что размеры в обычных массивах, там память зарезервирована а здесь это просто метка того какого размера может быть список, памяти он не занимает
«trimToSize()» еще тоже есть для обрезки

Доступ к элементам через ф-ии «get/set», добавление новых элементов только через add(), set() можно использовать только для замены уже имеющегося элемента, иначе ошибка будет

Интерфейсы, лямбда-выражения, внутренние классы

:!: Интерфейсы. Notes

Интерфейс классом не является, это скорее соглашение о том что именно должен делать класс, но не как это делать
Может содержать описание методов, как и сами (простые) методы, не содержит полей

Для указания того что класс реализует интерфейс существует ключевое слово «implements»
С-но этот класс должен реализовывать все методы перечисленные в классе

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

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

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

:!: Клонирование. Notes

С клонированием в java немного треш, у класса object есть метод «clone()», но он делает только неполное клонирование т.е. подобъекты (объекты внутри объекта) все еще остаются теми же самыми. Для полного клонирования нужно переопределять этот метод, определять интерфейс «Cloneable» и учитывать кучу деталей

:!: Лямбда выражения Notes

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

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

Лямбда-выражения имеют следующую структуру:

  • Блок кода
  • Параметры
  • и значения свободных переменных (т.е. переменных которые не являются параметрами и не определены в кода)

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

Собственные
Лямбда выражения применяются именно для отложенного выполнения, например в потоке или неоднократное выполнение или по какому-нибудь событию и т.д.
Пример реализации в собственном методе:

# вызов
repeat(10, () -> System.out.println("Hello, world"));
 
# реализация
public static void repeat()int n, Runnable action){
  for(int i = 0; i < n; i++) action.run();
}

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

:!: Ссылки в лямбда-выражениях

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

Ссылки на методы. Разновидности:

  • x → System.out.println(x) - Объект::МетодЭкземпляра
  • (x,y) → x.compareToIgnoreCase(y) - Класс::МетодЭкземпляра
  • (x,y) → Math.pow(x,y) - Объект::СтатическийМетод

Еще примеры:

  • separator::equals - x → separator.equals(x) - задан объект и метод экземпляра
  • String::trim - x → x.trim() - задан класс и метод экземпляра
  • String::concat - (x,y) → x.concat(y) - тоже метод экземпляра но уже с явным параметром
  • IntegerValueOf - x → Integer::valueOf(x) - статический метод
  • Integer[]::new - n → new Integer[n] - Ссылка на конструктор массива

Но ссылку на метод можно применить только в том случае, если в его теле вызывается единственный метод и больше ничего не делается. Например это не подойдет, т.к. есть еще и сравнение «s → s.length() == 0»

Ссылки на конструкторы. Все тоже самое, только имя метод передается как «new»
Если несколько конструкторов, выбирается исходя из контекста
Эти ссылки помогают обойти ограничение java в том что нельзя создать массив обобщенного типа, при попытке будет перезаписываться тип или что то такое
Но такую ссылку можно передать в метод типа «toArray()», тогда все чики-пики, например «Person[] pople = stream.toArray(Person[]::new)«

Исключения, утверждения и протоколирование

:!: Обработка ошибок

В случае возникновения исключения, следует стремится к одному из двух состояний:

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

Иерархия исключений в Java:

Иерархия Error описывает внутренние ошибки и ситуации в связи с нехваткой ресурсов в исполняющей системе Java. Эти объекты нельзя сгенерировать самостоятельно. Считаются Непроверяемыми

Исключения типа RuntimeException возникают в следствии ошибок программирования:

  • неверное приведение типов
  • выход за пределы массива
  • обращение к объекту по пустой ссылке «null»

Остальные в случае непредвиденного стечения обстоятельств:

  • попытка чтения при достижении конца файла
  • попытка открыть несуществующий файл

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

public FileInputStream(String name) throws FileNotFoundException

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

При наследовании (обычных классов) нельзя добавить спецификатор «throws» в переопределенный метод если его нет в базовом

:!: Работа с исключениями

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

// Генерация
String readData(Scanner in) throw EOFException{
  ...
  while(...){
    if(!in.hasNext()){
      throw new EOFException();
      // throw new EOFException("Добавить текст описания");
    }
  }
}
 
// Перехват
(...)
try{
  InputStream in = new FileInputStream(filename);
  int b;
  while((b = read()) != -1){ (...) }
}
catch(IOException e){
  e.printStackTrace();
}
 
// finaly
// Ничего особенного, как обычно, выполняется после всего, предназначен для освобождения ресурсов
// Либо try с ресурсами
try(var i = new (..)){
  // неважно как завершится блок, гарантированно вызовется метод "res.close()"
}

При обработке есть два варианта, либо мы перехватываем исключения самостоятельно, либо указываем их в блоке «throws» что означает как бы делегирование обработки вызывающему коду, приемлемы оба случая в той или иной ситуации
Если нет ни того ни другого то компилятор выдаст предупреждение в местах где возможны проверяемые исключения
Несколько исключения перехватываются несколькими блоками catch либо перечислить через вертикальный слеш в одном блоке

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

// Рекомендуется так переопределять, используя метод Cause, чтобы получить доступ к исходному
catch(SQLException original){
  var e = new ServletException("db error");
  e.initCause(original);
  throw e;
}

Есть альтернатива гарантированному освобождению, «try с ресурсами», только вот «метод res.close()»? ну это наверно только к внешним ресурсам относится, файлы и т.д., переменные чистятся сборщиком мусора и машиной так то

Исключения не заменяют проверку
Обработка исключения занимает гораздо больше времени чем простая проверка типа «isEmpty()» или что нибудь такое, поэтому проверка всегда предпочтительнее исключения, последние отлько для исключительных ситуаций

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

Передавать исключения выше тоже приемлемо
В вызывающем коде может быть больше контекста, больше способов и средств оповещения и т.д. и т.п.

:!: Утверждения

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

:!: Протоколирование

В подсистеме протоколирования используются регистраторы (logger) и обработчики (hendler). Каждый из них имеет уровень, событие должно попадать под оба уровня
Некоторые моменты:

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

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

Logger logger = Logger.getLogger("com.mycompany.myapp");
logger.setLevel(level.FINE);
logger.setUseParentHandlers(false);
var handler = new ConsoleHandler();
handler.setLevel(Level.FINE);
logger.addHandler(handler);

По умолчанию регистратор отправляет события своему и всем родительским обработчикам, чтобы этого избежать можно задать «logger.setUseParentHandlers(false);»

Фильтры
Помимо стандартных уровней, можно фильтровать сообщения по любому признаку, для этого нужно реализовать интерфейс «Filter» и переопределить метод «isLoggable()», далее метод «setFilter()» у обработчика

Параллелизм

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

:!: Пример

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

 // Так же можно использовать лямбда-выражения\\
Runnable r = () -> {код задачи};
var t = new Thread(r);
t.start();

При прямом вызове метода run() код будет выполнен в том же потоке, т.ч. нужно запускать методом «start()«

:!: Состояние потоков

Новый
Создан операцией new, но еще не запущен на исполнение

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

Блокированный,Ожидающий,Временно ожидающий
Когда поток пытается получить блокировку объектов которая в настоящий момент захвачена другим потоком, он становится заблокированным
Ожидающий он тогда когда ждет уведомления о каком либо событии (на практике мало отличается от блокировки). Ну а временно ожидающий, когда указано время ожидания, точнее таймаут

Завершенный
Завершится поток может нормальным способом когда выполнился весь код в методе run(), либо внезапно, если возникло исключение, обработчик которого прерывает выполнение потока (метод stop генерирует именно исключение, но его применение не рекомендуется)

:!: Свойства потоков

Прерывание
Приемлемых способов принудительно завершить поток из вне нет. Для запроса на прерывание можно вызвать метод interrup(), после этого у метода установится boolean состояние прерывания, благодаря которому поток уже сам может завершится
Для этого нужно в потоке либо проверять состояние, либо некоторые методы сами это делают, такие как «sleep()» например, он сгенерирует исключение «InterruptedException», которое в свою очередь можно обработать внутри метода для корректного завершения работы, очистки и тд, если не перехватится то завершится принудительно

Демон
Поток у которого нет других целей кроме как служить другим потокам. «t.setDaemon(true)«

Именование
Можно задать имя потоку, для удобства анализа, «t.setName('myThread');»

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

Приоритеты
setPriority(), зависит от системы исполнения, ряд нюансов, в линуксе вроде вообще игнорируется, в целом особо не распространено

Синхронизация потоков
Проблема в общих ресурсах возникает тогда, когда обращение к ним происходит одновременно, т.к. операции в основном не атомарные (например a=+2), то в байт коде они разбиты на несколько шагов, и прерывание потоков может происходить внутри этих шагов, т.е. до полного завершения операции, в основном в этом самая главная и опасная проблема одновременного доступа

Блокировка
Есть по сути аналог критической секции. Класс ReentrantLock

private Lock myLock = new ReentrantLock();
myLock.lock();
try:
{
  критический раздел кода
}
finally
{
  myLock.unlock();
}

Есть еще объекты блокировки с условиями

Коллекции данных

Разделение интерфейсов и реализаций

Собсна такое разделение является стандартом и дает множество преимуществ

:!: на примере структуры данных «Очередь (Queue)»

Самая простая форма интерфейса может выглядеть так:

interface Queue<E>
{
  void add(E e lement);
  Е remove();
  int size();
}

А реализация с-но может сильно варьироваться и определяется классом, реализующим интерфейс «Queue»

class CircularArrayQueue<E> implements Queue<E>
{
	CircularArrayQueue(int capacity)
	public void add(E element)
	public Е remove ()
	public int size () 
	private Е[] elements;
	private int head;
	private int tail;
}
// или 
class LinkedListQueue<E> implements Queue<E>
{
	LinkedListQueue ()
	void add (Е element)
	public Е remove ()
	public int size()
	private Link head;
	private Link tail;
}
:!: Интерфейс Collection

Основополагающий для классов коллекций

public interface Collection<E>
{
  boolean add(E element);
  Iterator<E> iterator();
}
:!: Итераторы
public interface Iterator<E>
{
  E next();
  boolean hasNext();
  void remove();
}

Нужно проверять наличие через «hasNext()» иначе генерится исключение при достижении конца
Порядок в котором перебираются элементы, зависит от типа коллекции, может быть последовательный а может быть и случайный
Итераторы в java отличаются от сишных, к ним не применим инкремент, они не моделируются по индексам, скорее это «объекты между элементами», он возвращает ссылку на элемент который он только что «прошел», когда перескочил при вызове «next()«
Тобишь для каких либо д-й с итератором, его нужно позиционировать, затем вызывать например «remove()», примечательно что после этого он автоматом не перепозиционируется, и для повторного remove() надо повторно вызывать next()

Коллекции в Java

:!: Notes


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

Списочные массивы
Интерфейс List описывает упорядоченную коллекцию, в которое имеет значение расположение элемента
К слову, отличие Vector и ArrayList в том что первый потокобезопасный, но на это тратятся доп ресурсы, а второй нет, с-но выбор зависит от задач и баланса эффективности

Хеш-множества
Более эффективный поиск элементов в списке
Из-за специфики реализации, элементы располагаются (извлекаются) с (псвдо-)случайном порядке

Древовидные множества
Основным отличием является то что все элементы в нем всегда отсортированы, сортируются они сразу же перед записью, вычисляется место для вставки
Работает немного медленнее чем хеш, но многое зависит от типа данных, должен быть реализован интерфейс «comparable» для сравнения элементов

Одно- и двустроние очереди
Впринципе рассмотрено уже
Так же существуют очереди по приоритету

develop/java/in.txt · Последнее изменение: 2023/05/03 15:18 — admin