class OuterClass {
...
static class StaticNestedClass {
...
}
class InnerClass {
...
}
}
تسمى هذه الفئات الداخلية متداخلة. وهي مقسمة إلى نوعين:
- فئات متداخلة غير ثابتة. وتسمى هذه أيضًا الطبقات الداخلية.
- فئات متداخلة ثابتة.
- فئة محلية
- فئة مجهولة
public class Bicycle {
private String model;
private int weight;
public Bicycle(String model, int weight) {
this.model = model;
this.weight = weight;
}
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!");
}
}
public class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
}
هنا لدينا الفصل Bicycle
. يحتوي على حقلين وطريقة واحدة: start()
. وهو يختلف عن الفصل العادي في أنه يحتوي على فئتين: Handlebar
و Seat
. يتم كتابة التعليمات البرمجية الخاصة بهم داخل Bicycle
الفصل. هذه فصول كاملة: كما ترون، كل واحد منهم لديه أساليبه الخاصة. عند هذه النقطة، قد يكون لديك سؤال: لماذا بحق السماء نضع فئة داخل أخرى؟ لماذا جعلهم الطبقات الداخلية؟ حسنًا، لنفترض أننا بحاجة إلى فصول منفصلة لمفاهيم المقود والمقعد في برنامجنا. بالطبع، ليس من الضروري بالنسبة لنا أن نجعلها متداخلة! يمكننا أن نجعل الطبقات العادية. على سبيل المثال، مثل هذا:
public class Handlebar {
public void right() {
System.out.println("Steer right!");
}
public void left() {
System.out.println("Steer left");
}
}
public class Seat {
public void up() {
System.out.println("Seat up!");
}
public void down() {
System.out.println("Seat down!");
}
}
سؤال جيد جدا! بالطبع، نحن لسنا مقيدين بالتكنولوجيا. القيام بذلك هو بالتأكيد خيار. الشيء المهم هنا هو التصميم الصحيح للفصول الدراسية من منظور برنامج معين والغرض منه. الطبقات الداخلية مخصصة لفصل كيان مرتبط بشكل لا ينفصم بكيان آخر. تعتبر المقاود والمقاعد والدواسات من مكونات الدراجة. منفصلة عن الدراجة، فهي لا معنى لها. إذا جعلنا كل هذه المفاهيم منفصلة عن الفئات العامة، لكان لدينا كود مثل هذا في برنامجنا:
public class Main {
public static void main(String[] args) {
Handlebar handlebar = new Handlebar();
handlebar.right();
}
}
حسنًا... من الصعب أيضًا شرح معنى هذا الرمز. لدينا بعض المقود الغامض (لماذا هو ضروري؟ لأكون صادقًا، ليس لدي أي فكرة). وهذا المقبض يستدير لليمين... من تلقاء نفسه، بدون دراجة... لسبب ما. ومن خلال فصل مفهوم المقود عن مفهوم الدراجة، فقدنا بعض المنطق في برنامجنا. باستخدام فئة داخلية، يبدو الرمز مختلفًا تمامًا:
public class Main {
public static void main(String[] args) {
Bicycle peugeot = new Bicycle("Peugeot", 120);
Bicycle.Handlebar handlebar = peugeot.new Handlebar();
Bicycle.Seat seat = peugeot.new Seat();
seat.up();
peugeot.start();
handlebar.left();
handlebar.right();
}
}
إخراج وحدة التحكم:
Seat up!
Let's go!
Steer left!
Steer right!
الآن ما نراه فجأة أصبح منطقيًا! :) لقد أنشأنا كائنًا للدراجة. لقد أنشأنا "شيئين فرعيين" للدراجة - المقود والمقعد. لقد رفعنا المقعد من أجل الراحة وانطلقنا: استخدام الدواسات والتوجيه حسب الحاجة! :) يتم استدعاء الأساليب التي نحتاجها على الكائنات المناسبة. كل شيء بسيط ومريح. في هذا المثال، يؤدي فصل المقود والمقعد إلى تحسين التغليف (نقوم بإخفاء البيانات المتعلقة بأجزاء الدراجة داخل الفئة ذات الصلة) ويتيح لنا إنشاء تجريد أكثر تفصيلاً. الآن دعونا ننظر إلى وضع مختلف. لنفترض أننا نريد إنشاء برنامج يحاكي متجر دراجات وقطع غيار للدراجات. في هذه الحالة، لن يكون حلنا السابق ناجحًا. في متجر الدراجات، يكون كل جزء فردي من أجزاء الدراجة منطقيًا حتى عند فصله عن الدراجة. على سبيل المثال، سنحتاج إلى أساليب مثل "بيع الدواسات للعميل"، و"شراء مقعد جديد"، وما إلى ذلك. سيكون من الخطأ استخدام الفئات الداخلية هنا - فكل جزء فردي من أجزاء الدراجة في برنامجنا الجديد له معنى يقف على خاص بها: يمكن فصله عن مفهوم الدراجة. هذا هو بالضبط ما تحتاج إلى الانتباه إليه إذا كنت تتساءل عما إذا كان يجب عليك استخدام الفئات الداخلية أو تنظيم جميع الكيانات كفئات منفصلة. تعد البرمجة الشيئية جيدة لأنها تجعل من السهل نمذجة كيانات العالم الحقيقي. يمكن أن يكون هذا هو المبدأ التوجيهي الخاص بك عند تحديد ما إذا كنت تريد استخدام الفئات الداخلية أم لا. في المتجر الحقيقي، تكون قطع الغيار منفصلة عن الدراجات - وهذا أمر جيد. وهذا يعني أنه لا بأس أيضًا عند تصميم البرنامج. حسنًا، لقد اكتشفنا "الفلسفة" :) الآن دعنا نتعرف على الميزات "التقنية" المهمة للطبقات الداخلية. إليك ما تحتاج بالتأكيد إلى تذكره وفهمه:
-
لا يمكن أن يوجد كائن من فئة داخلية بدون كائن من فئة خارجية.
هذا منطقي: ولهذا السبب قمنا بإنشاء الصفوف
Seat
الداخليةHandlebar
في برنامجنا - حتى لا ينتهي بنا الأمر بمقود ومقاعد يتيمة.لا يتم تجميع هذا الرمز:
public static void main(String[] args) { Handlebar handlebar = new Handlebar(); }
ميزة أخرى مهمة تتبع هذا:
-
كائن من فئة داخلية لديه حق الوصول إلى متغيرات الفئة الخارجية.
على سبيل المثال، دعونا نضيف متغيرًا
int seatPostDiameter
(يمثل قطر عمود المقعد) إلى فئتناBicycle
.ثم في
Seat
الطبقة الداخلية، يمكننا إنشاءdisplaySeatProperties()
طريقة تعرض خصائص المقعد:public class Bicycle { private String model; private int weight; private int seatPostDiameter; public Bicycle(String model, int weight, int seatPostDiameter) { this.model = model; this.weight = weight; this.seatPostDiameter = seatPostDiameter; } public void start() { System.out.println("Let's go!"); } public class Seat { public void up() { System.out.println("Seat up!"); } public void down() { System.out.println("Seat down!"); } public void displaySeatProperties() { System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter); } } }
والآن يمكننا عرض هذه المعلومات في برنامجنا:
public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); Bicycle.Seat seat = bicycle.new Seat(); seat.displaySeatProperties(); } }
إخراج وحدة التحكم:
Seat properties: seatpost diameter = 40
ملحوظة:يتم الإعلان عن المتغير الجديد باستخدام معدل الوصول الأكثر صرامة (
private
). وما زال للطبقة الداخلية حق الوصول! -
لا يمكن إنشاء كائن من فئة داخلية بطريقة ثابتة لفئة خارجية.
يتم تفسير ذلك من خلال الميزات المحددة لكيفية تنظيم الطبقات الداخلية. يمكن أن تحتوي الفئة الداخلية على مُنشئات ذات معلمات، أو مُنشئ افتراضي فقط. ولكن بغض النظر، عندما نقوم بإنشاء كائن من فئة داخلية، يتم تمرير مرجع إلى كائن من الفئة الخارجية بشكل غير مرئي إلى الكائن الذي تم إنشاؤه من الفئة الداخلية. بعد كل شيء، فإن وجود مثل هذا المرجع للكائن هو مطلب مطلق. وإلا فلن نتمكن من إنشاء كائنات من الفئة الداخلية.
لكن إذا كانت إحدى طرق الطبقة الخارجية ثابتة، فقد لا يكون لدينا كائن من الطبقة الخارجية! وهذا من شأنه أن يشكل انتهاكًا لمنطق كيفية عمل الطبقة الداخلية. في هذه الحالة، سيولد المترجم خطأ:
public static Seat createSeat() { // Bicycle.this cannot be referenced from a static context return new Seat(); }
-
لا يمكن للفئة الداخلية أن تحتوي على متغيرات وأساليب ثابتة.
المنطق هو نفسه: يمكن أن توجد الأساليب والمتغيرات الثابتة ويمكن استدعاؤها أو الرجوع إليها حتى في حالة عدم وجود كائن.
لكن بدون كائن من الطبقة الخارجية، لن نتمكن من الوصول إلى الطبقة الداخلية.
تناقض واضح! هذا هو السبب في عدم السماح بالمتغيرات والأساليب الثابتة في الفئات الداخلية.
سيقوم المترجم بإنشاء خطأ إذا حاولت إنشائها:
public class Bicycle { private int weight; public class Seat { // An inner class cannot have static declarations public static void displaySeatProperties() { System.out.println("Seat properties: seatpost diameter = " + Bicycle.this.seatPostDiameter); } } }
-
عند إنشاء كائن من فئة داخلية، يعد معدل الوصول الخاص به مهمًا.
يمكن تمييز الفئة الداخلية باستخدام معدِّلات الوصول القياسية:
public
وprivate
وprotected
و وpackage private
.لماذا يهم هذا؟
يؤثر هذا على المكان الذي يمكننا فيه إنشاء مثيلات للطبقة الداخلية في برنامجنا.
إذا
Seat
تم الإعلان عن فئتنا كـpublic
، فيمكننا إنشاءSeat
كائنات في أي فئة أخرى. الشرط الوحيد هو أن كائن الطبقة الخارجية يجب أن يكون موجودًا أيضًا.بالمناسبة، لقد فعلنا هذا بالفعل هنا:
public class Main { public static void main(String[] args) { Bicycle peugeot = new Bicycle("Peugeot", 120); Bicycle.Handlebar handlebar = peugeot.new Handlebar(); Bicycle.Seat seat = peugeot.new Seat(); seat.up(); peugeot.start(); handlebar.left(); handlebar.right(); } }
لقد تمكنا بسهولة من الوصول إلى
Handlebar
الطبقة الداخلية منMain
الفصل.إذا أعلنا أن الفئة الداخلية هي
private
، فسنكون قادرين على إنشاء كائنات داخل الطبقة الخارجية فقط.لم يعد بإمكاننا إنشاء
Seat
كائن "من الخارج":private class Seat { // Methods } public class Main { public static void main(String[] args) { Bicycle bicycle = new Bicycle("Peugeot", 120, 40); // Bicycle.Seat has private access in Bicycle Bicycle.Seat seat = bicycle.new Seat(); } }
ربما تكون قد فهمت المنطق بالفعل :)
-
تعمل معدّلات الوصول للفئات الداخلية بنفس الطريقة بالنسبة للمتغيرات العادية.
يوفر المعدل
protected
الوصول إلى متغير مثيل في الفئات الفرعية والفئات الموجودة في نفس الحزمة.protected
يعمل أيضًا للطبقات الداخلية. يمكننا إنشاءprotected
كائنات من الطبقة الداخلية:- في الطبقة الخارجية؛
- في فئاتها الفرعية.
- في الفئات الموجودة في نفس الحزمة.
إذا لم يكن للفئة الداخلية معدل وصول (
package private
)، فيمكن إنشاء كائنات للفئة الداخلية:- في الطبقة الخارجية؛
- في الفئات الموجودة في نفس الحزمة.
لقد كنت على دراية بالمعدلات لفترة طويلة، لذلك لا توجد مشاكل هنا.
GO TO FULL VERSION