أهلاً بكم. في هذا الموضوع أريد أن أتحدث بالتفصيل عن فئات Java وأنواعها حتى أساعد المبتدئين على فهم هذا الموضوع، وربما يتعلم غير المبتدئين شيئًا جديدًا. حيثما أمكن، سيتم عرض كل شيء باستخدام أمثلة واقعية مع أمثلة التعليمات البرمجية المصاحبة. هيا بنا نبدأ. وأود أن أشير إلى أن الشيء الرئيسي هو فهم النوعين الأولين من الفئات، والمحلية والمجهولة هي مجرد أنواع فرعية من الطبقة الداخلية.
ما هي الطبقة؟
الفصل هو وصف منطقي لشيء ما، وهو قالب يمكنك من خلاله إنشاء أمثلة حقيقية لذلك الشيء بالذات. بمعنى آخر، إنه مجرد وصف لما ينبغي أن تكون عليه الكيانات المخلوقة: ما هي الخصائص والأساليب التي ينبغي أن تكون عليها. الخصائص هي خصائص الكيان، والأساليب هي الإجراءات التي يمكن أن يؤديها. من الأمثلة الجيدة على الفصل الدراسي من الحياة الواقعية، والذي يعطي فهمًا لماهية الفصل الدراسي، الرسومات: تُستخدم الرسومات لوصف الهياكل (المنجنيق، مفك البراغي)، لكن الرسم ليس تصميمًا. مثلما يستخدم المهندسون المخططات لإنشاء التصميمات، تستخدم البرمجة الفئات لإنشاء كائنات تصف الخصائص والأساليب.public class Student {
private String name, group, specialty;
public Student(String name, String group, String specialty) {
this.name = name;
this.group = group;
this.specialty = specialty;
}
// getters/setters
}
في هذا المثال، قمنا بإنشاء فئة Java تصف كيان "الطالب": كل طالب لديه اسم ومجموعة وتخصص. الآن، في أماكن أخرى من البرنامج، يمكننا إنشاء أمثلة حقيقية لهذه الفئة. بمعنى آخر: إذا كان الفصل Student
صورة لما يجب أن يكون عليه الطالب، فإن المثيل الذي تم إنشاؤه هو الطالب الفعلي نفسه. مثال على إنشاء طالب جديد: يبحث new Student("Ivan", "KI-17-2", "Computer Engineering");
عامل التشغيل new
عن الفصل Student
ثم يستدعي طريقة خاصة (منشئ) لهذا الفصل. يقوم المُنشئ بإرجاع كائن صفي جاهز Student
- طالبنا العزيز الجائع بدون منحة دراسية :))
أنواع الفئات في جافا
يوجد في Java 4 أنواع من الفئات داخل فئة أخرى:-
الفئات الداخلية المتداخلة هي فئات غير ثابتة داخل فئة خارجية.
-
الفئات الثابتة المتداخلة هي فئات ثابتة داخل فئة خارجية.
-
فئات Java المحلية هي فئات ضمن الأساليب.
-
فئات Java المجهولة هي فئات يتم إنشاؤها بسرعة.
فئات غير ثابتة داخل فئة خارجية
أولاً، أريدك أن تفهم ما هو الأمر بمثال حقيقي، لأنه يجعل فهمه أسهل بكثير. والآن سنقوم بتقسيم الشيء الكبير إلى مكونات أصغر، وسنقوم بتفكيك طائرة! لكن، على سبيل المثال، يكفي أن نبين القليل، ولن نقسمه بالكامل. لتصور هذه العملية، سوف نستخدم مخطط الطائرة. أولاً، نحتاج إلى إنشاء فصلAirplane
يمكننا من خلاله إضافة القليل من الوصف: اسم الطائرة، ورمز التعريف، والرحلة.
public class Airplane {
private String name, id, flight;
public Airplane(String name, String id, String flight) {
this.name = name;
this.id = id;
this.flight = flight;
}
// getters/setters
}
الآن نريد أن نضيف الأجنحة. إنشاء فئة منفصلة؟ ربما هذا هو المنطق إذا كان لدينا برنامج معقد لتصميم الطائرات، حيث نحتاج إلى إنشاء عدد كبير من الفئات المشتقة (فئات لها نفس منطق الطبقة الأم، أي الطبقة التي ترث منها، ولكن لذلك يقومون بتوسيع الفئة الأصلية عن طريق إضافة خصائص منطقية أو أكثر تفصيلاً)، ولكن ماذا لو كانت لدينا لعبة بها مستوى واحد فقط؟ عندها سيكون من الأكثر عقلانية أن نكمل الهيكل بأكمله في مكان واحد (في فصل واحد). هذا هو المكان الذي تلعب فيه الفئات المتداخلة غير الثابتة. في الأساس، هذا وصف أكثر تفصيلاً لبعض تفاصيل فئتنا الخارجية. في هذا المثال، نحن بحاجة إلى إنشاء أجنحة للطائرة - اليسار واليمين. دعونا نخلق!
public class Airplane {
private String name, id, flight;
private Wing leftWing = new Wing("Red", "X3"), rightWing = new Wing("Blue", "X3");
public Airplane(String name, String id, String flight) {
this.name = name;
this.id = id;
this.flight = flight;
}
private class Wing {
private String color, model;
private Wing(String color, String model) {
this.color = color;
this.model = model;
}
// getters/setters
}
// getters/setters
}
لذلك أنشأنا فئة متداخلة غير ثابتة Wing
(جناح) داخل فئة Airplane
(طائرة)، وأضفنا متغيرين - الجناح الأيسر والجناح الأيمن. ولكل جناح خصائصه الخاصة (اللون، النموذج) التي يمكننا تغييرها. بهذه الطريقة يمكنك توظيف الهياكل بقدر ما تحتاج إليه. ولاحظ: في وقت سابق من الرسم التخطيطي، كان هناك الكثير من أجزاء الطائرة، وفي الواقع، يمكننا تقسيم جميع الأجزاء إلى فئات داخلية، لكن مثل هذه العملية لا ينصح بها دائمًا. يجب تتبع مثل هذه اللحظات اعتمادًا على المهمة. قد لا تحتاج إلى أجنحة على الإطلاق لحل المشكلة. ثم ليست هناك حاجة للقيام بها. إنه مثل تقطيع شخص إلى ساقين وذراعين وجذع ورأس - إنه أمر ممكن، ولكن لماذا إذا تم استخدام هذه الفئة فقط لتخزين البيانات حول الأشخاص؟ ميزات فئات Java المتداخلة غير الثابتة:
- إنها موجودة فقط في الكائنات، لذا لإنشاءها تحتاج إلى كائن. بمعنى آخر: لقد صممنا جناحنا ليكون جزءًا من الطائرة، لذا لإنشاء جناح نحتاج إلى طائرة، وإلا فلن نحتاج إليها.
- لا يمكن أن يكون هناك متغيرات ثابتة داخل فئة Java. إذا كنت بحاجة إلى بعض الثوابت أو أي شيء آخر ثابت، فأنت بحاجة إلى نقلها إلى فئة خارجية. ويرجع ذلك إلى الاقتران الوثيق بين الطبقة المتداخلة غير الثابتة والطبقة الخارجية.
- يتمتع الفصل بحق الوصول الكامل إلى جميع الحقول الخاصة بالفصل الخارجي. تعمل هذه الميزة بطريقتين.
- يمكنك الحصول على مرجع لمثيل فئة خارجية. مثال: طائرة. هذا رابط لطائرة، وهذا رابط لجناح.
فئات ثابتة داخل فئة خارجية
لا يختلف هذا النوع من الفئة عن الفئة الخارجية العادية، باستثناء شيء واحد: لإنشاء مثيل لهذه الفئة، تحتاج إلى إدراج المسار بأكمله من الفئة الخارجية إلى الفئة المرغوبة، مفصولة بنقطة. على سبيل المثال:Building.Plaftorm platform = new Building.Platform();
يتم استخدام الفئات الثابتة لوضع الفئات المرتبطة جنبًا إلى جنب بحيث يسهل التعامل مع البنية المنطقية. على سبيل المثال: يمكننا إنشاء فئة خارجية Building
، حيث ستكون هناك قائمة محددة من الفئات التي ستمثل مبنى معين.
public abstract class Building {
private String name, address, type;
Building(String name, String address) {
this.name = name;
this.address = address;
}
public static class Platform extends Building {
public Platform(String name, String address) {
super(name, address);
setType("Platform");
}
// some additional logic
}
public static class House extends Building {
public House(String name, String address) {
super(name, address);
setType("House");
}
// some additional logic
}
public static class Shop extends Building {
public Shop(String name, String address) {
super(name, address);
setType("Shop");
}
// some additional logic
}
// getters/setters
}
يوضح هذا المثال كيف تسمح لك الفئات الثابتة بحزم البنية المنطقية في نموذج أكثر ملاءمة. إذا لم تكن موجودة، فسنحتاج إلى إنشاء 4 فئات مختلفة تمامًا. مزايا هذا النهج:
- لقد انخفض عدد الفصول.
- جميع الفصول موجودة داخل الفصل الأصلي. نحن قادرون على تتبع التسلسل الهرمي بأكمله دون فتح كل فئة على حدة.
- يمكننا الرجوع إلى فئة البناء، وسيقوم IDE بالفعل بمطالبة القائمة الكاملة لجميع الفئات الفرعية لهذه الفئة. سيؤدي ذلك إلى تسهيل العثور على الفصول الدراسية التي تحتاجها وإظهار الصورة بأكملها بشكل أكثر شمولاً.
Building.Shop myShop = new Building.Shop(“Food & Fun!”, “Kalyaeva 8/53”);
أود أيضًا أن أشير إلى أن هذه الإستراتيجية تُستخدم في فئات AWT 2D لوصف الأشكال، مثل Line2D وArc2D وEllipse2D وغيرها.
الطبقات المحلية
يتم الإعلان عن هذه الفئات داخل طرق أخرى. في الواقع، لديهم جميع خصائص فئة متداخلة غير ثابتة، ولا يمكن إنشاء سوى مثيلاتها فقط في طريقة، ولا يمكن أن تكون الطريقة ثابتة (لإنشاءها، تحتاج إلى مثيل لفئة خارجية، مرجع إلى فئة خارجية) يتم تمرير مثيل كائن الاستدعاء ضمنيًا إلى طرق غير ثابتة، وفي الطريقة الثابتة لا توجد طريقة لهذا الارتباط). لكن لديهم خصائصهم الخاصة:- يمكن أن تعمل الفئات المحلية فقط مع متغيرات الطريقة النهائية. الشيء هو أنه يمكن تخزين مثيلات الفئات المحلية في الكومة بعد اكتمال الطريقة، ويمكن مسح المتغير. إذا تم إعلان المتغير نهائيًا، فيمكن للمترجم حفظ نسخة من المتغير لاستخدامه لاحقًا بواسطة الكائن. وشيء آخر: نظرًا لوجود أكثر من 8 إصدارات من Java، يمكنك استخدام المتغيرات غير النهائية في الفئات المحلية، ولكن بشرط ألا تتغير.
- لا يمكن الإعلان عن الفئات المحلية باستخدام معدّلات الوصول.
- الفئات المحلية لديها حق الوصول إلى متغيرات الطريقة.
Person
(من المفترض أن هذا شخص) لها خصائص street
(شارع)، house
(منزل). نود إرجاع بعض العناصر للوصول إلى موقع الشخص فقط. للقيام بذلك، قمنا بإنشاء واجهة AddressContainer، والتي تتضمن تخزين البيانات حول موقع الشخص.
public class Person {
private String name, street, house;
public Person(String name, String street, String house) {
this.name = name;
this.street = street;
this.house = house;
}
private interface AddressContainer {
String getStreet();
String getHouse();
}
public AddressContainer getAddressContainer() {
class PersonAddressContainer implements AddressContainer {
final String street = Person.this.street, house = Person.this.house;
@Override
public String getStreet() {
return this.street;
}
@Override
public String getHouse() {
return this.house;
}
}
return new PersonAddressContainer();
}
public static void main(String[] args) {
Person person = new Person("Nikita", "Sholohova", "17");
AddressContainer address = person.getAddressContainer();
System.out.println("Address: street - " + address.getStreet() + ", house - " + address.getHouse());
}
// getters/setters
}
كما ترون، قمنا داخل الطريقة بإنشاء فئة تنفذ تخزين موقع الشخص، وقمنا بإنشاء متغيرات ثابتة هناك (بحيث يتم تخزين المتغيرات في كائن بعد الخروج من الطريقة) وقمنا بتنفيذ طريقة للحصول على العنوان و منزل. يمكننا الآن استخدام هذا الكائن في أماكن أخرى في البرنامج للحصول على موقع الشخص. أفهم أن هذا المثال ليس مثاليًا وسيكون من الأصح القيام بذلك بمجرد ترك الحروف في الفصل Person
، ومع ذلك، تم عرض إنشاء هذا الفصل واستخدامه المحتمل، ثم الأمر متروك لك.
فئات مجهولة
تحت الغطاء، تعتبر الفئات المجهولة مجرد فئات متداخلة عادية وغير ثابتة. خصوصيتها هي سهولة استخدامها. يمكنك كتابة فصلك مباشرةً عند إنشاء مثيل لفئة أخرى.public class Animal {
public void meow() {
System.out.println("Meow!");
}
public static void main(String[] args) {
Animal anonTiger = new Animal() {
@Override
public void meow() {
System.out.println("Raaar!");
}
};
Animal notAnonTiger = new Animal().new Tiger();
anonTiger.meow(); // будет выведено Raaar!
notAnonTiger.meow(); // будет выведено Raaar!
}
private class Tiger extends Animal {
@Override
public void meow() {
System.out.println("Raaar!");
}
}
}
في الأساس، نحن ببساطة نجمع بين شيئين في مكان واحد: إنشاء مثيل لفئة واحدة ( Animal
) وإنشاء مثيل لفئة داخلية موروثة ( Tiger
). بخلاف ذلك، سنحتاج إلى إنشاء الفصل بشكل منفصل واستخدام بنيات أطول لتحقيق نفس النتيجة. إن استخدام الفئات المجهولة له ما يبرره في كثير من الحالات، لا سيما عندما:
- جسم الفصل قصير جدًا؛
- هناك حاجة إلى مثيل واحد فقط من الفئة؛
- يتم استخدام الفصل في المكان الذي تم إنشاؤه فيه أو بعده مباشرة؛
- اسم الفئة ليس مهمًا ولا يسهل فهم الكود.
JButton b2 = new JButton("Click");
b2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
System.out.println("Кнопка нажата!");
}
});
ومع ذلك، بعد Java 8، بدأوا في استخدام تعبيرات lambda، ولكن لا يزال هناك الكثير من التعليمات البرمجية مكتوبة قبل الإصدار 8 وقد تواجه (وستواجه أثناء تدريبك في JavaRush) مثل هذه النقوش.\ التناظرية مع lambdas:
JButton b2 = new JButton("Click");
b2.addActionListener(e -> System.out.println("Кнопка нажата!"));
نهاية المقال أشكركم جميعا على اهتمامكم وأتمنى أن تكونوا قد تعلمتم شيئا جديدا أو فهمتم شيئا لم تفهموه من قبل. أود أيضًا أن أوضح أن هذه المقالة تنتمي إلى فئة "الاهتمام بالتفاصيل" . هذا هو عملي الأول، لذلك آمل أن يكون مفيدا لشخص ما. وفي المستقبل القريب عندما تأتي أفكار جديدة سأحاول أن أكتب شيئا آخر، لدي فكرة واحدة فقط... بالتوفيق للجميع والنجاح في البرمجة :)
GO TO FULL VERSION