НМЦ

ОБ’ЄКТНО ОРІЄНТОВАНЕ ПРОГРАМУВАННЯ

Електронний посібник

 

ВФПО

Модуль 6

 

6. Проєктування та реалізація архітектури програм. забезпечення якості програмних продуктів

6.1. Проєктування ієрархії класів. Використання UML. Особливості створення класів

6.2. Рефакторинг. Типові архітектурні рішення та антипатерни

6.3. Обробка помилок та виключень. Відлагодження, тестування та профілювання

6.4. Колекції та дженерики

 

6. Проєктування та реалізація архітектури програм. забезпечення якості програмних продуктів

6.1. Проєктування ієрархії класів. Використання UML. Особливості створення класів

 

Відеозапис лекції:

 

 

Частина 1: Проєктування ієрархіїї класів. Використання UML

https://drive.google.com/file/d/1sdNFsiUzUCulWxFCvkPxLdYaOB_q_mtY/view

 

Успадкування або наслідування (англ. inheritance) – це ще один важливий механізм об'єктно орієнтованого програмування, який дозволяє створювати нові класи на основі вже існуючих.

 

Клас, на базі якого створюється підклас, називають надкласом, суперкласом або ж батьківським класом. Новий клас, що розширює (extends) батьківський клас називають підкласом або дочірнім класом. На відміну від С++, де клас може мати кілька батьківських класів, мова програмування Java підтримує лише одинарне успадкування, тобто може бути лише один безпосередній надклас. У надкласу звичайно може бути свій надклас, проте також лише один безпосередній. Множинне успадкування доволі складне у застосуванні і вимагає обережного підходу, тому творці Java вирішили відмовитися від нього.

Для графічного представлення ієрархії успадкування зазвичай використовують діаграму класів UML статичне представлення структури моделі. Вона відображає статичні (декларативні) елементи, такі як: класи, типи даних, їх зміст та відношення.

Діаграма класів може містити позначення для пакетів та позначення для вкладених пакетів. Також діаграма класів може містити позначення деяких елементів поведінки, однак їх динаміка розкривається в інших типах діаграм. Діаграма класів служить для представлення статичної структури моделі системи в термінології класів об'єктно орієнтованого програмування. На цій діаграмі показують класи, інтерфейси, об'єкти й кооперації, а також їхні стосунки. Приклад діаграми класів:

 

 

Для створення діаграм класів прямо в середовищі NetBeans можна скористатись плагіном easyUML, який підтримує також кодогенерацію та зворотний інжиніринг.

 

Генерація коду або кодогенерація – частина процесу компіляції, коли спеціальна частина компілятора, кодогенератор, конвертує синтаксично коректну програму або діаграму класів UML в послідовність інструкцій, які можуть виконуватися на машині.

 

Слід зазначити, що на основі діаграми класів можна отримати лише каркасний код, який вимагає доопрацювання.

Зворотна розробка або зворотний інжиніринг – дослідження деякого пристрою чи програми з метою розуміння принципів роботи досліджуваного об'єкта. Найчастіше використовується з метою створення об'єкта, за функціональністю аналогічного досліджуваному але без точного копіювання його функцій.

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

В нашому випадку зворотний інжиніринг полягає в побудові діаграми класів за існуючим кодом. Такі діаграми можуть слугувати засобом контролю – відслідковування прогресу проєкту.

Ще один доступний інструмент для UML-проєктування, кодогенерації та зворотного інжинірингу – StarUML – інструмент, який було ліцензовано під модифікованою версією GNU GPL до 2014 року, коли переписана версія 2.0.0 була випущена для бета-тестування під власною ліцензією.

У 2014 році була випущена переписана версія як запатентоване програмне забезпечення. Заявленою метою проєкту було замінити комерційні програми, такі як Rational Rose та Borland Together. StarUML підтримує більшість типів діаграм, зазначених у UML 2.0 . Починаючи з версії 4.0.0 (29 жовтня 2020 р.), вона включає оглядові діаграми та діаграми взаємодії. За допомогою плагінів StarUML підтримує кодогенерацію та зворотний інжиніринг для таких мов, як C/C++, C# та Java.

 

