Показаны сообщения с ярлыком import. Показать все сообщения
Показаны сообщения с ярлыком import. Показать все сообщения

17 авг. 2015 г.

Внутренние классы. Часть 2 – статические вложенные классы.

Статические вложенные классы (nested) очень похожи на классы верхнего уровня. Их обычно используют если необходимо логически связать два класса – внешний и внутренний. Интерфейсы так же могут быть статическими вложенными интерфейсами располагающимися внутри внешнего класса или интерфейса.

Статический вложенный класс определяется внутри другого внешнего класса. Это может выглядеть так:

NC0001

Как видно из кода, внешним классом является класс OuterClass, статическим вложенным классом является StaticNesterdClass, статическим вложенным интерфейсом является StaticNestedInterface.

Обратите внимание на границы внешнего класса, а так же вложенного статического класса и интерфейса.

По существу внешний класс для статического вложенного класса является как бы мини пакетом.

 

Статический вложенный класс или интерфейс определен как static член окружающего класса, что делает его аналогом поля и метода класса, которые так же объявлены как static. Как и метод класса, статический вложенный класс не связан ни с одним экземпляром внешнего класса, то есть может использоваться без создания экземпляра внешнего класса. Тем не менее статический вложенный класс имеет доступ ко всем static членам окружающего класса, включая любые другие статические вложенные классы и интерфейсы. Статический вложенный класс может использовать любой статический член окружающего класса без указания имени этого окружающего класса.

Статический вложенный класс имеет доступ ко всем статическим членам окружающего класса, включая private члены. Обратное тоже верно: методы окружающего класса имеют доступ ко всем static членам статического вложенного класса, включая его private static члены, однако при обращении к ним необходимо указывать имя статического вложенного класса, которому они принадлежат.

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

Статический вложенный класс не может называться так же, как называется любой из окружающих классов. Кроме того, статические вложенные классы и интерфейсы могут быть объявлены только в классах верхнего уровня и других статических вложенных классах и интерфейсах. В действительности это часть более общего запрета на использование static членов любого вида во внутренних, локальных и анонимных классах.

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

Если в статический вложенный класс вкладывается еще один класс, то это не делает его автоматически статическим, он будет вложенным inner классом, даже если его окружающий класс является вложенным в интерфейс. Чтобы класс вложенный в статический класс был статическим необходимо это явно указать при помощи ключевого слова static.

Ну и теперь немного практики…

NC0002

NC0003

NC0004

Вывод данной программы представлен слева. Обратите внимание на то, каким образом был создан экземпляр класса Nested. Перед ним использовалось уточняющее имя внешнего класса Outer. Данный синтаксис можно изменить импортом внутреннего класса Nested.

nst.getName() выводит private поле nestedName. Заметьте что оно не статическое и private. nst.getOuterName() выводит статическое private поле outerName внешнего класса Outer. Далее выводиться статическое поле out внешнего класса Outer. Мы смогли это сделать так как оно статическое. Далее выводится статическое поле nst вложенного класса Nested через уточнение именем класса Outer.

Затем мы создаем экземпляр класса Outer и получаем через него доступ к private static полю outerName через вызов метода getName() на экземпляре класса Outer. И последняя строка выводит private static поле вложенного класса через метод внешнего класса getStnst(), который имеет доступ даже к private static полям вложенного класса. Ну как не запутались? :) По идее пока все не очень сложно :)

NC0005

Ну и еще стоит отметить, что компилятор все равно нам создал три .class файла. Но вложенный класс Nested создан с уточняющим именем внешнего класса Outer через знак $. Поэтому все вложенные классы являются чистым синтаксическим сахарком, но достаточно нужным и удобным.

В коде, расположенном вне окружающего класса, на статический вложенный класс или интерфейс ссылаются по имени внешнего класса с последующим добавлением имени внутреннего класса (например, Outer.Nested). Для импорта статических классов-членов можно применять директиву import:

import pro.java.nested.Outer.Nested; // импорт вложенного класса Nested
import pro.java.nested.Outer.*; // импорт всех вложенных классов из класса Outer

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

Nested nst = new Nested("MyNested");

