→ Пошук по сайту       Увійти / Зареєструватися
Знання Програмування на Java Навчальний курс по JAVA

Імена. Пакети

 Java

Введення

Імена (names) використовуються в програмі для доступу до оголошених (declared) раніше "об'єктів", "елементів", "конструкцій" мови (всі ці слова-синоніми були використані тут в їх загальному сенсі, а не як терміни ООП, наприклад). Конкретніше, в Java є імена:

  • пакети;
  • класи;
  • інтерфейси;
  • елементи (member) вказівних типів:
  • поля;
  • методи;
  • внутрішні класи та інтерфейси;
  • аргументи:
  • методи;
  • конструктори;
  • обробники помилок;
  • локальні змінні.

Відповідно, всі вони повинні бути оголошені спеціальним чином, що буде поступово розглядатися по ходу курсу. Так само оголошуються конструктори.

Нагадаємо, що пакети (packages) в Java - це спосіб логічно групувати класи, що необхідно, оскільки часто кількість класів у системі становить кілька тисяч, або навіть десятків тисяч. Крім класів та інтерфейсів в пакетах можуть перебувати вкладені пакети. Синонімами цього слова в інших мовах є бібліотека або модуль.

Імена

Прості і складені імена. Елементи

Імена бувають простими (simple), що складаються з одного ідентифікатора (вони визначаються під час оголошення) і складними (qualified), що складаються з послідовності ідентифікаторів, розділених крапкою. Для пояснення цих термінів необхідно розглянути ще одне поняття.

У пакетів і вказівних типів (класів, інтерфейсів, масивів) є елементи (members). Доступ до елементів здійснюється за допомогою виразу, що складається з імен, наприклад, пакету і класу, розділених крапкою.

Далі класи та інтерфейси будуть називатися об'єднуючим терміном тип (type).

Елементами пакету є класи та інтерфейси, що містяться в ньому, а також вкладені пакети. Щоб отримати складене ім'я пакету, необхідно до повного імені пакету, в якому він розташовується, додати точку, а потім його власне просте ім'я. Наприклад, складене ім'я основного пакету мови Java - java.lang (тобто просте ім'я цього пакету lang, і він знаходиться в загальному пакеті java). Усередині нього є вкладений пакет, призначений для типів технології reflection, який згадувався в попередніх розділах. Проста назва пакету reflect, а значить, складена - java.lang.reflect.

Просте ім'я класів та інтерфейсів дається при оголошенні, наприклад, Object, String, Point. Щоб отримати складене ім'я таких типів, треба до складеного імені пакету, в якому знаходиться тип, через крапку додати просте ім'я типу. Наприклад, java.lang.Object, java.lang.reflect.Method або com.myfirm.MainClass. Зміст останнього вираження такий: спочатку йде звернення до пакету com, потім до його елементу - вкладеного пакету myfirm, а потім до елементу пакета myfirm - класу MainClass. Тут com.myfirm - складене ім'я пакету, де лежить клас MainClass, а MainClass - просте ім'я. Складаємо їх і поділяємо точкою - виходить повне ім'я класу com.myfirm.MainClass.

Для вказівних типів елементами є поля і методи, а також внутрішні типи (класи та інтерфейси). Елементи можуть бути як безпосередньо оголошені в класі, так і отримані у спадок від батьківських класів і інтерфейсів, якщо такі є. Просте ім'я елементів також дається при ініціалізації. Наприклад, toString (), PI, InnerClass. Складене ім'я виходить шляхом об'єднання простого чи складеного імені типу, або змінною об'єктного типу з ім'ям елемента. Наприклад, ref.toString (), java.lang.Math.PI, OuterClass.InnerClass. Інші звернення до елементів вказівних типів вже неодноразово застосовувалися в попередніх розділах.

Імена та ідентифікатори

Тепер, коли ми розглянули прості і складові імена, уточнимо різницю між ідентифікатором (нагадаємо, що це вид лексеми) і ім'ям. Зрозуміло, що просте ім'я складається з одного ідентифікатора, а складене - з декількох. Однак не всякий ідентифікатор входить до складу імені.

По-перше, у виразі оголошення (declaration) ідентифікатор ще не є ім'ям. Іншими словами, він стає ім'ям після першої появи в коді в місці об'явлення.

По-друге, існує можливість звертатися до полів і методів об'єктного типу не через ім'я типу або об'єктної змінної, а через посилання на об'єкт, отриману в результаті виконання вираження. Приклад такого виклику:

 country.getCity (). getStreet ();

У даному прикладі getStreet є не ім'ям, а ідентифікатором, оскільки відповідний метод викликається в об'єкта, отриманого в результаті виклику методу getCity (). Причому country.getCity якраз є складовим ім'ям методу.

Нарешті, ідентифікатори також використовуються для назв міток (label). Ця конструкція розглядається пізніше, проте наведемо приклад, який показує, що простори імен і назв міток повністю розділені.

 num:
   for (int num = 2; num <= 100; num + +)
   {
      int n = (int) Math.sqrt (num) +1;
      while (- n! = 1)
     {
         if (num % n == 0)
{
            continue num;
            }
      }
      System.out.print (num + "");
   }  

Результатом будуть прості числа менше 100:

 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97

Ми бачимо, що тут застосовуються однойменні змінна і мітка num, причому остання використовується для виходу з внутрішнього циклу while на зовнішній for.

