Когда программист пишет программу на Java, то создает множество классов. Каждый класс содержит свой собственный код, который выполняет определенную задачу. Реальное приложение на Java может содержать сотни и даже тысячи классов, часть из которых создается самостоятельно, а другие поставляются вместе с установленными библиотеками. При таком количестве классов практически наверняка возникнет ситуация, когда разные программисты создадут классы с одинаковыми именами. Если два класса с одинаковыми именами оказываются в одном проекте, программа не будет компилироваться. Переименовать свой класс можно, но с классами из библиотек так просто не получится. Требование уникальности имен классов делает использование кода, написанного другими людьми, затруднительным, поскольку это постоянно будет приводить к конфликтам имен.
Для решения этой проблемы в Java используются пакеты. Пакеты представляют собой механизм для организации классов в логически связанные группы. Они похожи на папки в файловой системе, которые используются для организации файлов. Пакеты позволяют избежать конфликтов имен классов, так как разные пакеты могут иметь классы с одинаковыми именами.
Определение пакетов
Для определения пакета используется ключевое слово package
в самом начале файла с кодом, после которого следует имя пакета.
Структура пакетов тесно связана с файловой структурой. Имя пакета всегда соответствует директории проекта, в которых находятся файлы с исходным кодом.
// Файл company/User.java
package company;
public class User {
// тут код для работы с пользователем
}
Пакеты могут быть вложенными, то есть один пакет может содержать в себе другие пакеты. Если пакет вложенный, то и директория тоже вложенная. Например, если классы размещены в пакете io.hexlet.model
, то и соответствующие java файлы в проекте должны быть расположены в директории io/hexlet/model. Некоторые IDE, например популярная IntelliJ IDEA, при создании пакета автоматически создают соответствующую файловую структуру в директории проекта.
Как правило, имя пакета содержат какой-то префикс, который закреплен за конкретной компанией или разработчиком. Чаще всего это имя домена в обратном порядке. Например, если компания имеет домен hexlet.io, то пакеты будут начинаться с io.hexlet
После того как мы определились с префиксом пакета, дальнейшая структура пакетов зависит от архитектуры приложения и принятых в проекте соглашений. Обычно пакеты организуются по функциональности или слоям приложения. Например, мы можем создать пакет model
, в котором будут располагаться основные сущности нашего приложения — пользователи и курсы
// Файл io/hexlet/model/User.java
package io.hexlet.model;
public class User {
public static String getGreeting(String userName) {
return "Hello, " + userName + "!";
}
}
Использование пакетов
Классы из одного пакета могут обращаться друг к другу просто по имени. Компилятор поймет, что мы хотим использовать класс User
, расположенный в том же пакете
// Файл io/hexlet/model/Course.java
package io.hexlet.model;
public class Course {
public static User getAuthor() {
// тут код метода
}
}
Но в разработке постоянно приходится использовать классы и из других пакетов. Чтобы в своем коде использовать классы из другого пакета, их нужно импортировать. Для импорта класса используется ключевое слово import
, после которого идет полное имя класса, который мы хотим импортировать:
package io.hexlet;
import io.hexlet.model.User;
class App {
public static void main(String[] args) {
var greeting = User.getGreeting("John");
System.out.println(greeting);
}
}
Импортирование позволяет в коде обращаться к классу просто по его имени, иначе пришлось бы писать полное имя, включая название пакета (fully qualified), что может быть не удобно:
var greeting = io.hexlet.model.User.getGreeting("John");
Как мы уже обсудили выше, пакеты решают проблему конфликта имен, так как разные пакеты могут содержать классы с одинаковым именем. Но что делать, если в одном месте нужны два класса из разных пакетов с одинаковым именем? В таком случае можно использовать полное имя класса:
package io.hexlet;
class App {
public static void main(String[] args) {
var name = io.hexlet.utils.User.getUserName();
var greeting = io.hexlet.model.User.getGreeting(name);
System.out.println(greeting);
}
}
Чтобы немного упростить код, можно импортировать один из классов. Мы импортируем тот, который используем чаще всего. А для наименее используемого будем писать полное имя класса. В таком случае код будет выглядеть так:
package io.hexlet;
import io.hexlet.model.User;
class App {
public static void main(String[] args) {
var name = io.hexlet.utils.User.getUserName();
var greeting = User.getGreeting(name);
System.out.println(greeting);
}
}
Существует еще один способ импорта. Вы можете импортировать все классы из пакета при помощи *
import java.util.*
Такой способ импортирует весь пакет целиком, то есть в коде мы можем обратиться к любому классу пакета напрямую по имени. Этот способ на первый взгляд может показаться удобным, так как не требуется импортировать каждый класс по отдельности при необходимости использования нескольких классов из одного пакета. Однако, c таким способом нужно быть осторожным, так как он захламляет пространство имен, и классы из разных пакетов могут пересечься по именам.
Статический импорт
Java позволяет импортировать и использовать статические методы без явного указания класса. Особенно полезна эта возможность, когда часто приходится использовать какие-то методы. Это делает код более читаемым и компактным, так как не нужно каждый раз указывать имя класса при вызове статического метода:
import static java.lang.Math.*;
public class Main {
public static void main(String[] args) {
// Можем опустить указание класса при вызове метода
double result = sqrt(16);
System.out.println(result);
}
}