То есть в данном случае мы уже не видим что класс Nested является вложенным классом класса Outer, как это было явно видно до импорта:

Outer.Nested nst = new Outer.Nested("MyNested");

 

NC0007Теперь еще немного поговорим о вложенных интерфейсах. Я добавил в класс Outer вложенный интерфейс IGetNames и вложенный класс GetNames в котором реализовал этот интерфейс, а так же в класс Main добавил создание экземпляра класса GetNames и использование его методов.

NC0008

Здесь я привел лишь отрывки кода который был добавлен. В этом примере класс GetNames является вложенным в класс Outer, но ни что не мешает вложить его в интерфейс IGetNames.

 

Но тогда уже придется уточнять имя класса вложенного в интерфейс через имя внешнего класса и имя вложенного интерфейса:

Outer.IGetNames.GetNames gn = new Outer.IGetNames.GetNames();

NC0009

Для наглядности, так же приведу отрывки измененного кода в классе Outer. в классе Main была изменена лишь строка создания экземпляра класса GetNames, которая уже была приведена выше.

Оба два предыдущих примера генерируют следующий вывод:

NC0010

Три последние строки генерируются вызовом методов на экземпляре класса GetNames.

Вызов этих методов приведен на отрывке кода представленного на скриншоте выше.

Интересно так же посмотреть на те .class файлы которые получились в результате наших изменений в коде классов.

NC0011

Как видим у нас получилось пять .class файлов. По одному на каждый из наших классов и интерфейсу. Особое внимание следует обратить на имена вложенных классов и интерфейса. Уровень вложенности классов разделяется знаком $. Знак доллара является валидным символов в именах классов, но предназначен для использования только компилятором и JVM.

Кстати сказать избавиться от длинной строки создания экземпляра класса GetNames можно все тем же импортом. Для этого нам надо импортировать этот класс по его полному имени:

import pro.java.nested.Outer.IGetNames.GetNames;

Тогда строка создания экземпляра класса GetNames будет выглядеть так:

GetNames gn = new GetNames();

Но опять же тут не очевидно что GetNames это вложенный класс, хотя эта запись гораздо короче.

NC0012Вложенные классы можно размещать и просто в интерфейсах. Любой класс, помещенный в интерфейс, автоматически объявляется как открытый (public) и статический (static). Так как класс объявляется как static, он не нарушает правил обращения с интерфейсом – этот вложенный класс всего лишь размещается в пространстве имен интерфейса. И как я уже показывал, вы даже можете реализовать окружающий интерфейс во внутреннем классе. Хитрый пример показан слева. И если вы его просто попробуете запустить из Eclipse, то ни чего не выйдет, хотя этот файл без проблем и ошибок скомпилируется. И он даже работает и его даже можно запустить, но используя магию правильное понимание. Это понимание можно почерпнуть здесь. Но все же его стоит чуть обновить до нынешних наших знаний.

И поможет нам в этом магия утилиты javap

NC0013

То есть нам надо запускать класс содержащий метод main() по его полному имени и делать это правильным образом из правильного места.

NC0014

Но можно сделать это и из Eclipse указав класс где находится метод main():

NC0015

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

Статические вложенные классы можно так же использовать для проверки работоспособности классов, то есть для их тестирования. Поскольку в них вы можете расположить метод main(). Как вы уже знаете при компиляции будет скомпилирован отдельный .class файл для вложенного класса, который вы можете запускать для тестирования работы отдельного класса не запуская всю программу. Кроме того, в окончательную сборку программы можно не включать файлы .class которые были сгенерированы для тестирования, их можно просто удалить и собрать программу, таким образом в ней не будет лишнего кода, который вы создали для тестирования.

NC0017

NC0016

На примерах слева и сверху представлены два класса Main, содержащий метод main() и SomeClass, содержащий вложенный класс Test, который так же содержит метод main().

После компиляции получается соответственно три файла с расширением .class:

NC0018

Если посмотреть на содержимое файлов SomeClass.class и SomeClass$Test.class при помощи javap, то увидим следующее:

NC0019

