Статическое связывание php. Различия между ранним и поздним связыванием в Java

(Late Static Binding, LSB) является бурно темой обсуждений последние три года в кругах разработчиков PHP (и наконец мы его получили в PHP 5.3). Но зачем оно нужно? В данной статье, как раз и будет рассматриваться, как позднее статическое связывание может значительно упростить ваш код.

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

С тех пор как позднее статическое связывание было объявлено как грядущая фишка, прошло два года. И вот наконец LSB стало доступно для использования в PHP 5.3. Но это событие прошло незаметно для разработчиков использующих PHP, из заметок только страничка в мануале .

Если кратко, новая функциональность позднего статического связывания, позволяет объектам все также наследовать методы у родительских классов, но помимо этого дает возможность унаследованным методам иметь доступ к статическим константам, методам и свойствам класса потомка, а не только родительского класса. Давайте рассмотрим пример:

Class Beer { const NAME = "Beer!"; public function getName() { return self::NAME; } } class Ale extends Beer { const NAME = "Ale!"; } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getName() ."\n"; echo "Ale is: " . $aleDrink->getName() ."\n";

Этот код выдаст такой результат:

Beer is: Beer! Ale is: Beer!

Класс Ale унаследовал метод getName() , но при этом self все еще указывает на класс в котором оно используется (в данном случае это класс Beer ). Это осталось и в PHP 5.3, но добавилось слово static . И снова рассмотрим пример:

Class Beer { const NAME = "Beer!"; public function getName() { return self::NAME; } public function getStaticName() { return static::NAME; } } class Ale extends Beer { const NAME = "Ale!"; } $beerDrink = new Beer; $aleDrink = new Ale; echo "Beer is: " . $beerDrink->getName() ."\n"; echo "Ale is: " . $aleDrink->getName() ."\n"; echo "Beer is actually: " . $beerDrink->getStaticName() ."\n"; echo "Ale is actually: " . $aleDrink->getStaticName() ."\n";

Новое ключевое слово static указывает, что необходимо использовать константу унаследованного класса, вместо константы которая была определена в классе где объявлен метод getStaticName() . Слово static было добавлено, чтобы реализовать новый функционал, а для обратной совместимости self работает также как и в предыдущих версиях PHP.

Внутренне, основное отличие (и, собственно, причина почему связывание назвали поздним) между этими двумя способами доступа, в том, что PHP определят значение для self::NAME во время «компиляции» (когда симовлы PHP преобразуются в машинный код, который будет обрабатываться движком Zend), а для static::NAME значение будет определено в момент запуска (в тот момент, когда машинный код будет выполнятся в движке Zend).

Это еще один инструмент для PHP-разработчиков. Во второй части рассмотрим как его можно использовать во благо.

данных . Целью полиморфизма, применительно к объектно-ориентированному программированию, является использование одного имени для задания общих для класса действий.

В языке Java объектные переменные являются полиморфными (polymorphic). Например:
class King { public static void main(String args) { King king = new King() ; king = new AerysTargaryen() ; king = new RobertBaratheon() ; } } class RobertBaratheon extends King { } class AerysTargaryen extends King { }
Переменная типа King может ссылаться как на объект типа King, так и на объект любого подкласса King.
Возьмем следующий пример:

