Приложение 1. Пакеты и интерфейсы




Оглавление

 

Цель работы.. 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 — https://java.sun.com/ (есть раздел на русском языке с учебником).

3. Java™ 2 SDK, Standard Edition Documentation — https://java.sun.com/products/jdk/1.5/index.html.

4. Джеймс Гослинг, Билл Джой, Гай Стил. Спецификация языка Java (The Java Language Specification — https://www.javasoft.com/docs/books/jls/). Перевод на русский язык — https://www.uni-vologda.ac.ru/java/jls/index.html

5. Официальный сайт проекта Eclipse — https://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 () выполняет то, что написано в классе А, т. е. это метод A.f ()? Может, он переопределен в классах В и С? Если так, то каким вариантом мы пользуемся: B.f() или С.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-u-u!");

}

}

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 по умолчанию являются статическими.

 




Поделиться:




Поиск по сайту

©2015-2024 poisk-ru.ru
Все права принадлежать их авторам. Данный сайт не претендует на авторства, а предоставляет бесплатное использование.
Дата создания страницы: 2019-06-03 Нарушение авторских прав и Нарушение персональных данных


Поиск по сайту: