Брюс Эккель - Философия Java3
- Название:Философия Java3
- Автор:
- Жанр:
- Издательство:неизвестно
- Год:неизвестен
- ISBN:нет данных
- Рейтинг:
- Избранное:Добавить в избранное
-
Отзывы:
-
Ваша оценка:
Брюс Эккель - Философия Java3 краткое содержание
Философия Java3 - читать онлайн бесплатно полную версию (весь текст целиком)
Интервал:
Закладка:
Завершаем Description животное, не растение Завершаем Characteristic имеет сердце disposeO в LivingCreature Завершаем Description обычное живое существо Завершаем Characteristic живое существо *///:-
Каждый класс в иерархии содержит объекты классов Characteristic и Description, которые также необходимо «завершать». Очередность завершения должна быть обратной порядку инициализации в том случае, если объекты
print ("Amphibian (Г);
}
protected void disposeO {
print ("disposeO в Amphibian "); t.disposeO; p.disposeO; super.disposeO,
зависят друг от друга. Для полей это означает порядок, обратный последовательности объявления полей в классе (инициализация соответствует порядку объявления). В базовых классах сначала следует выполнять финализацию для производного класса, а затем — для базового класса. Это объясняется тем, что завершающий метод производного класса может вызывать некоторые методы базового класса, для которых необходимы действительные компоненты базового класса. Из результатов работы программы видно, что все части объекта Frogбудут финализованы в порядке, противоположном очередности их создания.
Также обратите внимание на то, что в описанном примере объект Frogявляется «владельцем» встроенных объектов. Он создает их, определяет продолжительность их существования (до тех пор, пока существует Frog)и знает, когда вызывать dispose()для встроенных объектов. Но если встроенный объект используется совместно с другими объектами, ситуация усложняется и вы уже не можете просто вызвать dispose().В таких случаях для отслеживания количества объектов, работающих со встроенным объектом, приходится использовать подсчет ссылок. Вот как это выглядит:
// polymorphism/ReferenceCounting.java
11 Уничтожение совместно используемых встроенных объектов
import static net mindview.util.Print.*;
class Shared {
private int refcount = 0; private static long counter = 0, private final long id = counter++, public SharedO {
print("Создаем " + this);
}
public void addRefO { refcount++; } protected void disposeO { if(--refcount == 0)
printODisposing " + this),
}
public String toStringO { return "Shared " + id; }
}
class Composing {
private Shared shared; private static long counter = 0. private final long id = counter++, public Composing(Shared shared) { print("Создаем " + this); this.shared = shared, this shared addRefO.
}
protected void disposeO {
printC'disposing " + this), shared disposeO,
}
public String toStringO { return "Composing " + id; }
}
public class ReferenceCounting {
public static void main(String[] args) {
Shared shared = new SharedO;
Composing[] composing = { new Composing(shared).
new Composing(shared), new Composing(shared), new Composing(shared), new Composing(shared) }; for(Composing с • composing) с disposeO.
}
} /* Output: Создаем Shared 0 Создаем Composing 0 Создаем Composing 1 Создаем Composing 2 Создаем Composing 3 Создаем Composing 4 уничтожаем Composing 0 уничтожаем Composing 1 уничтожаем Composing 2 уничтожаем Composing 3 уничтожаем Composing 4 уничтожаем Shared 0 *///:-
В переменной static long counter хранится количество созданных экземпляров Shared. Для счетчика выбран тип long вместо int для того, чтобы предотвратить переполнение (это всего лишь хороший стиль программирования; в рассматриваемых примерах переполнение вряд ли возможно). Поле id объявлено со спецификатором final, поскольку его значение остается постоянным на протяжении жизненного цикла объекта
Присоединяя к классу общий объект, необходимо вызвать addRef(), но метод dispose() будет следить за состоянием счетчика ссылок и сам решит, когда нужно выполнить завершающие действия. Подсчет ссылок требует дополнительных усилий со стороны программиста, но при совместном использовании объектов, требующих завершения, у вас нет особого выбора.
Поведение полиморфных методов при вызове из конструкторов
В иерархиях конструкторов возникает интересный вопрос. Что происходит, если вызвать в конструкторе динамически связываемый метод конструируемого объекта?
В обычных методах представить происходящее нетрудно — динамически связываемый вызов обрабатывается во время выполнения, так как объект не знает, принадлежит ли этот вызов классу, в котором определен метод, или классу, производному от этого класса. Казалось бы, то же самое должно происходить и в конструкторах.
Но ничего подобного. При вызове динамически связываемого метода в конструкторе используется переопределенное описание этого метода. Однако последствия такого вызова могут быть весьма неожиданными, и здесь могут крыться некоторые коварные ошибки.
По определению, задача конструктора — дать объекту жизнь (и это отнюдь не простая задача). Внутри любого конструктора объект может быть сформирован лишь частично — известно только то, что объекты базового класса были проини-циализированы. Если конструктор является лишь очередным шагом на пути построения объекта класса, производного от класса данного конструктора, «производные» части еще не были инициализированы на момент вызова текущего конструктора. Однако динамически связываемый вызов может перейти во «внешнюю» часть иерархии, то есть к производным классам. Если он вызовет метод производного класса в конструкторе, это может привести к манипуляциям с неинициализированными данными — а это наверняка приведет к катастрофе. Следующий пример поясняет суть проблемы:
// polymorphism/PolyConstructors java // Конструкторы и полиморфизм дают не тот // результат, который можно было бы ожидать import static net mindview util Print *.
class Glyph {
void drawO { print("Glyph drawO"), } GlyphO {
printCGlyphO перед вызовом drawO");
drawO.
print ("GlyphO после вызова drawO").
class RoundGlyph extends Glyph { private int radius = 1; RoundGlyph(int r) { radius = r.
print("RoundGlyph RoundGlyph(). radius = " + radius);
}
void drawO {
print ("RoundGlyph. drawO, radius = " + radius);
public class PolyConstructors {
public static void main(String[] args) { new RoundGlyph(5);
}
} /* Output-
GlyphO перед вызовом drawO RoundGlyph drawO, radius = 0 GlyphO после вызова drawO RoundGlyph RoundGlyphO, radius = 5 *///:-
Метод Glyph.draw() изначально предназначен для переопределения в производных классах, что и происходит в RoundGlyph. Но конструктор Glyph вызывает этот метод, и в результате это приводит к вызову метода RoundGlyph.draw(), что вроде бы и предполагалось. Однако из результатов работы программы видно — когда конструктор класса Glyph вызывает метод draw(), переменной radius еще не присвоено даже значение по умолчанию 1. Переменная равна 0. В итоге класс может не выполнить свою задачу, а вам придется долго всматриваться в код программы, чтобы определить причину неверного результата.
Порядок инициализации, описанный в предыдущем разделе, немного неполон, и именно здесь кроется ключ к этой загадке. На самом деле процесс инициализации проходит следующим образом:
• Память, выделенная под новый объект, заполняется двоичными нулями.
• Конструкторы базовых классов вызываются в описанном ранее порядке. В этот момент вызывается переопределенный метод draw() (да, перед вызовом конструктора класса RoundGlyph), где обнаруживается, что переменная radius равна нулю из-за первого этапа.
Читать дальшеИнтервал:
Закладка: