It - archiv

Ідентифікація типу під час виконання

Розглянемо механізм ідентифікації типу в часі виконання (RTTI - run-time type identification).

Даний механізм складається з декількох частин, які відрізняються своєю важливістю, складністю і частотою використання.

  • Базові засоби RTTI лежать в основі механізму поліморфізму. Відповідно, вони використовуються постійно при програмуванні на Java.
  • При першому доступі до деякого класу з програми, створюється об'єкт класу Class для даного класу. Це теж відноситься до засобів RTTI.
  • Механізми визначення типів (класів) об'єктів під час виконання використовуються досить часто. Найпростіший з цих механізмів, операцію instanceof. ми вже розглядали.
  • Найбільш складним механізмом RTTI є рефлексія. Це набір засобів, що дозволяють отримати повну інформацію про клас, таку як список полів, конструкторів, методів і т.д. Отримана інформація може бути використана для створення об'єктів цього класу, виклику його методів.

Базові засоби RTTI

Будь-яке приведення типів від базового до породженому (спадний перетворення типів, downcasting) виконується з динамічним контролем коректності типу.

Так, нехай у нас є клас Base:

І від нього породжені класи Derived1. Derived2 Derived3. У програмі ми будуємо об'єкти цих класів і заносимо їх в список ArrayList. Далі ми хочемо пройти по цих об'єктах і викликати для кожного з них метод f (). Це буде виглядати приблизно так.

Десь в програмі у нас описаний ArrayList.

В іншій точці програми

Тут метод next інтерфейсу Iterator повертає Object. Але ми знаємо, що в списку знаходяться об'єкти класів Derived1. Derived2 і Derived3. базовим класом для яких є Base. Ми не можемо викликати f () для Object. Тому ми наводимо його до Base і викликаємо f ().

Це шаблонний приклад застосування поліморфізму. Але найголовніше, що не можна забувати, це те, що в даному випадку, як і в інших подібних випадках, під час виконання програми буде виконуватися динамічний контроль типів. Якщо в списку виявиться об'єкт якогось іншого класу, для якого Base не є базовим, то в процесі виконання виникне виключення ClassCastException.

Крім того, динамічні засоби контролю типів в даній ситуації забезпечать виклик потрібного методу f (). Ми нічого не говорили про класи Derived1. Derived2 і Derived3. Але в будь-якому з них ми можемо перевизначити (override) метод f (). При цьому в даному фрагменті для кожного об'єкта зі списку буде викликаний метод f () його класу. І нам не потрібно робити для цього ніяких спеціальних заходів.

Об'єкти класу Class

Об'єкти класу Class створюються для кожного використовуваного в програмі класу при першому доступі до нього. Першим доступом до класу може бути створення екземпляра класу або звернення до статичного методу або полю. Є також і інші варіанти в рамках механізму RTTI.

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

Звернемося до документації. Пакет java.lang клас Class.

Перше, на що слід звернути увагу, це відсутність public конструкторів і опис самого класу:

Це означає, що ми не можемо ні успадкувати цей клас, ні явно створювати об'єкти цього класу. Клас Class влаштований так, що не можна створити більше одного об'єкта для кожного класу. Віртуальна машина Java (VM) створює такий об'єкт автоматично при першому зверненні до класу.

Є кілька способів отримати цей об'єкт для даного класу.

  • За допомогою статичного методу класу Class
  • За допомогою синтаксичної конструкції, іменованої літерали об'єктів класу Class.
  • За допомогою методу getClass класу Object.

У першому випадку нам потрібно знати повне ім'я класу. У другому, клас повинен бути доступний в даному java-файлі (за допомогою оператора import). Останній варіант може бути використаний для отримання об'єкта класу Class по об'єкту деякого класу.

Можна застосовувати будь-який з цих способів, залежно від того, що нам є в даній точці програми. Але при інших рівних умовах краще віддати перевагу другому із зазначених способів - використання литералов об'єктів класу Class.

Літерали об'єктів класу Class

Синтаксис даної конструкції досить простий:

дасть посилання на об'єкт класу Class для класу java.util.ArrayList.

