JavaRush/Java блог/Random UA/Внутрішні класи у локальному методі

Внутрішні класи у локальному методі

Стаття з групи Random UA
учасників
Вітання! Давай поговоримо про ще один різновид вкладених класів. А саме – про локальні класи (Method local inner classes). Перше, що треба згадати перед вивченням, — їхнє місце у структурі вкладених класів. Внутрішні класи в локальному методіВиходячи з нашої схеми, ми можемо зрозуміти, що локальні класи — це підвид внутрішніх класів, про які ми говорабо докладно в одному з попередніх матеріалів . Проте, локальні класи мають ряд важливих особливостей і відмінностей від внутрішніх класів. Головне полягає в їхньому оголошенні: Локальний клас оголошується тільки в блоці коду. Найчастіше – усередині якогось методу зовнішнього класу. Наприклад, це може виглядати так:
public class PhoneNumberValidator {

   public void validatePhoneNumber(String number) {

        class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       //...код валидации номера
   }
}
ВАЖЛИВО!Цей код не скомпілюється при вставці в IDEA, якщо в тебе встановлено Java 7. Про причини цього ми поговоримо наприкінці лекції. У кількох словах робота локальних класів сильно залежить від версії мови. Якщо цей код у тебе не компілюється, ти можеш переключити версію мови в IDEA на Java 8, або додати слово finalдо параметра методу, щоб вийшло так: validatePhoneNumber(final String number). Після цього все почне працювати. Це невелика програма – валідатор телефонних номерів. Її метод validatePhoneNumber()приймає на вхід рядок і визначає, чи є номером телефону. І всередині цього методу ми оголосабо наш локальний класPhoneNumber. У тебе могло виникнути логічне запитання: навіщо? Навіщо оголошувати клас саме усередині методу? Чому не використовувати нормальний внутрішній клас? Справді, можна було б зробити так: зробити клас PhoneNumberвнутрішнім. Інша річ, що підсумкове рішення залежить від структури та призначення твоєї програми. Давай згадаємо наш приклад із лекції про внутрішні класи:
public class Bicycle {

   private String model;
   private int mawWeight;

   public Bicycle(String model, int mawWeight) {
       this.model = model;
       this.mawWeight = mawWeight;
   }

   public void start() {
       System.out.println("Поїхали!");
   }

   public class HandleBar {

       public void right() {
           System.out.println("Руль праворуч!");
       }

       public void left() {

           System.out.println("Руль вліво!");
       }
   }
}
У ньому ми зробабо HandleBar(кермо) внутрішнім класом велосипеда. У чому різниця? Насамперед, у використанні класу. Клас HandleBarіз другого прикладу — сутність складніша, ніж PhoneNumberіз першого. По-перше, HandleBarє публічні методи rightі left(не є сеттером і геттером). По-друге, заздалегідь не можна припустити, де він і його зовнішній клас Bicycleможуть нам знадобитися — це можуть бути десятки різних місць і методів навіть у рамках однієї програми. А ось із класом PhoneNumberвсе набагато простіше. Програма у нас дуже проста. Вона має одну функцію — перевірити, чи є число номером телефону. У більшості випадків нашPhoneNumberValidatorбуде навіть не самостійною програмою, а просто частиною логіки авторизації для основної програми. Наприклад, на різних сайтах під час реєстрації часто просять ввести номер телефону. І якщо надрукувати якусь нісенітницю замість цифр, сайт видасть помилку: «Це не номер телефону!». Для роботи такого сайту (а точніше механізму авторизації користувача) його розробники можуть включити в код аналог нашогоPhoneNumberValidator. Іншими словами, ми маємо один зовнішній клас з одним методом, який буде використаний в одному місці програми і більше ніде. А якщо й буде, то в ньому нічого не зміниться: один метод робить свою роботу і все. У цьому випадку, якщо вся логіка роботи зібрана в одному методі, буде набагато зручніше і правильніше інкапсулювати там і додатковий клас. Своїх методів, крім геттера та сеттера, у нього немає. Нам, по суті, потрібні лише дані із нього конструктора. В інших методах він не задіяний. Тому немає причин виносити інформацію про нього за межі єдиного методу, в якому він використовується. Ми і навели приклад з оголошенням локального класу у методі, але це не єдина можливість. Його можна оголосити просто в блоці коду:
public class PhoneNumberValidator {

   {
       class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

   }

   public void validatePhoneNumber(String phoneNumber) {


       //...код валидации номера
   }
}
Або навіть у циклі for!
public class PhoneNumberValidator {


   public void validatePhoneNumber(String phoneNumber) {

       for (int i = 0; i < 10; i++) {

           class PhoneNumber {

               private String phoneNumber;

               public PhoneNumber(String phoneNumber) {
                   this.phoneNumber = phoneNumber;
               }
           }

           //...якая-то логика
       }

       //...код валидации номера
   }
}
Але такі випадки дуже рідко трапляються. У більшості випадків оголошення буде відбуватися все ж таки всередині методу. Отже, з оголошенням ми розібралися, про «філософію» теж поговорабо :) Які ж локальні класи мають ще особливості та відмінності від внутрішніх класів? Об'єкт локального класу неспроможна створюватися поза методу чи блоку, у якому його оголосабо. Уяви, що нам потрібен метод generatePhoneNumber(), який генерував би випадковий номер телефону і повертав об'єкт PhoneNumber. Ми не зможемо створити такий метод у нашому класі-валідоторі в поточній ситуації:
public class PhoneNumberValidator {

   public void validatePhoneNumber(String number) {

        class PhoneNumber {

           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }

           public String getPhoneNumber() {
               return phoneNumber;
           }

           public void setPhoneNumber(String phoneNumber) {
               this.phoneNumber = phoneNumber;
           }
       }

