JavaRush /Java блог /Random UA /Кава-брейк #165. Пакети на мові Java. Потокобезпечні мето...

Кава-брейк #165. Пакети на мові Java. Потокобезпечні методи для початківців

Стаття з групи Random UA

Пакети на мові Java

Джерело: Usemynotes Ця публікація допоможе вам краще вивчити пакети в Java, зрозуміти їх призначення та способи реалізації. Кава-брейк #165.  Пакети на мові Java.  Потокобезпечні методи для початківців.

Що таке пакети в Java

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

Деякі види пакетів у Java:

  • java.lang – за замовчуванням поставляється в комплекті з Java.
  • java.io містить класи, методи та інші елементи, пов'язані з введенням-виведенням.

Для чого потрібні пакети?

  • Щоб уникнути конфліктів імен.
  • Для забезпечення контрольованого доступу.
  • Щоб досягти інкапсуляції даних.

Як створити пакет Java?

Давайте створимо пакет з ім'ям computer . Зазвичай ім'я пакета пишеться малими літерами. Це тільки для того, щоб уникнути конфліктів імен з іменами класів. Також ми створимо інтерфейс з ім'ям Pluggable , який перебуватиме в пакеті computer .
package computer;

interface Pluggable {
   public void pluggedIn();
   public void pluggedOut();
}
Тепер ми створимо клас з ім'ям PenDrive , який буде реалізовувати вказаний вище інтерфейс.
package computer;

public class PenDrive implements Pluggable {

   int storage = 64;

   public void pluggedIn () {
     System.out.println("Pen Drive is connected");
   }

   public void pluggedOut () {
     System.out.println("Pen Drive is removed");
   }

   public int storage() {
     return storage;
   }

   public static void main(String args[]) {
     PenDrive p = new PenDrive ();
     p.pluggedIn();
     System.out.println("Pen Drive Storage: " + p.storage());
     p.pluggedOut();
   }
}

Як створити ієрархію пакетів у Java?

При формуванні ієрархії пакети Java називаються у зворотному порядку. Це робить їх дуже схожими на каталоги чи папки. Так само як і на персональному комп'ютері, де всередині однієї папки може міститися одна або кілька підпапок, те саме стосується і пакетів Java. Давайте розглянемо пакет з ім'ям Asia.India.Kolkata . Все це існуючі папки, але з огляду на географію, то зрозуміло, що Калькутта знаходиться в Індії, а Індія знаходиться в Азії. Основна мета ієрархії – полегшити пошук класів.

Типи пакетів у Java

Вбудовані пакети (Built-in Packages)

Вбудовані пакети – це пакети, що складаються з великої кількості вбудованих класів, які є частиною Java API. До таких пакетів входять:
  • java.util - цей пакет містить кінцеву кількість службових класів, які використовуються для реалізації структур даних, таких як пов'язаний список, набори і таке інше. Він також підтримує операції з датою та часом та багато іншого.
  • java.net - він містить класи, які використовуються для мережевих операцій.

Пакунки користувача (User-defined packages)

Пакети, що визначаються користувачем, називаються пакетами користувача. Користувач може створити вручну пакет і мати в ньому стільки класів, скільки захоче.

Як отримати доступ до іншого пакета?

Доступ до пакета з іншого пакета можна здійснити трьома простими способами:
  • Використання зірочки в операторі імпорту
Символ зірочки ( * ) використовується для позначення всіх речей в Java. Використовуючи його, ми можемо імпортувати все, що знаходиться всередині пакета. Приклад: Розглянемо пакет з назвою tools . Якщо ми хочемо імпортувати все, що знаходиться всередині цього пакета, тоді нам потрібно використовувати оператор імпорту як:
import tools.*;
  • Використання імпорту package.ClassName;
Згадування імені класу в пакеті – це ефективний спосіб імпортувати у програму лише необхідні класи, що дозволяє уникнути імпорту непотрібних класів. Приклад: Розглянемо пакет з назвою books . Якщо ми хочемо імпортувати з нього тільки певний клас або інтерфейс (ми розглядатимемо клас Pages ), тоді ми можемо імпортувати тільки його, використовуючи:
import book.Pages;
  • Використання повного імені
Існує спосіб безпосередньо використовувати пакет Java або його класи, використовуючи їхнє повне ім'я. Так вам не доведеться імпортувати цей пакет і ви можете використовувати його безпосередньо в програмі. Приклад:
java.awt.List aSimpleList = new java.awt.List();

Розмір пакета за замовчуванням у Java

За замовчуванням Java імпортує пакет java.lang . Він має багато класів, які зазвичай використовуються в простих програмах Java, таких як String , Integer та інших. Одним з найважливіших класів є клас Object , який, у свою чергу, також знаходиться в пакеті java.lang . Розмір цього пакету заснований на його складових: 8 інтерфейсів, 37 класів, 3 перерахування, 27 винятків, 23 типи помилок та 5 типів анотацій.