6.2. Рефакторинг. Типові архітектурні рішення та антипатерни

 

Відеозапис лекції:

 

 

Частина 1: Рефакторинг. Типові архітектурні та антипатерни

https://drive.google.com/file/d/1XXIXGb-kQQuX6I0f1xYs0CCXrH4KodO2/view

 

Рефакторинг – перетворення програмного коду, зміна внутрішньої структури програмного забезпечення для полегшення розуміння коду і легшого внесення подальших змін без зміни зовнішньої поведінки самої системи.

 

Слово "рефакторинг" пішло від терміну "факторинг" в структурному програмуванні, який означав декомпозицію програми на максимально автономні та елементарні частини.

Існує міф про те, що правильно організований процес розробки продукту методично дотримується поставлених вимог, визначає однозначний, стабільний список обов’язків програми, і при цьому програмний код може бути написаний майже лінійно від початку до закінчення, кожна ділянка один раз написана, відтестована й забута. Згідно з цим міфом, єдиний випадок, коли наявний код може змінюватись, – це в процесі підтримки і адміністрування програми, коли початкова версія продукту уже здана замовнику.

Однак реальність є дещо інакшою. Насправді код еволюціонує в процесі розробки продукту. Як правило, кодування, відлагодження та модульне тестування займають в середньому 30–65% зусиль від загального часу існування проєкту (залежно від величини проєкту). Навіть в ході добре організованих проєктів вимоги змінюються в середньому на 1–4% за місяць, що неминуче спричиняє зміни до програмного коду – як дрібні, так і досить серйозні.

Також, на відміну від старіших методик розробки програмних продуктів, де основний акцент ставився на мінімізації змін до коду, сучасна методика вбачає великий потенціал у внесенні змін. Вона є сфокусованою на коді (code-centered) і під час розробки можна очікувати, що код буде вдосконалюватися більше, ніж зазвичай.

 

Підстави для проведення рефакторингу:

