— Привіт, Аміго! Тема нашої лекції сьогодні – розширення та звуження типів. З розширенням та звуженням примітивних типів ти вже знайомий. Сьогодні ми поговоримо про те, як це працює для типів посилань, тобто для класових об'єктів.

Тут все насправді доволі просто. Уяви собі ланцюжок успадкування класу: клас, його батько, батько батька і т.д. до самого класу Object. Оскільки клас містить усі методи класу, від якого його успадковано, то об'єкт цього класу можна зберегти до змінної будь-якого з типів його батьків.

Приклад:

Код Опис
class Animal
{
  public void doAnimalActions(){}
}
class Cat extends Animal
{
  public void doCatActions(){}
}
class Tiger extends Cat
{
  public void doTigerActions(){}
}
Тут бачимо три оголошених класи: тварина, кіт і тигр. Кіт успадковується від Тварини. А Тигр – від Кота.
public static void main(String[] args)
{
  Tiger tiger = new Tiger();
  Cat cat = new Tiger();
  Animal animal = new Tiger();
  Object obj = new Tiger();
}
Об'єкт класу Tiger завжди можна спокійно присвоїти змінній з типом класу-батька. Для класу Tiger це Cat, Animal і Object.

Тепер розглянемо, що таке розширення і звуження типів.

Якщо в результаті присвоєння ми рухаємося ланцюжком успадкування вгору (до типу Object), то це – розширення типу (воно ж – висхідне перетворення, або upcasting), а якщо вниз, до типу об'єкта, то це звуження типу (воно ж – низхідне перетворення, або downcasting).

Рух вгору ланцюжком успадкування називається розширенням, оскільки він призводить до більш загального типу. А проте втрачається можливість викликати методи, які були додані до класу при спадкуванні.

Код Опис
public static void main(String[] args)
{
  Object obj = new Tiger();
  Animal animal = (Animal) obj;
  Cat cat = (Cat) obj;
  Tiger tiger = (Tiger) animal;
  Tiger tiger2 = (Tiger) cat;
}
При звуженні типу потрібно використовувати оператор перетворення типу. Тобто, ми виконуємо явне перетворення.

Водночас Java-машина виконує перевірку того, чи дійсно цей об'єкт успадкований від Типу, до якого ми хочемо його перетворити.

Таке невелике нововведення зменшило кількість помилок у перетворенні типів у рази і суттєво підвищило стабільність роботи Java-програм.

Код Опис
public static void main(String[] args)
{
  Object obj = new Tiger();
  if (obj instanceof Cat)
  {
    Cat cat = (Cat) obj;
    cat.doCatActions();
  }
}
Ще краще – використати перевіркуinstanceof
public static void main(String[] args)
{
  Animal animal = new Tiger();
  doAllAction(animal);

  Animal animal2 = new Cat();
  doAllAction(animal2);

  Animal animal3 = new Animal();
  doAllAction(animal3);
}

public static void doAllAction(Animal animal)
{
  if (animal instanceof Tiger)
  {
    Tiger tiger = (Tiger) animal;
    tiger.doTigerActions();
  }

  if (animal instanceof Cat)
  {
    Cat cat = (Cat) animal;
    cat.doCatActions();
  }

  animal.doAnimalActions();
}
І ось чому. Дивимося на приклад зліва.

Ми (наш код) не завжди знаємо, з об'єктом якого типу ми працюємо. Це може бути як об'єкт того ж типу, що і змінна (Animal), так і будь-який тип-спадкоємець (Cat, Tiger).

Розглянемо метод doAllAction. Він коректно працює незалежно від того, об'єкт якого типу до нього передали.

Тобто він коректно працює для всіх трьох типів – Animal, Cat, Tiger.

public static void main(String[] args)
{
  Cat cat = new Tiger();
  Animal animal = cat;
  Object obj = cat;
}
Тут ми бачимо три присвоєння. Усі є прикладами розширення типу.

Оператор перетворення типу тут не потрібен, тому що не потрібна перевірка. Посилання на об'єкт завжди можна зберегти у змінну будь-якого його базового типу.

— О, на передостанньому прикладі все стало зрозуміло. І для чого потрібна перевірка, і для чого потрібне перетворення типів.

— Сподіваюся, що так. Хочу звернути твою увагу на наступну річ:

З об'єктом за такого присвоєння нічого не відбувається! Змінюється лише кількість методів, яке можна викликати за допомогою конкретної змінної-посилання.

Наприклад, змінна класу Cat дозволяє викликати методи doAnimalActions & doCatActions, і нічого не знає про метод doTigerActions, навіть якщо посилається на об'єкт класу Tiger.

— Ну, це вже зрозуміло. Виявилося легше, ніж я думав.