Потокобезпечні методи Java для початківців

Джерело: Medium Використовуючи цю статтю, ви зможете дізнатися про роботу потокобезпечних методів Java. Кава-брейк #165.  Пакети на мові Java.  Потокобезпечні методи для початківців.Я виявив, що багато junior/middle Java-розробників неправильно розуміють, як повинні працювати потокобезпечні (thread-safety) методи в реальних проектах. А оскільки ми зазвичай працюємо в багатопотоковому середовищі, то правильне їх використання дуже важливе. Потокобезпечний метод - це метод, який можна викликати з кількох потоків одночасно, не впливаючи на стан один одного. Недостатнє розуміння цієї концепції призводить до появи багів, які важко знайти та виправити. Щоб уникнути таких помилок, давайте подивимося на приклади.

Приклад №1:

public static int countLetters(String input) {
    int counter = 0;

    for (Character c : input.toCharArray()) {
        if (Character.isAlphabetic(c)) {
            counter++;
        }
    }

    return counter;
}
  • Метод countLetters є статичним, він повертає значення int та приймає введення рядкового параметра.
  • Всередині методу створюється примітивний лічильник змінних, потім цикл перебирає символи вхідного рядка і збільшує лічильник змінних щоразу, коли трапляється буквений символ.
Чи є цей метод потокобезпечним? Багато розробників кажуть ні, тому що в цьому випадку ми маємо операцію збільшення, яка не є потокобезпечною. Давайте розберемося. У моделі пам'яті Java у нас є стек та купа. Кожен потік має власний стек, і всі потоки використовують одну купу. У цьому плані дані стека завжди потокобезопасны, а ці купи — немає. Стек зберігає примітиви та посилання на об'єкти. Купа містить самі об'єкти. Це означає, що в даному прикладі коду кожен потік зберігає в стеку свій власний лічильник змінних і не впливає на дані інших потоків, тому метод є потокобезпечним . Зверніть увагу, що вхідне значення String також є об'єктом, але оболонки String і примітивів ( Integer , Long , Double , Boolean тощо) потокобезпечні, оскільки вони незмінні.

Приклад №2:

public static int countLetters2(String input) {
    List<Character> listCounter = new ArrayList<>();

    for (Character c : input.toCharArray()) {
        if (Character.isAlphabetic(c)) {
            listCounter.add(c);
        }
    }

    return listCounter.size();
}
У цьому коді використовувалася та ж логіка, що і в першому прикладі, але замість примітивної змінної int використовувався об'єкт List . З попередньої частини ми знаємо, що об'єкти Java зберігаються в купі, а List - це об'єкт. Також ми знаємо, що стек зберігає посилання на об'єкти в купі. У прикладі №2 кожен потік створює новий об'єкт ArrayList : а змінна listCounter зберігає посилання на свій об'єкт у купі, тому ніякий інший потік не може змінити цей об'єкт.
List<Character> listCounter = new ArrayList<>();
Це означає, що цей метод є безпечним.

Приклад №3:

public class CounterUtil { // singleton

    List<Character> listCounter = new ArrayList<>();

    public int countLetters3(String input) {
        for (Character c : input.toCharArray()) {
            if (Character.isAlphabetic(c)) {
                listCounter.add(c);
            }
        }

        return listCounter.size();
    }
}
У цьому прикладі ми маємо одноелементний (це важливо) клас CounterUtil з глобальною змінною listCounter . Ця змінна створюється одночасно з екземпляром синглтону. Коли кілька потоків викликають метод countChars3 , всі вони використовують одну і ту ж глобальну змінну listCounter , яка зберігає посилання на той самий об'єкт у купі, і потоки там будуть впливати один на одного. Тому ми можемо зробити висновок, що цей метод не є безпечним. І навіть якщо ми поміняємо List<Character> listCounter на примітивну змінну int counter , це також не буде потокобезпечним, тому що всі потоки будуть використовувати одну і ту ж примітивну змінну.

Останній приклад:

public static int countLetters4(List<Character> inputList) {
    List<Character> listCounter = new ArrayList<>();

    for (Character c : inputList) {
        if (Character.isAlphabetic(c)) {
            listCounter.add(c);
        }
    }

    return listCounter.size();
}
Метод countLetters4 приймає список символів замість параметра String . Тут ми не можемо гарантувати , що цей метод є безпечним. Чому? Тому що ми не можемо бути впевнені, як розробники використовуватимуть цей метод. Якщо інший потік ззовні змінить дані в inputList одночасно з нашим методом counterLetters4 це може вплинути на остаточний результат.

Висновок

Ми розглянули лише чотири приклади, і вони не охоплюють всі аспекти безпеки потоків у Java-проектах, але це добрий початок для практики. Наступного разу, коли ви побачите будь-який метод у своєму коді, запитайте себе: “Цей метод є безпечним?”. І дуже скоро ви чітко розумітимете відповідь.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