Очевидно, що зручніше використовувати просте ім'я, а не складене, тому що воно коротше і його легше запам'ятати. Однак зрозуміло, що якщо в системі є дуже багато класів з безліччю змінних, можна зіткнутися з ситуацією, коли в різних класах є однойменні змінні або методи. Для вирішення цієї та інших подібних проблем вводиться нове поняття - область видимості.

Область видимості (введення)

Щоб не змушувати програмістів, які працюють разом над різними класами однієї системи, координувати імена, які вони дають різним конструкціям мови, у кожного імені є область видимості (scope). Якщо звернення, наприклад, до поля, йде з частини коду, що потрапляє в область видимості його імені, то можна користуватися простим ім'ям, якщо ні - необхідно застосовувати складене.

Наприклад:

 class Point {
   int x, y;
   int getX () {
      return x; / / просте ім'я
   }
}
class Test {
   void main () {
      Point p = new Point ();
      p.x = 3; / / складене ім'я
   }
}

Видно, що до поля x зсередини класу можна звертатися по простому імені. До нього ж з іншого класу можна звернутися тільки за складеним імені. Воно складається з імені змінної, що посилається на об'єкт, та імені поля.

Тепер необхідно розглянути області видимості для всіх елементів мови. Однак перш з'ясуємо, що таке пакети, як і для чого вони використовуються.

Пакети

Програма на Java являє собою набір пакетів (packages). Кожен пакет може включати вкладені пакети, тобто вони утворюють ієрархічну систему.

Крім того, пакети можуть містити класи та інтерфейси і таким чином групують типи. Це необхідно відразу для кількох цілей. По-перше, чисто фізично неможливо працювати з великою кількістю класів, якщо вони "звалені в купу". По-друге, модульна декомпозиція полегшує проектування системи. До того ж, як буде показано нижче, існує спеціальний рівень доступу, що дозволяє типам з одного пакету більш тісно взаємодіяти один з одним, ніж з класами з інших пакетів. Таким чином, за допомогою пакетів проводиться логічне групування типів. З ООП відомо, що велика зв'язність системи, тобто середня кількість класів, з якими взаємодіє кожний клас, помітно ускладнює розвиток і підтримку такої системи. Використовуючи пакети, набагато простіше організувати ефективну взаємодію підсистем один з одним.

Нарешті, кожен пакет має свій простір імен, що дозволяє створювати однойменні класи в різних пакетах. Таким чином, розробникам не доводиться витрачати час на вирішення конфлікту імен.

Елементи пакету

Ще раз повторимо, що елементами пакету є вкладені пакети і типи (класи та інтерфейси). Однойменні елементи заборонені, тобто не може бути однойменних класу і інтерфейсу, або вкладеного пакету і типу. В іншому випадку виникне помилка компіляції.

Наприклад, в JDK 1.0 пакет java містив пакети applet, awt, io, lang, net, util і не містив жодного типу. У пакет java.awt входив вкладений пакет image і 46 класів і інтерфейсів.

Складене ім'я будь-якого елементу пакету - це складене ім'я цього пакету плюс просте ім'я елемента. Наприклад, для класу Object в пакеті java.lang складовим ім'ям буде java.lang.Object, а для пакета image в пакеті java.awt - java.awt.image.

Ієрархічна структура пакетів була введена для зручності організації пов'язаних пакетів, однак вкладені пакети, або сусідні, тобто вкладені в один і той же пакет, не мають ніяких додаткових зв'язків між собою, крім обмеження на розбіжність імен. Наприклад, пакети space.sun, space.sun.ray, space.moon і factory.store абсолютно "рівні" між собою і типи одного з цих пакетів не мають ніякого особливого доступу до типів інших пакетів.

Платформна підтримка пакетів

Найпростішим способом організації пакетів і типів є звичайна файлова структура. Розглянемо виразний приклад, коли всі пакети, вихідний і бінарний код розташовуються в одному каталозі і його підкаталогах.

У цьому кореневому каталозі повинна бути папка java, відповідна основного пакету мови, а в ній, у свою чергу, вкладені папки applet, awt, io, lang, net, util.

Припустимо, розробник працює над моделлю сонячної системи, для чого створив класи Sun, Moon і Test та розташував їх у пакеті space.sunsystem. У такому випадку в кореневому каталозі повинна бути папка space, відповідна однойменним пакету, а в ній - папка sunsystem, в якій зберігаються класи цього розробника.

Як відомо, вихідний код розташовується у файлах з розширенням. Java, а бінарний - з розширенням Class. Таким чином, вміст папки sunsystem може виглядати наступним чином:

 Moon.java
Moon.class
Sun.java
Sun.class
Test.java
Test.class

Іншими словами, вихідний код класів

 space.sunsystem.Moon
space.sunsystem.Sun
space.sunsystem.Test 

зберігається в файлах

 space \ sunsystem \ Moon.java
space \ sunsystem \ Sun.java
space \ sunsystem \ Test.java

а бінарний код - у відповідних. class-файлах. Зверніть увагу, що перетворення імен пакетів у файлові шляху зажадало заміни роздільника. (Точки) на символ-роздільник файлів (для Windows це зворотний слеш \). Таке перетворення може виконати як компілятор для пошуку вихідних текстів та бінарного коду, так і віртуальна машина для завантаження класів та інтерфейсів.

