Привіт! Сьогодні ми поговоримо про оператор instanceof, розглянемо приклади його використання та зачепимо деякі моменти, пов'язані з його роботою :) На ранніх рівнях JavaRush ти вже зустрічав цей оператор. Пам'ятаєш, навіщо він потрібен? Якщо ні — не біда, давай пригадаємо разом. Оператор instanceof потрібен, щоб перевірити, чи був об'єкт, на який посилається змінна X, створений на основі якогось класу Y. Звучить просто. Чому ж ми повернулися до цієї теми? Перш за все тому, що тепер ти добре знайомий із механізмом наслідування в Java й іншими принципами ООП. Тема instanceof буде зрозумілішою, і ми розглянемо більш просунуті приклади використання. Починаємо!Як працює оператор Instanceof - 1Ти напевно пам'ятаєш, що оператор instanceof повертає значення true, якщо перевірка показала істинність, або false, якщо результат був хибним. Відповідно, найчастіше він трапляється в різного роду перевірочних умовах (if…else). Почнемо з простіших прикладів:

public class Main {

   public static void main(String[] args) {

       Integer x = new Integer(22);

       System.out.println(x instanceof Integer);
   }
}
Як думаєш, що буде виведено в консоль? Ну, тут це очевидно :) Об'єкт х є Integer, тому результатом буде true. Вивід у консоль: true Спробуємо перевірити його на належність, наприклад, до String:

public class Main {

   public static void main(String[] args) {

       Integer x = new Integer(22);

       System.out.println(x instanceof String);// помилка!
   }
}
Ми отримали помилку. Причому зверни увагу: компілятор видав її ще до виконання коду! Він одразу побачив, що Integer і String не можуть бути автоматично перетворені один на одного і не перебувають у зв'язках наслідування. Відповідно, об'єкт класу Integer не створиться на основі String. Це зручно і допомагає уникнути дивних помилок вже під час виконання програми, тож тут компілятор нас виручив :) Тепер давай спробуємо розглянути складніші приклади. Раз вже ми згадали наслідування, попрацюємо ось із такою невеликою системою класів:

public class Animal {
  
}

public class Cat extends Animal {
  
}

public class MaineCoon extends Cat {
  
}
Ми вже знаємо, як поводиться instanceof, коли ми перевіряємо належність об'єкта до якогось класу в звичайній ситуації, але що буде, якщо ми додамо сюди відношення «батько-нащадок»? Як працює оператор Instanceof - 2 Наприклад, як думаєш, що видасть ось така перевірка:

public class Main {

   public static void main(String[] args) {

       Cat cat = new Cat();

       System.out.println(cat instanceof Animal);

       System.out.println(cat instanceof MaineCoon);

   }
}
Вивід: true false Головне питання, на яке треба відповісти, — як саме instanceof розшифровує поняття «об'єкт створений на основі класу»? У нас у результаті вийшло Сat instanceof Animal == true, але ж до такої формулювання можна і причепитися. Чому це об'єкт Cat створюється на основі класу Animal? Хіба він не створюється лише на основі власного класу? Відповідь досить проста, і ти, можливо, вже здогадався до неї. Згадай порядок виклику конструкторів й ініціалізації змінних під час створення об'єкта. Ми вже розглядали цю тему в статті про конструктор класів. Ось приклад з тієї лекції:

public class Animal {

   String brain = "Початкове значення brain у класі Animal";
   String heart = "Початкове значення heart у класі Animal";

   public static int animalCount = 7700000;

   public Animal(String brain, String heart) {
       System.out.println("Виконується конструктор базового класу Animal");
       System.out.println("Чи вже були проініціалізовані змінні класу Animal?");
       System.out.println("Поточне значення статичної змінної animalCount = " + animalCount);
       System.out.println("Поточне значення brain у класі Animal = " + this.brain);
       System.out.println("Поточне значення heart у класі Animal = " + this.heart);

       this.brain = brain;
       this.heart = heart;
       System.out.println("Конструктор базового класу Animal завершив роботу!");
       System.out.println("Поточне значення brain = " + this.brain);
       System.out.println("Поточне значення heart = " + this.heart);
   }
}

