p align="left">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; }
Страницы: 1, 2, 3
|