Даний варіант найкращим за всі інші тому, що він контролюється під час компіляції програми. Крім того, він простіше інших варіантів.

Об'єкти класу Class створюються не тільки для всіх класів, задіяних в програмі, але і для примітивних типів. Причому, літерали об'єктів класу Class - один з двох способів отримати доступ до цих об'єктів. Головне - знати, як це записати в програмі.

Наприклад, для типу int відповідний літерал виглядає так: int.class.

Альтернативним способом є використання статичного поля TYPE в класі-оболонці (wrapper-class) для даного простого типу. Наприклад, для int. Integer.TYPE.

Визначення типу об'єкта в програмі

Іноді в програмі необхідно визначити чи перевірити тип об'єкта. Найчастіше така необхідність виникає при проведенні спадного перетворення класів (downcasting) для того, щоб перевірити допустимість такого перетворення.

Один з таких способів ми вже розглядали на 8-му занятті. Це операція instanceof.

Інший спосіб є наслідком розглянутої вище матеріалу за класом Class.

Третій спосіб полягає у використанні методу isInstance класу Class.

Розглянемо і порівняємо ці способи.

На 8-му занятті ми розглядали такий приклад

Як видно з прикладу, лівим операндом операції instanceof є об'єкт, правим - клас.

Ще один спосіб порівняння полягає в порівнянні об'єктів класу Class. Оскільки для кожного класу такий об'єкт присутній в однині, то, отримавши два посилання на об'єкт класу Class. їх можна просто порівняти на рівність. У наведеному прикладі це могло б виглядати так:

Однак цей спосіб порівняння відрізняється від порівняння з використанням instanceof. Зокрема, в даному прикладі він застосовується лише в тому випадку, якщо клас Book не має спадкоємців. Якщо ж уявити, що у класу Book є спадкоємці (наприклад, Fantasy і DetectiveStory), то для об'єктів цих класів перевірка

У той же час перевірка

(Тобто із застосуванням операції instanceof) відпрацює коректно, оскільки ця операція працює з урахуванням успадкування класів.

Ця відмінність описаних вище способів порівняння потрібно враховувати. Найчастіше нам потрібно те, що робить instanceof. і лише в рідкісних випадках нас може цікавити точне порівняння класів, яке можна виконати порівнюючи посилання на об'єкти класу Class.

І, нарешті, є варіант порівняння з використанням методу isInstance класу Class. За своєю семантикою цей спосіб нічим не відрізняється від застосування операції instanceof. Їх відмінності чисто синтаксичні. Так, для нашого прикладу застосування isInstance виглядало б так

Створення об'єктів за допомогою методу newInstance

Повернемося до документації по класу Class. Серед методів даного класу є метод newInstance.

Цей метод дозволяє породжувати об'єкт того класу, для якого створений даний об'єкт Class. Правда при цьому застосовується конструктор за замовчуванням (використання інших конструкторів розглянемо далі).

Для знайомства з даним методом розглянемо приклад.

Це діалогова програма з текстовим полем для введення імені класу і текстової областю для виведення результатів створення об'єктів класу з використанням методу newInstance.

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

Як імена класів в даному тесті вводилися 5 рядків: abc. String. java.lang.String. java.lang.Integer і javax.swing.JLabel. Розберемося з випадками невдалих тестів.

  • abc - немає такого класу.
  • String - потрібно задавати повне ім'я класу із зазначенням пакета.
  • java.lang.Integer - для даного класу не існує конструктора за замовчуванням.

Дана програма демонструє роботу методів forName і newInstance. Основні десйствія програми зосереджені в методі createObject. Цей метод спочатку вибирає ім'я класу, введене користувачем, з текстового поля. Потім, в блоці try. за допомогою методу forName отримує посилання на об'єкт Class для даного класу і створює об'єкт цього класу методом newInstance.

Як метод forName. так і метод newInstance можуть генерувати виключення. Приклад тестування, наведений на рис. демонструє це. При введенні імен abc і String виняток ClassNotFoundException виникло в методі forName. При введенні java.lang.Integer виникло виключення InstantiationException в методі newInstance.

Ми розглянули базові можливості RTTI. Нам залишилося розглянути найбільш складну частину RTTI - рефлексію.