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

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

Жанры

Философия 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:

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

Один на миллион. Трилогия

Земляной Андрей Борисович
Один на миллион
Фантастика:
боевая фантастика
8.95
рейтинг книги
Один на миллион. Трилогия

Защитник

Кораблев Родион
11. Другая сторона
Фантастика:
боевая фантастика
попаданцы
рпг
5.00
рейтинг книги
Защитник

Как я строил магическую империю 10

Зубов Константин
10. Как я строил магическую империю
Фантастика:
попаданцы
аниме
фантастика: прочее
5.00
рейтинг книги
Как я строил магическую империю 10

Рубежник

Билик Дмитрий Александрович
1. Бедовый
Фантастика:
юмористическая фантастика
городское фэнтези
мистика
5.00
рейтинг книги
Рубежник

Наследие Маозари 6

Панежин Евгений
6. Наследие Маозари
Фантастика:
попаданцы
постапокалипсис
рпг
фэнтези
эпическая фантастика
5.00
рейтинг книги
Наследие Маозари 6

Сапер. Том II

Вязовский Алексей
2. Сапер
Фантастика:
альтернативная история
4.25
рейтинг книги
Сапер. Том II

Выбор

Хайд Адель
4. История Ирэн
Фантастика:
попаданцы
альтернативная история
фэнтези
5.00
рейтинг книги
Выбор

Двенадцатая реинкарнация. Трилогия

Богдашов Сергей Александрович
Фантастика:
боевая фантастика
5.60
рейтинг книги
Двенадцатая реинкарнация. Трилогия

Кодекс Императора

Сапфир Олег
1. Кодекс Императора
Фантастика:
аниме
фэнтези
попаданцы
4.25
рейтинг книги
Кодекс Императора

Тихие ночи

Владимиров Денис
2. Глэрд
Фантастика:
фэнтези
боевая фантастика
попаданцы
5.00
рейтинг книги
Тихие ночи

Отморозок 5

Поповский Андрей Владимирович
5. Отморозок
Фантастика:
попаданцы
альтернативная история
5.00
рейтинг книги
Отморозок 5

Я до сих пор князь. Книга XXII

Дрейк Сириус
22. Дорогой барон!
Фантастика:
юмористическое фэнтези
аниме
попаданцы
5.00
рейтинг книги
Я до сих пор князь. Книга XXII

Крепость над бездной

Лисина Александра
4. Гибрид
Фантастика:
боевая фантастика
попаданцы
аниме
фэнтези
5.00
рейтинг книги
Крепость над бездной

Цвет

Васильев Сергей Викторович
Фантастика:
научная фантастика
5.00
рейтинг книги
Цвет