Інтерфейси зворотні виклики внутрішні класи

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Факультет "Інформатика та системи управління"
Методичні вказівки до лабораторної роботи
за курсом "Розподілені системи обробки інформації"
Інтерфейси, зворотні виклики, внутрішні класи
Москва 2004

Зміст
Мета роботи .. 3
Завдання для домашньої підготовки. 3
Завдання до лабораторної роботи. 3
Зміст звіту. 3
Контрольні питання. 4
Література. 5
Додаток 1. Пакети і інтерфейси .. 6
Додаток 2. Вкладені класи .. 22
Додаток 3. Інтерфейси і зворотні виклики .. 28

Мета роботи
Отримати первинні знання про інтерфейси, зворотних виклики та внутрішніх класах мови Java. Навчитися писати програми, що використовують інтерфейси, зворотні виклики та внутрішні класи.

Завдання для домашньої підготовки

Ознайомитися з матеріалом, наданим у додатку до даних методичних вказівок. Ознайомитися з текстом завдання до лабораторної роботи і написати програму.

Завдання до лабораторної роботи

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

Зміст звіту

Звіт повинен містити:
1. Постановку задачі, розв'язуваної налагодженої програмою.
2. Керівництво користувача налагодженої програми, що містить опис інтерфейсів всіх функцій програми.
3. Лістинг програми з необхідними коментарями.


Контрольні питання

1. Що таке інтерфейс в Java?
2. Чи можливо розширювати інтерфейс з використанням механізму наслідування?
3. Чи припускають інтерфейси множинне наслідування?.
4. Як організований зворотний виклик в Java?.
5. Що називається внутрішнім класом?
6. Чи включає зовнішній клас область видимості внутрішнього класу?.
7. Чи можна звернутися з внутрішнього класу до елементів зовнішнього класу?

Література