Как видим в байт коде класса SomeClass не присутствует код вложенного в него класса Test, поскольку он находится в отдельном своем .class файле, в котором есть метод main(). И этот .class файл мы можем запустить отдельно от класса Main. Так же мы можем запустить на исполнение и класс Main, и даже если мы удалим откомпилированный .class файл класса Test, то наша программа все равно будет работать правильно.

NC0020

В дополнение ко всему вышесказанному предлагаю посмотреть хорошее видео по вложенным статическим классам:

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

NC0021

NC0022

Что тут стоить отметить, так это то что в классе Human, а так же в его вложенном классе Relations используется идентификатор relations во множестве мест и при этом не происходит ни какой путаницы. Кроме того, в отличие от примера на видео, я сперва создал объект статического вложенного класса, а уже только потом создал объект внешнего класса, чтобы подчеркнуть независимость одного от другого.

Теперь еще стоит поговорить о наследовании в статических вложенных классах.

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

И теперь опять попрактикуемся…

NC0023

NC0024

Вывод у программы следующий:

NC0025

Как видим в этом примере у нас есть два класса наследника вложенного класса Nested. Это классы Ext1 и Ext2. Причем Ext1 является внутренним классом для класса Nested и соответственно имеет доступ ко всем полям внешнего класса Outer. Обращаю внимание что класс Ext1 не статический, а внутренний, то есть inner (это наша следующая тема). Класс же Ext2 не является внутренним классом Nested хотя и наследует его и поэтому он не имеет доступа к полям класса Nested. Следует обратить внимание на интересный синтаксис создания экземпляра класса Ext1. Поскольку класс Ext1 не статический, то он не может быть создан без привязки к экземпляру его внешнего класса.

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

Outer.Nested.Ext1 ext1 = new Outer.Nested.Ext1();

Во всем остальном программа не изменится и будет работать так же как и работала.

Можно так же привести примеры использования статических вложенных классов из реальной жизни. Что может быть более реального чем стандартная библиотека java в которой существует класс java.awt.geom.Rectangle2D который имеет два вложенных класса, Float и Double. Это очень простые классы форм и было бы просто ни к чему умножать количество высокоуровневых классов в этом пакете еще на два.

NC0026

Ну и завершим все небольшим примером реализации простенького стека с помощью вложенных классов. Это несколько измененный пример стека который мы уже делали, но там я не приводил вывода данной программы. Да и вообще текущий пример я сильно переделал. Теперь в стеке можно размещать вообще любые объекты а не только целые числа, кроме того я добавил наследование, что сократило код класса вложенного класса DynStack. Данный пример я сделал двумя коммитами, первый без реализации динамического стека, второй с реализацией.

NC0027

NC0028

Тут представлен окончательный вариант с реализацией интерфейса IStack двумя классами FixedStack и DynStack. Причем класс DynStack является вложенным в класс FixedStack, который в свою очередь вложен в интерфейс IStack. Класс DynStack наследуется от класса FixedStack и поэтому там переопределен только один метод push(), который увеличивает размер стека в два раза при его заполнении.

Стоит обратить внимание на то что для класса FixedStack мы не писали слово static, хотя он таковым и является поскольку является вложенным в интерфейс классом. А вот для класса DynStack нам уже пришлось написать слово static, так как если бы мы этого не сделали, то этот класс был бы внутренним, то есть inner – не статическим.

Ну и на последок длинная портянка вывода данной программы:

NC0029

Первая строка выводится сразу же после создания объекта fix. Тогда еще стек пустой.

Затем мы добавили в стек единицу и вывели его состояние.

После добавили строку и опять вывели состояние.

Затем добавили значение типа double 55.55 и вывели состояние.

Затем попытались добавить ссылку null, что вполне легитимно для Object, но нам выдалось сообщение что стек полон.

После этого мы произвели операцию изъятия из стека значения и вывели опять стек, теперь у нас там два элемента – строка и единица.

После этого напечатали звездочки и создали объект динамического стека.

Добавили туда 10 вывели значение.

Добавили другую строку и снова вывели значение.

Затем добавили туда значение 77.77, но поскольку стек у нас был из двух элементов, то произошло увеличение размера стека, о чем мы и получили сообщение и уже в увеличенные стек было помещено значение 77.77 что мы и видим в выводе программы.