Ø Код дублюється («Copy And Paste is a Design Error»). Це нерідко призводить до необхідності вносити однакові зміни до кількох скопійованих ділянок коду, що не відповідає принципам «DRY» (Don't Repeat Yourself).

Ø Підпрограма занадто довга. Хоча питання, яку максимальну довжину може мати підпрограма, є досить суперечливим, однак загальноприйнятим неофіційним стандартом є написання підпрограм довжиною не більше, ніж один екран коду (20-25 рядків).

Ø Тіло циклу занадто довге або рівень вкладеності циклів занадто великий.

Ø Клас має багато обов'язків, слабко пов'язаних між собою, що порушує принцип єдиного обов'язку (Single responsibility principle). У такому разі краще розділити клас на кілька атомарних.

Ø Інтерфейс класу не забезпечує достатній рівень абстракції.

Ø Назва класу чи методу недостатньо точно відповідає його змісту.

Ø Пов'язані дані, які використовуються разом, не організовані в клас.

Ø Функція має занадто багато параметрів.

Ø У ланцюжку виклику методів передається багато зайвих даних.

Ø Потрібно одночасно змінювати кілька паралельних ієрархій класів. Для вирішення цієї проблеми можна, наприклад, скористатися шаблоном «Міст».

Ø Клас не виконує ніякої роботи самостійно, а тільки передоручає її іншим класам.

Ø Клас має занадто багато відкритих (public) членів.

Ø Нестатичний клас складається тільки з даних або тільки з методів.

Ø Занадто широке застосування глобальних змінних.

 

Існує чимало прийомів (методів) рефакторингу – Відокремлення методу (Extract Method), відокремлення базового класу (Extract Superclass), інкапсуляція поля (Encapsulate Field) тощо.

Багато інтегрованих середовищ розробки, зокрема Netbeans, містять вбудовані механізми рефакторингу коду. Крім інтегрованої функціональності, існує також багато продуктів сторонніх виробників, які, як правило, реалізовані у вигляді додатків (plugins) до відповідного IDE.

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

Патерни відрізняються за рівнем складності, охоплення і деталізації проєктованої системи. Проводячи аналогію з будівництвом, ви можете підвищити безпеку на перехресті, встановивши світлофор, а можете замінити перехрестя цілою автомобільною розв’язкою з підземними переходами.

Низькорівневі та найпростіші патерниідіоми. Вони не дуже універсальні, позаяк мають сенс лише в рамках однієї мови програмування.

Максимально універсальними є архітектурні патерни, які можна реалізувати практично будь-якою мовою. Вони потрібні для проєктування всієї програми, а не окремих її елементів. Крім цього, патерни відрізняються і за призначенням.

 

Існує три основні групи патернів:

Ø Породжуючі патерни піклуються про гнучке створення об’єктів без внесення в програму зайвих залежностей.

Ø Структурні патерни показують різні способи побудови зв’язків між об’єктами.

Ø Поведінкові патерни піклуються про ефективну комунікацію між об’єктами.

 

Взагалі існує 32 класичних патерни, які відрізняються за призначенням.

Поряд з патернами існують і антипатерни. Антипатерн, або антишаблон, загальний спосіб вирішення проблеми, що часто виникає під час проєктування програмного забезпечення, який, як правило, неефективний та зменшує продуктивність комп'ютерної програми. Інакше кажучи, антипатерн – шкідливий і неефективний патерн.

Концепція антипатерну є універсальною і придатна не лише для програмної інженерії, але й для практично будь-якої сфери людської діяльності; втім термін не набув поширення поза межами IT-індустрії.

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

Найважливіша мета документування антипатернів – полегшити можливість розпізнавання шкідливого рішення і успішного виправлення помилок ще на ранніх етапах роботи. Антипатерн не просто застерігає "Не роби цього! ", а вказує інженеру на те, що він, можливо, не усвідомлює, що рішення такого типу принесе набагато більше шкоди, ніж користі.

 

Існує три ключових правила, коли рішення вважається антипатерном:

Ø загальноприйнятий процес, структура чи план дій, який на перший погляд дає ефективне рішення, найчастіше дає більше негативних наслідків, ніж позитивних;

Ø цей процес (структура, план), незважаючи на свою шкідливість, достатньо поширений на практиці;

Ø правильне рішення вже існує, воно задокументоване та перевірене на ефективність.

 

У більшості випадків рефакторинг має на меті саме усунення антипатернів.


 

6.3. Обробка помилок та виключень. Відлагодження, тестування та профілювання

 

Відеозапис лекції:

 

 

Частина 2: Debugging in NetBeans with Breakpoints

https://youtu.be/9DBQyLXXvYI

 

 

Частина 3: NetBeans 8.1 Profiler

https://youtu.be/DI4EFkzqCCg

 

Виняткова ситуація, або просто виняток, виключення чи exception, – це аварійний стан, який відбувається саме під час виконання програми.

 

Прикладом є ділення на нуль, помилки читання з файлу та мережі тощо. Іншими словами – це помилки, які можуть виникнути під час виконання програми. В деяких мовах програмування необхідно заздалегідь передбачити можливість тієї чи іншої помилки і визначити шлях її обробки. В Java для цього передбачений спеціальний механізм винятків.

 

Виняток в Java – це об'єкт, який описує виняткову (тобто помилкову) ситуацію, що відбулась в певному місці коду.

 

Коли така ситуація виникає, створюється об'єкт, який передається ("вкидається") в метод, в якому виникла помилка. Далі в методі цей виняток може оброблятися або бути переданий ще кудись для обробки.

Розглянемо для прикладу наступну програму DivZero.java

public class DivZero {

    public static void main(String[] args) {

        int my = 0;

        int medium = 44 / my;

        System.out.println("medium=" + medium);

    }

}

Як бачимо, в програмі присутнє ділення на нуль. При компіляції ми не отримаємо помилок.

Проте після запуску програми отримаємо наступне:

*      Exception in thread "main" java.lang.ArithmeticException: / by zero

*      at DivZero.main(DivZero.java:4)

Це так звана траса стеку викликів. Перший рядок означає тип винятку. Як бачимо, тут маємо ArithmeticException з діленням на нуль.

 

Java.lang – це пакет класів, який завжди доступний в програмі і який містить найбільш використовувані класи. Тобто його не потрібно імпортувати

 

Другий рядок вказує, де саме відбулась виняткова ситуація: 4-й рядок у файлі DivZero.java в методі main() класу DivZero. При цьому, як бачимо, програма завершила своє виконання аварійно. Для уникнення цього існує відповідний механізм обробки винятків, який дозволяє перехопити виняток, одержати інформацію про нього, обробити його, здійснивши певні дії для нормального закінчення або продовження виконання програми.

Усі типи винятків є підкласами класу Throwable, який входить у базовий пакет класів Javajava.lang. Тобто він є вершиною ієрархії класів винятків. Його два підкласи Error та Exception утворюють дві основні гілки винятків.

 

Клас Error з його підкласами – це помилки виконавчого середовища Java, які зазвичай не виникають за нормальної роботи середовища Java. Такі винятки зазвичай не можуть бути оброблені в програмі.

 

Гілка класу Exception – це винятки, які програма повинна вловлювати (catch). Від цього класу та його підкласів можна утворювати власні підкласи. Важливим його підкласом є клас RuntimeException. Винятки такого типу включають ділення на нуль та помилкову індексацію масивів.

Актуальну ієрархію класів винятків можна подивитися і уточнити в офіційній документації до JDK.

Для обробки виняткових ситуацій використовується п’ять ключових слів: try, catch, throw, throws та finally. Інструкції програми, в яких може виникнути помилка, контролюються за допомогою конструкції try.

Загальна форма наступна:

try{

//блок коду для контролю над помилками

} catch (тип-винятку1 об’єкт-винятку) {

   //дії при виникненні типу винятку1 (обробник винятку)

} catch (тип-винятку2 об’єкт-винятку) {

   //дії при виникненні типу винятку2 (обробник винятку)

}

 //….

[finally{

//дії при виході з конструкції try.

 }]

 

Після інструкції try ми розміщуємо "небезпечний" код, у блоці catch відбувається обробка винятку, причому може бути кілька інструкцій catch. Завершувати конструкцію може інструкція finally, в ній розміщується код, який буде виконаний після обробки винятку в інструкції catch.

 

Таким чином можемо переписати програму з діленням на нуль:

public class DivZero {

    public static void main(String args[]){

        int my=0;

        try{

        int medium=44/my;

        System.out.println("medium="+medium);

        }catch(ArithmeticException e){

            System.out.println("Ділення на нуль!");

        }

        System.out.println("Продовження виконання...");

    }

}

 

Результат виконання:

Ділення на нуль!

Продовження виконання...

Як бачимо, для перехоплення винятку код, через який виникала помилка, знаходиться у середині конструкції try. Також зверніть увагу, що після виникнення виняткової ситуації наступний рядок System.out.println("medium="+medium); не було виведено, оскільки виняток був переданий для обробки в catch. Після інструкції програми, в якій відбулась виняткова ситуація, всі наступні рядки до інструкції catch пропускаються і не будуть виконуватись. І, як бачимо з результату виконання, наша програма продовжила виконання після закінчення блоку try, а не завершила своє виконання аварійно.

За використання всередині try певних ресурсів, наприклад, файлів, при виникненні винятку необхідно було передбачити закриття відкритих ресурсів. Для цієї мети раніше приходилося використовувати блок finally. У Java 7 з'явилася конструкція try з ресурсами (англ. try-with-resources). Тепер просто можна створити ресурс у дужках зразу ж після ключового слова try і Java сама потурбується про закриття ресурсу. Приклад:

static String readFirstLineFromFile(String path) throws IOException {

    try (BufferedReader br =

                   new BufferedReader(new FileReader(path))) {

        return br.readLine();

    }

}

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

Загальна форма її наступна:

   throw ThrowableInstance;

 

Тут ThrowableInstance – це тип винятку, який повинен бути або типом Throwable, або мати тип його підкласів.

 

Щоб програма викинула ваш виняток, необхідно скористатися оператором new. Для того, щоб одержати (перехопити) тип винятку, можна скористатися інструкцією catch, як це ми робили вище.

Після інструкції throw відбувається аналогічне породження винятку як у вищенаведених прикладах. Тобто усі інструкції пропускаються до найближчого блоку інструкції catch, де необхідно здійснити обробку винятку. Якщо catch не буде знайдено, то обробник винятків, що використовується за замовчуванням, призупиняє виконання програми і друкує відбиток стеку (stack trace).

Приклад:

public class ThrowNullException {

    public static void main(String args[]){

        try{

            throw new NullPointerException("Пробний виняток");

        }catch(Exception e){

            System.out.println("Наш витяток: "+e);

        }          

    }

}

Результат:

  Наш виняток: java.lang.NullPointerException: Пробний виняток

Програма демонструє як створювати один із стандартних винятків. Більшість вбудованих runtime-винятків Java мають щонайменше два конструктори. Один за замовчуванням без параметрів і один із параметром String, який дозволяє задати додатковий опис. Опис можна вивести на консоль за допомогою методів print(), println(). Також його можна отримати, використавши метод getMessage() класу Throwable.

Можна створити власний тип винятку як підклас уже існуючого типу.

Якщо метод породжує виняток і не обробляє його, то він повинен вказати про це, щоб обробка винятку була здійснена у місці виклику цього методу. Це здійснюється за допомогою застереження throws в оголошенні методу. Після нього вказуються підряд через кому усі винятки, які можуть бути викинуті методом, окрім винятків класів Error та RuntimeException і їхніх підкласів. Нагадаємо, що клас Error – це необроблювані винятки, RuntimeException – винятки, які виникають в результаті помилки програміста (вихід за межі масиву, нульове посилання, невірне перетворення типів). Інші винятки – це помилки доступу, які доволі часто вимагають відповідної обробки.

Загальна форма оголошення методу наступна:

тип ім’я_методу(список_параметрів) throws список_винятків

{

// тіло методу

}

 

Наступна програма демонструє використання throws у методі, де виникає виняток IllegalAccessException.

public class ThrowsException {

    public static void exceptionMethod () throws IllegalAccessException{

        System.out.println("Всередині exceptionMethod().");

        throw new IllegalAccessException("Помилка доступу");

         

    }

     public static void main(String args[]){

        try{

           exceptionMethod();

           System.out.println("Кінець програми"); //даний рядок не буде виведений

        }catch(IllegalAccessException e){

           System.out.println("Наш виняток: "+e);

        }          

    }

}

 

Результат:

Всередині ExceptionMethod().

Наш виняток: java.lang.IllegalAccessException: Помилка доступу

Як бачимо, тепер обробка винятку відбувається у методі main(). Без інструкції try-catch програма призупинятиметься з друком відбитку стеку. Слід зауважити, що у методах інструкція throw поводить себе подібно до інструкції return. Тобто виконання методу припиняється і відбувається повернення в місце виклику методу.

 

6.4. Колекції та дженерики

 

Відеозапис лекції:

 

 

Частина 1: Колекції та дженерики в Java

https://youtu.be/8nlQb5hCEYk

 

За об’єктно орієнтованого програмування доводиться працювати з великою кількістю об’єктів. Зручно мати засоби групування об’єктів. З цією метою в Java розроблено набір інтерфейсів і класів на їх основі під назвою колекції. В основі ієрархії колекцій знаходиться інтерфейс Collection.

Згадаймо, що інтерфейс не містить реалізації методів, а лише їхні оголошення. Можна реалізувати безліч реалізацій інтерфейсу. Програмісту, який використовуватиме ці реалізації, достатньо знати базовий інтерфейс для роботи з його реалізаціями, тобто знати методи, які він передбачає.

 

Collection – базовий інтерфейс, крім нього на його основі в структурі колекцій є ще декілька інтерфейсів, які розширюють базовий інтерфейс Collection, зокрема List, Set та SortedSet.

 

Окремо виділяють ще інтерфейс Map. Він не походить напряму від інтерфейсу Collection, проте його також відносять до колекцій. На їх основі створено набір класів, які згодяться програмістам для більшості випадків роботи з набором об’єктів. Тож вам не прийдеться самим їх реалізовувати.

Якщо вам цікаво, для чого стільки різних класів в колекціях? Суть в тому, що різні класи по-різному реалізовують роботу з даними. Одні класи швидше здійснюють читання даних, інші – вставлення і видалення, одні перевіряють, щоб не було дублювань, інші дозволяють вставляти дані за певним ключем і т.д. Доволі важливо підібрати клас, який найкраще підходить для вашого завдання і забезпечить найбільшу швидкодію. Особливо це актуально, коли кількість об’єктів величезна.

Клас ArrayList призначений для читання об'єктів за індексом. Тож недарма у назві є слово Array (масив). Після створення колекції на основі ArrayList прочитати дані можна кількома способами. Наступний приклад демонструє створення ArrayList, його наповнення об'єктами типу String та їх читання за допомогою методу get (int index) та за допомогою ітератора.

import java.util.ArrayList;

import java.util.ListIterator;

 

public class TestArrayList {

 

    private ArrayList<String> a1;

 

    public static void main(String[] args) {

        TestArrayList test = new TestArrayList();

        test.create();

        test.getData();

        test.iterateData();

    }

 

    void create() {

        //створюємо і наповнюємо ArrayList

        a1 = new ArrayList<String>();

        a1.add("Привіт");

        a1.add("тобі");

        a1.add("божевільний");

        a1.add("світе!");

    }

 

    //читаємо дані по індексу

    void getData() {

        for (int i = 0; i < a1.size(); i++) {

            System.out.print(a1.get(i) + " ");

        }

    }

 

    //Читаємо вміст ArrayList з допомогою ітератора

    void iterateData() {

        ListIterator<String> it = a1.listIterator();

        while (it.hasNext()) {

            System.out.print(it.next() + " ");

        }

    }

}

Результат:

Привіт тобі божевільний світе! Привіт тобі божевільний світе!

Крім вищенаведених способів, можна передати вміст ArrayList у звичайний масив за допомогою методу toArray(). Якщо ви хочете детально розібратися з ArrayList і його методами, то для цього також дивіться інформацію про інтерфейси Collection, List та Iterator.

Окремо розглянемо перегляд даних з допомогою ітератора.

   ListIterator<String> it=a1.listIterator();

 

Таким чином створюється об’єкт ітератора, посилання на який передається об'єктній змінній it типу ListIterator. ListIterator – це інтерфейс, який розширює інтерфейс Iterator декількома новими методами.

 

Базовими ж методами інтерфейсу Iterator є ті, що використані у нас в програмі, а саме:

Ø boolean hasNext() – повертає true, якщо ітерація має наступний елемент

Ø E next() – повертає наступний елемент ітерації (буква E вказує, що це може бути елемент будь-якого типу)

Ø void remove() – знищує останній елемент, що повертався ітератором.

 

LinkedList – це структура даних, що є пов’язаним списком елементів (об’єктів).

 

Різниця між ArrayList та LinkedList полягає в тому, що ArrayList реалізований у вигляді масиву, а LinkedList у вигляді пов’язаних між собою об’єктів. ArrayList швидко виконує читання і заміну елементів (посилань на об’єкти), проте, щоб вставити новий елемент в середину ArrayList або видалити існуючий, в середині ArrayList здійснюється послідовний зсув цілого ряду елементів масиву. В LinkedList доволі швидко відбувається вставлення нового елементу або видалення існуючого. Це відбувається тому, що в середині реалізації LinkedList змінюються лише посилання на попередній і наступний об’єкти (елементи). Проте доступ до об’єктів за індексом в LinkedList відбувається повільніше, ніж в ArrayList. Тож загалом, LinkedList корисний, коли необхідно часто вставляти та видаляти елементи зі списку, а в інших випадках краще використовувати ArrayList.

 

Існує два конструктури LinkedList:

LinkedList()

LinkedList (Collection c)

 

Перший конструктор створює пустий список, а другий – створює пов’язаний список із іншої колекції. Клас LinkedList розширює клас AbstractSequentalList та реалізує інтерфейси List, Dequeue та Queue. Реалізація останніх двох інтерфейсів (черг) означає, що ми можемо працювати із пов’язаним списком як із стеком з використанням методів pop(), push(), poll(), pollFirst(), pollLast() та ін. Детальніше дивіться документацію за заданими інтерфейсами.

 

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

 

Якщо потрібна впорядкована множина, то використовуйте TreeSet. HashSet також не гарантує стабільного порядку збереження об’єктів. Тобто при додаванні об’єктів порядок зберігання елементів змінюється. Вони можуть бути збережені як в кінці множини, так і в середині. Якщо потрібен один порядок зберігання об’єктів, використовуйте LinkedHashSet.

Сам термін "множина" означає, що елементи не будуть повторюватися. Для зберігання і пошуку елементів використовується хеш-код об’єкта. HashSet також може містити значення null. Власне всередині самої реалізації HashSet використовується клас HashMap, який дозволяє зберігати елементи у вигляді двох складових ключа та хеш-коду. У класі HashSet хеш-код недоступний і використовується неявно для користувача.

Клас HashSet розширює клас AbstractSet та реалізує інтерфейс Set. Також реалізовує інтерфейси Serializable та Clonable.

Клас LinkedHashSet розширює клас HashSet, не додаючи ніяких нових методів. Працює він дещо довше за HashSet, проте зберігає порядок, в якому елементи додаються до нього. Відповідно це дозволяє організувати послідовну ітерацію вставлення та витягнення елементів. Всі конструктори та методи роботи з LinkedHashSet аналогічні методам класу HashSet.

Клас TreeSet дозволяє створювати відсортовану множину. Тобто елементи не повторюються та зберігаються у відсортованому порядку. Для зберігання елементів застосовується бінарна деревоподібна структура. Об'єкти зберігаються у відсортованому порядку та за зростанням. Час доступу та одержання елементів доволі малий, тому клас TreeSet підходить для зберігання великих об’ємів відсортованих даних, які повинні бути швидко знайдені.

Клас TreeSet розширює клас AbstractSet та реалізує інтерфейс NavigableSet. NavigableSet реалізується на базі TreeMap.

Ранні версії Java не включали структуру Collections. Там було визначено декілька класів та інтерфейсів, що надавали методи для зберігання об'єктів. Структура Collections була додана в Java 2 (j2se 1.2). Тоді початкові класи були перероблені для підтримки інтерфейсів колекцій. Ці ранні класи також знані як успадковані класи (Legacy classes).

В Java 5 успадковані класи та інтерфейси були перероблені для підтримки узагальнень. Їх підтримують, тому що й досі існує код, який їх використовує. Успадковані класи – синхронізовані. Класи входять в пакет java.util.

 

Успадкованими є наступні класи:

Ø Dictionary

Ø HashTable

Ø Properties

Ø Stack

Ø Vector

 

Успадкованим є інтерфейс Enumeration, на заміну якому прийшов інтерфейс Iterator. Enumeration інтерфейс й досі використовується в кількох методах класів Vector та Properties.

 

Додаткові матеріали

1. Основи програмування на Java – безкоштовний відеокурс (Prometheus): https://courses.prometheus.org.ua/courses/EPAM/JAVA101/2016_T2/about

2. Java Professional – авторський відеокурс (Бабич О.В, ITVDN): https://www.youtube.com/watch?v=H77pBuf582M&list=PLvItDmb0sZw9DXLBDs4IBcalvA1Nx56o9

3. Основи програмування на Java – безкоштовний відеокурс (Prometheus): https://courses.prometheus.org.ua/courses/EPAM/JAVA101/2016_T2/about

4. Java Professional – авторський відеокурс (Бабич О.В, ITVDN): https://www.youtube.com/watch?v=H77pBuf582M&list=PLvItDmb0sZw9DXLBDs4IBcalvA1Nx56o9

5. Основи програмування на Java – безкоштовний відеокурс (Prometheus): https://courses.prometheus.org.ua/courses/EPAM/JAVA101/2016_T2/about

6. Java Professional – авторський відеокурс (Бабич О.В, ITVDN): https://www.youtube.com/watch?v=H77pBuf582M&list=PLvItDmb0sZw9DXLBDs4IBcalvA1Nx56o9

7. Основи програмування на Java – безкоштовний відеокурс (Prometheus): https://courses.prometheus.org.ua/courses/EPAM/JAVA101/2016_T2/about

8. Java Professional – авторський відеокурс (Бабич О.В, ITVDN): https://www.youtube.com/watch?v=H77pBuf582M&list=PLvItDmb0sZw9DXLBDs4IBcalvA1Nx56o9

 

 

Попередня тема

На початок

Наступна тема