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