После этого значение 77.77 было изъято из стека и содержимое стека было выведено на экран.

За сим все! Кино про статические вложенные классы закончено :)

Далее в программе изучение внутренних inner классов.

Да и вообще далее еще много чего :)

30 июн. 2015 г.

Пакеты в Java.

Пакет (package) представляет собой именованную совокупность классов (и, возможно, подпакетов). Пакеты группируют классы и определяют пространства имен для классов, которые в них входят.

Зачем нужны пакеты в Java и что это такое

  • Задание пространства имен, предотвращение коллизий имен классов
  • Логическая группировка связанных классов
  • Инкапсуляция - сокрытие деталей реализации за счет модификаторов доступа

Платформа Java содержит пакеты, имена которых начинаются на com, java, javax и org. Посмотрите это в сырцах, не поленитесь. Основные классы языка входят в пакет java.lang. Различные вспомогательные классы находятся в java.util. Классы для ввода и вывода входят в java.io, а классы для работы в сети – в java.net. Некоторые их этих пакетов содержат подпакеты. Например, java.lang содержит два специализированных пакета java.lang.reflect и java.lang.ref, а java.util содержит подпакет java.util.zip, который в свою очередь содержит классы для работы с ZIPархивами.

Каждый класс имеет как простое имя, данное ему в определении, так и полное имя, включающее имя пакета, в который он входит. Например, класс String является частью пакета java.lang, а его полное имя – java.lang.String.

В стандартную библиотеку Java API входят сотни классов. Каждый программист в ходе работы добавляет к ним десятки своих классов. Множество классов растет и становится необозримым. Уже давно принято отдельные классы, решающие какую-то одну определенную задачу, объединять в библиотеки классов. Но библиотеки классов, кроме стандартной библиотеки, не являются частью языка.

Разработчики Java включили в язык дополнительную конструкцию — пакеты (packages). Все классы Java распределяются по пакетам. Кроме классов пакеты могут содержать интерфейсы и вложенные подпакеты (subpackages). Образуется древовидная структура пакетов и подпакетов.

Эта структура в точности отображается на структуру файловой системы. Все файлы с расширением class (содержащие байт-коды), образующие один пакет, хранятся в одном каталоге файловой системы. Подпакеты образуют подкаталоги этого каталога.

Каждый пакет создает одно пространство имен (namespace). Это означает, что все имена классов, интерфейсов и подпакетов в пакете должны быть уникальны. Имена в разных пакетах могут совпадать, но это будут разные программные единицы. Таким образом, ни один класс, интерфейс или подпакет не может оказаться сразу в двух пакетах. Если надо в одном месте программы использовать два класса с одинаковыми именами из разных пакетов, то имя класса уточняется именем пакета: пакет.Класс. Такое уточненное имя называется полным именем класса (fully qualified name).

Все эти правила, опять-таки, совпадают с правилами хранения файлов и подкаталогов в каталогах, только в файловых системах для разделения имен каталогов в пути к файлу обычно используется наклонная черта или двоеточие, а не точка.

Еще одно отличие от файловой системы — подпакет не является частью пакета, и классы, находящиеся в нем, не относятся к пакету, а только к подпакету. Поэтому, для того чтобы создать подпакет, не надо предварительно создавать пакет. С другой стороны, включение в программу пакета не означает включение его подпакетов.

Определение пакета

Чтобы определить пакет для какого-либо класса, следует использовать директиву package. Если в Java-коде присутствует ключевое слово package, оно должно быть первой лексемой кода в файле Java, то есть должно следовать сразу после комментариев и пробелов. После ключевого слова должно стоять имя требуемого пакета и точка с запятой. Например:

package pro.java.pkg001;

Все файлы с исходными текстами и тем-более откомпилированные файлы с байт-кодом (расширение .calss) должны находится в соответствующих под-каталогах, как, например в данном случае в подкаталоге pro\java\pkg001. Все современные IDE при создании пакета, сразу же создают соответствующую файловую структуру в каталоге проекта, так что об этом можно особо не беспокоиться, но знать необходимо.

Поскольку строка package имя; только одна и это обязательно первая строка файла, каждый класс попадает только в один пакет или подпакет.

