Чтение онлайн

на главную - закладки

Жанры

Философия Java3

Эккель Брюс

Шрифт:

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

• Память, выделенная под новый объект, заполняется двоичными нулями.

• Конструкторы базовых классов вызываются в описанном ранее порядке. В этот момент вызывается переопределенный метод draw (да, перед вызовом конструктора класса RoundGlyph), где обнаруживается, что переменная radius равна нулю из-за первого этапа.

• Вызываются инициализаторы членов класса в порядке их определения.

• Исполняется тело конструктора

производного класса.

У происходящего есть и положительная сторона — по крайней мере, данные инициализируются нулями (или тем, что понимается под нулевым значением для определенного типа данных), а не случайным «мусором» в памяти. Это относится и к ссылкам на объекты, внедренные в класс с помощью композиции. Они принимают особое значение null. Если вы забудете инициализировать такую ссылку, то получите исключение во время выполнения программы. Остальные данные заполняются нулями, а это обычно легко заметить по выходным данным программы.

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

При написании конструктора руководствуйтесь следующим правилом: не пытайтесь сделать больше для того, чтобы привести объект в нужное состояние, и по возможности избегайте вызова каких-либо методов. Единственные методы, которые можно вызывать в конструкторе без опаски — неизменные (final) методы базового класса. (Сказанное относится и к закрытым (private) методам, поскольку они автоматически являются неизменными.) Такие методы невозможно переопределить, и поэтому они застрахованы от «сюрпризов».

Ковариантность возвращаемых типов

В Java SE5 появилась концепция ковариантности возвращаемых типов; этот термин означает, что переопределенный метод производного класса может вернуть тип, производный от типа, возвращаемого методом базового класса:

//: polymorph!sm/CovanantReturn java

class Grain {

public String toStringO { return "Grain"; }

}