class King { public void speech() { System .out .println ("I"m the King of the Andals!" ) ; } public void speech(String quotation) { System .out .println ("Wise man said: " + quotation) ; } public void speech(Boolean speakLoudly) { if (speakLoudly) System .out .println ("I"M THE KING OF THE ANDALS!!!11" ) ; else System .out .println ("i"m... the king..." ) ; } } class AerysTargaryen extends King { @Override public void speech() { System .out .println ("Burn them all..." ) ; } @Override public void speech(String quotation) { System .out .println (quotation+ " ... And now burn them all!" ) ; } } class Kingdom { public static void main(String args) { King king = new AerysTargaryen() ; king.speech ("Homo homini lupus est" ) ; } }
Что происходит, когда вызывается метод, принадлежащий объекту king ?
1. Компилятор проверяет объявленный тип объекта и имя метода, нумерует все методы с именем speech в классе AerusTargarien и все открытые методы speech в суперклассах AerusTargarien . Теперь компилятору известны возможные кандидаты при вызове метода.
2. Компилятор определяет типы передаваемых в метод аргументов. Если найден единственный метод, сигнатура которого совпадает с аргументами, происходит вызов. Этот процесс king.speech("Homo homini lupus est") компилятор выберет метод speech(String quotation) , а не speech() .
Если компилятор находит несколько методов с подходящими параметрами (или ни одного), выдается сообщение об ошибке.



Теперь компилятор знает имя и типы параметров метода,подлежащего вызову.
3. В случае, если вызываемый метод является private , static , final или конструктором, используется статическое связывание (early binding ). В остальных случаях метод, подлежащий вызову, определяется по фактическому типу объекта, через который происходит вызов. Т.е. во время выполнения программы используется динамическое связывание (late binding) .

4. Виртуальная машина заранее создает таблицу методов для каждого класса, в которой перечисляются сигнатуры всех методов и фактические методы, подлежащие вызову.
Таблица методов для класса King выглядит так:
  • speech() - King . speech()
  • speech(String quotation) - King . speech(String quotation )
  • King . speech(Boolean speakLoudly )
А для класса AerysTargaryen - так:
  • speech() - AerysTargaryen . speech()
  • speech(String quotation) - AerysTargaryen . speech(String quotation )
  • speech(Boolean speakLoudly) - King . speech(Boolean speakLoudly )
Методы, унаследованные от Object, в данном примере игнорируются.
При вызове king. speech() :
  1. Определяется фактический тип переменной king . В данном случае это AerysTargaryen .
  2. Виртуальная машина определяет класс, к которому принадлежит метод speech()
  3. Происходит вызов метода.
Связывание всех методов в Java осуществляется полиморфно, через позднее связывание. Динамическое связывание обладает одной важной особенностью: оно позволяет модифицировать программы без перекомпиляции их кодов. Это делает программы динамически расширяемыми (extensible ).
А что произойдет, если вызвать в конструкторе динамически связываемый метод конструируемого объекта? Например:
class King { King() { System .out .println ("Call King constructor" ) ; speech() ; //polymorphic method overriden in AerysTargaryen } public void speech() { System .out .println ("I"m the King of the Andals!" ) ; } } class AerysTargaryen extends King { private String victimName; AerysTargaryen() { System .out .println ("Call Aerys Targaryen constructor" ) ; victimName = "Lyanna Stark" ; speech() ; } @Override public void speech() { System .out .println ("Burn " + victimName + "!" ) ; } } class Kingdom { public static void main(String args) { King king = new AerysTargaryen() ; } } Результат:

Call King constructor Burn null! Call Aerys Targaryen constructor Burn Lyanna Stark !
Конструктор базового класса всегда вызывается в процессе конструирования производного класса. Вызов автоматически проходит вверх по цепочке наследования, так что в конечном итоге вызываются конструкторы всех базовых классов по всей цепочке наследования.
Это значит, что при вызове конструктора new AerysTargaryen() будут вызваны:
  1. new Object()
  2. new King()
  3. new AerysTargaryen()
По определению, задача конструктора — дать объекту жизнь. Внутри любого конструктора объект может быть сформирован лишь частично — известно только то, что объекты базового класса были проинициализированы. Если конструктор является лишь очередным шагом на пути построения объекта класса, производного от класса данного конструктора, «производные» части еще не были инициализированы на момент вызова текущего конструктора.

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

Результат работы программы обусловлен выполнение алгоритма иницализации объекта:

  1. Память, выделенная под новый объект, заполняется двоичными нулями.
  2. Конструкторы базовых классов вызываются в описанном ранее порядке. В этот момент вызывается переопределенный метод speech() (да, перед вызовом конструктора класса AerysTargaryen ), где обнаруживается, что переменная victimName равна null из-за первого этапа.
  3. Вызываются инициализаторы членов класса в порядке их определения.
  4. Исполняется тело конструктора производного класса.
В частности из-за таких поведенческих моментов стоит придерживаться следующего правила написания конструкторов:
- выполняйте в конструкторе лишь самые необходимые и простые действия по инициализации объекта
- по возможности избегайте вызова методов, не определенных как private или final (что в данном контексте одно и то же).
Использованы материалы:
  1. Eckel B. - Thinking in Java , 4th Edition - Chapter 8
  2. Cay S. Horstmann, Gary Cornell - Core Java 1 - Chapter 5
  3. Wikipedia

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

Существует два типа связывания методов в языке Java: ранее связывание (его ещё называют статическим) и позднее (соответственно, динамическое) связывание . Вызов метода в Java означает, что этот метод привязывается к конкретному коду или в момент компиляции, или во время выполнения, при запуске программы и создании объектов. Можно понять из названия, статическое связывание носит более статический характер, так как происходит во время компиляции, то есть код «знает», какой метод вызывать после компиляции исходного кода на Java в файлы классов. А поскольку это относится к ранней стадии жизненного цикла программы, то называется также ранним связыванием (early binding). С другой стороны, динамическое связывание происходит во время выполнения, после запуска программы виртуальной машиной Java. В этом случае то, какой метод вызвать, определяется конкретным объектом, так что в момент компиляции информация недоступна, ведь объекты создаются во время выполнения. А поскольку это происходит на поздней стадии жизненного цикла программы, то называется в языке Java поздним связыванием (late binding). Давайте рассмотрим еще несколько отличий, чтобы лучше разобраться с этим, а, кроме того, мочь ответить на этот очень популярный вопрос, который задают на собеседованиях по Java.

Раннее и позднее связывание в Java

Существует множество различий статического и динамического связывания в языке Java, но важнейшее – то, как их использует JVM. Задумывались ли вы когда-нибудь, каким образом JVM решает, какой метод вызвать, если в области видимости содержится более одного метода с одним именем? Если вы когда-либо использовали перегрузку или переопределение методов, то знаете, что в Java может быть несколько методов с одним именем. В случае с Java виртуальная машина JVM использует как статическое, так и динамическое связывание для выбора нужного метода.

Пример статического и динамического связывания в Java

В этой программе вы увидите, что привязка виртуальных методов не происходит во время компиляции при помощи статического связывания, поскольку в этом случае вызывался бы метод из суперкласса, как происходит со статическими методами, которые связываются рано. Если будет вызван метод из подкласса, то для связывания функции использовался конкретный объект во время выполнения, а, следовательно, для связывания виртуальных функций используется динамическое связывание. public class Main { public static void main (String args) { // Пример статического и динамического связывания в Java Insurance current = new CarInsurance () ; // Динамическое связывание на основе объекта int premium = current. premium () ; // Статическое связывание на основе класса String category = current. category () ; System. out. println ("premium: " + premium) ; System. out. println ("category: " + category) ; } } class Insurance { public static final int LOW = 100 ; public int premium () { return LOW; } public static String category () { return "Insurance" ; } } class CarInsurance extends Insurance { public static final int HIGH = 200 ; public int premium () { return HIGH; } public static String category () { return "Car Insurance" ; } } Результаты выполнения: premium : 200 category : Insurance Как вы видите, вызов метода premium() привел к выполнению метода из подкласса, в то время как вызов метода category() привел к выполнению метода суперкласса. Это происходит из-за того, что premium() – виртуальный метод, который разрешается при помощи позднего связывания, в то время как category() – статический метод, который разрешается при помощи статического связывания во время компиляции по имени класса.
Интересно читать о Java? Вступайте в группу !

Различия между ранним и поздним связыванием в языке Java

Теперь, когда вы разобрались и понимаете, как в языке Java связываются вызовы методов и как функционирует статическое и динамическое связывание, давайте еще раз перечислим ключевые различия между ранним и поздним связыванием в языке Java:
  1. Статическое связывание происходит во время компиляции, а динамическое – во время выполнения.

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

  3. Статическое связывание используется в языке Java для разрешения перегруженных методов, в то время как динамическое связывание используется в языке Java для разрешения переопределенных методов.

  4. Аналогично, приватные, статические и терминальные методы разрешаются при помощи статического связывания, поскольку их нельзя переопределять, а все виртуальные методы разрешаются при помощи динамического связывания.

  5. В случае статического связывания используются не конкретные объекты, а информация о типе, то есть для обнаружения нужного метода используется тип ссылочной переменной. С другой стороны, при динамическом связывании для нахождения нужного метода в Java используется конкретный объект.
Вот неплохое упражнение, основанное на понятиях статического и динамического связывания в языке Java. Сможете ли вы ответить на вопрос: "Что будет выведено при выполнении следующей программы?" Что выведет эта программа? Collection , Set или HashSet ? Вот и все, что мы хотели рассказать вам о различиях между ранним (статическим) и поздним (динамическим) связыванием в языке Java. Это один из лучших вопросов для телефонного собеседования по языку Java, поскольку оно предоставляет немало возможностей проверки глубины знаний кандидата. Всегда помните, что приватные , статические и final-методы связываются при помощи статического связывания , а виртуальные – динамического . Аналогично, лучший пример статического связывания – перегрузка методов, а переопределение – динамического.

Данный параграф , несмотря на краткость, является очень важным – практически все профессиональное программирование в Java основано на использовании полиморфизма. В то же время эта тема является одной из наиболее сложных для понимания учащимися. Поэтому рекомендуется внимательно перечитать этот параграф несколько раз.

Методы классов помечаются модификатором static не случайно – для них при компиляции программного кода действует статическое связывание . Это значит, что в контексте какого класса указано имя метода в исходном коде, на метод того класса в скомпилированном коде и ставится ссылка . То есть осуществляется связывание имени метода в месте вызова с исполняемым кодом этого метода. Иногда статическое связывание называют ранним связыванием , так как оно происходит на этапе компиляции программы. Статическое связывание в Java используется еще в одном случае – когда класс объявлен с модификатором final ("финальный", "окончательный").

Методы объектов в Java являются динамическими, то есть для них действует динамическое связывание . Оно происходит на этапе выполнения программы непосредственно во время вызова метода, причем на этапе написания данного метода заранее неизвестно, из какого класса будет проведен вызов. Это определяется типом объекта, для которого работает данный код - какому классу принадлежит объект , из того класса вызывается метод. Такое связывание происходит гораздо позже того, как был скомпилирован код метода. Поэтому такой тип связывания часто называют поздним связыванием .

Программный код, основанный на вызове динамических методов , обладает свойством полиморфизма – один и тот же код работает по -разному в зависимости от того, объект какого типа его вызывает, но делает одни и те же вещи на уровне абстракции, относящейся к исходному коду метода.

Для пояснения этих не очень понятных при первом чтении слов рассмотрим пример из предыдущего параграфа – работу метода moveTo. Неопытным программистам кажется, что этот метод следует переопределять в каждом классе-наследнике. Это действительно можно сделать, и все будет правильно работать. Но такой код будет крайне избыточным – ведь реализация метода будет во всех классах-наследниках Figure совершенно одинаковой:

public void moveTo(int x, int y){ hide(); this.x=x; this.y=y; show(); };

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

Еще часто вызывает недоумение, зачем в абстрактном классе Figure писать реализацию данного метода. Ведь используемые в нем вызовы методов hide и show , на первый взгляд, должны быть вызовами абстрактных методов – то есть, кажется, вообще не могут работать!

Но методы hide и show являются динамическими, а это, как мы уже знаем, означает, что связывание имени метода и его исполняемого кода производится на этапе выполнения программы. Поэтому то, что данные методы указаны в контексте класса Figure , вовсе не означает, что они будут вызываться из класса Figure ! Более того, можно гарантировать, что методы hide и show никогда не будут вызываться из этого класса. Пусть у нас имеются переменные dot1 типа Dot и circle1 типа Circle , и им назначены ссылки на объекты соответствующих типов. Рассмотрим, как поведут себя вызовы dot1.moveTo(x1,y1) и circle1.moveTo(x2,y2) .

При вызове dot1.moveTo(x1,y1) происходит вызов из класса Figure метода moveTo . Действительно, этот метод в классе Dot не переопределен, а значит, он наследуется из Figure . В методе moveTo первый оператор – вызов динамического метода hide . Реализация этого метода берется из того класса, экземпляром которого является объект dot1 , вызывающий данный метод. То есть из класса Dot . Таким образом, скрывается точка. Затем идет изменение координат объекта, после чего вызывается динамический метод show . Реализация этого метода берется из того класса, экземпляром которого является объект dot1 , вызывающий данный метод. То есть из класса Dot . Таким образом, на новом месте показывается точка.

Для вызова circle1.moveTo(x2,y2) все абсолютно аналогично – динамические методы hide и show вызываются из того класса, экземпляром которого является объект circle1 , то есть из класса Circle . Таким образом, скрывается на старом месте и показывается на новом именно окружность .

То есть если объект является точкой, перемещается точка. А если объект является окружностью - перемещается окружность . Более того, если когда-нибудь кто-нибудь напишет, например, класс Ellipse , являющийся наследником Circle , и создаст объект Ellipse ellipse=new Ellipse(…) , то вызов ellipse.moveTo(…) приведет к перемещению на новое место эллипса. И происходить это будет в соответствии с тем, каким образом в классе Ellipse реализуют методы hide и show . Заметим, что работать будет давным-давно скомпилированный полиморфный код класса Figure . Полиморфизм обеспечивается тем, что ссылки на эти методы в код метода moveTo в момент компиляции не ставятся – они настраиваются на методы с такими именами из класса вызывающего объекта непосредственно в момент вызова метода moveTo .

В объектно-ориентированных языках программирования различают две разновидности динамических методов – собственно динамические и виртуальные . По принципу работы они совершенно аналогичны и отличаются только особенностями реализации. Вызов виртуальных методов быстрее. Вызов динамических медленнее, но служебная таблица динамических методов ( DMT – Dynamic Methods Table ) занимает чуть меньше памяти, чем таблица виртуальных методов ( VMT – Virtual Methods Table ).

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

6.8. Базовый класс Object

Класс Object является базовым для всех классов Java . Поэтому все его поля и методы наследуются и содержатся во всех классах. В классе Object содержатся следующие методы:

  • public Boolean equals(Object obj) – возвращает true в случае, когда равны значения объекта, из которого вызывается метод, и объекта, передаваемого через ссылку obj в списке параметров. Если объекты не равны, возвращается false . В классе Object равенство рассматривается как равенство ссылок и эквивалентно оператору сравнения "==" . Но в потомках этот метод может быть переопределен, и может сравнивать объекты по их содержимому. Например, так происходит для объектов оболочечных числовых классов. Это легко проверить с помощью такого кода:

    Double d1=1.0,d2=1.0; System.out.println("d1==d2 ="+(d1==d2)); System.out.println("d1.equals(d2) ="+(d1.equals(d2)));

    Первая строка вывода даст d1==d2 =false , а вторая d1. equals (d2) =true

  • public int hashCode() – выдает хэш-код объекта. Хэш-кодом называется условно уникальный числовой идентификатор, сопоставляемый какому-либо элементу. Из соображений безопасности выдавать адрес объекта прикладной программе нельзя. Поэтому в Java хэш-код заменяет адрес объекта в тех случаях, когда для каких-либо целей надо хранить таблицы адресов объектов.
  • protected Object clone() throws CloneNotSupportedException – метод занимается копированием объекта и возвращает ссылку на созданный клон (дубликат) объекта. В наследниках класса Object его обязательно надо переопределить, а также указать, что класс реализует интерфейс Clonable . Попытка вызова метода из объекта, не поддерживающего клонирования , вызывает возбуждение исключительной ситуации CloneNotSupportedException ("Клонирование не поддерживается"). Про интерфейсы и исключительные ситуации будет рассказано в дальнейшем.

    Различают два вида клонирования : мелкое (shallow ), когда в клон один к одному копируются значения полей оригинального объекта, и глубокое (deep ), при котором для полей ссылочного типа создаются новые объекты, клонирующие объекты, на которые ссылаются поля оригинала. При мелком клонировании и оригинал, и клон будут ссылаться на одни и те же объекты. Если объект имеет поля только примитивных типов , различия между мелким и глубоким клонированием нет. Реализацией клонирования занимается программист, разрабатывающий класс, автоматического механизма клонирования нет. И именно на этапе разработки класса следует решить, какой вариант клонирования выбирать. В подавляющем большинстве случаев требуется глубокое клонирование .

  • public final Class getClass() – возвращает ссылку на метаобъект типа класс. С его помощью можно получать информацию о классе, к которому принадлежит объект, и вызывать его методы класса и поля класса .
  • protected void finalize() throws Throwable – вызывается перед уничтожением объекта. Должен быть переопределен в тех потомках Object , в которых требуется совершать какие-либо вспомогательные действия перед уничтожением объекта (закрыть файл, вывести сообщение, отрисовать что-либо на экране, и т.п.). Подробнее об этом методе говорится в соответствующем параграфе.
  • public String toString() – возвращает строковое представление объекта (настолько адекватно, насколько это возможно). В классе Object этот метод реализует выдачу в строку полного имени объекта (с именем пакета), после которого следует символ "@" , а затем в шестнадцатеричном виде хэш-код объекта. В большинстве стандартных классов этот метод переопределен. Для числовых классов возвращается строковое представление числа, для строковых – содержимое строки, для символьного – сам символ (а не строковое представление его кода!). Например, следующий фрагмент кода

    Object obj=new Object(); System.out.println(" obj.toString() дает "+obj.toString()); Double d=new Double(1.0); System.out.println(" d.toString()дает "+d.toString()); Character c="A"; System.out.println("c.toString() дает "+c.toString());

    обеспечит вывод

    obj.toString() дает java.lang.Object@fa9cf d.toString()дает 1.0 c.toString()дает A

Также имеются методы notify() , notifyAll() , и несколько перегруженных вариантов метода wait , предназначенные для работы с потоками (threads). О них говорится в разделе, посвященном потокам.

6.9. Конструкторы. Зарезервированные слова super и this. Блоки инициализации

Как уже говорилось, объекты в Java создаются с помощью зарезервированного слова new , после которого идет конструктор – специальная подпрограмма , занимающаяся созданием объекта и инициализацией полей создаваемого объекта. Для него не указывается тип возвращаемого значения, и он не является ни методом объекта (вызывается через имя класса когда объекта еще нет), ни методом класса (в конструкторе доступен объект и его поля через ссылку this ). На самом деле конструктор в сочетании с оператором new возвращает ссылку на создаваемый объект и может считаться особым видом методов, соединяющим в себе черты методов класса и методов объекта.

Если в объекте при создании не нужна никакая дополнительная инициализация , можно использовать конструктор , который по умолчанию присутствует для каждого класса. Это имя класса , после которого ставятся пустые круглые скобки – без списка параметров. Такой конструктор при разработке класса задавать не надо, он присутствует автоматически.

Если требуется инициализация , обычно применяют конструкторы со списком параметров. Примеры таких конструкторов рассматривались нами для классов Dot и Circle . Классы Dot и Circle были унаследованы от абстрактных классов , в которых не было конструкторов. Если же идет наследование от неабстрактного класса, то есть такого, в котором уже имеется конструктор (пусть даже и конструктор по умолчанию), возникает некоторая специфика. Первым оператором в конструкторе должен быть вызов конструктора из суперкласса . Но его делают не через имя этого класса, а с помощью зарезервированного слова super (от " superclass "), после которого идет необходимый для прародительского конструктора список параметров. Этот конструктор инициализирует поля данных, которые наследуются от суперкласса (в том числе и от всех более ранних прародителей). Например, напишем класс FilledCircle - наследник от Circle , экземпляр которого будет отрисовываться как цветной круг.

package java_gui_example; import java.awt.*; public class FilledCircle extends Circle{ /** Creates a new instance of FilledCircle */ public FilledCircle(Graphics g,Color bgColor, int r,Color color) { super(g,bgColor,r); this.color=color; } public void show(){ Color oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size,size); graphics.setColor(oldC); graphics.setPaintMode(); } public void hide(){ Color oldC=graphics.getColor(); graphics.setColor(color); graphics.setXORMode(bgColor); graphics.fillOval(x,y,size,size); graphics.setColor(oldC); graphics.setPaintMode(); }}

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

В данном классе мы применяем более совершенный способ отрисовки и "скрывания" фигур по сравнению с предыдущими классами. Он основан на использовании режима рисования XOR ("исключающее или"). Установка этого режима производится методом setXORMode . При этом повторный вывод фигуры на то же место приводит к восстановлению первоначального изображения в области вывода. Переход в обычный режим рисования осуществляется методом setPaintMode .

В конструкторах очень часто используют


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

Что происходит в коде ниже:

Статическое связывание или динамическое связывание?
Что это за полиморфизм?

Class Animal { void eat() { System.out.println("Animal is eating"); } } class Dog extends Animal { void eat() { System.out.println("Dog is eating"); } } public static void main(String args) { Animal a=new Animal(); a.eat(); }


2018-05-20 10:33


2018-05-20 10:46

Проверь это класс сотрудника имеет абстрактный earning() функции, и каждый класс имеет toString() реализация

Employee employees = new Employee; // initialize array with Employees employees = new SalariedEmployee(); employees = new HourlyEmployee(); employees = new CommissionEmployee(); employees = new BasePlusCommissionEmployee(); for (Employee currentEmployee: employees){ System.out.println(currentEmployee); // invokes toString System.out.printf("earned $%,.2f%n", currentEmployee.earnings()); }

Все вызовы метода toString а также earnings разрешаются на execution time , на основе type of the object к которому относится текущий сотрудник,

Этот процесс известен как dynamic binding или late binding