       //...код валидации номера
   }

   //ошибка! компилятор не понимает, что это за класс - PhoneNumber
   public PhoneNumber generatePhoneNumber() {

   }

}
Ще одна важлива особливість локальних класів – можливість доступу до локальних змінних та параметрів методу. Якщо ти раптом забув, «локальною» називають змінну, оголошену всередині методу. Тобто, якщо ми створимо для якихось своїх цілей локальну змінну String russianCountryCodeвсередині методу validatePhoneNumber(), ми можемо отримати доступ до неї з локального класу PhoneNumber. Однак тут є дуже багато тонкощів, які залежать від версії мови, що використовується в програмі. На початку лекції ми зробабо відмітку про те, що код одного з прикладів може не компілюватися в Java 7, пам'ятаєш? Зараз розглянемо причини цього :) У Java 7 локальний клас може отримати доступ до локальної змінної або параметр методу, тільки якщо вони оголошені в методі як final:
public void validatePhoneNumber(String number) {

   String russianCountryCode = "+7";

   class PhoneNumber {

       private String phoneNumber;

       //ошибка! параметр метода должен быть объявлен як final!
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printRussianCountryCode() {

           //ошибка! локальная переменная должна быть объявлена як final!
           System.out.println(russianCountryCode);
       }

   }

   //...код валидации номера
}
Тут компілятор викинув дві помилки. А ось тут все гаразд:
public void validatePhoneNumber(final String number) {

   final String russianCountryCode = "+7";

    class PhoneNumber {

       private String phoneNumber;


       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printRussianCountryCode() {

           System.out.println(russianCountryCode);
       }

    }

   //...код валидации номера
}
Тепер ти знаєш причину, з якої код з початку лекції не компілювався: локальний клас у версії Java 7 має доступ тільки до finalпараметрів методу і finalлокальних змінних. У Java 8 поведінка локальних класів було змінено. У цій версії мови локальний клас має доступ не тільки до finalлокальних змінних і параметрів, але і до effective-final. Effective-finalназивають змінну, значення якої змінювалося після ініціалізації. Наприклад, у Java 8 ми без проблем можемо вивести в консоль змінну russianCountryCode, навіть якщо вона – не final. Головне, щоб вона не змінювала свого значення. Ось у цьому прикладі все працює як слід:
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";

    class PhoneNumber {

       public void printRussianCountryCode() {

           //в Java 7 здесь была бы ошибка
           System.out.println(russianCountryCode);
       }

    }

   //...код валидации номера
}
А от якщо ми змінимо значення змінної відразу після ініціалізації, код не скомпілюється.
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";
  russianCountryCode = "+8";

    class PhoneNumber {

       public void printRussianCountryCode() {

           //помилка!
           System.out.println(russianCountryCode);
       }

    }

   //...код валидации номера
}
Але недарма локальний клас – підвид внутрішнього класу! Вони мають і загальні моменти. У локального класу є доступ до всіх (навіть приватних) полів та методів зовнішнього класу: і до статичних, і до нестатичних. Для прикладу, додамо до нашого класу-валідатора статичне поле String phoneNumberRegex:
public class PhoneNumberValidator {

   private static String phoneNumberRegex = "[^0-9]";

   public void validatePhoneNumber(String phoneNumber) {
       class PhoneNumber {

           //......
       }
   }
}
За допомогою цієї статичної змінної і виконуватиметься валідація. Метод перевіряє, чи є рядку, який йому передали, символи, що не відповідають регулярному виразу " [^0-9]" (тобто символ не є цифрою від 0 до 9). Ми легко можемо отримати доступ до цієї змінної з локального класу PhoneNumber. Наприклад, написати гетер:
public String getPhoneNumberRegex() {

   return phoneNumberRegex;
}
Локальні класи схожі на внутрішні класи, оскільки вони можуть визначати чи оголошувати якісь статичні члени. Локальні класи в статичних методах можуть посилатися тільки на статичні члени класу, що включає. Наприклад, якщо ти не визначив змінну (поле) класу, що включає, як статичну, компілятор Java генерує помилку: «З статичного контексту не можна посилатися на нестатичну змінну». Локальні класи не статичні, тому що у них є доступ до членів екземпляра блоку, що вміщає. Отже, вони можуть містити більшість видів статичних оголошень. Не можна оголосити інтерфейс усередині блоку; інтерфейси за своєю природою статичні. Цей код не скомпілюється:
public class PhoneNumberValidator {
   public static void validatePhoneNumber(String number) {
       interface I {}

       class PhoneNumber implements I{
           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }
       }

       //...код валидации номера
   }
}
Але якщо інтерфейс оголошено всередині зовнішнього класу, клас PhoneNumberможе його реалізувати:
public class PhoneNumberValidator {
   interface I {}

   public static void validatePhoneNumber(String number) {

       class PhoneNumber implements I{
           private String phoneNumber;

           public PhoneNumber() {
               this.phoneNumber = number;
           }
       }

       //...код валидации номера
   }
}
У локальних класах не можна оголошувати статичні ініціалізатори (блоки ініціалізації) чи інтерфейси. Але в локальних класів може бути статичні члени за умови, що вони постійні змінні ( static final). Отакі вони, локальні класи! Як бачиш, вони мають чимало відмінностей від внутрішніх класів. Нам навіть довелося поринути особливо версії мови, щоб розібратися в їх роботі :) На наступній лекції поговоримо про анонімні внутрішні класи — останню групу вкладених класів. Успіхів у навчанні! :)
Коментарі
  • популярні
  • нові
  • старі
Щоб залишити коментар, потрібно ввійти в систему
Для цієї сторінки немає коментарів.