Теперь, когда были подробно рассмотрены все примеры преобразований, нужно вернуться к вопросу переменной и ее значений.
Как уже говорилось, переменная определяется тремя базовыми характеристиками: имя, тип, значение. Имя дается произвольным образом и никак не сказывается на свойствах переменной. А вот значение всегда имеет некоторый тип, не обязательно совпадающий с типом самой переменной. Поэтому необходимо рассмотреть все возможные типы переменных и выяснить, значения каких типов они могут иметь.
Начнем с переменных примитивных типов. Поскольку эти переменные действительно хранят само значение, то их тип всегда точно совпадает с типом значения.
Проиллюстрируем это правило на примере:
byte b=3; char с='А'+3; long nn=b+c; double cl=m-3F;
Здесь переменная b будет хранить значение типа byte после сужения целочисленного литерала типа int. Переменная с будет хранить тип char после того, как компилятор осуществит сужающее преобразование результата суммирования, который будет иметь тип int. Для переменной m выполнится расширение результата суммирования типа от int к типу long. Наконец, переменная d будет хранить значение типа double, получившееся в результате расширения результата разности, который имеет тип float.
Переходим к ссылочным типам. Во-первых, значение любой перееденной такого типа - ссылка, которая может указывать лишь на объекты, Порожденные от тех или иных классов, и далее обсуждаются только свойва данных классов. (Также объекты могут порождаться от массивов, эта тема рассматривается в отдельной лекции.)
Кроме того, ссылочная переменная любого типа может иметь значение null. Большинство действий над такой переменной, например, обращение к полям или методам, приведет к ошибке.
Итак, какова связь между типом ссылочной переменной и ее значения? Здесь главное ограничение — проверка компилятора, который следит, чтобы все действия, выполняющиеся над объектом, были корректны. ^Компилятор не может предугадать, на объект какого класса будет реально
ссылаться та или иная переменная. Все, чем он располагает, — тип самой переменной. Именно его и использует компилятор для проверок. А значит, все допустимые значения переменной должны гарантированно обладать свойствами, определенными в классе-типе этой переменной. Такую гарантию дает только наследование. Отсюда получаем правило: ссылочная переменная типа А может указывать на объекты, порожденные от самого типа А или его наследников.
Point р = newPoJntO;
В этом примере переменная и ее значение одинакового типа, поэтому над объектом можно совершать все возможные для данного класса действия.
Parent р = new ChildO;
Такое присвоение корректно, так как класс Child порожден от Parent. Однако теперь допустимые действия над переменной р, а значит, над объектом, только что созданным на основе класса Child, ограничены возможностями класса Parent. Например, если в классе Child определен некий новый метод newChildMethodO, то попытка его вызвать p.newChildMethod() будет порождать ошибку компиляции Необходимо подчеркнуть, что никаких изменений с самим объектом не происходит, ограничение порождается используемым способом доступа к этому объекту - переменной типаParent.
Чтобы показать, что объект не потерял никаких свойств, произведем следующее обращение:
((Child)p).newChildMethodO;
Здесь вначале проводится явное сужение к типу Child. Во время исполнения программы JVM проверит, совместим ли тип объекта, на который ссылается переменная р, с типом Child. В нашем случае это именно так. В результате получается ссылка типа Child, поэтому становится допустимым вызов метода newChildMethodO, который вызывается у объекта. созданного в предыдущей строке.
Обратим внимание на важный частный случай - переменная типа Object может ссылаться на объекты любого типа.
В дальнейшем, с изучением новых типов (абстрактных классов, интерфейсов, массивов) этот список будет продолжаться, а пока коротко обобщим то, что было рассмотрено в данном разделе.