Пакеты
Пришло время упомянуть о пакетах. Вполне возможно, что вы уже задумались над следующим вопросом: «Если классов будет много, то их все в одной директории держать? И как потом с ними разбираться?».
Вы совершенно правы — в большом приложении классов создается тысячи и десятки тысяч. Конечно же необходим механизм каталогизации. И такой механизм создан. Причем достаточно простой и очевидный — директории. Мы уже привыкли, что на диске наши файлы лежат в разных директориях, которые мы сами организовываем в удобном порядке. В Java сделано тоже самое — физически класс кладется в определенную директорию. Существует даже некоторые правила именования этих директорий. Например для коммерчески проектов директория должна начинаться сначала с префикса «com» а за ним следует название компании — например «mycompany». Далее следует название проекта. Потом уже идет более точное разделение по какому-либо признаку — чаще всего функциональному, но никто не мешает их разбивать даже в алфавитном порядке (хотя это вряд ли хорошая идея). Т.е. когда компания IBM создает проект education, то полный путь до классов вполне может выглядеть вот так: com/ibm/education.
Для проектов OpenSource часто начинается все с org. Существуют и другие варианты — например для государственных органов РФ в Санкт-Петербурге начало может выглядеть как ru/spb.
Если вы не работали в Unix, то вас возможно смутит, что я пишу разделитель директорий не как в Windows — там используется обратный слэш — «\». Я опять немного забегу вперед — как мы уже говорили, Java является кросс-платформенной системой, т.е. не зависит от операционной системы. Но сама операционная система все-таки влияет на программы на Java. Одним из таких проявлений является разделитель директорий. Открою небольшую тайну — удобнее использовать прямой слэш — «/» — даже в Windows это работает. Когда мы будем знакомиться с системой ввода-вывода, мы узнаем еще более официальный способ.
В итоге все выглядит достаточно понятно, но есть один момент. Вы уже обратили внимание, что NetBeans по умолчанию помещает все наши классы в папку src внутри директории с проектом. Но тогда может возникнуть еще один вопрос: «Мы же не указали вообще никаких пакетов и файл лежит в директории C:\JavaLesson\Robot\src. Значит ли это, что пакет по умолчанию вот так и называется?».
Конечно же нет. На самом деле в проекте на Java всегда существует некоторая корневая директория относительно которой и строится дерево пакетов. В проекте в NetBeans этой корневой директорией является src. Но если мы не используем NetBeans, то какая директория является корневой ? Определение корневой директории определяется специальной директивой package в начала файла с классом. Например если я хочу поместить класс Robot в пакет edu.robot и моя корневая директория называется C:\JavaLesson\Robot\src, то я должен сделать три действия:
- Создать в директории src директорию edu и уже внутри нее robot
- Поместить файл Robot.java в директорию edu/robot
- Поместить в файл Robot.java директиву package edu.robot
Полный текст нашего робота (я опустил пока все его методы) будет выглядеть вот так:
1 2 3 4 5 6 |
// Название пакета идет в самом начале файла package edu.robot; public class Robot { // Код,который мы еще напишем } |
Обратите внимание, что название пакета идет в самом начале файла и разделителем для директорий является не слэш, а точка — чуть дальше мы опять ее увидим.
Теперь компилятор будет знать, что класс объявляет себя находящимся в определенной директории и будет за этим следить. Давайте снова перенесемся в командную строку (надеюсь вы ее еще не забыли) и посмотрим как компилировать и запускать программу, когда класс находится не в корневой диреткории, а уже в пакете (в поддиректориях). Давайте запустим командную строку (кто не помнит — идем в главуОсновные шаги).
Я создал директорию Robot3 в нашей папке C:\javaLesson и в ней директории edu/robot. В самой «нижней» директории robot я создал файл Robot.java вот такого вида (чтобы его можно было запустить и посмотреть результат):
1 2 3 4 5 6 7 8 |
package edu.robot; public class Robot { public static void main(String[] args) { System.out.println("Hello from Robot"); } } |
Выполним команду dir edu\robot и у нас должно получиться то, что вы видете на рисунке.
Теперь нам надо скомпилировать наш файл. Для этого надо выполнить команду
javac edu\robot\*.java
Если у вас все сделано правильно, то компилятор вам ничего не скажет а в директории edu/robot появиться файл Robot.class. Теперь нам надо его выполнить вот такой командой
java edu.robot.Robot
Результат наших действий можно видеть на рисунке.
Здесь важно отметить следующие моменты:
- Нашей текущей директорией является «корневая» директория наших пакетов
- Вызов класса происходит по так называемому «полному имени класса», которое включает полное название пакета и само имя
Полное имя класса — весьма важный момент. Разделение классов по пакетам служит не только для удобства, но решает еще одну важную задачу — уникальность имен классов. Наверняка в большом проекте будет участвовать много людей и каждый будет писать свои классы. И наверняка имена этих классов нередко будут одинаковые. Если опять забегать немного вперед, то вы скорее всего будете подключать внешние библиотеки (мы узнаем, как это делается) и в них будут классы, которые будут называться так же как ваши. Единственным спасением различать их — поместить в разные пакеты. Классы с названиями User, Session, Controller, Handler встречаются достаточно часто и разделение на пакеты спасает нас от кошмара искать новое подходящее имя для переменной. Кстати хорошее название очень важно при разработке — писать комментарии программисты не любят и хорошее название может заменить много строк комментариев.
А теперь самое время использовать наши знания о классах, как строительных блоках, и новые знания о пакетах. Напишем простое графическое приложение, которое создаст нам обычное окно. Наш классSimpleFrame создаст экземпляр уже готового класса JFrame и будет устанавливать его свойства. Представьте себе, что вы получили в распоряжение конструктор с деталями, у которых можно менять размеры, выставлять цвет, надписи и т.д. попробуйте отнестись к классу JFrame именно так. Наш класс SimpleFrame мы поместили в пакет (что является чуть ли не обязательным — помещать классы в корневую директорию считается очень плохой практикой. Так что первые наши программы были с этой точки зрения ужасающими — не делайте так больше. Хотя иногда для проведения каких-либо простых экспериментов я создаю такие классы). В коде нашего класса мы воспользовались директивой import для подключения класса JFrame. мы поговорим о ней несколько позже, но сначала посмотрим код. Полный пример проекта можно взять тут — SimpleFrame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
package edu.javacourse; // Мы ИМПОРТИРОВАЛИ класс из пакета import javax.swing.JFrame; public class SimpleFrame { public static void main(String[] args) { // Создали экземпляр класса - объект JFrame sf = new JFrame(); // Установим заголовок sf.setTitle("First window"); // Установим свойство - завершить приложение при закрытии окна sf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Выставим координаты и размеры // Верхний левый угол - первые два числа 100 и 100 // Ширина и высота - вторы два числа 400 и 200 sf.setBounds(100, 100, 400, 200); // Отобразим окно, сделав его видимым sf.setVisible(true); // Теперь у нас на экране появилось окно, которым // можно управлять - перетаскивать, менять размеры. // Разве не красиво ? } } |
Как видно из кода, мы создали экземпляр объекта класса JFrame, а потом использовали его методы для того, чтобы наш объект сделал определенные действия. Методы, котоыре мы используем УЖЕ существуют в классе JFrame и нам остается их только использовать.
Не появилось ощущения, что мы по сути «управляем» нашим объектом, который умеет делать многое ? Когда вы научитесь использовать объекты, программирование превратится в увлекательные конструкторские задачки — как построить систему из готовых объектов и какие объекты (классы) с какими свойствами надо еще добавить. Мы обязательно вернемся к таким задачкам — я очень люблю использовать графические программы для демонстрации — они наглядно показывают идею. Предвосхищая вопрос «а где можно посмотреть какие есть классы и методы у классов» могу предложить совершенно не оригинальный способ — читайте документацию. Если ее нет — читайте исходные коды. Если и их нет — я иногда для понимания использую декомпиляцию. Если же классы обработаны специальными средствами, чтобы их нельзя было декомпилировать — может тогда не надо использовать такие классы совсем ?
Но т.к. сейчас мы рассматриваем пакеты, давайте обратим наше внимание на директиву import.
Т.к. мы сами не создавали код класса JFrame разумно предположить, что он находится где-то вне нашего проекта. Это так и есть — этот класс входит в стандартную библиотеку Java и может использоваться всеми программами. Но если это так, то все-таки зачем нам импортировать этот класс ? Если он есть — ну и здорово. Но мы также говорили, что у классов бывают одинаковые имена (имена файлов). А вот полные имена (с учетом пакетов) у них всегда должны быть разные. Иногда по ошибке программистов может случится ситуация, в которой два класса будут иметь полностью одинаковые имена. В этом случае возникнет ошибка при загрузке такого класса и программа просто не заработает. Хотя если пойти еще глубже, то и это случай можно сделать работоспособоным (больше не буду туманить вам голову — может позже).
Но как вы заметили мы пишем простые имена — в нашей программе мы используем простое имя JFrame. Так вот директива import и указывает, из какого пакета берется класс JFrame, чтобы в остальном коде не надо было использовать полное имя. Хотя мы можем это сделать сами.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
package edu.javacourse; public class SimpleFrame { public static void main(String[] args) { // Создали экземпляр класса - объект javax.swing.JFrame sf = new javax.swing.JFrame(); // Установим заголовок sf.setTitle("First window"); // Установим свойсво - завершить приложение при закрытии окна sf.setDefaultCloseOperation(javax.swing.JFrame.EXIT_ON_CLOSE); // Выставим координаты и размеры // Верхний левый угол - первые два числа 100 и 100 // Ширина и высота - вторы два числа 400 и 200 sf.setBounds(100, 100, 400, 200); // Отобразим окно, сделав его видимым sf.setVisible(true); // Теперь у нас на экране появилось окно, которым // можно управлять - перетаскивать, менять размеры. // Разве не красиво ? } } |
Как видим, в этом варианте мы не делаем импорт, но вынуждены каждый раз при обращении в классу JFrame использовать его полное имя с указанием всех пакетов.
Возможно вы уже догадались, но тем не менее давайте укажем это явно — если в коде класса A вам потребовалась ссылка на класс B, то директива import для класса B потребуется тогда, когда класс B находится в другом пакете, нежели класс A. Если же оба класса находятся в одном пакете, то директива import не потребуется.
Вы можете задаться вопросом: «А зачем может потребоваться полное имя в коде?». Такое возможно в случае когда в одном классе надо обращаться к двум классам с одинаковыми короткими именами, но разными полными именами. Например, существует два очень используемых класса java.util.Date и java.sql.Date. и бывают случаи, когда оба класса нужны внутри одного класса. Тогда надо применить именно такой прием — один класс может быть определен через import, а второй — по полному пути во всем коде.
На этом мы пока закончим рассмотрение пакетов, хотя возможно будем их вспоминать в связи с новыми вопросами. Пока же запомните одно хорошее правило — не создавайте классы вне пакетов.
И теперь нас ждет следующая статья: Переопределение и перегрузка