CodeGym /בלוג Java /Random-HE /כיתות פנימיות בשיטה מקומית
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
   }
}
חָשׁוּב!אם מותקנת אצלך Java 7, קוד זה לא יקמפל כשהוא מודבק ב-IDEA. על הסיבות לכך נדבר בסוף השיעור. בקיצור, אופן הפעולה של השיעורים המקומיים תלוי מאוד בגרסה של השפה. אם הקוד הזה לא מסתדר עבורך, אתה יכול להחליף את גרסת השפה ב-IDEA ל-Java 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משתנה מקומי בתוך validatePhoneNumber()השיטה משום מה, נוכל לגשת אליו מהמחלקה המקומית PhoneNumber. עם זאת, יש הרבה דקויות התלויות בגרסה של השפה שבה נעשה שימוש בתוכנית. בתחילת השיעור, ציינו שייתכן שהקוד של אחת הדוגמאות אינו קומפילציה ב-Java 7, זוכרים? עכשיו בואו נבחן את הסיבות לכך :) ב-Java 7, מחלקה מקומית יכולה לגשת למשתנה מקומי או פרמטר מתודה רק אם הם מוצהרים כמו finalבשיטה:
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
}
עכשיו אתה יודע למה הקוד מתחילת השיעור לא היה קומפילציה: ב-Java 7, למחלקה מקומית יש גישה רק לפרמטרים של finalמתודה ומשתנים finalמקומיים. ב-Java 8, ההתנהגות של מחלקות מקומיות השתנתה. בגרסה זו של השפה, למחלקה מקומית יש גישה לא רק למשתנים finalופרמטרים מקומיים, אלא גם לאלה שהם effective-final. Effective-finalהוא משתנה שערכו לא השתנה מאז האתחול. לדוגמה, ב-Java 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;
}
מחלקות מקומיות דומות למחלקות פנימיות, מכיוון שהן אינן יכולות להגדיר או להכריז על איברים סטטיים. מחלקות מקומיות בשיטות סטטיות יכולות להתייחס רק לחברים סטטיים של המחלקה המקיפה. לדוגמה, אם אתה לא מגדיר משתנה (שדה) של המחלקה המקיפה כסטטי, אז מהדר Java יוצר שגיאה: "לא ניתן להפנות למשתנה לא סטטי מהקשר סטטי." מחלקות מקומיות אינן סטטיות, מכיוון שיש להן גישה לחברי מופע בבלוק המקיף. כתוצאה מכך, הם אינם יכולים להכיל את רוב סוגי ההצהרות הסטטיות. אתה לא יכול להכריז על ממשק בתוך בלוק: ממשקים הם סטטיים מטבעם. קוד זה אינו קומפילציה:
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