JavaRush /وبلاگ جاوا /Random-FA /کلاس های داخلی به روش محلی

کلاس های داخلی به روش محلی

در گروه منتشر شد
سلام! بیایید در مورد نوع دیگری از کلاس تودرتو صحبت کنیم. یعنی در مورد کلاس های محلی (روش کلاس های داخلی محلی). اولین چیزی که قبل از مطالعه باید به خاطر بسپارید، جایگاه آنها در ساختار کلاس های تودرتو است. کلاس های داخلی به روش محلی - 2بر اساس نمودار ما، می‌توان فهمید که کلاس‌های محلی زیرمجموعه‌ای از کلاس‌های داخلی هستند که در یکی از مطالب قبلی به تفصیل درباره آن صحبت کردیم . با این حال، کلاس های محلی دارای تعدادی ویژگی مهم و تفاوت با کلاس های داخلی هستند. کلید در اعلان آنها است: یک کلاس محلی فقط در یک بلوک کد اعلان می شود. اغلب - در داخل برخی از روش های یک کلاس خارجی. به عنوان مثال، ممکن است به شکل زیر باشد:
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;
           }
       }

       //...code валидации номера
   }
}
مهم!اگر جاوا 7 را نصب کرده باشید، این کد با چسباندن به IDEA کامپایل نمی شود. در پایان سخنرانی در مورد دلایل آن صحبت خواهیم کرد. در چند کلمه، کار کلاس های محلی به شدت به نسخه زبان وابسته است. اگر این کد برای شما کامپایل نمی‌شود، می‌توانید نسخه زبان IDEA را به جاوا 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("Go!");
   }

   public class HandleBar {

       public void right() {
           System.out.println("Steering wheel to the right!");
       }

       public void left() {

           System.out.println("Steering wheel to the left!");
       }
   }
}
در آن ما HandleBar(دسته فرمان) یک کلاس داخلی دوچرخه را ساختیم. تفاوت در چیست؟ اول از همه در استفاده از کلاس. کلاس HandleBarمثال دوم موجودیت پیچیده تری نسبت PhoneNumberبه نمونه اول است. اولا، y HandleBarمتدهای عمومی دارد rightو left(setter و getter نیستند). ثانیاً، نمی‌توانیم پیش‌بینی کنیم که کجا Bicycleممکن است به آن و کلاس خارجی آن نیاز داشته باشیم - اینها می‌توانند ده‌ها مکان و روش مختلف، حتی در یک برنامه باشند. اما با یک کلاس PhoneNumberهمه چیز بسیار ساده تر است. برنامه ما بسیار ساده است. این فقط یک عملکرد دارد - بررسی اینکه آیا شماره یک شماره تلفن است یا خیر. در بیشتر موارد، برنامه ما PhoneNumberValidatorحتی یک برنامه مستقل نخواهد بود، بلکه صرفاً بخشی از منطق مجوز برای برنامه اصلی خواهد بود. به عنوان مثال، در وب سایت های مختلف، هنگام ثبت نام، اغلب از شما خواسته می شود که یک شماره تلفن وارد کنید. و اگر به جای اعداد، مزخرفات را تایپ کنید، سایت این خطا را نشان می دهد: "این شماره تلفن نیست!" برای عملکرد چنین سایتی (یا بهتر است بگوییم مکانیسم مجوز کاربر)، توسعه دهندگان آن می توانند آنالوگ ما را در کد قرار دهند PhoneNumberValidator. به عبارت دیگر، ما یک کلاس خارجی با یک متد داریم که در یک مکان در برنامه استفاده می شود و هیچ جای دیگر. و اگر این کار را کرد، هیچ چیز در آن تغییر نخواهد کرد: یک روش کار خود را انجام می دهد - همین. در این حالت، از آنجایی که تمام منطق کار در یک روش جمع آوری می شود، بسیار راحت تر و صحیح تر خواهد بود که یک کلاس اضافی را در آنجا کپسوله کنید. به غیر از گیر و ستر روش های خاص خودش را ندارد. ما اساسا فقط به داده های سازنده از آن نیاز داریم. در روش های دیگر استفاده نمی شود. بنابراین، دلیلی وجود ندارد که اطلاعات مربوط به آن را فراتر از روش واحدی که در آن مورد استفاده قرار می‌گیرد، گسترش دهیم. مثالی از اعلان کلاس محلی در یک متد زدیم، اما این تنها امکان نیست. می توان آن را به سادگی در یک بلوک کد اعلام کرد:
public class PhoneNumberValidator {

   {
       class PhoneNumber {

           private String phoneNumber;

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

   }

