Ошибки, сложный материал, вопросы >
Нашли опечатку или неточность?

Выделите текст, нажмите ctrl + enter и отправьте его нам. В течение нескольких дней мы исправим ошибку или улучшим формулировку.

Что-то не получается или материал кажется сложным?

Загляните в раздел «Обсуждение»:

  • задайте вопрос нашим менторам. Вы быстрее справитесь с трудностями и прокачаете навык постановки правильных вопросов, что пригодится и в учёбе, и в работе программистом;
  • расскажите о своих впечатлениях. Если курс слишком сложный, подробный отзыв поможет нам сделать его лучше;
  • изучите вопросы других учеников и ответы на них. Это база знаний, которой можно и нужно пользоваться.
Об обучении на Хекслете

Модуль 2. Урок 4. Пакеты в Java.

alt text Это базовая тема. Двигаться дальше, к следующему уроку, стоит только после вдумчивого понимания этой темы. Смелее задавайте вопросы в обсуждениях и не ленитесь читать уже заданные вопросы - возможно там уже есть для вас ответ или подсказка :)


Что такое пакеты?

Пакеты, по сути, являются файловой и логической структурой связей классов в мире java. Очень схоже с файловой системой компьютера. На уровне файловой системы пакеты это и есть папки, в которых лежат другие папки (подпакеты) и классы. Но пакеты не всегда описывают напрямую всю структуру проекта. На практике проект включает в себя различные ресурсы, а структура папок, которую мы назначаем как имена пакетов для наших классов — может быть лишь небольшой частью целого проекта. Ведь, кроме основного кода в пакетах, у нас должны быть еще и тесты, библиотеки или даже другие языки программирования в проекте в целом.

Пакеты необходимы не только для красивого размещения файликов по папочкам или решения серьезной проблемы коллизии имен, но и для разделения доступа классов друг к другу. Именно благодаря пакетам мы можем через точку . получать доступ к нужным нам классам различных библиотек.

Как аналогию, можно привести пример с адресами.

Представим себя в роли некого класса, который выполняет некие функции для этого мира. Для того чтобы этот мир к нам обратился - нам нужно иметь конкретный адрес, путь по которому можно будет взаимодействовать с нами. Пускай это будет адрес, например:

package   страна.область.город.район.улица.дом.имяЧеловека
         корневой_пакет.подпакет.подпакет.подпакет.имяКласса

Что такое пакет для класса

Для класса его пакет — это его местоположение в проекте, относительно других классов. Благодаря разделению классов на несколько пакетов — мы организовываем структуру программы.

Сильно забегая вперед скажу, что такое разделение нужно не только для красоты, но и для ограничения доступа к некоторым членам класса. Например:

  • отсутствие модификатора перед методом или полем (еще говорят package-private) — означает, что данный член класса будет виден другим классам, но только из этого же пакета;
  • 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 всегда автоматически, неявно, импортирует в текущий класс два пакета:

  1. java.lang.*;. В ручную в коде это можно прописать так: import java.lang.*;. Это одна из стандартных библиотек java.
  2. Текущий пакет, в котором находится текущий класс. В ручную это выглядело бы так: 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.

**Некоторые терминалы не понимают звездочки `. Эту опцию нужно включать. В 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 (путь к папке со всеми пакетами и скомпилированными классами в них);
  • путь к стартовому классу в пакетной структуре программы.

-classpath, сокращенно: -cp. После этого ключа нужно указать имя папки, в которой лежат пакеты содержащие скомпилированные классы, в нашем случае это out.

Путь к стартовому классу, в пакетной структуре программы, нужно прописывать через точку: io.StartClass.

Команда запуска StartClass.class в одну строчку будет выглядеть так: java -cp out io.StartClass.

Обратите внимание:

  • В данной команде ключ пути к классам (-cp) должен идти первым, а потом уже путь и имя запускаемого класса.
  • И имя класса должно быть без расширения (в отличии от javac)!
  • Для запуска программы нужно указывать только тот класс, который содержит main-метод, остальные зависимости будут найдены и подключены из папки out автоматически, благодаря ключу -cp.

Полезные ссылки:

Документация:

Группы для общения:


<span class="translation_missing" title="translation missing: ru.web.courses.lessons.mentors.mentor_avatars">Mentor Avatars</span>

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят менторы из команды Хекслета или другие студенты.

Для полного доступа к курсу, нужна профессиональная подписка

Профессиональная подписка откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, даст возможность обращаться за помощью к менторам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
115
курсов
892
упражнения
2241
час теории
3196
тестов

Зарегистрироваться

или войти в аккаунт

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно.

  • 115 курсов, 2000+ часов теории
  • 800 практических заданий в браузере
  • 250 000 студентов

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Rambler
Логотип компании Bookmate
Логотип компании Botmother

Есть вопрос или хотите участвовать в обсуждении?

Зарегистрируйтесь или войдите в свой аккаунт

Нажимая кнопку «Зарегистрироваться», вы даёте своё согласие на обработку персональных данных в соответствии с «Политикой конфиденциальности» и соглашаетесь с «Условиями оказания услуг».