היי! היום נסתכל על מנגנון חשוב: ירושה במחלקות מקוננות. האם אי פעם חשבת מה תעשה אם תצטרך לגרום למחלקה מקוננת לרשת מחלקה אחרת. אם לא, תאמין לי: המצב הזה יכול להיות מבלבל, כי יש הרבה ניואנסים.
- האם אנחנו גורמים למחלקה מקוננת לרשת מחלקה כלשהי? או שאנחנו גורמים למחלקה כלשהי לרשת מחלקה מקוננת?
- האם כיתת הילד/הורה היא כיתה ציבורית רגילה, או שהיא גם כיתה מקוננת?
- לבסוף, באיזה סוג של מחלקות מקוננות אנו משתמשים בכל המצבים האלה?
מחלקות מקוננות סטטיות
כללי הירושה שלהם הם הפשוטים ביותר. כאן אתה יכול לעשות כמעט כל מה שלבך חפץ. מחלקה מקוננת סטטית יכולה לרשת:- כיתה רגילה
- מחלקה מקוננת סטטית המוכרזת במחלקה חיצונית או באבותיה
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
public static int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
בואו ננסה לשנות את הקוד וליצור Drawing
מחלקה מקוננת סטטית וצאצא שלה - Boeing737Drawing
.
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
}
public static class Boeing737Drawing extends Drawing {
public static int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
כפי שאתה יכול לראות, אין בעיה. אנחנו אפילו יכולים לשלוף את Drawing
המחלקה ולהפוך אותה למחלקה ציבורית רגילה במקום מחלקה מקוננת סטטית - שום דבר לא ישתנה.
public class Drawing {
}
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Boeing737Drawing extends Drawing {
public static int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
אנחנו מבינים את זה. אבל אילו מחלקות יכולות לרשת מחלקה מקוננת סטטית? כמעט כל! מקונן/לא מקונן, סטטי/לא סטטי - זה לא משנה. כאן אנו גורמים Boeing737Drawing
למחלקה הפנימית לרשת את Drawing
המחלקה המקוננת הסטטית:
public class Boeing737 {
private int manufactureYear;
private static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
}
public class Boeing737Drawing extends Drawing {
public int getMaxPassengersCount() {
return maxPassengersCount;
}
}
}
אתה יכול ליצור מופע Boeing737Drawing
כזה:
public class Main {
public static void main(String[] args) {
Boeing737 boeing737 = new Boeing737(1990);
Boeing737.Boeing737Drawing drawing = boeing737.new Boeing737Drawing();
System.out.println(drawing.getMaxPassengersCount());
}
}
למרות Boeing737Drawing
שהכיתה שלנו יורשת מחלקה סטטית, היא לא סטטית בעצמה! כתוצאה מכך, הוא תמיד יצטרך מופע של המעמד החיצוני. אנחנו יכולים להסיר את Boeing737Drawing
הכיתה מהכיתה Boeing737
ולהפוך אותה לכיתה ציבורית פשוטה. כלום לא משתנה. זה עדיין יכול לרשת את Drawing
המחלקה המקוננת הסטטית.
public class Boeing737 {
private int manufactureYear;
public static int maxPassengersCount = 300;
public Boeing737(int manufactureYear) {
this.manufactureYear = manufactureYear;
}
public int getManufactureYear() {
return manufactureYear;
}
public static class Drawing {
}
}
public class Boeing737Drawing extends Boeing737.Drawing {
public int getMaxPassengersCount() {
return Boeing737.maxPassengersCount;
}
הנקודה החשובה היחידה היא שבמקרה זה עלינו להפוך את maxPassengersCount
המשתנה הסטטי לציבורי. אם זה נשאר פרטי, אז לכיתה ציבורית רגילה לא תהיה גישה אליו. גילינו שיעורים סטטיים! :) עכשיו נעבור לשיעורים פנימיים. הם מגיעים ב-3 סוגים: כיתות פנימיות פשוטות, כיתות מקומיות וכיתות פנימיות אנונימיות. שוב, בואו נעבור מפשוט למורכב :)
כיתות פנימיות אנונימיות
כיתה פנימית אנונימית לא יכולה לרשת כיתה אחרת. אף כיתה אחרת לא יכולה לרשת כיתה אנונימית. זה לא יכול להיות יותר פשוט! :)חוגים מקומיים
מחלקות מקומיות (למקרה ששכחת) מוצהרות בתוך בלוק קוד של מחלקה אחרת. לרוב, זה קורה בתוך שיטה כלשהי של המחלקה החיצונית. באופן הגיוני, רק מחלקות מקומיות אחרות בתוך אותה שיטה (או בלוק קוד) יכולות לרשת מחלקה מקומית. הנה דוגמא:public class PhoneNumberValidator {
public void validatePhoneNumber(final 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;
}
}
class CellPhoneNumber extends PhoneNumber {
}
class LandlinePhoneNumber extends PhoneNumber {
}
// ...number validation code
}
}
זה הקוד מהשיעור שלנו על שיעורים מקומיים. למחלקת אימות המספרים שלנו יש PhoneNumber
מחלקה מקומית. אם אנחנו צריכים את זה כדי לייצג שתי ישויות נפרדות, למשל, מספר טלפון נייד ומספר טלפון קווי, נוכל לעשות זאת רק באותה שיטה. הסיבה פשוטה: ההיקף של מחלקה מקומית מוגבל למתודה (גוש קוד) שבה היא מוצהרת. כתוצאה מכך, לא נוכל להשתמש בו באופן חיצוני (כולל עבור ירושה כיתתית). עם זאת, האפשרויות להורשה בתוך המעמד המקומי עצמו רחבות הרבה יותר! מחלקה מקומית יכולה לרשת:
- כיתה רגילה.
- מעמד פנימי המוכרז באותה מעמד כמו המעמד המקומי או באחד מאבותיו.
- מחלקה מקומית נוספת שהוכרזה באותה שיטה (קוד בלוק).
public class PhoneNumberValidator {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
public void validatePhoneNumber(final String number) {
class CellPhoneNumber extends PhoneNumber {
public CellPhoneNumber(String phoneNumber) {
super(number);
}
}
class LandlinePhoneNumber extends PhoneNumber {
public LandlinePhoneNumber(String phoneNumber) {
super(number);
}
}
// ...number validation code
}
}
כאן הסרנו את PhoneNumber
המחלקה מהשיטה validatePhoneNumber()
והפכנו אותה למחלקה פנימית במקום למחלקה מקומית. זה לא מונע מאיתנו לגרום ל-2 הכיתות המקומיות שלנו לרשת אותו. דוגמה 2 - "... או באבות של מעמד זה." עכשיו זה כבר יותר מעניין. אנחנו יכולים להתקדם PhoneNumber
אפילו גבוה יותר בשרשרת הירושה. בואו נכריז על AbstractPhoneNumberValidator
מעמד מופשט, שיהפוך לאב הקדמון של PhoneNumberValidator
הכיתה שלנו:
public abstract class AbstractPhoneNumberValidator {
class PhoneNumber {
private String phoneNumber;
public PhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
}
}
כפי שאתה יכול לראות, לא רק הכרזנו על זה - העברנו גם את PhoneNumber
המעמד הפנימי אליו. עם זאת, בצאצאיו PhoneNumberValidator
, מחלקות מקומיות המוצהרות בשיטות יכולות לרשת PhoneNumber
ללא כל בעיה!
public class PhoneNumberValidator extends AbstractPhoneNumberValidator {
public void validatePhoneNumber(final String number) {
class CellPhoneNumber extends PhoneNumber {
public CellPhoneNumber(String phoneNumber) {
super(number);
}
}
class LandlinePhoneNumber extends PhoneNumber {
public LandlinePhoneNumber(String phoneNumber) {
super(number);
}
}
// ...number validation code
}
}
בשל יחסי הירושה, המעמדות המקומיים בתוך מעמד צאצא "רואות" את המעמדות הפנימיים בתוך אב קדמון. ולבסוף, בואו נמשיך לקבוצה האחרונה :)
כיתות פנימיות
מעמד פנימי שהוכרז באותו מעמד חיצוני (או בצאצא שלו) יכול לרשת מעמד פנימי אחר. בואו נחקור את זה באמצעות הדוגמה שלנו עם אופניים מהשיעור על כיתות פנימיות.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!");
}
class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
class SportSeat extends Seat {
// ...methods
}
}
כאן הכרזנו על Seat
המעמד הפנימי בתוך Bicycle
הכיתה. סוג מיוחד של מושב מירוץ, SportSeat
, יורש אותו. אבל, נוכל ליצור סוג נפרד של "אופני מירוץ" ולהכניס אותם למחלקה נפרדת:
public class SportBicycle extends Bicycle {
public SportBicycle(String model, int maxWeight) {
super(model, maxWeight);
}
class SportSeat extends Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
}
זו גם אופציה. המעמד הפנימי של הצאצא ( SportBicycle.SportSeat
) "רואה" את המעמדות הפנימיים של האב הקדמון ויכול לרשת אותם. לרשת מעמדות פנימיים יש תכונה אחת חשובה מאוד! בשתי הדוגמאות הקודמות, SportSeat
הכיתה שלנו הייתה כיתה פנימית. אבל מה אם נחליט לעשות SportSeat
מעמד ציבורי רגיל שיורש בו זמנית את Seat
המעמד הפנימי?
// Error! No enclosing instance of type 'Bicycle' is in scope
class SportSeat extends Bicycle.Seat {
public SportSeat() {
}
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
קיבלנו שגיאה! אתה יכול לנחש למה? :) הכל פשוט. כשדיברנו על Bicycle.Seat
המחלקה הפנימית, הזכרנו שהפניה למופע של המחלקה החיצונית מועברת באופן מרומז לבנאי המחלקה הפנימית. משמעות הדבר היא שאינך יכול ליצור Seat
אובייקט מבלי ליצור Bicycle
אובייקט. אבל מה לגבי יצירת SportSeat
? שלא כמו Seat
, אין לו מנגנון מובנה זה להעברת הבנאי באופן מרומז הפניה למופע של המחלקה החיצונית. S till, ללא Bicycle
אובייקט, איננו יכולים ליצור SportSeat
אובייקט, בדיוק כמו במקרה של Seat
. לכן, נותר לנו רק דבר אחד לעשות - להעביר במפורש לבנאי SportSeat
הפניה לאובייקט Bicycle
. הנה איך לעשות את זה:
class SportSeat extends Bicycle.Seat {
public SportSeat(Bicycle bicycle) {
bicycle.super();
}
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
אנו קוראים לבנאי מחלקת העל באמצעות super();
Now, אם אנו רוצים ליצור SportSeat
אובייקט, שום דבר לא יעצור אותנו מלעשות זאת:
public class Main {
public static void main(String[] args) {
Bicycle bicycle = new Bicycle("Peugeot", 120);
SportSeat peugeotSportSeat = new SportSeat(bicycle);
}
}
פיו! השיעור הזה היה די ארוך :) אבל למדת הרבה! עכשיו הגיע הזמן לפתור כמה משימות! :)
GO TO FULL VERSION