Зверніть увагу, що було б помилкою запускати Java прямо з папки space \ sunsystem і намагатися звертатися до класу Test, незважаючи на те, що файл-опис лежить саме в ній. Необхідно піднятися на два рівні каталогів вище, щоб Java, побудувавши шлях з назви пакунка, змогла знайти потрібний файл.

Крім того, важливо, що Java завжди розрізняє регістр ідентифікаторів, а значить, назви файлів і каталогів повинні точно відповідати запрограмованим іменах. Хоча в деяких випадках операційна система може забезпечити доступ, незважаючи на регістр, при зміні обставин розбіжності можуть призвести до збоїв.

Існує спеціальне вираз, що оголошує пакет (детально розглядається нижче). Воно передує оголошенню типу і позначає, до якого пакету буде належати цей тип. Таким чином, набір доступних пакетів залежить від доступних файлів, що містять оголошення типів і пакетів. Наприклад, якщо створити порожній каталог, або заповнити його сторонніми файлами, це аж ніяк не приведе до появи пакету в Java.

Які файли доступні для утиліт Java SDK (компілятора, інтерпретатора і т.д.), встановлюється на рівні операційної системи, адже утиліти - це звичайні програми, які виконуються під управлінням ОС і, звичайно, слідують її правилами. Наприклад, якщо пакет містить один тип, але описуючий його файл недоступний активному користувачу ОС для читання, для Java цей тип і цей пакет не будуть існувати.

Зрозуміло, що далеко не завжди зручно зберігати всі файли в одному каталозі. Найчастіше класи знаходяться в різних місцях, а деякі можуть навіть поширюватися у вигляді архівів, для прискорення завантаження через мережу. Копіювати всі такі файли в одну папку було б украй важко.

Тому Java використовує спеціальну змінну оточення, яка називається classpath. Аналогічно тому, як мінлива path допомагає системі знаходити і завантажувати динамічні бібліотеки, ця змінна допомагає працювати з Java-класами. Її значення повинно складатися із шляхів до каталогів або архівів, розділених крапкою з комою. З версії 1.1 підтримуються архіви типів ZIP і JAR (Java ARchive) - спеціальний формат, розроблений на основі ZIP для Java.

Наприклад, змінна classpath може мати таке значення:

C: \ java \ classes; d: \ lib \ 3Dengine.zip;
d: \ lib \ fire.jar

У результаті всі зазначені каталоги і вміст всіх архівів "додається" до вихідного кореневого каталогу. Java в пошуках класу буде шукати його за описаним вище правилом у всіх зазначених папках і архівах по порядку. Зверніть увагу, що першим у змінній вказаний поточний каталог (представлений точкою). Це робиться для того, щоб пошук завжди починався з вихідного кореневого каталогу. Звичайно, такий запис не є обов'язковим і робиться на розсуд розробника.

Незважаючи на явні зручності такої конструкції, вона приховує у собі і небезпеку. Якщо розроблювані класи зберігаються в деякому каталозі і він вказаний у classpath пізніше, ніж якийсь інший каталог, в якому виявляються однойменні типи, розібратися в такій ситуації буде непросто. У класи будуть вноситися зміни, які ніяк не проявляються при запуску з-за того, що Java насправді завантажує одні й ті ж файли з сторонньої папки.

Тому до даної змінної середовища оточення необхідно ставитися з особливою увагою. Корисно пам'ятати, що необов'язково встановлювати її значення відразу для всієї операційної системи. Його можна явно вказувати при кожному запуску компілятора або віртуальної машини як опцію, що, по-перше, ніколи не вплине на інші Java-програми, а по-друге, помітно спрощує пошук помилок, пов'язаних з некоректним значенням classpath.

Нарешті, можна застосовувати і альтернативні підходи до зберігання пакетів і файлів з вихідним і бінарним кодом. Наприклад, в якості такого сховища може використовуватися база даних. Більше того, існує обмеження на розміщення оголошень класів в .java-файлах, яке розглядається нижче, а при використанні БД будь-які обмеження можна зняти. Тим не менш, при такому підході рекомендується надавати утиліти імпорту / експорту з урахуванням обмеження для перетворень з / у файли.

Модуль компіляції

Модуль компіляції (compilation unit) зберігається в текстовому .java-файлі і є одиничною порцією вхідних даних для компілятора. Він складається з трьох частин:

  • оголошення пакету;
  • import-вирази;
  • оголошення верхнього рівня.

Оголошення пакету одночасно вказує, якому пакету будуть належати всі декларовані нижче типи. Якщо даний вираз відсутній, значить, ці класи розташовуються в безіменному пакеті (інша назва - пакет за замовчуванням).

Import-вирази дозволяють звертатися до типів з інших пакетів по їх простим іменам, "імпортувати" їх. Ці висловлювання також необов'язкові.

Нарешті, оголошення верхнього рівня містять оголошення одного або декількох типів. Назва "верхнього рівня" протиставляє ці класи та інтерфейси, що розташовуються в пакетах, внутрішнім типам, які є елементами і розташовуються всередині інших типів. Як не дивно, ця частина також є необов'язковою, в тому сенсі, що в разі її відсутності компілятор не видасть помилки. Однак ніяких .сlass-файлів згенеровано теж не буде.

Доступність модулів компіляції визначається підтримкою платформи, тому що утиліти Java є звичайними програмами, які виконуються операційною системою за загальними правилами.

Розглянемо всі три частини більш докладно.

Оголошення пакету