Офіційні джерела:
1. Кен Арнольд, Джеймс Гослінг, Девід Холмс. Мова програмування Java ™.
2. Офіційний сайт Java - http://java.sun.com/ (є розділ російською мовою з підручником).
3. Java ™ 2 SDK, Standard Edition Documentation - http://java.sun.com/products/jdk/1.5/index.html.
4. Джеймс Гослінг, Білл Джой, Гай Стіл. Специфікація мови Java (The Java Language Specification - http://www.javasoft.com/docs/books/jls/). Переклад на російську мову - http://www.uni-vologda.ac.ru/java/jls/index.html
5. Офіційний сайт проекту Eclipse - http://www.eclipse.org/.
Інше:
1. Дмитро Рамодін. Починаємо програмувати на мові Java.
2. Микола Смирнов. Java 2: Навчальний посібник.
3. Картузов А. В. Програмування на мові Java.
4. Вязовік Н.А. Програмування на Java.
5. Електронний підручник. Автор не відомий.

Додаток 1. Пакети і інтерфейси

У стандартну бібліотеку Java API входять сотні класів. Кожен програміст в ході роботи додає до них десятки своїх. Класів. Безліч класів стає безмежним. Вже давно прийняти класи об'єднувати в бібліотеки. Але бібліотеки класів, окрім стандартної, не є частиною мови.
Розробники Java включили в мову додаткову конструкцію - пакети (packages). Всі класи Java розподіляються по пакетах. Крім класів пакети можуть включати в себе інтерфейси і вкладені подпакетах (subpackages). Утворюється деревоподібна структура пакетів і подпакетах.
Ця структура в точності відображається на структуру файлової системи. Всі файли з розширенням class (що містять байт-коди), що утворюють пакет, зберігаються в одному каталозі файлової системи. Подпакетах зібрані в підкаталоги цього каталогу.
Кожен пакет утворює одне простір імен (namespace). Це означає, що всі імена класів, інтерфейсів і подпакетах в пакеті повинні бути унікальні. Імена в різних пакетах можуть збігатися, але це будуть різні програмні одиниці. Таким чином, жоден клас, інтерфейс або подпакетах не може виявитися відразу в двох пакетах. Якщо треба використовувати два класи з однаковими іменами з різних пакетів, то ім'я класу уточнюється назвою пакунка: пакет.класс. Таке уточнене ім'я називається повним ім'ям класу.
Пакетами користуються ще й для того, щоб додати до вже наявних прав доступу до членів класу private, protected і public ще один, "пакетний" рівень доступу.
Якщо член класу не відмічений ні одним з модифікаторів private, protected, public, то, за умовчанням, до нього здійснюється пакетний доступ, а саме, до такого члену може звернутися будь-який метод будь-якого класу з того ж пакету. Пакети обмежують і доступ до класу цілком - якщо клас не помічений модифікатором public, то всі його члени, навіть відкриті, public, не буде видно з інших пакетів.
Пакет і подпакетах
Щоб створити пакет треба просто в першому рядку Java-файлу з початковим кодом записати рядок package ім'я;, наприклад:
package mypack;
Тим самим створюється пакет з вказаним ім'ям mypack і всі класи, записані в цьому файлі, потраплять в пакет mypack. Повторюючи цей рядок на початку кожного вихідного файлу, включаємо в пакет нові класи.
Ім'я підпакету уточнюється назвою пакунка. Щоб створити подпакетах з ім'ям, наприклад, subpack, слід в першому рядку вихідного файлу написати;
package mypack.subpack;
і всі класи цього файла і всіх файлів з такою ж першим рядком потраплять в подпакетах subpack пакету mypack.
Можна створити і подпакетах підпакету, написавши щось на кшталт
package mypack.subpack.sub;
і т. д. скільки завгодно разів.
Оскільки рядок package ім'я; тільки одна і це обов'язково перший рядок файлу, кожен клас потрапляє тільки в один пакет або подпакетах.
Компілятор Java може сам створити каталог з тим же ім'ям mypack, a в ньому підкаталог subpack, і розмістити в них class-файли з байт-кодами.
Повні імена класів А і В будуть виглядати так: mypack.A, mypack.subpack.В.
Фірма SUN рекомендує записувати імена пакетів малими буквами, тоді вони не будуть збігатися з іменами класів, які, за угодою, починаються з великої. Крім того, фірма SUN радить використовувати в якості імені пакета або підпакету доменне ім'я свого сайту, записане у зворотному порядку, наприклад:
com.sun.developer
До цих пір ми жодного разу не створювали пакет. Компілятор завжди створює для таких класів безіменний пакет, якому відповідає поточний каталог файлової системи. Ось тому в нас class-файл завжди опинявся в тому ж каталозі, що й відповідний Java-файл.
Безіменний пакет служить зазвичай сховищем невеликих пробних або проміжних класів. Великі проекти краще зберігати в пакетах. Наприклад, бібліотека класів Java 2 API зберігається в пакетах java, javax, org.omg. Пакет Java містить тільки подпакетах applet, awt, beans, io, lang, math, net, rmi, security, sql, text, util і ні одного класу. Ці пакети мають свої подпакетах, наприклад, пакет створення графічного інтерфейсу користувача (ГІП) і графіки java.awt містить подпакетах color, datatransfer, dnd, event, font, geometry, image, print.
Звичайно, склад пакетів змінюється від версії до версії.
Права доступу до членів класу
Розглянемо великий приклад. Нехай є п'ять класів, розміщених в двох пакетах, як показано на рис. П.1.

Рис. П.1. Розміщення наших класів по пакетах
У файлі Base.java описані три класи: inpi, Base і клас Derivedpi, що розширює клас Base. Ці класи розміщені в пакеті pi. У класі Base визначені змінні всіх чотирьох типів доступу, а в методах f () класів inp1 і Derivedp1 зроблена спроба доступу до всіх полів класу вазі. Невдалі спроби відзначені коментарями. У коментарях поміщені повідомлення компілятора. Лістинг 3.1 показує вміст цього файлу.
Лістинг П.1. Файл Base.java з описом пакету p1
package p1;
class Inp1 {
public void f () {
Base b = new Base ();
/ / B.priv = 1; / / "priv has private access in p1.Base"
b.pack = 1;
b.prot = 1;
b.publ = 1;
}
}
public class Base {
private int priv = 0;
int pack = 0;
protected int prot = 0;
public int publ = 0;
}
class Derivedpi extends Base {
public void f (Base a) {
/ / A.priv = 1; / / "priv hаs private access in pi.Base"
a.pack = 1;
a.prot = 1;
a.publ = 1;
/ / Priv = 1; / / "priv has private access in pi.Base"
pack = 1;
prot = 1;
publ = 1;
}
}
Як видно з лістингу П.1, в пакеті недоступні тільки закриті, private, поля іншого класу.
У файлі Inp2.java описані два класи: Inp2 і клас Derivedp2, що розширює клас Base. Ці класи знаходяться в іншому пакеті р2. У цих класах теж зроблена спроба звернення до полів класу Base. Невдалі спроби прокоментовані повідомленнями компілятора. Лістинг П.2 показує вміст цього файлу.
Нагадаємо, що клас Base повинен бути помічений при своєму описі в пакеті p1 модифікатором public, інакше з пакету р2 не буде видно ні одного його члена.
Лістинг П.2. Файл Inp2.java з описом пакету р2
package p2;
import pl.Base;
class Inp2 {
public static void main (String [] args) {
Base b = new Base ();
/ / B.priv = 1; / / "priv has private access in pl.Base"
/ / B.pack = 1; / / "pack is not public in pl.Base;
/ / Cannot be accessed from outside package "
/ / B.prot = 1; / / "" prot has protected access in pi.Base "
b.publ = 1;
}
}
class Derivedp2 extends Base {
public void, f (Base a) {
/ / A.priv = 1; / / "priv has private access in. P1.Base"
/ / A.pack = 1; / / "pack, is not public in pi.Base; cannot
/ / Be accessed from outside package "
/ / A.prot = 1; / / "prot has protected access in p1.Base"
a.publ = 1;
/ / Priv = 1; / / "priv has private access in pi.Base"
/ / Pack = 1; / / "pack is not public in pi.Base; cannot
/ / Be accessed from outside package "
prot = 1;
publ = 1;
super.prot = 1;
}
}
Тут, в іншому пакеті, доступ обмежений більшою мірою.
З незалежного класу можна звернутися тільки до відкритих, public, полів класу іншого пакету. З підкласу можна звернутися ще й до захищених, protected, полях, але тільки успадкованим безпосередньо, а не через примірник суперкласу.
Усе зазначене відноситься не тільки до полів, але і до методів. Підсумуємо все сказане в табл. П.1.
Таблиця П.1. Права доступу до полів і методів класу
Клас
Пакет
Пакет і підкласи
Всі класи
private
+
"Package"
+
+
protected
+
+
*
public
+
+
+
+
Особливість доступу до protected-полів і методів з чужого пакету відзначена зірочкою.
Розміщення пакетів по файлах
Та обставина, що class-файли, що містять байт-коди класів, повинні бути розміщені по відповідних каталогах, накладає свої особливості на процес компіляції і виконання програми.
Звернімося до того ж прикладу. Нехай в каталозі D: \ jdkl.3 \ MyProgs \ ch3 є порожній підкаталог classes і два файли - Base.java і Inp2.java, - вміст яких показано в лістингах П.1 і П.2. Рис. П.2 демонструє структуру каталогів вже після компіляції.
Ми можемо виконати всю роботу вручну.
1. У каталозі classes створюємо підкаталоги р1 і р2.
2. Переносимо файл Base.java в каталог р1 і робимо р1 поточним каталогом.
3. Компілюємо Base.java, отримуючи в каталозі р1 три файли: Base.class, Inpl.class, Derivedpl.class.
4. Переносимо файл Inp2java в каталог р2.
5. Знову робимо поточним каталог classes.
6. Компілюємо другий файл, вказуючи шлях p2 \ Inp2.java.
7. Запускаємо програму java p2.inp2.
Замість кроків 2 і 3 можна просто створити три class-файлу в будь-якому місці, а потім перенести їх в каталог pi. У class-файлах не зберігається жодної інформації про шляхи до файлів.
Сенс дій 5 і 6 в тому, що при компіляції файлу Inp2.java компілятор вже повинен знати клас p1.Base, а відшукує він файл з цим класом по шляху p1.Base.class, починаючи від поточного каталогу.
Зверніть увагу на те, що в останній дії 7 треба вказувати повне ім'я класу.
Якщо використовувати ключі (options) командного рядка компілятора, то можна виконати всю роботу швидше.
1. Викликаємо компілятор з ключем-d шлях, вказуючи параметром шлях початковий каталог для пакету:
javac-d classes Base.java
Компілятор створить в каталозі classes підкаталог р1 і помістить туди три class-файлу.
2. Викликаємо компілятор з ще одним ключем-classpath шлях, вказуючи параметром шлях каталог classes, в якому знаходиться підкаталог з вже скомпільованих пакунків pi:
javac-classpath classes-d classes Inp2.java
Компілятор, керуючись ключем-d, створить в каталозі classes підкаталог р2 і помістить туди два class-файлу, при створенні яких він "заглядав" в каталог pi, керуючись ключем-classpath.
3. Робимо поточним каталог classes.
4. Запускаємо профамми java p2.inp2.

Рис. П.2. Структура каталогів
Звичайно, якщо ви використовуєте для роботи не компілятор командного рядка, а яке-небудь IDE, то всі ці дії будуть зроблені без вашої участі.
На рис. П.2 відображено структуру каталогів після компіляції.
Імпорт класів і пакетів
У другому рядку лістингу П.2 новий оператор import. Для чого він потрібен?
Справа в тому, що компілятор буде шукати класи тільки в одному пакеті, саме, в тому, що вказаний в першому рядку файлу. Для класів з іншого пакета треба вказувати повні імена. У нашому прикладі вони короткі, і ми могли б писати в лістингу П.2 замість Base повне ім'я p1.Base.
Але якщо повні імена довгі, а використовуються класи часто, то ми пишемо оператори import, вказуючи компілятору повні імена класів.
Правила використання оператора import дуже прості: пишеться слово import і, через пропуск, повне ім'я класу, завершене крапкою з комою. Скільки класів треба вказати, стільки операторів import і пишеться.
Це теж може стати утомливим і тоді використовується друга форма оператора import - вказується ім'я пакету або підпакету, а замість короткого імені класу ставиться зірочка *. Цією записом компілятору пропонується переглянути весь пакет. У нашому прикладі можна було написати
import p1 .*;
Нагадаємо, що імпортувати можна тільки відкриті класи, помічені модифікатором public. Пакет java.lang (стандартна бібліотека класів) проглядається завжди, його необов'язково імпортувати. Решта пакетів стандартної бібліотеки треба вказувати в операторах import або записувати повні імена класів.
Підкреслимо, що оператор import вводиться тільки для зручності програмістів і слово "імпортувати" не означає ніяких переміщень класів.
Зауваження
Оператор import не еквівалентний директиві препроцесора include в С / С + +. Він не підключає ніяких файлів.
Java-файли
Тепер можна описати структуру вихідного файлу з текстом програми на мові Java.
· У першому рядку файлу може бути необов'язковий оператор package.
· У наступних рядках можуть бути необов'язкові оператори import.
· Далі йдуть описи класів та інтерфейсів.
Ще два правила.
· Серед класів файлу може бути тільки один відкритий public-клас.
· Назва файлу повинна співпадати з ім'ям відкритого класу, якщо останній існує.
Звідси випливає, що, якщо в проекті є декілька відкритих класів, то вони повинні знаходитися в різних файлах.
Угоду. Рекомендує відкритий клас,, якщо він є у файлі, описувати першим.
Інтерфейси
У Java отримати розширення можна тільки від одного класу, кожен клас В або С відбувається з неповної сім'ї, як показано на рис. П.4, а. Всі класи відбуваються тільки від "Адама", від класу Оbject. Але часто виникає необхідність породити клас D від двох класів В і С, як показано на рис. П.4, б. Це називається множинним спадкуванням (multiple inheritance). У множині спадкуванні немає нічого поганого. Труднощі виникають, якщо класи В і С самі породжені від одного класу А, як показано на рис. П.4 ст. Це так зване "ромбовидное" спадкування.

Рис. П.4. Різні варіанти спадкування
Нехай у класі А визначено метод f (), до якого ми звертаємося з якогось методу класу D. Чи можемо ми бути впевнені, що метод f () виконує те, що написано в класі А, тобто це метод Af ()? Може, він перевизначений в класах В і С? Якщо так, то яким варіантом ми користуємося: Bf () або С.f ()? Звичайно, можна визначити екземпляри класів і звертатися до методів цих примірників, але це зовсім інша розмова.
У різних мовах програмування це питання вирішується по-різному, головним чином, уточненням імені методу f ().
Творці мови Java заборонили множинне спадкування взагалі. При розширенні класу після слова extends можна написати тільки одне ім'я суперкласу. За допомогою уточнення super можна звернутися тільки до членів безпосереднього суперкласу.
Але що робити, якщо все-таки при породженні треба використовувати кілька предків? Наприклад, у нас є загальний клас автомобілів Automobile, від якого можна породити клас вантажівок Truck і клас легкових автомобілів Саг. Але от треба описати пікап Pickup. Цей клас повинен успадковувати властивості і вантажних, і легкових автомобілів.
У таких випадках використовується ще одна конструкція мови Java-інтерфейс. Уважно проаналізувавши ромбовидное спадкування, теоретики ООП з'ясували, що проблему створює тільки реалізація методів, а не їх опис.
Інтерфейс (interface), на відміну від класу, містить лише константи і заголовки методів без їх реалізації.
Інтерфейси розміщуються у тих же пакетах і подпакетах, що і класи, і компілюються теж у class-файли.
Опис інтерфейсу починається зі слова interface, перед яким може стояти модифікатор public, що означає, як і для класу, що інтерфейс доступний всюди. Якщо ж модифікатора public немає, інтерфейс буде видно тільки у своєму пакеті.
Після слова interface записується ім'я інтерфейсу,. Потім може стояти слово extends і список інтерфейсів-предків через кому. Таким чином, інтерфейси можуть породжуватися від інтерфейсів, утворюючи свою, незалежну від класів, ієрархію, причому в ній допускається множинна спадковість інтерфейсів. У цій ієрархії немає кореня (загального предка).
Потім, у фігурних дужках, записуються в будь-якому порядку константи і заголовки методів. Можна сказати, що в інтерфейсі всі методи абстрактні, але слово abstract писати не треба. Константи завжди статичні, але слова static і final вказувати не потрібно.
Всі константи і методи в інтерфейсах завжди відкриті, не треба навіть вказувати модифікатор public.
Ось яку схему можна запропонувати для ієрархії автомобілів:
interface Automobile {. . . }
interface Car extends Automobile {. . . }
interface Truck extends Automobile {. . . }
interface Pickup extends Car, Truck {. . . }
Таким чином, інтерфейс - це лише начерк, ескіз. У ньому зазначено, що робити, але не зазначено, як це робити.
Як же використовувати інтерфейс, якщо він повністю абстрактний, в ньому немає ні одного повного методу?
Використовувати потрібно не інтерфейс, а його реалізацію (implementation). Реалізація інтерфейсу - це клас, в якому розписуються методи одного або кількох інтерфейсів. У заголовку класу після його імені або після імені його суперкласу, якщо він є, записується слово implements і, через кому, перераховуються імена інтерфейсів.
Ось як можна реалізувати ієрархію автомобілів:
interface Automobile {. . . }
interface Car extends Automobile! . . . }
class Truck implements Automobile! . . . }
class Pickup extends Truck implements Car {. . . }
або так:
interface Automobile {. . . }
interface Car extends Automobile {. . . }
interface Truck extends Automobile {. . . }
class Pickup implements Car, Truck {. . . }
Реалізація інтерфейсу може бути неповною, деякі методи інтерфейсу розписані, а інші - ні. Така реалізація - абстрактний клас, його обов'язково треба помітити модифікатором abstract.
Як реалізувати в класі Рickup метод f (), описаний і в інтерфейсі саг, і в інтерфейсі Truck з однаковою сигнатурою? Відповідь проста - ніяк. Таку ситуацію не можна реалізувати в класі Pickup. Програму треба спроектувати по-іншому.
Отже, інтерфейси дозволяють реалізувати засобами Java чисте об'єктно-орієнтоване проектування, не відволікаючись на питання реалізації проекту.
Ми можемо, приступаючи до розробки проекту, записати його у вигляді ієрархії інтерфейсів, не думаючи про реалізацію, а потім побудувати за цим проектом ієрархію класів, з огляду на обмеження одиночного наслідування і видимості членів класів.
Цікаво те, що ми можемо створювати посилання на інтерфейси. Звичайно, вказувати таке посилання може тільки на яку-небудь реалізацію інтерфейсу. Тим самим ми отримуємо ще один спосіб організації поліморфізму.
Лістинг П.3 показує, як можна зібрати за допомогою інтерфейсу «хор» домашніх тварин.
Лістинг П.3. Використання інтерфейсу для організації поліморфізму
interface Voice {
void voice ();
}
class Dog implements Voice {
public void voice () {
System.out.println ("Gav-gav!");
}
}
class Cat implements Voice {
public void voice () {
System.out.println ("Miaou!");
}
}
class Cow implements Voice {
public void voice () {
System.out.println ("Mu-uu!");
}
}
public class Chorus {
public static void main (String [] args) {
Voiced singer = new Voice [3];
singer [0] = new Dog ();
singer [1] = new Cat ();
singer [2] = new Cow ();
for (int i = 0; i <singer.length; i + +)
singer [i]. voice ();
}
}
Тут використовується інтерфейс voice.
Що ж краще використовувати: абстрактний клас або інтерфейс? На це питання немає однозначної відповіді.
Створюючи абстрактний клас, ви волею-неволею занурює його в ієрархію класів, пов'язану умовами одиночного наслідування і єдиним предком - класом Оbject. Користуючись інтерфейсами, ви можете вільно проектувати систему, не замислюючись про ці обмеження.
З іншого боку, в абстрактних класах можна відразу реалізувати частину методів. Реалізуючи ж інтерфейси, ви приречені на перевизначення всіх методів.
Є ще одне обмеження: всі реалізації методів інтерфейсів повинні бути відкритими, public, оскільки при перевизначенні можна лише розширювати доступ, а методи інтерфейсів завжди відкриті.
Взагалі ж наявність і класів, і інтерфейсів дає розробнику багаті можливості проектування. У нашому прикладі, ви можете включити в хор будь-який клас, просто реалізувавши в ньому інтерфейс voice.
Нарешті, можна використати інтерфейси просто для визначення констант, як показано в лістингу П.4.
Лістинг П.4. Система управління світлофором
interface Lights {
int RED = 0;
int YELLOW = 1;
int GREEN = 2;
int ERROR = -1;
}
class Timer implements Lights {
private int delay;
private static int light = RED;
Timer (int sec) (delay = 1000 * sec;}
public int shift () {
int count = (light + +)% 3;
try {
switch (count) {
case RED: Thread.sleep (delay); break;
case YELLOW: Thread.sleep (delay / 3); break;
case GREEN: Thread.sleep (delay / 2); break;
}
} Catch (Exception e) {return ERROR;}
return count;
}
}
class TrafficRegulator {
private static Timer t = new Timer (1);
public static void main (String [] args) {
for (int k = 0; k <10; k + +)
switch (t.shift ()) {
case Lights.RED: System.out.println ("Stop!"); break;
case Lights.YELLOW: System.out.println ("Wait!"); break;
case Lights.GREEN: System.out.println ("Go!"); break;
case Lights.ERROR: System.err.println ("Time Error"); break;
default: System.err.println ("Unknown light."); return;
}
}
}
Тут, в інтерфейсі Lights, визначені константи, загальні для всього проекту.
Клас Timer реалізує цей інтерфейс і використовує константи безпосередньо як свої власні. Метод shift () цього класу подає сигнали перемикання світлофора з різною затримкою в залежності від кольору. Затримку здійснює метод sleep () класу Thread із стандартної бібліотеки, якому передається час затримки в мілісекундах. Цей метод потребує обробки винятків try {} catch () {}.
Клас TrafficReguiator не реалізує інтерфейс Lights і користується повними іменами Lights.RED і т.д. Це можливо тому, що константи RED, YELLOW і GREEN за замовчуванням є статичними.

