Иллюстрированный самоучитель по Java

         

Design patterns


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

Нет ли подобных общих методов в программировании? Есть.

Допустим, вам поручили автоматизировать метеорологическую станцию. Информация от различных датчиков или, другими словами,

контроллеров

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

модель

метеорологической картины местности. Затем прогноз выводится по разным каналам: на экран монитора, самописец, передается по сети. Он представляется в разных

видах,

колонках чисел, графиках, диаграммах.

Естественно спроектировать такую автоматизированную систему из трех частей.

Первая часть, назовем ее

Контроллером

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

 Вторая часть, назовем ее

Моделью

(model), принимает эту унифицированную информацию от Контроллера, ничего не зная о датчике и не интересуясь тем, от какого именно датчика она поступила, и преобразует ее по своим алгоритмам опять-таки к какому-то однообразному виду, например, к последовательности чисел.

Третья часть системы,

Вид

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




В чем удобство такой трехзвенной схемы? Она очень гибка. Замена одного датчика приведет к замене только одного модуля в Контроллере, ни Модель, ни Вид этого даже не заметят. Надо представить прогноз в каком-то новом виде, например, для телевидения? Пожалуйста, достаточно написать один модуль и вставить его в Вид. Изменился алгоритм обработки данных? Меняем Модель.

Эта схема разработана еще в 80-х годах прошлого столетия [То есть

XX

века. —

Ред.

] в языке Smalltalk и получила название MVG (Model-View-Controller). Оказалось, что она применима во многих областях, далеких от метеорологии, всюду, где удобно отделить обработку от ввода и вывода информации.

Сбор информации часто организуется так. На экране дисплея открывается поле ввода, в которое вы набиваете сведения, допустим, фамилии в произвольном порядке, а в соседнем поле вывода отображается обработанная информация, например, список фамилий по алфавиту. Будьте уверены, что эта программа организована по схеме МУС. Контроллером служит поле ввода, Видом — поле вывода, а Моделью — метод сортировки фамилий. В третьей части книги мы рассмотрим примеры реализации этой схемы.

К середине 90-х годов накопилось много подобных схем. В них сконцентрирован многолетний опыт тысяч программистов, выражены наилучшие решения типовых задач.

Вот, пожалуй, самая простая из этих схем. Надо написать класс, у которого можно создать только один экземпляр, но этим экземпляром должны пользоваться объекты других классов. Для решения этой задачи предложена схема Singleton, представленная в листинге 3.5.



Листинг 3.5.

Схема Singleton

final class Singleton{

  private static Singleton s = new Singleton(0); 

  private int k;

  private Singleton(int i){k = i;} 

  public static Singleton getReference()(return s;} 

  public int getValue(){return k;} 

  public void setValue(int i){k = i;} 



public class SingletonTest {