Перший вираз в модулі компіляції - оголошення пакету. Воно записується за допомогою ключового слова package, після якого вказується повна назва пакету.

Наприклад, першим рядком (після коментарів) у файлі java / lang / Object.java йде:

 package java.lang;

Це одночасно слугує оголошенням пакету lang, вкладеного в пакет java, і вказівкою, що оголошений нижче клас Object знаходиться в даному пакеті. Так складається повне ім'я класу java.lang.Object.

Якщо цей вислів відсутній, то такий модуль компіляції належить безіменному пакету. Цей пакет за замовчуванням обов'язково повинен підтримуватися реалізацією Java-платформи. Зверніть увагу, що він не може мати вкладених пакетів, так як складене ім'я пакету має обов'язково починатися з імені пакета верхнього рівня.

Таким чином, найпростіша програма може виглядати наступним чином:

 class Simple {
   public static void main (String s [])
{ System.out.println ("Hello!"); }
}

Цей модуль компіляції буде належати безіменному пакету.

Пакет за замовчуванням був введений в Java для полегшення написання дуже невеликих або тимчасових програм, для експериментів. Якщо ж програма буде поширюватися для користувачів, то рекомендується розташувати її в пакеті, який, у свою чергу, повинен бути правильно названо. Угоди по іменування розглядаються нижче.

Доступність пакета визначається за доступністю модулів компіляції, у яких він оголошується. Точніше, пакет доступний тоді і тільки тоді, коли виконується будь-яке з наступних двох умов:

  • доступний модуль компіляції з оголошенням цього пакета;
  • доступний один із вкладених пакетів цього пакета.

Таким чином, для наступного коду:

 package space.star;
class Sun {}

якщо файл, який зберігає цей модуль компіляції, доступний Java-платформі, то пакети space і вкладений у нього star (повна назва space.star) також стають доступні для Java.

Якщо пакет доступний, то область видимості його оголошення - всі доступні модулі компіляції. Простіше кажучи, всі існуючі пакети доступні для всіх класів, ніяких обмежень на доступ до пакетів в Java немає.

Потрібно, щоб пакети java.lang і java.io, а значить, і java, завжди були доступні для Java-платформи, оскільки вони містять класи, необхідні для роботи будь-якої програми.

Імпорт-вирази

Як буде розглянуто нижче, область видимості оголошення типу - пакет, в якому він розташовується. Це означає, що всередині даного пакету допускається звернення до типу за його простим ім’ям. З усіх інших пакетів необхідно звертатися за складеним іменем, тобто повне ім'я пакету плюс просте ім'я типу, розділені крапкою. Оскільки пакети можуть мати досить довгі імена (наприклад, додатковий пакет у складі JDK1.2 називається com.sun.image.codec.jpeg), а тип може багаторазово використовуватися в модулі компіляції, таке обмеження може призвести до ускладнення вихідного коду і труднощам в розробці.

Для вирішення цієї проблеми вводяться import-вирази, які дозволяють імпортувати типи в модуль компіляції і далі звертатися до них за простими іменами. Існує два види таких виразів:

  • імпорт одного типу;
  • імпорт пакета.

Важливо підкреслити, що імпорт виразів є, по суті, підказкою для компілятора. Він користується ними, щоб для кожного простого імені типу з іншого пакета отримати його повне ім'я, яке і потрапляє в скомпільований код. Це означає, що імпортів виразів може бути дуже багато, включаючи і ті, що імпортують невживані пакети і типи, але це ніяк не відіб'ється ні на розмірі, ані на якості бінарного коду. Також байдуже, звертатися до типу за його повним ім’ям, або включити його в імпорт-вираз і звертатися по простому імені - результат буде один і той же.

Імпортери вирази мають ефект тільки всередині модуля компіляції, в якому вони оголошені. Всі оголошення типів вищого рівня, що знаходяться в цьому ж модулі, можуть однаково користуватися імпортованими типами. До імпортованим типам можливий і звичайний доступ по повному імені.

Вираз, що імпортує один тип, записується за допомогою ключового слова import і повного імені типу. Наприклад:

 import java.net.URL;

Такий вираз означає, що надалі в цьому модулі компіляції просте ім'я URL буде позначати однойменний клас з пакету java.net. Спроба імпортувати тип, недоступний на момент компіляції, викличе помилку. Якщо один і той же тип імпортується кілька разів, то це не створює помилки, а дубльовані вираження ігноруються. Якщо ж імпортуються типи з однаковими іменами простими з різних пакетів, то така ситуація породить помилку компіляції.

Вираз, що імпортує пакет, включає в себе повне ім'я пакету наступним чином.

 import java.awt .*;

Цей вираз робить доступними всі типи, що знаходяться в пакеті java.awt, за їх простому імені. Спроба імпортувати пакет, недоступний на момент компіляції, викличе помилку. Імпорт одного пакету багаторазово не створює помилки, дубльовані вираження ігноруються. Зверніть увагу, що імпортувати вкладений пакет не можна.

Наприклад:

 // Приклад викличе помилку компіляції
import java.awt.image;

Створюється враження, що тепер ми можемо звертатися до типів пакету java.awt.image за спрощеним імені, наприклад, image.ImageFilter. Насправді приклад викличе помилку компіляції, так як дане вираження розцінюється як імпорт типу, а в пакеті java.awt відсутній тип image.

Аналогічно, вираз

import java.awt .*; 

