JavaRush /Java блог /Random /Кофе-брейк #165. Пакеты на языке Java. Потокобезопасные м...

Кофе-брейк #165. Пакеты на языке Java. Потокобезопасные методы для начинающих

Статья из группы Random

Пакеты на языке Java

Источник: Usemynotes Эта публикация поможет вам лучше изучить пакеты в Java, понять их предназначение и способы реализации. Кофе-брейк #165. Пакеты на языке Java. Потокобезопасные методы для начинающих - 1

Что такое пакеты в 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. Потокобезопасные методы для начинающих - 2Я обнаружил, что многие 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-проектах, но это хорошее начало для практики. В следующий раз, когда вы увидите какой-либо метод в своем коде, спросите себя: “Этот метод потокобезопасен?”. И очень скоро вы четко будете понимать ответ.
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