Вход/Регистрация
Философия Java3
вернуться

Эккель Брюс

Шрифт:

// 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 равна нулю из-за первого этапа.

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

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

У происходящего есть и положительная сторона — по крайней мере, данные инициализируются нулями (или тем, что понимается под нулевым значением для определенного типа данных), а не случайным «мусором» в памяти. Это относится и к ссылкам на объекты, внедренные в класс с помощью композиции. Они принимают особое значение 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.

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

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

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

  • Читать дальше
  • 1
  • ...
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • ...

Ебукер (ebooker) – онлайн-библиотека на русском языке. Книги доступны онлайн, без утомительной регистрации. Огромный выбор и удобный дизайн, позволяющий читать без проблем. Добавляйте сайт в закладки! Все произведения загружаются пользователями: если считаете, что ваши авторские права нарушены – используйте форму обратной связи.

Полезные ссылки

  • Моя полка

Контакты

  • chitat.ebooker@gmail.com

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