  public static void main(String[] args){ 



    Singleton ref = Singleton.getReference(); 

    System.out.println(ref.getValue()); 

    ref.setValue(ref.getValue() + 5); 

    System.out.println(ref.getValue()); 

  } 

}

Класс

singleton

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

s

класса

singleton

— статический, он создается внутри класса. Зато любой объект может получить ссылку на экземпляр методом

getReference ()

, Изменить состояние экземпляра s методом s

etValue()

или просмотреть его текущее состояние методом

getValue()

.

Это только схема — класс

singleton

надо еще наполнить полезным содержимым, но идея выражена ясно и полностью.

Схемы проектирования были систематизированы и изложены в книге [7]. Четыре автора этой книги были прозваны "бандой четырех" (Gang of Four), а книга, коротко, "GoF". Схемы обработки информации получили название "Design Patterns". Русский термин еще не устоялся. Говорят о "шаблонах", "схемах разработки", "шаблонах проектирования".

В книге GoF описаны 23 шаблона, разбитые на три группы:

1. Шаблоны создания объектов: Factory, Abstract Factory, Singleton, Builder, Prototype.

2. Шаблоны структуры объектов: Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy.

3. Шаблоны поведения объектов: Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template, Visitor.

Описания даны, в основном, на языке C++. В книге [8] те же шаблоны представлены на языке Java. Той же теме посвящено электронное издание [9]. В книге [10] подробно обсуждаются вопросы разработки систем на основе design patterns.

Мы, к сожалению, не можем разобрать подробно design patterns в этой кни-те. Но каждый программист начала XXI века должен их знать. Описание многих разработок начинается словами: "Проект решен на основе шаблона", и структура проекта сразу становится ясна для всякого, знакомого с design patterns.

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


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


Внимательный читатель заметил во второй строке листинга 3.2 новый оператор

import

. Для чего он нужен?

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

Base

полное имя

p1.Base.

Но если полные имена длинные, а используются классы часто, то стучать по клавишам, набирая полные имена, становится утомительно. Вот тут-то мы и пишем операторы

import

, указывая компилятору полные имена классов.

Правила использования оператора

import

очень просты: пишется слово

import

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

import

и пишется.

Это тоже может стать утомительным и тогда используется вторая форма оператора

import

— указывается имя пакета или подпакета, а вместо короткого имени класса ставится звездочка *. Этой записью компилятору предписывается просмотреть весь пакет. В нашем примере можно было написать

import p1.*;

Напомним, что импортировать можно только открытые классы, помеченные модификатором

public

.

Внимательный читатель и тут настороже. Мы ведь пользовались методами классов стандартной библиотеки, не указывая ее пакетов? Да, правильно.

Пакет

java.iang

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

import

, либо записывать полные имена классов.

Подчеркнем, что оператор

import

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

Знатокам C/C++

Оператор

import

не эквивалентен директиве препроцессора

include

— он не подключает никакие файлы.



Интерфейсы


Вы уже заметили, что получить расширение можно только от одного класса, каждый класс в или с происходит из неполной семьи, как показано на рис. 3.4, а. Все классы происходят только от "Адама", от класса

object

. Но часто возникает необходимость породить класс о от двух классов вис, как показано на рис. 3.4, б. Это называется

множественным наследованием

(multiple inheritance). В множественном наследовании нет ничего плохого. Трудности возникают, если классы вис сами порождены от одного класса А, как показано на рис. 3.4* в. Это так называемое "ромбовидное" наследование.

Рис. 3.4.

Разные варианты  наследования

В самом деле, пусть в классе А определен метод f (), к которому мы обращаемся из некоего метода класса о. Можем мы быть уверены, что метод f о выполняет то, что написано в классе А, т. е. это метод A.f о? Может, он переопределен в классах в и с? Если так, то каким вариантом мы пользуемся: B.f() или c.f()? Конечно, можно определить экземпляры классов и обращаться к методам этих экземпляров, но это совсем другой разговор.

В разных языках программирования этот вопрос решается по-разному, главным образом, уточнением имени метода ft). Но при этом всегда нарушается принцип KISS. Вокруг множественного наследования всегда много споров, есть его ярые приверженцы и столь же ярые противники. Не будем встревать в эти споры, наше дело — наилучшим образом использовать средства языка для решения своих задач.

Создатели языка 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

.

Как реализовать в классе

pickup

метод

f()

, описанный и в интерфейсе саг, и в интерфейсе

Truck

с одинаковой сигнатурой? Ответ простой — никак. Такую ситуацию нельзя реализовать в классе

Pickup

. Программу надо спроектировать по-другому.

Итак, интерфейсы позволяют реализовать средствами Java чистое объектно-ориентированное проектирование, не отвлекаясь на вопросы реализации проекта.

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

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

Листинг 3.3 показывает, как можно собрать с помощью интерфейса хор домашних животных из листинга 2.2.



Листинг 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

вместо абстрактного класса

Pet

, описанного в листинге 2.2.

Что же лучше использовать: абстрактный класс или интерфейс? На этот вопрос нет однозначного ответа.

Создавая абстрактный класс, вы волей-неволей погружаете его в иерархию классов, связанную условиями одиночного наследования и единым предком — классом

object

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

С другой стороны, в абстрактных классах можно сразу реализовать часть методов. Реализуя же интерфейсы, вы обречены на скучное переопределение всех методов.

Вы, наверное, заметили и еще одно ограничение: все реализации методов интерфейсов должны быть открытыми,

public

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

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

voice

.

Наконец, можно использовать интерфейсы просто для определения констант, как показано в листинге 3.4.



Листинг 3.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() {}

, о которой мы будем говорить в

главе 16.

Класс

TrafficReguiator

не реализует интерфейс

Lights

и пользуется полными именами

Lights.RED

и т.д. Это возможно потому, что константы

RED, YELLOW

и

GREEN

по умолчанию являются статическими.