class Wheat extends Grain {

public String toStringO { return "Wheat"; }

class Mill {

Grain process О { return new GrainO; }

}

class WheatMill extends Mill {

Wheat process О { return new WheatO; }

}

public class CovariantReturn {

public static void main(String[] args) { Mill m = new Mi 11; Grain g = m.processO; System out println(g); m = new WheatMi 110; g = m process О, System out.println(g);

}

} /* Output Grain Wheat */// ~

Главное отличие Java SE5 от предыдущих версий Java заключается в том, что старые версии заставляли переопределение process возвращать Grain вместо Wheat, хотя тип Wheat, производный от Grain, является допустимым возвращаемым типом. Ковариантность возвращаемых типов позволяет вернуть более специализированный тип Wheat.

Разработка с наследованием

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

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

выбирать тип (а следовательно, и поведение), тогда как наследование требует, чтобы точный тип был известен уже во время компиляции. Следующий пример демонстрирует это:

// polymorphi sm/Transmogrify.java // Динамическое изменение поведения объекта // с помощью композиции (шаблон проектирования «Состояние») • import static net.mindview.util.Print.*;

class Actor {

public void act О {}

}

class HappyActor extends Actor {

public void actO { pri nt ("HappyActor"), }

class SadActor extends Actor {

public void act { printCSadActor"). }

}

class Stage {

private Actor actor = new HappyActor; public void changeO { actor = new SadActorO. } public void performPlayO { actor.act, }

}

public class Transmogrify {

public static void main(String[] args) { Stage stage = new StageO; stage. performPlayO; stage. changeO; stage. performPlayO;

}

} /* Output-

HappyActor

SadActor

*///:-

Объект Stage содержит ссылку на объект Actor, которая инициализируется объектом HappyActor. Это значит, что метод performPlayO имеет определенное поведение. Но так как ссылку на объект можно заново присоединить к другому объекту во время выполнения программы, ссылке actor назначается объект SadActor, и после этого поведение метода performPlayO изменяется. Таким образом значительно улучшается динамика поведения на стадии выполнения программы. С другой стороны, переключиться на другой способ наследования во время работы программы невозможно; иерархия наследования раз и навсегда определяется в процессе компиляции программы.

Нисходящее преобразование и динамическое определение типов

Так как при проведении восходящего преобразования (передвижение вверх по иерархии наследования) теряется информация, характерная для определенного типа, возникает естественное желание восстановить ее с помощью нисходящего преобразования. Впрочем, мы знаем, что восходящее преобразование абсолютно безопасно; базовый класс не может иметь «больший» интерфейс, чем производный класс, и поэтому любое сообщение, посланное базовому классу, гарантированно дойдет до получателя. Но при использовании нисходящего преобразования вы не знаете достоверно, что фигура (например) в действительности является окружностью. С такой же вероятностью она может оказаться треугольником, прямоугольником или другим типом.

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

В некоторых языках (подобных С++) для проведения безопасного нисходящего преобразования типов необходимо провести специальную операцию, но в Java каждое преобразование контролируется! Поэтому, хотя внешне все выглядит как обычное приведение типов в круглых скобках, во время выполнения программы это преобразование проходит проверку на фактическое соответствие типу. Если типы не совпадают, происходит исключение ClassCastException. Процесс проверки типов во время выполнения программы называется динамическим определением типов (run-time type identification, RTTI). Следующий пример демонстрирует действие RTTI:

Поделиться:
Популярные книги

Бастард Императора. Том 3

Орлов Андрей Юрьевич
3. Бастард Императора
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Бастард Императора. Том 3

Бастард Императора. Том 11

Орлов Андрей Юрьевич
11. Бастард Императора
Фантастика:
городское фэнтези
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Бастард Императора. Том 11

Возвышение Меркурия. Книга 16

Кронос Александр
16. Меркурий
Фантастика:
попаданцы
аниме
5.00
рейтинг книги
Возвышение Меркурия. Книга 16

Эволюционер из трущоб. Том 3

Панарин Антон
3. Эволюционер из трущоб
Фантастика:
попаданцы
аниме
фэнтези
фантастика: прочее
6.00
рейтинг книги
Эволюционер из трущоб. Том 3

Кодекс Охотника. Книга XII

Винокуров Юрий
12. Кодекс Охотника
Фантастика:
боевая фантастика
городское фэнтези
аниме
7.50
рейтинг книги
Кодекс Охотника. Книга XII

Адвокат Империи 2

Карелин Сергей Витальевич
2. Адвокат империи
Фантастика:
городское фэнтези
попаданцы
аниме
фэнтези
фантастика: прочее
5.00
рейтинг книги
Адвокат Империи 2

Идеальный мир для Лекаря 10

Сапфир Олег
10. Лекарь
Фантастика:
юмористическое фэнтези
аниме
5.00
рейтинг книги
Идеальный мир для Лекаря 10

Котенок. Книга 3

Федин Андрей Анатольевич
3. Котенок
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Котенок. Книга 3

Империя Хоста 3

Дмитрий
3. Империя Хоста
Фантастика:
боевая фантастика
попаданцы
5.50
рейтинг книги
Империя Хоста 3

Кодекс Крови. Книга I

Борзых М.
1. РОС: Кодекс Крови
Фантастика:
фэнтези
попаданцы
аниме
5.00
рейтинг книги
Кодекс Крови. Книга I

Вадбольский

Никитин Юрий Александрович
1. Вадбольский
Фантастика:
попаданцы
5.00
рейтинг книги
Вадбольский

Воспитание бабочек

Карризи Донато
Детективы:
триллеры
прочие детективы
5.00
рейтинг книги
Воспитание бабочек

Никчёмная Наследница

Кат Зозо
Любовные романы:
любовно-фантастические романы
5.00
рейтинг книги
Никчёмная Наследница

Имперский Курьер. Том 3

Бо Вова
3. Запечатанный мир
Фантастика:
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Имперский Курьер. Том 3