не робить більш доступними класи пакету java.awt.image, їх необхідно імпортувати окремо.

Оскільки пакет java.lang містить типи, без яких неможливо створити жодну програму, він неявним чином імпортується в кожен модуль компіляції. Таким чином, всі типи з цього пакету доступні за їх простим імен без будь-яких додаткових зусиль. Спроба імпортувати даний пакет ще раз буде проігноровано.

Допускається одночасно імпортувати пакет і який-небудь тип з нього:

import java.awt .*;  
import java.awt.Point;

Може виникнути питання, як же краще чинити - імпортувати типи окремо або весь пакет відразу? Чи є яка-небудь різниця в цих підходах?

Різниця полягає в алгоритмі роботи компілятора, який призводить кожне просте ім'я до повного. Він складається з трьох кроків:

  • спочатку проглядаються вираження, які імпортують типи;
  • потім інші типи, оголошені в поточному пакеті, у тому числі в поточному модулі компіляції;
  • нарешті, проглядаються вираження, які імпортують пакети.

Таким чином, якщо тип явно імпортований, то неможливо ні оголошення нового типу з таким же ім'ям, ні доступ по простому імені до однойменного типу в поточному пакеті.

Наприклад:

// Приклад викли че помилку компіляції
package my_geom;
import java.awt.Point;





class Point {}

Цей модуль викличе помилку компіляції, так як ім'я Point в оголошенні вищого типу буде розглядатися як звернення до імпортованого класу java.awt.Point, а його перевизначати, звичайно, не можна.

Якщо в пакеті оголошений тип:

package my_geom;

class Point {}

то в іншому модулі компіляції:

package my_geom;
import java.awt.Point;
class Line {
   void main () {
      System.out.println (new Point ());
   }
}

складається невизначена ситуація - який з класів, my_geom.Point або java.awt.Point, буде використовуватися при створенні об'єкта? Результатом буде:

 java.awt.Point [x = 0, y = 0]

Відповідно до правил, ім'я Point було трактовано на основі імпорту типу. До класу поточного пакету все ще можна звертатися по повному імені: my_geom.Point. Якщо б розглядався безіменний пакет, то звернутися до такого "перекритого" типу було б уже неможливо, що є додатковим аргументом до рекомендації розташовувати важливі програми в поіменованих пакетах.

Тепер розглянемо імпорт пакета. Його ще називають "імпорт на вимогу", маючи на увазі, що ніякої "завантаження" всіх типів імпортованого пакету відразу при вказівці імпортуючого вирази не відбувається, їх повні імена підставляються у міру використання простих імен в коді. Можна імпортувати пакет і задіяти тільки один тип (або навіть жодного) з нього.

Змінимо розглянутий вище приклад:

package my_geom;  
import java.awt .*;
class Line {
   void main () {
      System.out.println (new Point ());
      System.out.println (new Rectangle ());
   }
}

Тепер результатом буде:

m y_geom.Point @ 92d342
java.awt.Rectangle [x = 0, y = 0, width = 0, height = 0]

Тип Point знайшовся в поточному пакеті, тому компілятору не довелося виконувати пошук по пакету java.awt. Другий об'єкт породжується від класу Rectangle, якого не існує в поточному пакеті, зате він виявляється в java.awt.

Також коректний тепер приклад:

 p ackage my_geom;
import java.awt .*;
class Point {} 

Таким чином, імпорт пакета не перешкоджає оголошенню нових типів або обігу до існуючих типів поточного пакету за простими іменами. Якщо все ж таки потрібно працювати саме з зовнішніми типами, то можна скористатися імпортом типу, або звертатися до них по повних іменах. Крім того, вважається, що імпорт конкретних типів допомагає при прочитанні коду відразу зрозуміти, які зовнішні класи та інтерфейси використовуються в цьому модулі компіляції. Однак повністю покладатися на таке міркування не варто, оскільки можливі випадки, коли імпортні типи не використовуються і, навпаки, в коді стоїть звернення до інших типів по повному імені.

Оголошення верхнього рівня

Далі модуль компіляції може містити одне або декілька оголошень класів та інтерфейсів. Докладно формат такого оголошення розглядається в наступних лекціях, проте наведемо коротку інформацію і тут.

Оголошення класу починається з ключового слова class, інтерфейсу - interface. Далі вказується ім'я типу, а потім у фігурних дужках описується тіло типу. Наприклад:

pac kage first;
class FirstClass {}
interface MyInterface {} 

Область видимості типу - пакет, в якому він описаний. З інших пакетів до типу можна звертатися або за складеним імені, або за допомогою імпортують виразів.

Однак, крім області видимості, в Java також є засоби розмежування доступу. За замовчуванням тип оголошується доступним тільки для інших типів свого пакету. Щоб інші пакети також могли використовувати його, можна вказати ключове слово public:

package s econd;
public class OpenClass {}
public interface PublicInterface {} 

Такі типи доступні для всіх пакетів.

Оголошення верхнього рівня описують класи та інтерфейси, що зберігаються в пакетах. У версії Java 1.1 були введені внутрішні (inner) типи, які оголошуються всередині інших типів і є їх елементами поряд з полями і методами. Дана можливість є допоміжною і досить заплутаною, тому в курсі докладно не розглядається, хоча деякі приклади і пояснення допоможуть в цілому її освоїти.