   public void validatePhoneNumber(String phoneNumber) {


       //...code валидации номера
   }
}
یا حتی در یک حلقه 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;
               }
           }

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

       //...code валидации номера
   }
}
اما چنین مواردی بسیار نادر است. در بیشتر موارد، اعلان همچنان در داخل متد رخ می دهد. پس ما به اعلامیه پرداختیم، در مورد "فلسفه" هم صحبت کردیم :) کلاس های محلی چه ویژگی ها و تفاوت های دیگری با کلاس های داخلی دارند؟ یک شی کلاس محلی نمی تواند خارج از متد یا بلوکی که در آن اعلان شده است ایجاد شود. تصور کنید به روشی نیاز داریم 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;
           }
       }

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

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

   }

}
یکی دیگر از ویژگی های مهم کلاس های محلی، امکان دسترسی به متغیرهای محلی و پارامترهای متد است. اگر فراموش کردید، "local" متغیری است که در یک متد اعلام شده است. یعنی اگر برای برخی از اهداف خود یک متغیر محلی String russianCountryCodeدر داخل یک متد ایجاد کنیم validatePhoneNumber()، می توانیم از کلاس محلی به آن دسترسی داشته باشیم PhoneNumber. با این حال، ظرافت های زیادی در اینجا وجود دارد که به نسخه زبان مورد استفاده در برنامه بستگی دارد. در ابتدای سخنرانی یادداشت کردیم که ممکن است کد یکی از نمونه ها در جاوا 7 کامپایل نشود، یادتان هست؟ حال بیایید به دلایل این امر نگاه کنیم :) در جاوا 7، یک کلاس محلی تنها زمانی می تواند به متغیر محلی یا پارامتر متد دسترسی داشته باشد که در متد به صورت اعلان شده باشد final:
public void validatePhoneNumber(String number) {

   String russianCountryCode = "+7";

   class PhoneNumber {

       private String phoneNumber;

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

       public void printRussianCountryCode() {

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

   }

   //...code валидации номера
}
در اینجا کامپایلر دو خطا ایجاد کرد. اما اینجا همه چیز مرتب است:
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);
       }

    }

   //...code валидации номера
}
حالا دلیل کامپایل نشدن کد ابتدای سخنرانی را می دانید: یک کلاس محلی در جاوا 7 فقط به finalپارامترهای -method و finalمتغیرهای -local دسترسی دارد. در جاوا 8، رفتار کلاس های محلی تغییر کرده است. در این نسخه از زبان، کلاس محلی نه تنها به finalمتغیرها و پارامترهای -local، بلکه به effective-final. Effective-finalمتغیری است که مقدار آن از زمان اولیه تغییر نکرده است. به عنوان مثال، در جاوا 8 به راحتی می توانیم یک متغیر را به کنسول نمایش دهیم russianCountryCode، حتی اگر اینطور نباشد final. نکته اصلی این است که معنای خود را تغییر نمی دهد. در این مثال، همه چیز همانطور که باید کار می کند:
public void validatePhoneNumber(String number) {

  String russianCountryCode = "+7";

    class PhoneNumber {

       public void printRussianCountryCode() {

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

    }

   //...code валидации номера
}
اما اگر مقدار متغیر را بلافاصله پس از مقداردهی اولیه تغییر دهیم، کد کامپایل نمی شود.
public void validatePhoneNumber(String number) {

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

    class PhoneNumber {

       public void printRussianCountryCode() {

           //error!
           System.out.println(russianCountryCode);
       }

    }

   //...code валидации номера
}
اما بی جهت نیست که یک کلاس محلی زیرمجموعه یک کلاس داخلی است! آنها نقاط مشترکی نیز دارند. یک کلاس محلی به تمام فیلدها و متدهای کلاس خارجی (حتی خصوصی) دسترسی دارد: هم ثابت و هم غیراستاتیک. به عنوان مثال، بیایید یک فیلد ثابت به کلاس اعتباردهنده خود اضافه کنیم 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;
}
کلاس های محلی شبیه به کلاس های داخلی هستند زیرا نمی توانند هیچ عضو ثابتی را تعریف یا اعلام کنند. کلاس های محلی در متدهای استاتیک فقط می توانند به اعضای استاتیک کلاس احاطه کننده ارجاع دهند. به عنوان مثال، اگر یک متغیر (فیلد) از کلاس احاطه کننده را به عنوان ثابت تعریف نکنید، کامپایلر جاوا این خطا را ایجاد می کند: "یک متغیر غیر ایستا نمی تواند از یک زمینه ایستا ارجاع شود." کلاس های محلی ایستا نیستند زیرا به اعضای نمونه بلوک حاوی دسترسی دارند. بنابراین، آنها نمی توانند حاوی اکثر انواع اعلان های ایستا باشند. شما نمی توانید یک رابط را در داخل یک بلوک اعلام کنید. رابط ها ماهیت ثابتی دارند. این کد کامپایل نمی شود:
public class PhoneNumberValidator {
   public static void validatePhoneNumber(String number) {
       interface I {}

       class PhoneNumber implements I{
           private String phoneNumber;

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

       //...code валидации номера
   }
}
اما اگر یک رابط در داخل یک کلاس خارجی اعلان شود، کلاس PhoneNumberمی تواند آن را پیاده سازی کند:
public class PhoneNumberValidator {
   interface I {}

   public static void validatePhoneNumber(String number) {

       class PhoneNumber implements I{
           private String phoneNumber;

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

       //...code валидации номера
   }
}
کلاس های محلی نمی توانند مقداردهی اولیه ایستا (بلوک های اولیه سازی) یا اینترفیس ها را اعلام کنند. اما کلاس های محلی می توانند اعضای ثابت داشته باشند، مشروط بر اینکه متغیرهای ثابت باشند ( static final). کلاس های محلی همین هستند! همانطور که می بینید تفاوت های زیادی با کلاس های داخلی دارند. ما حتی مجبور شدیم به ویژگی‌های نسخه زبان بپردازیم تا بفهمیم چگونه کار می‌کنند :) در سخنرانی بعدی، در مورد کلاس‌های داخلی ناشناس - آخرین گروه از کلاس‌های تودرتو صحبت خواهیم کرد. در تحصیلاتت موفق باشی :)
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION