Php позднее статическое связывание. Полиморфизм в Java. Динамическое и статическое связывание. Инициализация объектов. Поведение полиморфных методов при вызове из конструкторов. Различия между ранним и поздним связыванием в языке Java

Mar 23, 2010 dec5e

В PHP 5.3 появилась такая интересная возможность, как позднее статическое связывание (late static binding). Дальше немного вольный перевод описания из официального мануала .

Начиная с PHP 5.3.0 в языке реализована возможность, называемая поздним статическим связыванием, которая может использоваться для ссылки на вызываемый класс в контексте статического наследования.

Эту возможность назвали «позднее статическое связывание». «Позднее связывание» говорит о том, что static:: будет разрешаться не относительно класса, где определен метод, но будет вычисляться во время выполнения. «Статическое связывание» означает, что оно может быть использовано в вызовах статических методов (но не ограничивается только ими).

Ограничения self::

Пример № 1: использование self::

Пример выведет:

Использование позднего статического связывания

Позднее статическое связывание пытается решить это ограничение, вводя ключевое слово, ссылающееся на класс, первоначально вызванный в процессе выполнения. То есть, ключевое слово, которое позволит сослаться на B из test() в предыдущем примере. Было решено не вводить новое слово, а использовать уже зарезервированное static .

Пример № 2: простое использование static::

Пример выведет:

Замечание: static:: не работает как $this для статических методов! $this-> следует правилам наследования, а static:: нет. Это различие уточняется ниже.

Пример № 3: использование static:: в нестатическом контексте

test(); ?>

Пример выведет:

Замечание: Позднее статическое связывание останавливает процесс разрешения вызова. Статические вызовы с использованием ключевых слов parent:: или self:: передают информацию о вызове дальше.

Пример № 4: Передача и непередача вызовов

Пример выведет

Крайние случаи

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

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

foo; ?>

Статическое связывание как оптимизация

В некоторых случаях главным требованием является эффективность, и даже указанные выше небольшие накладные расходы нежелательны. В этом случае можно заметить, что они не всегда обоснованы. Вызов x.f (a, b, c...) не нуждается в динамическом связывании в следующих случаях:

1 f нигде в системе не переопределяется (имеет только одно объявление);

2 x не является полиморфной, иначе говоря, не является целью никакого присоединения, источник которого имеет другой тип.

В любом из таких случаев, выявляемых хорошим компилятором, сгенерированный для x.f (a, b, c...) код может быть таким же, как и код, генерируемый компиляторами C, Pascal, Ada или Fortran для вызова f (x, a, b, c...) . Никакие накладные расходы не потребуются.

Компилятор ISE, являющийся частью окружения, описанного в последней лекции, сейчас выполняет оптимизацию (1), планируется добавить и (2) (анализ (2) является, фактически, следствием механизмов анализа типов, описанных в лекции о типизации).

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

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

Такая подстановка означает расширение тела программы текстом вызываемой процедуры в месте ее вызова. Например, для процедуры

set_a (x: SOME_TYPE) is

Сделать x новым значением атрибута a.

компилятор может сгенерировать для вызова s.set_a (some_value) такой же код, какой компилятор Pascal сгенерирует для присваивания s.a:= some_value (недопустимое для нас обозначение, поскольку оно нарушает скрытие информации). В этом случае вообще нет накладных расходов, поскольку сгенерированный код не содержит вызова процедуры.

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

Для каждого вызова, к которому применимо автоматическое статическое связывание (1), ОО-компилятор может определить, основываясь на анализе соотношения между временем и памятью, стоит ли применять автоматическую подстановку кода процедуры (3). Это одна из самых поразительных оптимизаций - одна из причин, по которой можно достичь эффективности произведенного вручную кода Си или Фортрана, а иногда, на больших системах и превзойти ее.

К улучшению эффективности, растущему с увеличением размера и сложности программ, автоматическая подстановка кода добавляет преимущество большей надежности и гибкости. Как уже отмечалось, подстановка кода семантически корректна только для процедуры, которую можно статически ограничить, например, как в случаях (1) и (2). Это не только допустимо, но также вполне согласуется с ОО-методом, в частности, с принципом Открыт-Закрыт, если разработчик на полпути разработки большой системы добавит переопределение некоторого компонента, имевшего к этому моменту только одну реализацию. Если же код процедуры вставляется вручную, то в результате может получиться программа с ошибочной семантикой (поскольку в данном случае требуется динамическое связывание, а вставка кода, конечно, означает статическое связывание). Разработчики должны сосредотачиваться на построении корректных программ, не занимаясь утомительными оптимизациями, которые при выполнении вручную приводят к ошибкам, а на деле могут быть автоматизированы.

Последнее замечание об эффективности. Опубликованная статистика для ОО-языков показывает, что где-то от 30% до 60% вызовов на самом деле используют динамическое связывание. Это зависит от того, насколько интенсивно разработчики используют специфические свойства методов. В системе ISE это соотношение близко к 60%. С использованием только что описанных оптимизаций платить придется только за динамическое связывание только тех вызовов, которые действительно в нем нуждаются. Для оставшихся динамических вызовов накладные расходы не только малы (ограничены константой), но и логически необходимы, - в большинстве случаев для достижения результата, эквивалентного динамическому связыванию, придется использовать условные операторы (if ... then ... или case ... of ... ), которые могут оказаться дороже приведенного выше простого механизма, основанного на доступе к массивам. Поэтому неудивительно, что ОО-программы, откомпилированные хорошим компилятором, могут соревноваться с написанным вручную кодом на C.

Из книги Разгони свой сайт автора Мациевский Николай

Статическое архивирование в действии Есть способ обойтись просто парой строчек в конфигурационном файле (httpd.conf или.htaccess, первое предпочтительнее), если потратить пару минут и самостоятельно заархивировать все необходимые файлы. Предположим, что у нас есть

Из книги Справочное руководство по C++ автора Страустрап Бьярн

R.3.3 Программа и связывание Программа состоит из одного или нескольких файлов, связываемых вместе (§R.2). Файл состоит из последовательности описаний. Имя с файловой областью видимости, которое явно описано как static, является локальным в своей единице трансляции и может

Из книги Язык программирования С# 2005 и платформа.NET 2.0. автора Троелсен Эндрю

Динамическое связывание Упрощенно говоря, динамическое связывание, или динамическая привязка, - это подход, с помощью которого можно создавать экземпляры заданного типа и вызывать их члены в среде выполнения и условиях, когда во время компиляции о типе еще ничего не

Из книги ArchiCAD 11 автора Днепров Александр Г

Связывание видов Среди инструментов визуализации ArchiCAD существует механизм, назначение которого – одновременное совместное отображение двух различных видов. Какой в этом смысл?Необходимость в этом возникает довольно часто. Например, для визуальной привязки объектов

Из книги Основы объектно-ориентированного программирования автора Мейер Бертран

Динамическое связывание Сочетание последних двух механизмов, переопределения и полиморфизма, непосредственно предполагает следующий механизм. Допустим, есть вызов, целью которого является полиморфная сущность, например сущность типа BOAT вызывает компонент turn.

Из книги Системное программирование в среде Windows автора Харт Джонсон М

Связывание с АТД Класс, как неоднократно говорилось, является реализацией АТД, заданного формальной спецификацией или неявно подразумеваемого. В начале лекции отмечалось, что утверждения можно рассматривать, как способ введения в класс семантических свойств, лежащих в

Из книги TCP/IP Архитектура, протоколы, реализация (включая IP версии 6 и IP Security) автора Фейт Сидни М

Динамическое связывание Динамическое связывание дополнит переопределение, полиморфизм и статическую типизацию, создавая базисную тетралогию

Из книги VBA для чайников автора Каммингс Стив

Кнопка под другим именем: когда статическое связывание ошибочно К этому моменту должен стать понятным главный вывод из изложенных в этой лекции принципов наследования:Принцип динамического связыванияЕсли результат статического связывания не совпадает с результатом

Из книги Операционная система UNIX автора Робачевский Андрей М.

Типизация и связывание Хотя как читатель этой книги вы наверняка отличите статическую типизацию от статического связывания, есть люди, которым подобное не под силу. Отчасти это может быть связано с влиянием языка Smalltalk, отстаивающего динамический подход к обеим задачам

Из книги C++ для начинающих автора Липпман Стенли

Неявное связывание Неявное связывание, или связывание во время загрузки (load-time linking) является простейшей из двух методик связывания. Порядок действий в случае использования Microsoft C++ следующий:1. После того как собраны все необходимые для новой DLL функции, осуществляется

