На этом шаге мы рассмотрим приведение типов
Рассмотрим процесс принудительного преобразования одного типа в другой, называемый приведением типов. Для этой цели в Java предусмотрена специальная запись. Например, при выполнении следующего фрагмента кода значение переменной x преобразуется в целочисленное отбрасыванием дробной части:
double x = 3.405; int nx = (int) x;
И как иногда возникает потребность в преобразовании значения с плавающей точкой в целочисленное, ссылку на объект требуется порой привести к типу другого класса. Для такого приведения типов служат те же самые синтаксические конструкции, что и для числовых выражений. С этой целью имя нужного класса следует заключить в скобки и поставить перед той ссылкой на объект, которую требуется привести к искомому типу. Ниже приведен соответствующий тому пример:
Manager boss = (Manager) staff[0];
Для такого приведения типов существует только одна причина: необходимость использовать все функциональные возможности объекта после того, как его фактический тип был на время забыт. Например, в классе ManagerTest массив staff содержит объекты типа Employee. Этот тип выбран потому, что в некоторых элементах данного массива хранятся данные о рядовых сотрудниках. А для того чтобы получить доступ ко всем новым полям из класса Manager, скорее всего, придется привести некоторые элементы массива staff к типу Manager.
Как известно, у каждой объектной переменной в Java имеется свой тип. Тип объектной переменной определяет разновидность объекта, на который ссылается эта переменная, а также ее функциональные возможности. Например, переменная staff[1] ссылается на объект типа Employee, поэтому она может ссылаться и на объект типа Manager.
В процессе своей работы компилятор проверяет, не обещаете ли вы слишком много, сохраняя значение в переменной. Так, если вы присваиваете переменной суперкласса ссылку на объект подкласса, то обещаете меньше положенного, и компилятор просто разрешает вам сделать это. А если вы присваиваете объект суперкласса переменной подкласса, то обещаете больше положенного, и поэтому вы должны подтвердить свои обещания, указав в скобках имя класса для приведения типов. Таким образом, виртуальная машина получает возможность контролировать ваши действия при выполнении программы.
А что произойдет, если попытаться осуществить приведение типов вниз по цепочке наследования и попробовать обмануть компилятор в отношении содержимого объекта, как в приведенном ниже примере:
Manager boss = (Manager) staff[1];
При выполнении программы система обнаружит несоответствие и сгенерирует исключение типа ClassCastException. Если его не перехватить, то нормальное выполнение программы будет прервано (что такое исключения мы поговорим позднее). Таким образом, перед приведением типов следует непременно проверить его корректность. Для этой цели служит операция instanceof, как показано ниже:
if (staff[1] instanceof Manager) { boss = (Manager) staff[1]; ... }
Заметим, что если приведенном выше условии staff[1] содержало пустое значение null, исключение не будет сгенерировано и возвратится логическое значение false. И это вполне логично. Ведь пустая ссылка типа null не указывает ни на один из объектов, а следовательно, она не указывает ни на один из объектов типа Manager.
И наконец, компилятор не позволит выполнить некорректное приведение типов, если для этого нет никаких оснований. Например, наличие приведенной ниже строки в исходном коде программы приведет к ошибке во время компиляции, поскольку класс Date не является подклассом, производным от класса Employee.
Date c = (Date) staff[1];
Сформулируем следующие основные правила приведения типов при наследовании:
На самом деле приведение типов при наследовании - самое лучшее решение. В данном примере выполнять преобразование объекта типа Employee в объект типа Manager совсем не обязательно. Метод getSalary() вполне способен оперировать объектами обоих типов, поскольку при динамическом связывании правильный метод автоматически определяется благодаря принципу полиморфизма.
Приведение типов целесообразно лишь в том случае, когда для объектов, представляющих руководителей, требуется вызвать особый метод, имеющийся только в классе Manager, например метод setBounds(). Если же по какой-нибудь причине потребуется вызвать метод setBounds() для объекта типа Employee, следует задать себе вопрос: не свидетельствует ли это о недостатках суперкласса? Возможно, имеет смысл пересмотреть структуру суперкласса и добавить в него метод setBounds(). Не забывайте, что для преждевременного завершения программы достаточно единственного неперехваченного исключения типа ClassCastException. А в целом при наследовании лучше свести к минимуму приведение типов и выполнение операции instanceof.
На следующем шаге мы рассмотрим абстрактные классы