CodeGym /وبلاگ جاوا /Random-FA /کلاس های داخلی به روش محلی
John Squirrels
مرحله
San Francisco

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

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

       // ...number validation code
   }
}
مهم!اگر جاوا 7 را نصب کرده باشید، این کد وقتی در IDEA جایگذاری شود، کامپایل نمی شود. در پایان درس در مورد دلایل این موضوع صحبت خواهیم کرد. به طور خلاصه، نحوه کار کلاس های محلی به شدت به نسخه زبان بستگی دارد. اگر این کد برای شما کامپایل نمی‌شود، می‌توانید نسخه زبان IDEA را به جاوا 8 تغییر دهید یا کلمه را finalبه پارامتر متد اضافه کنید تا به این صورت باشد: validatePhoneNumber(final String number). پس از آن، همه چیز کار خواهد کرد. این یک برنامه کوچک است که شماره تلفن را تأیید می کند. روش آن validatePhoneNumber()یک رشته را به عنوان ورودی می گیرد و تعیین می کند که آیا شماره تلفن است یا خیر. و در داخل این متد PhoneNumberکلاس محلی خود را اعلام کردیم. ممکن است منطقی بپرسید که چرا. چرا دقیقاً یک کلاس را در یک متد اعلام می کنیم؟ چرا از یک کلاس داخلی معمولی استفاده نمی کنید؟ درست است، ما می توانستیم PhoneNumberکلاس را یک کلاس درونی کنیم. اما راه حل نهایی به ساختار و هدف برنامه شما بستگی دارد. بیایید مثال خود را از یک درس در مورد کلاس های داخلی به یاد بیاوریم:
public class Bicycle {

   private String model;
   private int maxWeight;

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

   public void start() {
       System.out.println("Let's go!");
   }

   public class HandleBar {

       public void right() {
           System.out.println("Steer right!");
       }

       public void left() {

           System.out.println("Steer left!");
       }
   }
}
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) {


       // ...number validation 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;
               }
           }

           // ...some logic
       }

       // ...number validation 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;
           }
       }

       // ...number validation code
   }

   // Error! The compiler does not recognize the PhoneNumber class
   public PhoneNumber generatePhoneNumber() {

   }

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

   String usCountryCode = "+1";

   class PhoneNumber {

       private String phoneNumber;

       // Error! The method parameter must be declared as final!
       public PhoneNumber() {
           this.phoneNumber = number;
       }

       public void printUsCountryCode() {

           // Error! The local variable must be declared as final!
           System.out.println(usCountryCode);
       }

   }

   // ...number validation code
}
در اینجا کامپایلر دو خطا ایجاد می کند. و اینجا همه چیز مرتب است:
public void validatePhoneNumber(final String number) {

   final String usCountryCode = "+1";

    class PhoneNumber {

       private String phoneNumber;


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

       public void printUsCountryCode() {

           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
اکنون می دانید که چرا کدهای ابتدای درس کامپایل نمی شوند: در جاوا 7، یک کلاس محلی فقط به finalپارامترهای متد و finalمتغیرهای محلی دسترسی دارد. در جاوا 8، رفتار کلاس های محلی تغییر کرده است. در این نسخه از زبان، یک کلاس محلی نه تنها به finalمتغیرها و پارامترهای محلی، بلکه به آنهایی که هستند نیز دسترسی دارد effective-final. Effective-finalمتغیری است که مقدار آن از زمان اولیه تغییر نکرده است. به عنوان مثال، در جاوا 8، ما می توانیم به راحتی usCountryCodeمتغیر را در کنسول نمایش دهیم، حتی اگر نباشد final. مهم این است که ارزش آن تغییر نمی کند. در مثال زیر، همه چیز همانطور که باید کار می کند:
public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Java 7 would produce an error here
           System.out.println(usCountryCode);
       }

    }

   // ...number validation code
}
اما اگر مقدار متغیر را بلافاصله پس از مقداردهی اولیه تغییر دهیم، کد کامپایل نخواهد شد.
public void validatePhoneNumber(String number) {

  String usCountryCode = "+1";
  usCountryCode = "+8";

    class PhoneNumber {

       public void printUsCountryCode() {

           // Error!
           System.out.println(usCountryCode);
       }

    }

   // ...number validation 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;
           }
       }

       // ...number validation 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;
           }
       }

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