Из книги Разработка ядра Linux автора Лав Роберт

Явное связывание Явное связывание, или связывание во время выполнения (run-time linking), требует, чтобы в программе содержались конкретные указания относительно того, когда именно необходимо загрузить или освободить библиотеку DLL. Далее программа получает адрес запрошенной

Из книги автора

11.9.3 Связывание Сервер DHCP хранит таблицу соответствия между клиентами и их конфигурационными параметрами. Связывание заключается в назначении каждому клиенту IP-адреса и набора конфигурационных

Из книги автора

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

Из книги автора

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

Из книги автора

9.1.7. Безопасное связывание A При использовании перегрузки складывается впечатление, что в программе можно иметь несколько одноименных функций с разными списками параметров. Однако это лексическое удобство существует только на уровне исходного текста. В большинстве

Из книги автора

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

Начиная с версии PHP 5.3.0 появилась особенность, называемая позднее статическое связывание, которая может быть использована для того чтобы получить ссылку на вызываемый класс в контексте статического наследования.

Если говорить более точно, позднее статическое связывание сохраняет имя класса указанного в последнем "не перенаправленном вызове". В случае статических вызовов это явно указанный класс (обычно слева от оператора :: ); в случае не статических вызовов это класс объекта. "Перенаправленный вызов" - это статический вызов, начинающийся с self:: , parent:: , static:: , или, если двигаться вверх по иерархии классов, forward_static_call() . Функция get_called_class() может быть использована чтобы получить строку с именем вызванного класса, и static:: представляет ее область действия.

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

Ограничения self::

Пример #1 Использование self::

class A {
echo __CLASS__ ;
}
public static function
test () {
self :: who ();
}
}

class B extends A {
public static function who () {
echo __CLASS__ ;
}
}

B :: test ();
?>

Использование позднего статического связывания

Позднее статическое связывание пытается устранить это ограничение предоставляя ключевое слово, которое ссылается на класс, вызванный непосредственно в ходе выполнения. Попросту говоря, ключевое слово, которое позволит вам ссылаться на B из test() в предыдущем примере. Было решено не вводить новое ключевое слово, а использовать static , которое уже зарезервировано.

Пример #2 Простое использованиеstatic::

class A {
public static function who () {
echo __CLASS__ ;
}
public static function
test () {
static:: who (); // Здесь действует позднее статическое связывание
}
}

class B extends A {
public static function who () {
echo __CLASS__ ;
}
}

B :: test ();
?>

Результат выполнения данного примера:

Замечание :

В нестатическом контексте вызванным классом будет тот, к которому относится экземпляр объекта. Поскольку $this-> будет пытаться вызывать закрытые методы из той же области действия, использование static:: может дать разные результаты. Другое отличие в том, что static:: может ссылаться только на статические поля класса.

Пример #3 Использование static:: в нестатическом контексте

class A {
private function foo () {
echo "success!\n" ;
}
public function test () {
$this -> foo ();
static:: foo ();
}
}

class B extends A {
/* foo() будет скопирован в В, следовательно его область действия по прежнему А,
и вызов будет успешен*/
}

class C extends A {
private function foo () {
/* исходный метод заменен; область действия нового метода С */
}
}

$b = new B ();
$b -> test ();
$c = new C ();
$c -> test (); //не верно
?>

Результат выполнения данного примера:

success! success! success! Fatal error: Call to private method C::foo() from context "A" in /tmp/test.php on line 9

Замечание :

Разрешающая область позднего статического связывания будет фиксирована вычисляющем ее статическим вызовом. С другой стороны, статические вызовы с использованием таких директив как parent:: или self:: перенаправляют информацию вызова.

Пример #4 Перенаправленные и не перенаправленные вызовы

Связывание в языке C++

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

Связывание методов в этом языке является довольно сложным. Для обычных переменных (не указателей или ссылок) оно осуществляется статически. Но когда объекты обозначаются с помощью указателей или ссылок, используется динамическое связывание. В последнем случае решение о выборе метода статического или динамического типа диктуется тем, описан ли соответствующий метод с помощью ключевого слова virtual. Если он объявлен именно так, то метод поиска сообщения базируется на динамическом классе, если нет на статическом. Даже в тех случаях, когда используется динамическое связывание, законность любого запроса определяется компилятором на основе статического класса получателя.