Якщо пакети, вихідний і бінарний код зберігаються у файловій системі, то Java може накладати обмеження на оголошення класів в модулях компіляції. Це обмеження створює помилку компіляції в разі, якщо опис типу не виявляється у файлі з назвою, складеним з імені типу та розширення (наприклад, java), і при цьому:

  • тип оголошений як public і, отже, може використовуватися з інших пакетів;
  • тип використовується з інших модулів компіляції в своєму пакеті.

Ці умови означають, що в модулі компіляції може бути максимум один тип відповідає цим умовам.

Іншими словами, в модулі компіляції може бути максимум один public тип, і його ім'я та ім'я файлу повинні збігатися. Якщо ж у ньому є не-public типи, імена яких не збігаються з ім'ям файлу, то вони повинні використовуватися тільки всередині цього модуля компіляції.

Якщо ж для зберігання пакетів застосовується БД, то таке обмеження не повинно накладатися.

На практиці ж програмісти часто поміщають в один модуль компіляції тільки один тип, незалежно від того, public він чи ні. Це істотно спрощує роботу з ними. Наприклад, опис класу space.sun.Size зберігається у файлі space \ sun \ Size.java, а бінарний код - у файлі Size.class в тому ж каталозі. Саме так влаштовані всі стандартні бібліотеки Java.

Зверніть увагу, що при оголошенні класів цілком допускаються перехресні звернення. Зокрема, наступний приклад абсолютно коректний:

 package test;
/*
   * Клас Human, описує людину
   */
class Human {
   String name;
   Car car; // належача людині машина
}
/*
   * Клас Car, описує автомобіль
   */
class Car {
   String model;
   Human driver; // водій, що керує машиною
}

Крім того, клас Car був використаний раніше, ніж був оголошений. Таке перехресне застосування типів також допускається в разі, якщо вони знаходяться в різних пакетах. Компілятор повинен підтримувати можливість транслювати їх одночасно.

Унікальність імен пакетів

Оскільки Java створювався як мова, призначений для розповсюдження додатків через Internet, а додатки складаються зі структури пакетів, необхідно докласти деяких зусиль, щоб не стався конфлікт імен. Імена двох використовуваних пакетів можуть збігтися після значного часу після їх створення. Виправити такий стан звичайному програмісту буде вкрай важко.

Тому творці Java пропонують наступний спосіб унікального іменування пакетів. Якщо програма створюється розробником, у якого є Internet-сайт, або ж він працює на організацію, у якої є сайт, і доменне ім'я такого сайту, наприклад, company.com, то імена пакетів повинні починатися з цих же слів, виписаних у зворотному порядку : com.company. Подальші вкладені пакети можуть носити назви підрозділів компанії, пакетів, прізвища розробників, імена комп'ютерів і т.д.

Таким чином, пакет верхнього рівня завжди записується ASCII-літерами в нижньому регістрі і може мати одне з наступних імен:

  • трьохбуквені com, edu, gov, mil, net, org, int (цей список розширюється);
  • дволітерні, що позначають імена країн, такі як ru, su, de, uk та інші.

Якщо ім'я сайту суперечить вимогам до ідентифікаторів Java, то можна зробити наступні кроки:

  • якщо в імені стоїть заборонений символ, наприклад, тире, то його можна замінити знаком підкреслення;
  • якщо ім'я збігається з зарезервованим словом, можна в кінці додати знак підкреслення;
  • якщо ім'я починається з цифри, можна на початку додати знак підкреслення.

Приклади назв пакетів, складених за такими правилами:

 com.sun.image.codec.jpeg

org.omg.CORBA.ORBPackage

oracle.jdbc.driver.OracleDriver

Однак, звичайно, ніхто не вимагає, щоб Java-пакети були обов'язково доступні на Internet-сайті, який дав їм ім'я. Швидше була зроблена спроба скористатися існуючою системою імен замість того, щоб створювати нову для іменування бібліотек.

Область видимості імен

Областю видимості оголошення деякого елемента мови називається частина програми, звідки допускається звернення до цього елемента за простим іменем.

При розгляді кожного елемента мови буде вказуватися його область видимості, проте має сенс зібрати цю інформацію в одному місці.

Область видимості доступного пакету - вся програма, тобто будь-який клас може використовувати доступний пакет. Однак необхідно пам'ятати, що звертатися до пакету можна тільки за його повним складеним ім’ям. До пакету java.lang ні з якого місця не можна звернутися як до просто lang.

Областю видимості імпортованого типу є всі оголошення верхнього рівня в цьому модулі компіляції.

Областю видимості типу (класу або інтерфейсу) верхнього рівня є пакет, в якому він оголошений. З інших пакетів доступ можливий або за складеним імені, або за допомогою імпортуючого вираження, яке допомагає компілятору відтворити складене ім'я.

Область видимості елементів класів або інтерфейсів - це все тіло типу, в якому вони оголошені. Якщо звернення до цих елементів відбувається з іншого типу, необхідно скористатися складеним ім'ям. Ім'я може бути складено з простого чи складеного імені типу, імені об'єктної змінної або ключових слів super або this, після чого через точку вказується просте ім'я елемента.

Аргументи методу, конструктора або обробника помилок видно тільки всередині цих конструкцій і не можуть бути доступні ззовні.

Область видимості локальних змінних починається з моменту їх ініціалізації і до кінця блоку, в якому вони оголошені. На відміну від полів типів, локальні змінні не мають значень за замовчуванням і повинні инициализироваться явно.

 int x;