Додаток 2. Вкладені класи
В тілі класу можна зробити опис іншого, вкладеного (nested) класу. А у вкладеному класі можна знову описати вкладений, внутрішній (inner) клас і т. д. Чи можна з вкладеного класу звернутися до членів зовнішнього класу? Можна, для того це все і замислювалося.
· А чи можна в такому випадку визначити примірник вкладеного класу, не визначаючи екземпляри зовнішнього класу? Ні, не можна, спочатку треба визначити хоч один примірник зовнішнього класу, матрьошка ж!
· А якщо примірників зовнішнього класу кілька, як дізнатися, з яким примірником зовнішнього класу працює даний екземпляр вкладеного класу? Ім'я примірника вкладеного класу уточнюється ім'ям пов'язаного з нею примірника зовнішнього класу. Більш того, при створенні вкладеного примірника операція new теж уточнюється ім'ям зовнішнього примірника.
Всі вкладені класи можна розділити на вкладені класи-члени класу (member classes), описані поза методів, і вкладені локальні класи (local classes), описані усередині методів і / чи блоків. Локальні класи, як і всі локальні змінні, не є членами класу.
Класи-члени можуть бути оголошені статичним модифікатором static. Поведінка статичних класів-членів нічим не відрізняється від поведінки звичайних класів, відрізняється тільки звернення до таких класів. Тому вони називаються вкладеними класами верхнього рівня (nestee tep-level classes), хоча статичні класи-члени можна вкладати один в одного. У них можна оголошувати статичні члени. Використовуються вони зазвичай для того, щоб згрупувати допоміжні класи разом з основним класом.
Всі нестатичні вкладені класи називаються внутрішніми (inner). У них не можна оголошувати статичні члени.
Локальні класи, як і всі локальні змінні, відомі тільки в блоці, в якому вони визначені. Вони можуть бути безіменними (anonymous classes).
Приклад.
Лістинг Вкладені класи
class Nested {
static private int pr; / / Змінна pr об'явленa статичної
/ / Щоб до неї був доступ з статичних класів А і АВ
String s = "Member of Nested";
/ / Вкладаємо статичний клас.
static class. А {/ / Повне ім'я цього класу - Nested.A
private int a = pr;
String s = "Member of A";
/ / У вложенньм клас А вкладаємо ще один статичний клас
static class AB {/ / Повне ім'я класу - Nested.А.АВ
private int ab = pr;
String s = "Member of AB";
}
}
/ / В клас Nested вкладаємо нестатичні клас
class В {/ / Повне ім'я цього класу - Nested.В
private int b = pr;
String s = "Member of B";
/ / В клас У вкладаємо ще один клас
class НД {/ / Повне ім'я класу - Nested.В.ВС
private int bc = pr;
String s = "Member of ВС";
}
void f (final int i) {/ / Без слова final змінні i і j
final int j = 99; / / не можна використовувати в локальному класі D
class D {/ / Локальний клас D відомий тільки всередині f ()
private int d = pr;
String s = "Member of D";
void pr () {
/ / Зверніть увагу на те, як різняться
/ / Змінні з одним і тим же ім'ям "s"
System.out.println (s + (i + j)); / / "s" еквівалентно "this.s"
System.out.println (B.this.s);
System.out.println (Nested.this.s);
/ / System.out.println (AB.this.s); / / Немає доступу
/ / System.out.println (A.this.s); / / Немає доступу
}
}
D d = new D (); / / Об'єкт визначається тут же, у методі f ()
d.pr (); / / Об'єкт відомий тільки в методі f ()
}
}
void m () {
new Object () {/ / Створюється об'єкт безіменного класу,
/ / Вказується конструктор його суперкласу
private int e = pr;
void g () {
System.out.println ("From g ());
}
}. G (); / / Тут же виконується метод щойно створеного об'єкта
}
}
public class NestedClasses {
public static void main (String [] args) {
Nested nest = new Nested (); / / Послідовно розкриваються
/ / Три матрьошки
Nested.A theA = nest.new A (); / / Повне ім'я класу і уточнена
/ / Операція new. Але конструктор тільки вкладеного класу
Nested.A.AB theAB = theA.new AB (); / / Ті ж правила. Операція
/ / New уточнюється тільки одним ім'ям
Nested.В theB = nest.new B (); / / Ще одна матрьошка
Nested.В.ВС theBC = theB.new BC ();
theB.f (999); / / Методи викликаються звичайним чином
nest.m ();
}
}
Дамо пояснення.
· Як бачите, доступ до полів зовнішнього класу Nested можливий звідусіль, навіть до закритого полю pr. Саме для цього в Java і введені вкладені класи. Решта конструкції введені вимушено, для того щоб пов'язати кінці з кінцями.
· Мова Java дозволяє використовувати одні й ті ж імена в різних областях видимості - довелося уточнювати константу this ім'ям класу: Nested.this, В.this.
· У безіменному класі не може бути конструктора, адже ім'я конструктора повинно збігатися з ім'ям класу, - довелося використовувати ім'я суперкласу, у прикладі це клас object. Замість конструктора в безіменному класі використовується блок ініціалізації екземпляра.
· Не можна створити екземпляр вкладеного класу, не створивши попередньо примірник зовнішнього класу, - довелося підстрахувати це правило уточненням операції new ім'ям екземпляра зовнішнього класу-nest.new, theA.new, theB.new.
· При визначенні примірника вказується повне ім'я вкладеного класу, але в операції new записується просто конструктор класу.
Введення вкладених класів сильно ускладнило синтаксис і поставило багато завдань розробникам мови.
Чи можна успадковувати вкладені класи? Можна.
· Як з підкласу звернутися до методу суперкласу? Константа super уточнюється ім'ям відповідного суперкласу, подібно константі this.
· А чи можуть вкладені класи бути розширеннями інших класів? Можуть.
Механізм вкладених класів стане зрозуміліше, якщо подивитися, які файли з байт-кодами створив компілятор:
· Nested $ l $ D.class - локальний клас про, вкладений в клас Nested;
· NestedSl.class - безіменний клас;
· Nested $ A $ AB.class - клас Nested.A.AB;
· Nested $ A.class - клас Nested.А;
· Nested $ B $ BC.class - клас Nested.B.BC;
· NestedSB.class - клас Nested.B;
· Nested.class - зовнішній клас Nested;
· NestedClasses.class - клас з методом main ().
Компілятор розклав матрьошки і, як завжди, створив окремі файли для кожного класу. При цьому, оскільки у ідентифікаторах неприпустимі точки, компілятор замінив їх знаками долара. Для безіменного класу компілятор придумав ім'я. Локальний клас компілятор позначив номером.
Виявляється, вкладені класи існують тільки на рівні програмного коду. Віртуальна машина Java нічого не знає про вкладені класах. Вона працює зі звичайними зовнішніми класами. Для взаємодії об'єктів вкладених класів компілятор вставляє в них спеціальні закриті поля. Тому в локальних класах можна використовувати лише константи осяжний методу, тобто змінні, помічені словом final. Віртуальна машина просто не здогадається передавати змінюються значення змінних у локальний клас. Таким чином, не має сенсу позначати вкладені класи private, все одно вони виходять на самий зовнішній рівень.
Всі ці питання можна не брати в голову. Вкладені класи в Java використовуються тільки в самому простому вигляді, головним чином, при обробці подій, що виникають при діях з мишею і клавіатурою.
У яких же випадках створювати вкладені класи? У теорії ООП питання про створення вкладених класів вирішується при розгляді відносин "бути частиною" і "бути".
Відносини "бути частиною" і "бути"
Тепер у нас з'явилися дві різні ієрархії класів. Одну ієрархію утворює наслідування класів, іншу - вкладеність класів.
Визначивши, які класи будуть написані у вашій програмі, і скільки їх буде, подумайте, як спроектувати взаємодію класів? Виростити пишне генеалогічне дерево класів-спадкоємців або розписати матрьошку вкладених класів?
Теорія ООП радить перш за все з'ясувати, в якому відношенні знаходяться ваші класи P і Q - у відношенні "клас Q є екземпляром класу P" ("a class Q is a class р") або у відношенні "клас Q - частина класу P" ( "a class Q has a class P").
Наприклад: "Собака є твариною" або "Собака - частина тварини"? Ясно, що правильне перше ставлення "is-a", тому ми і визначили клас Dog як розширення класу Pet.
Відношення "is-a" - це відношення "узагальнення-деталізація", ставлення більшою чи меншою абстракції, і йому відповідає наслідування класів.
Відношення "has-a" - це відношення "ціле-частина", йому відповідає вкладення.

Додаток 3. Інтерфейси і зворотні виклики

Зворотний виклик (callback) широко поширений в програмуванні. При зворотному виклику програміст задає дії, які повинні виконуватися щоразу, коли відбувається певна подія. Наприклад, можна задати дію, яке повинно бути виконано, якщо буде натиснута конкретна кнопка або виділити певний пункт меню.
Розглянемо просту ситуацію. Пакет java.swing містить клас Timer, який можна використовувати для відліку інтервалів часу. Наприклад, якщо в програмі передбачені годинник, то за допомогою класу Timer, можна відраховувати кожну секунду і оновлювати циферблат годинника. Встановлюючи таймер, ми задаємо інтервал часу і вказуємо, що має відбутися після його закінчення.
Як вказати таймером, що він повинен робити? У багатьох мовах програмування задається ім'я функції, яку таймер повинен періодично викликати.
Класи із стандартної бібліотеки мови Java використовують об'єктно-орієнтований підхід. Програміст повинен передати таймеру об'єкт деякого класу. Після цього таймер викликає один з методів даного об'єкта.
Передача об'єкта - більш гнучкий механізм, ніж виклик функцій, оскільки об'єкт може нести з собою додаткову інформацію. Значить, таймер повинен знати, який метод він повинен викликати. Для цього таймеру потрібно вказати об'єкт класу, що реалізує інтерфейс ActionListener з пакету java. awt. event.
Ось як виглядає цей інтерфейс
public interface ActionListener
{
void actionPerformed (ActionEvent event);
}
Після закінчення заданого інтервалу часу таймер викликає метод actionPerformed.
Розглянемо приклад. Нехай потрібно кожні 10 секунд виводити на екран повідомлення «Поточний час ...», супроводжуване звуковим сигналом. Для цього необхідно визначити клас, який реалізує інтерфейс ActionListener. Потім помістити оператори, які потрібно виконати, всередину методу actionPerformed.
class Timerprinter implements ActionListener
{
public void actionPerformed (ActionEvent event)
{
Date now = new Date ();
System.out.println ("Поточний час:» + now);
Toolkit.getDefaultToolkit (). Bеep ();
}
}
Потім слід створити об'єкт даного класу і передати його конструктору класу Timer.
ActionListener listener = new TimerPrinter ();
Timer t = new Timer (10000, listener);
Перший параметр конструктора Timer представляє собою інтервал часу між точками відліку, який вимірюється в мілісекундах. Повідомлення має видаватися на екран кожні десять секунд.
Другий параметр є об'єктом класу ActionListener
Запуск таймера
t.start ();
Кожні 10 секунд на екрані буде з'являтися повідомлення про поточний час. У лістингу 6.2. наведена програма, що реалізує описаний алгоритм.
import java.awt .*;
import java.awt.event *;
import javax.swing .*;
import javax.swing.Timer *; / / щоб вирішити конфлікт з класом javax.util.Timer
public class TimerTest
{
public static void main (string [] args)
{
ActionListener listener = new TimerPrinter ();
/ / Створює таймер, що викликає блок кожні 10 сек.
Timer t = new Timer (10000, listener);
t.start ();
JOptionPаne.showMessageDialog (null, "Вихід?");
System.exit (0);
}
}
class Timerprinter implements ActionListener
{
public void actionPerformed (ActionEvent event)
{
Date now = new Date ();
System.out.println ("Поточний час:» + now);
Toolkit.getDefaultToolkit (). Bеep ();
}
}
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Методичка
69.8кб. | скачати


Схожі роботи:
Зворотні задачі
Зворотні послідовності
Зворотні зв`язки в живих системах
Функція та її виклики
Зворотні матриці над кільцем цілих чисел
Судові повідомлення і виклики
Постюгославських простір проблеми виклики
Християнський шлюб і сучасні виклики
Послідовні інтерфейси
© Усі права захищені
написати до нас