До сих пор мы создавали классы без директивы package. Классы, определяемые файлом Java-кода без директивы package, входят в пакет без имени, существующий по умолчанию. Как вы узнаете чуть позже, классы одного пакета получают специальный доступ друг к другу. Именно поэтому, когда мы использовали в программе несколько классов, мы могли получать доступ из одного класса к методам или полям другого.

Компилятор всегда создает для таких классов безымянный пакет (unnamed package), которому соответствует текущий каталог (current working directory) файловой системы.

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

Имя пакета может быть любым, главное чтобы оно было уникальным. Соглашение "Code Conventions" рекомендует записывать имена пакетов строчными буквами. Тогда они не будут совпадать с именами классов, которые, по соглашению, начинаются с прописной буквы. Кроме того, соглашение советует использовать в качестве имени пакета или подпакета доменное имя своего сайта, записанное в обратном порядке, например:

com.oracle.developer

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

Одной из важных функций пакетов является разделение пространства имен Java и предотвращение конфликта имен между классами. Например, классы java.util.List и java.awt.List можно различить только по именам их пакетов. Однако важно, чтобы различались и имена самих пакетов.

Как работают пакеты

Использование класса из пакета:

  • Классы текущего пакета и пакета java.lang всегда видны
  • Классы других пакетов доступны по полному имени с пакетом, но если они имеют соответствующие модификаторы доступа
  • Можно использовать директиву import

Методы и поля классов в текущем пакете доступны другим классам из этого же пакета, если у них нет ни каких модификаторов доступа (о которых мы поговорим чуть позже).

А вот классы и их поля и методы принадлежащих другим пакетам, если у них нет ни каких модификаторов доступа, уже не доступны даже по полному имени пакета и класса.

Чтобы отдохнуть от теории и понять ее получше немного попрактикуемся и рассмотрим на примере первый пункт использование класса из пакета. Создадим пакет pro.java.pkg001 и в нем два класса:

P0001

P0002

Вывод у программы следующий:

P0003

Как видно у класса Print нет ни каких модификаторов, но все же его статические метод и переменная видны из класса Main и могут им использоваться.

Очень важно заметить, что при компиляции файлов находящихся в пакетах (за исключением пакета по умолчанию), в class-файл попадает полное имя класса, то есть имя_пакета.имя_класса. Поэтому и запускать класс содержащий метод main() надо по полному имени. Кроме того, все другие пакеты и классы, используемые вашей программой должны быть доступны через classpath. Более подробно мы поговорим об этом в конце данной статьи.

Импорт классов и пакетов

Класс пакета pro.java.pkg001 может ссылаться на любой другой класс в pro.java.pkg001 по его простому имени. А поскольку классы пакета java.lang являются базовыми для языка Java, любой Java-код может ссылаться на любой класс этого пакета по его простому имени. Значит, всегда можно набирать String вместо java.lang.String. Однако по умолчанию для всех других классов нужно указывать полные имена. Поэтому, чтобы применить класс File пакета java.io, следует набрать java.io.File.

Директива import бывает двух видов. Чтобы определить отдельный класс, на который можно ссылаться по его простому имени, следом за ключевым словом import введите имя класса и точку с запятой:

import java.io.File; // Теперь можно набирать File вместо java.io.File

Чтобы импортировать целый пакет классов, введите после import имя пакета, символы .* и точку с запятой. Например, чтобы использовать класс File вместе с несколькими другими классами пакета java.io, нужно просто импортировать целый пакет:

import java.io.*; // Теперь простые имена можно применять для всех классов из java.io

Этот синтаксис импорта пакета не относится к подпакетам. Даже если я импортирую пакет java.util, мне все еще придется обращаться к классу java.util.zip.ZipInputStream по его полному имени. Если два класса, импортированные из разных пакетов, называются одинаково, к ним нельзя обращаться по простому имени; во избежание двусмысленности к обоим классам следует обращаться по их полным именам.

Начиная с версии Java 5 в язык введена еще одна форма оператора import, предназначенная для импорта статических полей и методов класса — оператор import static. Например, можно написать оператор:

import static java.lang.Math.*;