for (int i = 0; i <10; i + +) {
   int t = 5 + i;
}
// Тут змінна t вже недоступна,
// Так як блок, в якому вона була
// Оголошена, вже завершено, а змінна
// X ще недоступна, тому що поки не була
// Ініціалізована

Певні проблеми виникають, коли відбувається перекриття областей видимості і виникає конфлікт імен різних конструкцій мови.

Затіняюче оголошення (Shadowing)

Найпоширенішими випадками виникнення конфлікту імен є вираз, що імпортує пакет, і оголошення локальних змінних, або параметрів методів, конструкторів, обробників помилок. Імпорт пакету детально розглядалося в цьому розділі. Якщо імпортований і поточний пакети містять однойменні типи, то їх області перетинаються. Як вже говорилося, перевага віддається типу з поточного пакету. Також розповідалося про те, як цю проблему вирішувати.

Перейдемо до проблеми перекриття імен полів класу і локальних змінних. Приклад:

 class Human {
   int age;
   // Вік
   int getAge () {
      return age;
   }
   void setAge (int age) {
      age = age; //??? }
}

У класі Human (чоловік) оголошено поле age (вік). Зручно визначити також метод setAge (), який повинен встановлювати нове значення віку для людини. Цілком логічно зробити у методу setAge () один вхідний аргумент, який також буде називатися age (адже в якості цього аргументу буде передаватися нове значення віку). Виходить, що в реалізації методу setAge () потрібно написати age = age, в першому випадку маючи на увазі поле класу, у другому - параметр методу. Зрозуміло, що хоча з точки зору компілятора це коректна конструкція, спроба послатися на дві різні змінні через одне ім'я успіхом не увінчається. Треба зауважити, що такі помилки трапляються часом навіть у досвідчених розробників.

По-перше, розглянемо, через що виникла конфліктна ситуація. Є два елементи мови - аргумент методу і поле класу, області видимості яких перетнулися. Область видимості поля класу більше, вона охоплює все тіло класу, у той час як область видимості аргументу методу включає тільки сам метод. У такому випадку всередині області перетину по простому імені доступний саме аргумент методу, а поле класу "затінюється" (shadowing) оголошенням параметра методу.

Залишається питання, як у такій ситуації все ж таки звернутися до поля класу. Якщо доступ по простому імені неможливий, треба скористатися складеним. Тут найзручніше застосувати спеціальне ключове слово this (воно буде детально розглядатися в наступних розділах). Слово this має значення посилання на об'єкт, всередині якого воно застосовується. Якщо викликати метод setAge () в об'єкта класу Human і використовувати в цьому методі слово this, то його значення буде посиланням на даний об'єкт.

Виправлений варіант прикладу:

 class Human {
   int age; // вік
   void setAge (int age) {
      this.age = age; / / вірне присвоєння!
   }
}

Конфлікт імен, що виникає через затінене оголошення, досить легко виправити за допомогою ключового слова this або інших конструкцій мови, залежно від обставин. Найбільшою проблемою є те, що компілятор ніяк не повідомляє про такі ситуації, і найскладніше - виявити її за допомогою тестування або контрольного перегляду коду.

«Затуляюче» оголошення (Obscuring)

Може виникнути ситуація, коли просте ім'я може бути одночасно розглянуто як ім'я змінної, типу або пакета.

Наведемо приклад, який частково ілюструє такий випадок:

 import java.awt .*;
public class Obscuring {
   static Point Test = new Point (3,2);
   public static void main (String s []) {
      print (Test.x);
   }
}
class Test {
   static int x = -5;
}

У методі main () просте ім'я Test одночасно позначає ім'я поля класу Obscuring та ім'я іншого типу, що знаходиться в тому ж пакеті, - Test. За допомогою цього імені відбувається звернення до поля x, яке визначено і в класі java.awt.Point і Test.

Результатом цього прикладу стане 3, тобто змінна має більш високий пріоритет. У свою чергу, тип має більш високий пріоритет, ніж пакет. Таким чином, звернення до доступного в звичайних умовах типу чи пакету може виявитися неможливим, якщо є оголошення однойменної змінної або типу, що має більш високий пріоритет. Таке оголошення називається «затуляючим» (obscuring).

Ця проблема швидше за все не виникне, якщо слідувати угодам щодо іменування елементів мови Java.

Угоди по іменуванню

Для того, щоб код, написаний на Java, було легко читати і зрозуміти не тільки його автору, але й іншим розробникам, а також для усунення деяких конфліктів імен, пропонуються наступні угоди щодо іменування елементів мови Java. Стандартні бібліотеки і класи Java також слідують їм там, де це можливо.

Угоди регулюють іменування наступних конструкцій:

  • пакети;
  • типи (класи та інтерфейси);
  • методи;
  • поля;
  • поля-константи;
  • локальні змінні і параметри методів та ін

Розглянемо їх послідовно.

Правила побудови імен пакетів вже докладно розглядалися в цьому розділі. Ім'я кожного пакету починається з маленької літери і являє собою, як правило, одне недовге слово. Якщо потрібно скласти назву з декількох слів, можна скористатися знаком підкреслення або починати наступне слово з великої літери. Ім'я пакету верхнього рівня звичайно відповідає доменному імені першого рівня. Назви java і javax (Java eXtension) зарезервовані компанією Sun для стандартних пакетів Java.

При виникненні ситуації «затуляючого» оголошення (obscuring) можна змінити ім'я локальної змінної, що не спричинить за собою глобальних змін у коді. Випадок же конфлікту з ім'ям типу не повинен виникати, згідно з правилами іменування типів.

Імена типів починаються з великої літери і можуть складатися з декількох слів, кожне наступне слово також починається з великої літери. Звичайно, треба прагнути до того, щоб імена були описовими.

Імена класів, як правило, є іменниками:

 Human
HighGreenOak
ArrayIndexOutOfBoundsException

(Останній приклад - помилка, що виникає при використанні індексу масиву, який виходить за межі допустимого.)

Аналогічно задаються імена інтерфейсів, хоча вони не обов'язково повинні бути іменниками. Часто використовується англійська суфікс "able":

 Runnable
Serializable
Cloneable

Проблема «затуляючого» оголошення (obscuring) для типів зустрічається рідко, тому що імена пакетів і локальних змінних (параметрів) починаються з маленької літери, а типів - з великої.

Імена методів повинні бути дієсловами і позначати дії, які робить даний метод. Ім'я повинно починатися з маленької літери, але може складатися з декількох слів, причому кожне наступне слово починається з великої літери. Існує ряд прийнятих назв для методів:

  • якщо методи призначені для читання і зміни значення змінної, то їхні імена починаються, відповідно, з get і set, наприклад, для змінної size це будуть getSize () і setSize ();
  • метод, який повертає довжину, називається length (), наприклад, в класі String;
  • ім'я методу, який перевіряє булеве умова, починається з is, наприклад, isVisible () у компоненту графічного інтерфейсу користувача;
  • метод, який перетворює величину у формат F, називається toF (), наприклад, метод toString (), який призводить будь-який об'єкт до рядка.

Взагалі, рекомендується скрізь, де можливо, називати методи схожим чином, як у стандартних класах Java, щоб вони були зрозумілі всім розробникам.

Поля класу мають імена, що записуються в тому ж стилі, що і для методів, починаються з маленької літери, можуть складатися з декількох слів, кожне наступне слово починається з великої літери. Імена повинні бути іменниками, наприклад, поле name в класі Human, або size в класі Planet.

Як для полів вирішується проблема «затуляючого» оголошення (obscuring), вже обговорювалося.

Поля можуть бути константами, якщо в їх оголошенні стоїть ключове слово final. Їх імена складаються з послідовності слів, скорочень, абревіатур. Записуються вони тільки великими літерами, слова поділяються знаками підкреслення:

 PI
MIN_VALUE
MAX_VALUE

Іноді константи утворюють групу, тоді рекомендується використовувати одне чи кілька однакових слів на початку імен:

 COLOR_RED
COLOR_GREEN
COLOR_BLUE

Нарешті, розглянемо імена локальних змінних і параметрів методів, конструкторів і обробників помилок. Вони, як правило, досить короткі, але, тим не менш, повинні бути осмислені. Наприклад, можна використовувати абревіатуру (ім'я cp для посилання на екземпляр класу ColorPoint) або скорочення (buf для buffer).

Поширені Однолітерні скорочення:

 byte b;
char c;
int i, j, k;
long l;
float f;
double d;
Object o;
String s;
Exception e; // об'єкт, що представляє помилку в Java

Дво- та трьохбуквені імена не повинні збігатися з прийнятими доменними іменами першого рівня Internet-сайтів.

Висновок

У цьому розділі було розглянуто механізм іменування елементів мови. Для того, щоб різні частини великої системи не залежали один від одного, вводиться поняття "область видимості імені", поза якою необхідно використовувати не просте, а складене ім'я. Потім були вивчені елементи (members), які можуть бути у пакетів і вказівних типів. Також розглядався зв'язок термінів "ідентифікатор" (з теми "Лексика") та ім'я.

Потім були розглянуті пакети, які використовуються в Java для створення фізичної та логічної структури класів, а також для більш точного розмежування області видимості. Пакет містить вкладені пакети і типи (класи та інтерфейси). Питання про платформну підтримку пакетів привів до розгляду модулів компіляції як текстових файлів, оскільки саме у вигляді файлів і каталогів, як правило, зберігаються і поширюються Java-додатки. Тоді ж вперше було розглянуто питання розмежування доступу, так як доступ до модулів компіляції визначається саме платформної підтримкою, а точніше - операційною системою.

Модуль компіляції складається з трьох основних частин - оголошення пакету, імпорт-вирази і оголошення верхнього рівня. Важливу роль відіграє безіменний пакет, або пакет за замовчуванням, хоча він і не рекомендується для застосування при створенні великих систем. Були вивчені деталі застосування двох видів імпорт-виразів - імпорт класу та імпорт пакета. Нарешті, було розпочато розгляд оголошень верхнього рівня (ця тема буде продовжена у розділі, що описує оголошення класів). Пакети, як і інші елементи мови, мають певні угоди іменування, покликані полегшити розуміння коду і зменшити можливість виникнення помилок і двозначних ситуацій у програмі.

Опис області видимості для різних елементів мови призводить до питання про можливі перекриттях таких областей і, як наслідок, про конфлікти імен. Розглядаються "затінюючі" і "затуляючі" оголошення. Для усунення або зменшення можливості виникнення таких ситуацій описуються угоди іменування для всіх елементів мови.

загрузка...
Теми розділу
Сторінки, близькі за змістом