JavaRush /בלוג Java /Random-HE /כיתות פנימיות בשיטה מקומית

כיתות פנימיות בשיטה מקומית

פורסם בקבוצה
שלום! בואו נדבר על סוג אחר של מחלקה מקוננת. כלומר, על כיתות מקומיות (שיטה מקומית פנימית). הדבר הראשון שאתה צריך לזכור לפני הלימוד הוא מקומם במבנה של כיתות מקוננות. כיתות פנימיות בשיטה מקומית - 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 валидации номера
   }
}
חָשׁוּב!קוד זה לא יקמפל כשהוא מודבק ב-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("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(אינן קובעות וגטר). שנית, אנחנו לא יכולים לחזות מראש היכן 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() {

   }

}
תכונה חשובה נוספת של מחלקות מקומיות היא היכולת לגשת למשתנים מקומיים ולפרמטרים של שיטה. למקרה ששכחת, "מקומי" הוא משתנה המוצהר בתוך שיטה. String russianCountryCodeכלומר, אם ניצור משתנה מקומי בתוך מתודה לכמה מהמטרות שלנו validatePhoneNumber(), נוכל לגשת אליו מהמחלקה המקומית PhoneNumber. עם זאת, יש כאן הרבה דקויות התלויות בגרסת השפה שבה נעשה שימוש בתוכנית. בתחילת ההרצאה רשמנו שייתכן שהקוד באחת הדוגמאות אינו קומפילציה ב-Java 7, זוכרים? עכשיו בואו נסתכל על הסיבות לכך :) ב-Java 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 валидации номера
}
עכשיו אתם יודעים את הסיבה לכך שהקוד בתחילת ההרצאה לא התקמפל: למחלקה מקומית ב-Java 7 יש גישה רק לפרמטרים final-method ולמשתנים final-local. ב-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);
       }

    }

   //...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;
}
מחלקות מקומיות דומות למחלקות פנימיות מכיוון שאינן יכולות להגדיר או להכריז על איברים סטטיים. מחלקות מקומיות בשיטות סטטיות יכולות להתייחס רק לחברים סטטיים של המחלקה המקיפה. לדוגמה, אם אינך מגדיר משתנה (שדה) של המחלקה המקיפה כסטטי, מהדר Java יוצר שגיאה: "לא ניתן להפנות למשתנה לא סטטי מהקשר סטטי." מחלקות מקומיות אינן סטטיות מכיוון שיש להן גישה לחברי המופע של הבלוק המכיל. לכן, הם אינם יכולים להכיל את רוב סוגי ההצהרות הסטטיות. אתה לא יכול להכריז על ממשק בתוך בלוק; ממשקים הם סטטיים באופיים. הקוד הזה לא יקמפל:
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