1. Приведение типов

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

Приведение типов

Приведение ссылочных типов, как и примитивных тоже делится на расширяющие и сужающее.

Мы видим, что класс Кот унаследован от класса ДомашнееЖивотное, а класс ДомашнееЖивотное в свою очередь от класса Животное.

Если мы напишем такой код:

Животное котик = new Кот();

Это расширяющее приведение типа: его еще называют неявным. Мы расширили ссылку котик, и теперь она ссылается на объект типа Кот. При таком приведении мы не сможем через ссылку котик вызвать методы, которые есть у класса Кот, но которых нет у класса Животное.

Сужающее приведение (или явное) происходит в обратную сторону:

Кот котэ = (Кот) котик;

Мы явно указали, что хотим привести ссылку, которая хранится в переменной котик (типа Животное) к типу Кот.



2. Проверка типа объекта

Но тут нужно быть очень осторожным. Если вы сделаете так:

Животное зверь = new Кот();
Волк серыйВолк = (Волк) зверь;

Компилятор пропустит этот код, а вот во время выполнения программы возникнет ошибка! JVM кинет вам исключение:

Exception in thread "main" java.lang.ClassCastException: Кот cannot be cast to Волк

Ссылку на объект Кот можно сохранить только в переменные, которые имеют тип класса-родителя для класса Кот: ДомашнееЖивотное, Животное и Object.

Почему же так?

Все дело в том, что ссылка на объект используется для того, чтобы обращаться к методам и переменным этого объекта. И если мы сохраним в переменную типа Животное ссылку на объект Кот, то никаких проблем с этим не возникнет: у типа Кот всегда будет переменная и методы типа Животное: он же их унаследовал!

А вот если бы JVM разрешила сохранить ссылку на объект Кот в переменную типа Волк, могла бы возникнуть ситуация, когда у переменной серыйВолк вызывается метод, который отсутствует у объекта Кот, на который эта переменная и ссылается. Поэтому такое сохранение не разрешается.

В Java есть специальный оператор — instanceof, который позволяет проверить, можно ли сохранить объект определенного типа в переменную определенного типа. Выглядит он достаточно просто:

переменная instanceof Тип

Пример:

Животное зверь = new Кот();
if (зверь instanceof Волк)
{
   Волк серыйВолк = (Волк) зверь;
}

Такой код не вызовет ошибок даже во время выполнения.

Вот еще несколько примеров с описанием ситуации:

Расширение типа Описание
Cow cow = new Whale();

Классическое расширение типа — оператор преобразования типа не требуется. Теперь у объекта типа Whale можно вызывать только методы, описанные в классе Cow.

Компилятор разрешит вызвать у переменной cow только те методы, которые есть у ее типа — класса Cow.

Сужение типа
Cow cow = new Whale();
if (cow instanceof Whale)
{
   Whale whale = (Whale) cow;
}
Классическое сужение типа: нужно добавить проверку типа и оператор приведения типа.
Переменная cow типа Cow хранит ссылку на объект класса Whale.
Мы проверяем, что это так и есть, и затем выполняем операцию преобразования (сужение) типа. Или как ее еще называют —
type cast
.

Cow cow = new Cow();
Whale whale = (Whale) cow; // exception
Ссылочное сужение типа можно провести и без проверки типа объекта.
При этом, если в переменной cow хранился объект не класса Whale, будет сгенерировано исключение — InvalidClassCastException.


3. Вызов оригинального метода: super

Иногда бывает нужно не заменить метод родительского класса на свой при переопределении метода, а лишь немного дополнить его.

Было бы классно, если бы мы могли в нашем методе вызвать такой же метод родительского класса, а потом еще выполнить какой-то свой код. Ну или сначала выполнить свой код, а потом вызвать метод родительского класса.

И такая возможность в Java есть. Вызов метода именно родительского класса делает так:

super.метод(параметры);

Примеры:

class МирноеВремя
{
   public double getPi()
   {
      return 3.14;
   }
}

class ВоенноеВремя extends МирноеВремя
{
   public double getPi()
   {
      return super.getPi()*2;  // 3.14*2
   }
}

В военное время значение Pi может достигать четырех, а в нашем случае вообще 6! Это, конечно, шутка, но она демонстрирует, как это все может работать.

Вот еще пара примеров, чтобы немного прояснить ситуацию:

Код Описание
class Cow
{
   public void printAll()
   {
      printColor();
      printName();
   }

   public void printColor()
   {
      System.out.println("Я — белый");
   }

   public void printName()
   {
      System.out.println("Я — корова");
   }
}

class Whale extends Cow
{
   public void printName()
   {
      System.out.print("Это неправда: ");
      super.printName();
      System.out.println("Я — кит");
   }
}
Классы Cow и Whale
public static void main(String[] args)
{
   Whale whale = new Whale();
   whale.printAll();
}
На экран будет выведена надпись:
Я — белый
Это неправда: Я — корова
Я — кит

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