Рассмотрим, например, следующее описание классов и глобальных переменных: class Mammal

printf («cant speak»);

class Dog: public Mammal

printf («wouf wouf»);

printf («wouf wouf, as well»);

Mammal *fido = new Dog;

Выражение fred.speak() печатает «cant speak», однако вызов fido->speak() также напечатает «cant speak», поскольку соответствующий метод в классе Mammal не объявлен как виртуальный. Выражение fido->bark() не допускается компилятором, даже если динамический тип для fido класс Dog. Тем не менее статический тип переменной всего лишь класс Mammal.

Если мы добавим слово virtual:

virtual void speak()

printf («cant speak»);

то получим на выходе для выражения fido->speak() ожидаемый результат.

Относительно недавнее изменение в языке С++ добавление средств для распознавания динамического класса объекта. Они образуют систему RTTI (Run-Time Type Identification идентификация типа во время выполнения).

В системе RTTI каждый класс имеет связанную с ним структуру типа typeinfo, которая кодирует различную информацию о классе. Поле данных name одно из полей данных этой структуры содержит имя класса в виде текстовой строки. Функция typeid может использоваться для анализа информации о типе данных. Следовательно, следующая ниже команда будет печатать строку «Dog» динамический тип данных для fido. В этом примере необходимо разыменовывать переменную-указатель fido, чтобы аргумент был значением, на которое ссылается указатель, а не самим указателем:

cout << «fido is a» << typeid(*fido).name() << endl;

Можно также спросить, используя функцию-член before, соответствует ли одна структура с информацией о типе данных подклассу класса, соотносящегося с другой структурой. Например, следующие два оператора выдают true и false:

if (typeid(*fido).before (typeid(fred)))…

if (typeid(fred).before (typeid(lassie)))…

До появления системы RTTI стандартный программистский трюк состоял в том, чтобы явным образом закодировать в иерархии класса методы быть экземпляром. Например, для проверки значения переменных типа Animal на принадлежность к типу Cat или к типу Dog можно было бы определить следующую систему методов:

virtual int isaDog()

virtual int isaCat()

class Dog: public Mammal

virtual int isaDog()

class Cat: public Mammal

virtual int isaCat()

Теперь для определения того, является ли текущим значением переменной fido величина типа Dog, можно использовать команду fido->isaDog(). Если возвращается ненулевое значение, то можно привести тип переменной к нужному типу данных.

Возвращая указатель, а не целое число, мы объединяем проверку на принадлежность к подклассу и приведение типа. Это аналогично другой части системы RTTI, называемой dynamic_cast, которую мы вкратце опишем. Если некая функция в классе Mammal возвращает указатель на Dog, то класс Dog должен быть предварительно описан. Результатом присваивания является либо нулевой указатель, либо правильная ссылка на класс Dog. Итак, проверка результата все еще должна осуществляться, но мы исключаем необходимость приведения типа. Это показано в следующем примере:

class Dog; // предварительное описание

virtual Dog* isaDog()

virtual Cat* isaCat()

class Dog: public Mammal

virtual Dog* isaDog()

class Cat: public Mammal

virtual Cat* isaCat()

Оператор lassie = fido->isaDog(); теперь выполним всегда. В результате переменная lassie получает ненулевое значение, только если fido имеет динамический класс Dog. Если fido не принадлежит Dog, то переменной lassie будет присвоен нулевой указатель.

lassie = fido->isaDog();

… // fido и в самом деле относится к типу Dog

… // присваивание не сработало

… // fido не принадлежит к типу Dog

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

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

// конвертировать только в том случае, если fido является собакой

lassie = dynamic_cast < Dog* > (fido);

// затем проверить, выполнено ли приведение

В язык C++ были добавлены еще три типа приведения (static_cast, const_cast и reinterpret_cast), но они используются в особых случаях и поэтому здесь не описываются. Программистам рекомендуется применять их как более безопасные средства вместо прежнего механизма приведения типов.

2. Проектная часть

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

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

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

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

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

public void moveTo(int x, int y){

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

Ещё часто вызывает недоумение, зачем в абстрактном классе 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 нет различения этих видов методов.

Базовый класс 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). О них говорится в разделе, посвящённом потокам.


Похожая информация.