После этого все статические поля и методы класса Math можно использовать без указания имени класса. Вместо записи:

double r = Math.cos(Math.PI * alpha);

можно записать просто

double r = cos(PI * alpha);

Теперь разгаданы и другие строчки, которые были в наших исходниках, например:

import static pro.java.util.Print.*;
import static pro.java.util.Strings.*;

Подчеркнем, что оператор import вводится только для удобства программистов и слово "импортировать" не означает никаких перемещений классов.

Директивы import позволяют компилятору получить полные имена всех используемых классов, полей и методов по их коротким именам. В class-файл попадают полные имена, подстановка содержимого не происходит. Поэтому при запуске программы все используемые классы должны присутствовать в classpath.

Структура файла Java

Теперь можно описать структуру исходного файла с текстом программы на языке Java.

  • В первой строке файла может быть необязательный оператор package.
  • В следующих строках могут быть необязательные операторы import.
  • Далее идут описания классов и интерфейсов (интерфейсы будут рассмотрены позже).

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

Директив import может быть сколько угодно.

Существует еще несколько важных ограничений по файлам Java. Во-первых, каждый файл может содержать максимум один класс, объявленный как public. Такой класс предназначен для использования другими классами в других пакетах. Более подробно public классы и связанные с ними модификаторы мы рассмотрим в следующей статье. Данное ограничение по открытым классам относится только к классам верхнего уровня; как вы скоро узнаете, класс может содержать любое количество вложенных, или внутренних, классов, объявленных как public.

Второе ограничение касается имени файла в Java. Если файл Java содержит класс public, то имя файла должно представлять собой имя public класса, к которому присоединено расширение .java. Например, если Point является public классом, его исходный код должен находиться в файле Point.java.

Отсюда следует, что если в проекте есть несколько открытых классов, то они должны находиться в разных файлах.

Соглашение "Code Conventions" рекомендует открытый класс, если он имеется в файле, описывать первым.

Даже если ваши классы не являются public, полезно определять только по одному классу на файл и давать файлу имя класса. Для технологии Java характерно записывать исходный текст каждого класса в отдельном файле. В конце концов, компилятор всегда создает class-файл для каждого класса.

Исполнение классов в пакетах и CLASSPATH

GrabliС запуском нашей простой программы из Eclipse, что была в самом начале, нет ни каких проблем, а вот при запуске из командной строки они могут возникнуть.

Дело в том, что как я уже говорил ранее, в откомпилированный  class файл попадает полное имя класса, то есть имя_пакета.имя_класса.

Давайте запустим и посмотрим (обращаем внимание на пути файлов):

P0004

Мы попробовали запустить наш класс Main способом как мы делали и раньше, находясь в каталоге с классом. Но вдруг, внезапно, выдалась ошибка, что не возможно найти метод main в классе Main.

javap спасет отца русской демократии! Давайте посмотрим что там у нас скомпилилось?

P0005

Как видим наш класс сейчас называется pro.java.pkg001.Main, то есть наш класс содержит полное имя, включая имя пакета. И кстати обратите внимание на то как объявлен массив String[].

Для контраста рассмотрим любой класс, где мы не использовали пакеты.

P0006

Рассмотрим откомпилированный класс Classes001.

Как видим в данном случае, когда мы не использовали пакеты, имя класса точно такое же как мы и задавали в коде программы. Именно поэтому мы могли запустить наш класс по его короткому имени.

Давайте попробуем запустить наш класс Main по полному имени и посмотрим что получиться.

 

P0007

Как видим у нас опять ошибка.

Чтобы наша программа запустилась, должно выполняться одно из следующих двух условий. Либо программа должна выполняться из каталога, расположенного непосредственно над каталогом pro, то есть в нашем случае запуск должен происходить из каталога bin, либо переменная среды CLASSPATH должна содержать путь к каталогу pro, либо параметр –classpath должен указывать путь к каталогу pro во время выполнения программы с помощью java.

P0008

P0009

CLASSPATH должен указывать путь к каталогу, где лежит пакет (верхний каталог пакета), но не на сам каталог или подкаталоги пакета.

Ну и на последок можно глянуть хорошее видео по этой теме

Пакеты (pagkages) в Java