Теперь нам известны все средства языка Java, позволяющие проектировать решение поставленной задачи. Заканчивая разговор о проектировании, нельзя не упомянуть о постоянно пополняемой коллекции образцов проектирования (design patterns).


Java-файлы


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

В первой строке файла может быть необязательный оператор

package

В следующих строках могут быть необязательные операторы

import

Далее идут описания классов и интерфейсов. 

Еще два правила.

Среди классов файла может быть только один открытый

public

-класс.

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

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

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



Пакет и подпакет


Чтобы создать пакет надо просто в первой строке 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

До сих пор мы ни разу не создавали пакет. Куда же попадали наши файлы с откомпилированными классами?

Компилятор всегда создает для таких классов

безымянный пакет

(unnamed package), которому соответствует текущий каталог (current working directory)

файловой системы. Вот поэтому у нас 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, im,image, print.

Конечно, состав пакетов меняется от версии к версии.



Права доступа к членам класса


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

Рассмотрим большой пример. Пусть имеется пять классов, размещенных в двух пакетах, как показано на рис. 3.1.

Рис. 3.1.

Размещение  наших классов по пакетам

В файле Base.java описаны три класса:

inpi, Base

и класс

Derivedpi

, расширяющий класс вазе. Эти классы размещены в пакете pi. В классе Base определены переменные всех четырех типов доступа, а в методах

f()

классов

inp1

и

Derivedp1

сделана попытка доступа ко всем полям класса вазе. Неудачные попытки отмечены комментариями. В комментариях помещены сообщения компилятора. Листинг 3.1 показывает содержимое этого файла.

Листинг 3.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 hds 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; 

  } 

}

Как видно из листинга 3.1, в пакете недоступны только закрытые,

private

, поля другого класса.

В файле Inp2.java описаны два класса:

inp2

и класс

Derivedp2

, расширяющий класс

base

. Эти классы находятся в другом пакете

р2

. В этих классах тоже сделана попытка обращения к полям класса вазе. Неудачные попытки прокомментированы сообщениями компилятора. Листинг 3.2 показывает содержимое этого файла.


Напомним, что класс вазе должен быть помечен при своем описании в пакете

p1

модификатором

public

, иначе из пакета

р2

не будет видно ни одного его члена.



Листинг 3.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

, полям, но только унаследованным непосредственно, а не через экземпляр суперкласса.

Все указанное относится не только к полям, но и к методам. Подытожим все сказанное в табл. 3.1.



Таблица 3.1.

Права доступа к полям и методам класса



Класс



Пакет



Пакет и подклассы



Все классы

private



+

"package"



+



+

protected



 +



+



*

public



+



+



+



+

Особенность доступа к

protected

-полям и методам из чужого пакета отмечена звездочкой.


Размещение пакетов по файлам


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

Обратимся к тому же примеру. Пусть в каталоге D:\jdkl.3\MyProgs\ch3 есть пустой подкаталог classes и два файла — Base.java и Inp2.java, — содержимое которых показано в листингах 3.1 и 3.2. Рис. 3.2 демонстрирует структуру каталогов уже после компиляции.

Мы можем проделать всю работу вручную.

1. В каталоге classes создаем подкаталоги р! и р2.

2. Переносим файл Base.java в каталог р! и делаем р] текущим каталогом.

3. Компилируем Base.java, получая в каталоге р! три файла: 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.





 Рис. 3.2.



Структура каталогов





Рис. 3.3.

Протокол компиляции и запуска программы

Для "юниксоидов" все это звучит, как музыка, ну а прочим придется вспомнить MS DOS.

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

На рис. 3.2 отображена структура каталогов после компиляции.

На рис. 3.3 показан вывод этих действий в окно

Command Prompt

и содержимое каталогов после компиляции.


и закончили первую часть книги.


Вот мы и закончили первую часть книги. Теперь вы знаете все основные конструкции языка Java, позволяющие спроектировать и реализовать проект любой сложности на основе ООП. Оставшиеся конструкции языка, не менее важные, но реже используемые, отложим до четвертой части. Вторую и третью часть книги посвятим изучению классов и методов, входящих в Core API. Это будет для вас хорошей тренировкой.
Язык Java, как и все современные языки программирования, — это не только синтаксические конструкции, но и богатая библиотека классов. Знание этих классов и умение пользоваться ими как раз и определяет программиста-практика.