class Cat extends Animal {

   String tail = "Початкове значення tail у класі Cat";

   static int catsCount = 37;

   public Cat(String brain, String heart, String tail) {
       super(brain, heart);
       System.out.println("Конструктор класу Cat почав роботу (конструктор Animal вже був виконаний)");
       System.out.println("Поточне значення статичної змінної catsCount = " + catsCount);
       System.out.println("Поточне значення tail = " + this.tail);
       this.tail = tail;
       System.out.println("Поточне значення tail = " + this.tail);
   }

   public static void main(String[] args) {
       Cat cat = new Cat("Мозок", "Серце", "Хвіст");
   }
}
І якщо ти його запустиш в IDE, вивід у консоль буде виглядати так: Виконується конструктор базового класу Animal Чи вже були проініціалізовані змінні класу Animal? Поточне значення статичної змінної animalCount = 7700000 Поточне значення brain у класі Animal = Початкове значення brain у класі Animal Поточне значення heart у класі Animal = Початкове значення heart у класі Animal Конструктор базового класу Animal завершив роботу! Поточне значення brain = Мозок Поточне значення heart = Серце Конструктор класу Cat почав роботу (конструктор Animal вже був виконаний) Поточне значення статичної змінної catsCount = 37 Поточне значення tail = Початкове значення tail у класі Cat Поточне значення tail = Хвіст Тепер згадав? :) Конструктор базового класу, якщо він є, завжди викликається першим під час створення будь-якого об'єкта. Instanceof керується саме цим принципом, коли намагається визначити, чи був об'єкт А створений на основі класу Б. Якщо конструктор базового класу викликаний, значить жодних сумнівів бути не може. З другою перевіркою все простіше:

System.out.println(cat instanceof MaineCoon);
Конструктор MaineCoon не викликався під час створення Cat, що логічно. Адже MaineCoon — нащадок Cat, а не предок. Але шаблоном для Cat він не є. Ок, із цим наче зрозуміло. А що буде, якщо ми зробимо ось так:

public class Main {

   public static void main(String[] args) {

       Cat cat = new MaineCoon();

       System.out.println(cat instanceof Cat);
       System.out.println(cat instanceof MaineCoon);


   }
}
Хм...тут вже складніше. Давай спробуємо порозмірковувати. У нас є змінна типу Cat, і їй ми присвоїли об'єкт типу MaineCoon. До речі, а чому це взагалі працює? Хіба так можна робити? Можна. Адже будь-який мейн-кун — це кішка. Якщо це не зовсім зрозуміло, згадай приклад із розширенням примітивних типів:

public class Main {

   public static void main(String[] args) {

       long x = 1024;
      
   }
}
Число 1024 — це short: він легко поміщається у змінну long, адже за кількістю байтів для нього достатньо місця (пам'ятаєш приклад із матрьошками?). Об'єкт-нащадок завжди можна присвоїти у змінну-предка. Поки просто запам'ятай це, а в наступних лекціях ми ще розберемо цей процес. Тож що ж виведе наш приклад?

Cat cat = new MaineCoon();
System.out.println(cat instanceof Cat);
System.out.println(cat instanceof MaineCoon);
Що буде перевіряти instanceof: нашу змінну класу Cat чи наш об'єкт класу MaineCoon? Насправді, відповісти на це запитання просто. Треба всього лише ще раз прочитати визначення нашого оператора: Оператор instanceof потрібен для того, щоб перевірити, чи був об'єкт, на який посилається змінна X, створений на основі якогось класу Y. Оператор instanceof перевіряє саме походження об'єкта, а не змінної. Тому в прикладі обидва рази в консолі виведе true: у нас об'єкт типу MaineCoon. Звісно, він був створений на основі класу MaineCoon, але й на основі батьківського класу Cat теж!