Выбрать главу

Следующий пример показывает, как композиция, наследование и полимор­физм влияют на порядок конструирования:

// polymorphism/Sandwich.java

// Порядок вызова конструкторов.

package polymorphism,

import static net mindview.util.Print.*;

class Meal {

Meal О { printCMealO"). }

}

class Bread {

BreadO { printCBreadO"). }

}

class Cheese {

CheeseO { printC'CheeseO"). }

}

class Lettuce {

LettuceO { print("Lettuce()"); }

}

class Lunch extends Meal {

Lunch0 { printC'LunchO"). }

}

class PortableLunch extends Lunch {

PortableLunchO { printC'PortableLunchO");}

}

public class Sandwich extends PortableLunch { private Bread b = new BreadO, private Cheese с = new CheeseO, private Lettuce 1 = new LettuceO; public Sandwich0 { print("Sandwich()"); } public static void main(String[] args) { new SandwichO;

}

} /* Output: Meal О LunchO

PortableLunchO BreadO CheeseO LettuceO SandwichO *///:-

В этом примере создается сложный класс, собранный из других классов, и в каждом классе имеется конструктор, который сообщает о своем выполне­нии. Самый важный класс — Sandwich, с тремя уровнями наследования (четырьмя, если считать неявное наследование от класса Object) и тремя встроенными объ­ектами. Результат виден при создании объекта Sandwich в методе main(). Это значит, что конструкторы для сложного объекта вызываются в следующей по­следовательности:

•        Сначала вызывается конструктор базового класса. Этот шаг повторяется рекурсивно: сначала конструируется корень иерархии, затем следующий за ним класс, затем следующий за этим классом класс и т. д., пока не дос­тигается «низший» производный класс.

•        Проводится инициализация членов класса в порядке их объявления.

•        Вызывается тело конструктора производного класса.

Порядок вызова конструкторов немаловажен. При наследовании вы распо­лагаете полной информацией о базовом классе и можете получить доступ к лю­бому из его открытых (public) или защищенных (protected) членов. Следова­тельно, при этом подразумевается, что все члены базового класса являются действительными в производном классе. При вызове нормального метода из­вестно, что конструирование уже было проведено, поэтому все части объекта инициализированы. Однако в конструкторе вы также должны быть уверены в том, что все используемые члены уже проинициализированы. Это можно га­рантировать только одним способом — сначала вызывать конструктор базового класса. В дальнейшем при выполнении конструктора производного класса можно быть уверенным в том, что все члены базового класса уже инициализи­рованы. Гарантия действительности всех членов в конструкторе — важная при­чина, по которой все встроенные объекты (то есть объекты, помещенные в класс посредством композиции) инициализируются на месте их определения (как в рассмотренном примере сделано с объектами Ь, с и I). Если вы будете следовать этому правилу, это усилит уверенность в том, что все члены базового класса и объекты-члены были проинициализированы. К сожалению, это помо­гает не всегда, в чем вы убедитесь в следующем разделе.

Наследование и завершающие действия

Если при создании нового класса используется композиция и наследование, обычно вам не приходится беспокоиться о проведении завершающих действий — подобъекты уничтожаются сборщиком мусора. Но если вам необходимо провес­ти завершающие действия, создайте в своем классе метод dispose() (в данном разделе я решил использовать такое имя; возможно, вы придумаете более удач­ное название). Переопределяя метод dispose() в производном классе, важно помнить о вызове версии этого метода из базового класса, поскольку иначе не будут выполнены завершающие действия базового класса. Следующий пример доказывает справедливость этого утверждения:

//: polymorphism/Frog.java

// Наследование и завершающие действия.

package polymorphism;

import static net.mindview util.Print.*;

class Characteristic { private String s;

CharacteristicCString s) { this s = s;

print("Создаем Characteristic " + s);

}

protected void disposeO {

print("Завершаем Characteristic " + s);

class Description {

private String s;

Description(String s) { this s = s.

print("Создаем Description " + s).

}

protected void disposeO {

print("Завершаем Description " + s);

}

}

// живое существо class LivingCreature {

private Characteristic p =

new Characteristic"живое существо");

private Description t =

new Description("обычное живое существо");

LivingCreatureO {

printCLivingCreatureO");

}

protected void disposeO {

print("dispose() в LivingCreature "), t.disposeO; p.disposeO;

// животное

class Animal extends LivingCreature { private Characteristic p =

new Characteristic("имеет сердце"); private Description t =

new Descripti0n(">khb0th0e. не растение"); Animal О { print("Animal()"); } protected void disposeO {

print("disposeO в Animal "); t.disposeO; p.disposeO; super, di sposeO;

// земноводное

class Amphibian extends Animal { private Characteristic p =

new Characteristic"может жить в воде"); private Description t =

new Descriptions в воде, и на земле"); Amphibian О {                  продолжение &

// лягушка

public class Frog extends Amphibian {

private Characteristic p = new CharacteristicC'KBaKaei"). private Description t = new Description"ест жуков"), public FrogO { printC'FrogO"), } protected void disposeO {

print С завершение Frog"), t disposeO; p disposeO; super.disposeO;

}

public static void main(String[] args) { Frog frog = new FrogO; print("Пока!"); frog. disposeO;

}

} /* Output:

Создаем Characteristic живое существо Создаем Description обычное живое существо LivingCreatureO

Создаем Characteristic имеет сердце Создаем Description животное, не растение Animal О

Создаем Characteristic может жить в воде Создаем Description и в воде, и на земле Amphibian О

Создаем Characteristic квакает Создаем Description ест жуков FrogO Пока!

завершение Frog

Завершаем Description ест жуков Завершаем Characteristic квакает disposeO в Amphibian

Завершаем Description и в воде, и на земле Завершаем Characteristic может жить в воде disposeO в Animal

Завершаем Description животное, не растение Завершаем Characteristic имеет сердце disposeO в LivingCreature Завершаем Description обычное живое существо Завершаем Characteristic живое существо *///:-

print ("Amphibian (Г);

}

protected void disposeO {

print ("disposeO в Amphibian "); t.disposeO; p.disposeO; super.disposeO,

Каждый класс в иерархии содержит объекты классов Characteristic и De­scription, которые также необходимо «завершать». Очередность завершения должна быть обратной порядку инициализации в том случае, если объекты

зависят друг от друга. Для полей это означает порядок, обратный последова­тельности объявления полей в классе (инициализация соответствует порядку объявления). В базовых классах сначала следует выполнять финализацию для производного класса, а затем — для базового класса. Это объясняется тем, что завершающий метод производного класса может вызывать некоторые методы базового класса, для которых необходимы действительные компоненты базово­го класса. Из результатов работы программы видно, что все части объекта Frog будут финализованы в порядке, противоположном очередности их создания.