Это базовая тема. Двигаться дальше, к следующему уроку, стоит только после вдумчивого понимания этой темы. Смелее задавайте вопросы в обсуждениях и не ленитесь читать уже заданные вопросы - возможно там уже есть для вас ответ или подсказка :)
Пакеты, по сути, являются файловой и логической структурой связей классов в мире java. Очень схоже с файловой системой компьютера. На уровне файловой системы пакеты это и есть папки, в которых лежат другие папки (подпакеты) и классы. Но пакеты не всегда описывают напрямую всю структуру проекта. На практике проект включает в себя различные ресурсы, а структура папок, которую мы назначаем как имена пакетов для наших классов — может быть лишь небольшой частью целого проекта. Ведь, кроме основного кода в пакетах, у нас должны быть еще и тесты, библиотеки или даже другие языки программирования в проекте в целом.
Пакеты необходимы не только для красивого размещения файликов по папочкам или решения серьезной проблемы коллизии имен, но и для разделения доступа классов друг к другу. Именно благодаря пакетам мы можем через точку .
получать доступ к нужным нам классам различных библиотек.
Как аналогию, можно привести пример с адресами.
Представим себя в роли некого класса, который выполняет некие функции для этого мира. Для того чтобы этот мир к нам обратился - нам нужно иметь конкретный адрес, путь по которому можно будет взаимодействовать с нами. Пускай это будет адрес, например:
package страна.область.город.район.улица.дом.имяЧеловека
корневой_пакет.подпакет.подпакет.подпакет.имяКласса
Для класса его пакет — это его местоположение в проекте, относительно других классов. Благодаря разделению классов на несколько пакетов — мы организовываем структуру программы.
Сильно забегая вперед скажу, что такое разделение нужно не только для красоты, но и для ограничения доступа к некоторым членам класса. Например:
protected
перед методом или полем — означает, что данный член класса будет виден не только классам текущего пакета, как с package-private, но и классам-наследникам.По сути нам нужно:
.java
в нужной папке;Рассмотрим этот процесс поэтапно.
Сначала пишем имя любимого редактора в терминале, потом существующий путь, а потом имя будущего текстового файла.
Пример для редактора nano
:
UserName BASH ~/projectName/src
nano -f /io/hexlet/xo/view/ConsoleView.java
Или с помощью sublime:
UserName BASH ~/projectName/src
subl /io/hexlet/xo/view/ConsoleView.java
Если команда subl
не найдена и Вы уверены в том, что sublime установлен — проверьте, в переменных среды окружения, наличие прописанного пути к папке, в которой лежит subl.
Можно и обычным блокнотом создать нужный файл в нужной директории. Создать сам файл можно и любым другим удобным для вас способом.
Файл создан. Но класса в нем нет. Создадим класс:
public class ConsoleView {
//some field & methods
}
Теперь у нас есть и файл в нужной папке, и класс в файле. Но сам класс ничего не знает про то, что он принадлежит некому пакету. Исправим это:
package io.hexlet.xo.view;
public class ConsoleView {
//some field & methods
}
Не забываем сохранять файл!
Ключевые моменты верного создания класса внутри пакета:
package
;package
должно быть в первой строке кода, перед импортами и объявлением самого класса;package
нужно указать полное имя пакета, с корня пакета до пакета(папки) в которой размещен класс;package
, — должны соответствовать друг другу.А как же задать корневой каталог (папку) как основу пакетов, в котором уже и происходит ветвление этих всех под-пакетов (под-папок)? Почему, например, папка io является корнем для классов проекта, а папка src — не является пакетом, да и вообще не входит в пакетную структуру проекта?
Все очень просто: корневым пакетом считается тот который первым прописан в каждом java-файле после слова package
, в текущем проекте. А в самом проекте помимо папки src, на ее уровне и выше, — могут быть много различных папок и файлов, для работы с проектом в целом. Но это не делает их корневым пакетом, или вообще частью пакетов java, в этом проекте, если они не включены в имена пакетов в классах, после ключевого слова package
. Пример структуры папок и пакетов:
-/projectName // Папка проекта. Может содержать кучу всякой всячины.
README.md // файл с описанием. В пакет никак не входит, но входит в проект.
+/out // Папка для скомпилированных файлов *.class
-/src // Папка для исходных файлов *.java
-/io // корень пакета
StartClass.java // package io;
-/hexlet
OneMoreClass.java // package io.hexlet;
-/xo // в этой папке может и не быть классов
-/controllers
ControllerOne.java // package io.hexlet.xo.controllers;
Controller2.java // package io.hexlet.xo.controllers;
-/model
ModelClaas1.java // package io.hexlet.xo.model;
ModelClass2.java // package io.hexlet.xo.model;
-/view
SomeView.java // package io.hexlet.xo.view;
Пример содержимого класса OneMoreClass.java
:
package io.hexlet;
public class OneMoreClass {
// тут некий код, переменные или просто пустота.
}
Пример содержимого класса SomeView.java
:
package io.hexlet.xo.view;
public class SomeView {
// просто метод для дальнейших примеров.
public void message() {
System.out.println("Some very important message!");
}
}
Как видно из примеров выше — любой класс в этом проекте всегда содержит в имени пакета на первом месте папку io. Это и будет корневым пакетом проекта. На месте папки io могла быть папка com или любое другое имя. Важно чтобы все java-классы текущего проекта указывали на эту папку как на первую после ключевого слова package
. Если хоть у одного класса в этом проекте папка io не будет прописана — то такой класс как бы и не в проекте, а просто мусор в папке.
Например, package com.MySuperLongPackageName.view
— плохая практика именования пакетов.
Совокупность имен под-пакетов делает проект уникальным, не похожим на миллионы других. Даже если, в каждом проекте в мире, 100% будет класс Main.java
— то они маловероятно пересекутся и помешают друг-другу. Но даже если пересекутся — это решаемо, рассмотрим это в дальнейших примерах.
Адреса (пакеты) классам, в нашем проекте, мы уже выдали. Они нужны для доступа классов друг к другу.
Как уже было сказано выше — пакет можно сравнить с адресом. Рассмотрим это на примере ветвления папок(пакетов).
Допустим, в классе StartClass.java
нам нужно создать экземпляр класса SomeView.java
, для использования его метода message()
. Как это можно реализовать:
package io;
public class StartClass {
// создаем объект класса SomeView
static io.hexlet.xo.view.SomeView someView = new io.hexlet.xo.view.SomeView();
public static void main(String... args) {
someView.message(); // на экран выведется: "Some very important message!"
}
}
Только благодаря прописанному пути к классу SomeView
, класс StartClass
может вообще узнать о его существовании.
Но прописывание таких длинных путей (io.hexlet.xo.view.SomeView
) — просто не удобно. Поэтому, к нам на выручку, приходит импортирование.
Для того чтобы не писать длинные пути, к каждому конкретному классу, при каждом его использовании — нужно его импортировать. Для этого применяется ключевое слово import
. Импорты пишутся между строчкой package ...
и строчкой объявления класса. Перепишем предыдущий пример StartClass
с применением ключевого слова import
:
package io;
import io.hexlet.xo.view.SomeView; // Расширение файла в конце не пишем!!! Только имя класса!
public class StartClass {
// создаем объект класса SomeView
static SomeView someView = new SomeView(); // имя класса доступно без имени пакета
public static void main(String... args) {
someView.message(); // на экран выведется: "Some very important message!"
}
}
Если в пакете io.hexlet.xo.view
очень много разных классов. И они нам все нужны тут, в классе StartClass
, как их все разом заимпортировать?
Решение приходит со звездочкой *
.
Перепишем предыдущий пример с импортированием любых классов из пакета io.hexlet.xo.view
:
package io;
import io.hexlet.xo.view.*; // все классы из пакета импортированы
public class StartClass {
// создаем объект класса SomeView
static SomeView someView = new SomeView(); // имя класса доступно без имени пакета
public static void main(String... args) {
someView.message(); // на экран выведется: "Some very important message!"
}
}
В случае возникновения коллизий имен классов, например, когда в разных пакетах есть классы с одинаковыми именами и оба пакета импортированы — тогда придется обращаться к таким классам с использованием полных имени пакетов. Например, io.hexlet.xo.view.SomeView
. Иначе java не будет знать из какого именно пакета использовать класс, в данном случае.
Независимо от того, написал ли программист вообще слово import
, перед определением своего класса — java всегда автоматически, неявно, импортирует в текущий класс два пакета:
java.lang.*;
. Вручную в коде это можно прописать так: import java.lang.*;
. Это одна из стандартных библиотек java.import .
или import полный.путь.к.папке.с.этим.файлом
.Обратите внимание на строку System.out.println("Some very important message!");
в классе SomeView
:
package io.hexlet.xo.view;
public class SomeView {
// просто метод для дальнейших примеров.
public void message() {
System.out.println("Some very important message!");
}
}
Класс SomeView
видит класс System
и его открытые поля, в частности поле out
, благодаря стандартному импорту java.lang.*;
, т.к. System
входит в пакет java.lang
.
Этот аспект будет рассмотрен в "Модуль 3. Урок 3. Уровни методов в Java." Поскольку там мы лучше познакомимся с ключевым словом static
.
Для компиляции, используется команда javac
.
Для компиляции отдельного файла в конкретном пакете, нужно прописать команду так: javac src/com/package_name/package_name/ClassName.java
. Обратите внимание, что для команды javac
— мы прописываем все пути через слеш (/
), а не через точки. И обязательно нужно указать расширение файлов (.java
).
Если этот файл (класс) ClassName.java
использует в своем коде другой класс, из соседнего пакета — это уже зависимость. Нужно подсказать компилятору, где еще лежат исходники (сорцы), от которых может зависеть компилируемый класс, с помощью ключа -sourcepath
. Выглядит это так: javac -sourcepath src
. А потом уже пишем путь к компилируемому файлу: src/com/package_name/package_name/ClassName.java
. Целиком, в терминале, команда будет выглядеть так: javac -sourcepath src src/com/package_name/package_name/ClassName.java
. -sourcepath src
можно написать и в конце данной строки.
Для того чтобы класс-файлы не лежали рядом с исходниками, а в отдельной папочке, можно применить ключ -d
, а за ним имя существующей папки, в которую складываются скомпилированные классы. Переделаем предыдущий пример так: javac src/com/package_name/package_name/ClassName.java -sourcepath src -d out
.
Для того, чтобы компилятор прошелся по всем директориям в папке, например, src
и скомпилировал там все исходники — нужно ввести команду так: javac src/**/*.java
(пройтись по всем под папкам в папке src
и скомпилировать там все файлы *.java
). Правда они опять будут лежать радом с исходниками. Лучше сразу разложить класс-файлы в отдельную папку: javac src/**/*.java -d out
. Все скомпилированные классы будут разложены по пакетам так же как они располагались в папке src
.
А если эти классы в папке src
зависят друг от друга, то команда будет выглядеть так: javac src/**/*.java -d out -sourcepath src
. В результате чего компилятор скомпилирует все *.java
-файлы в *.class
-файлы, свяжет их по зависимостям и уложит в директорию out
.
Некоторые терминалы не понимают звездочки
**
. Например, командаjavac src/**/*.java
может просто выдавать ошибку отсутствия файлов. "Понимание звездочек" нужно включать! В bash это можно включить командой:shopt -s globstar
.
Для запуска java-приложений используется командаjava
.
Для запуска отдельных классов, которые не в пакетах, используется простой подход, например: java Main
, java Start
, java MyStartClass
. Главное чтобы в этом классе был специальный метод:
public static void main(String[] args){
// TODO something
}
Именно с него начинается работа самостоятельной java-программы. В остальных классах одной программы — этот метод не нужен.
А для запуска "пакетного" класса на исполнение требуется иной подход.
Запустим наш класс StartClass
.
Поскольку наш исполняемый файл StartClass.class
лежит в отдельной папочке и в своей некой ветке директорий (пакете), то теперь нужно не просто написать java StartClass
, а указать:
-classpath
, сокращенно: -cp
. После этого ключа нужно указать имя папки, в которой лежат пакеты содержащие скомпилированные классы, в нашем случае это out
.
Путь к стартовому классу, в пакетной структуре программы, нужно прописывать через точку: io.StartClass
.
Команда запуска StartClass.class
в одну строчку будет выглядеть так: java -cp ./out io.StartClass
.
Обратите внимание:
-cp
) должен идти первым, а потом уже путь и имя запускаемого класса..class
(в отличие от javac
)!main
-метод, остальные зависимости будут найдены и подключены из папки out
автоматически, благодаря ключу -cp
.Полезные ссылки:
JDK
и настройка переменных окружающей среды. Чтобы работала команда javac
javac src/**/*.java
? Решение:
Вам ответят команда поддержки Хекслета или другие студенты.
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
Наши выпускники работают в компаниях:
Зарегистрируйтесь или войдите в свой аккаунт