أهلاً! اليوم سننظر في موضوع مهم للغاية يتعلق بأشياءنا. وبدون مبالغة، يمكننا القول أنك ستستخدم هذا الموضوع في الحياة الواقعية كل يوم! نحن نتحدث عن منشئي جافا. قد تكون هذه هي المرة الأولى التي تسمع فيها هذا المصطلح، ولكنك في الواقع استخدمت المُنشئات بالفعل. أنت فقط لم تدرك ذلك :) نقنع أنفسنا بهذا لاحقًا.
تذكر أننا قلنا في بداية الدرس أنك استخدمت المُنشئات بالفعل دون أن تدرك ذلك؟ لقد قصدنا ما قلناه. الحقيقة هي أن كل فئة في Java لديها ما يسمى المنشئ الافتراضي. لا يتطلب الأمر أية وسائط، ولكن يتم استدعاؤه في كل مرة تقوم فيها بإنشاء أي كائن من أي فئة.
(CarFactory.java:15) في CarFactory.main(CarFactory.java:23) انتهت العملية برمز الخروج 1
فقاعة! ينتهي البرنامج بنوع من الخطأ غير المفهوم. هل يمكنك محاولة تخمين السبب؟ المشكلة هي في المنطق الذي وضعناه في المنشئ. وبشكل أكثر تحديدا، هذا السطر:
ما هي الشركات المصنعة في العالم ولماذا هناك حاجة إليها؟
دعونا نفكر في مثالين.public class Car {
String model;
int maxSpeed;
public static void main(String[] args) {
Car bugatti = new Car();
bugatti.model = "Bugatti Veyron";
bugatti.maxSpeed = 378;
}
}
لقد صنعنا سيارتنا، وحددنا طرازها وسرعتها القصوى. لكن من الواضح أن كائن السيارة لن يحتوي على حقلين في مشروع حقيقي. على سبيل المثال، قد يحتوي على 16 حقلاً!
public class Car {
String model;// model
int maxSpeed;// maximum speed
int wheels;// wheel width
double engineVolume;// engine volume
String color;// color
int productionYear;// production year
String ownerFirstName;// first name of owner
String ownerLastName;// last name of owner
long price;// price
boolean isNew;// flag indicating whether car is new
int seatsInTheCar;// number of seats in the car
String cabinMaterial;// interior material
boolean insurance;// flag indicating whether car is insured
String manufacturerCountry;// manufacturer country
int trunkVolume;// size of the trunk
int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)
public static void main(String[] args) {
Car bugatti = new Car();
bugatti.color = "blue";
bugatti.accelerationTo100km = 3;
bugatti.engineVolume = 6.3;
bugatti.manufacturerCountry = "Italy";
bugatti.ownerFirstName = "Amigo";
bugatti.productionYear = 2016;
bugatti.insurance = true;
bugatti.price = 2000000;
bugatti.isNew = false;
bugatti.seatsInTheCar = 2;
bugatti.maxSpeed = 378;
bugatti.model = "Bugatti Veyron";
}
}
لقد أنشأنا كائن سيارة جديدًا . هناك مشكلة واحدة: لدينا 16 حقلاً، ولكننا قمنا بتهيئة 12 منها فقط ! انظر إلى الكود الآن وحاول العثور على الحقول التي نسيناها! ليس بهذه السهولة، هاه؟ في هذه الحالة، يمكن للمبرمج أن يرتكب خطأ بسهولة ويفشل في تهيئة بعض الحقول. ونتيجة لذلك، سوف يتصرف البرنامج بشكل غير صحيح:
public class Car {
String model;// model
int maxSpeed;// maximum speed
int wheels;// wheel width
double engineVolume;// engine volume
String color;// color
int productionYear;// production year
String ownerFirstName;// first name of owner
String ownerLastName;// last name of owner
long price;// price
boolean isNew;// flag indicating whether car is new
int seatsInTheCar;// number of seats in the car
String cabinMaterial;// interior material
boolean insurance;// flag indicating whether car is insured
String manufacturerCountry;// manufacturer country
int trunkVolume;// size of the trunk
int accelerationTo100km;// how long it takes to accelerate to 100 km/h (in seconds)
public static void main(String[] args) {
Car bugatti = new Car();
bugatti.color = "blue";
bugatti.accelerationTo100km = 3;
bugatti.engineVolume = 6.3;
bugatti.manufacturerCountry = "Italy";
bugatti.ownerFirstName = "Amigo";
bugatti.productionYear = 2016;
bugatti.insurance = true;
bugatti.price = 2000000;
bugatti.isNew = false;
bugatti.seatsInTheCar = 2;
bugatti.maxSpeed = 378;
bugatti.model = "Bugatti Veyron";
System.out.println("Model: Bugatti Veyron. Engine volume: " + bugatti.engineVolume + ". Trunk volume: " + bugatti.trunkVolume + ". Cabin material: " + bugatti.cabinMaterial +
". Wheel width: " + bugatti.wheels + ". Purchased in 2018 by Mr. " + bugatti.ownerLastName);
}
}
مخرج وحدة التحكم: الموديل: بوجاتي فيرون. حجم المحرك: 6.3. حجم صندوق السيارة: 0. مادة الكابينة: صفر. عرض العجلة: 0. تم شراؤها في عام 2018 من قبل السيد null ، من الواضح أن المشتري الذي تخلى عن 2 مليون دولار مقابل السيارة، لن يحب أن يطلق عليه " السيد null "! ولكن على محمل الجد، خلاصة القول هي أن برنامجنا أنشأ كائنًا بشكل غير صحيح: سيارة بعرض عجلة 0 (أي لا توجد عجلات على الإطلاق)، صندوق مفقود، مقصورة مصنوعة من مادة غير معروفة، وفوق كل شيء، مالك غير محدد . لا يمكنك إلا أن تتخيل كيف يمكن أن "ينفجر" مثل هذا الخطأ عند تشغيل البرنامج! نحن بحاجة لتجنب مثل هذه المواقف بطريقة أو بأخرى. نحن بحاجة إلى تقييد برنامجنا: عند إنشاء كائن سيارة جديد ، نريد أن يتم تحديد الحقول دائمًا، مثل النموذج والسرعة القصوى. وإلا فإننا نريد منع إنشاء الكائن. يتعامل المنشئون مع هذه المهمة بسهولة. لقد حصلوا على اسمهم لسبب ما. يقوم المنشئ بإنشاء نوع من "الهيكل العظمي" للفئة التي يجب أن يطابقها كل كائن جديد. للراحة، دعنا نعود إلى الإصدار الأبسط من فئة السيارة مع حقلين. بالنظر إلى متطلباتنا، سيبدو مُنشئ فئة السيارة كما يلي:
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
// And creating an object now looks like this:
public static void main(String[] args) {
Car bugatti = new Car("Bugatti Veyron", 378);
}
لاحظ كيف يتم الإعلان عن المنشئ. إنها تشبه الطريقة العادية، لكنها لا تحتوي على نوع إرجاع. علاوة على ذلك، يحدد المنشئ اسم الفئة ( Car ) بدءًا بحرف كبير. بالإضافة إلى ذلك، يتم استخدام المُنشئ مع كلمة أساسية جديدة بالنسبة لك: this . الكلمة الأساسية هذه مخصصة للإشارة إلى كائن معين. الكود في المنشئ
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
يمكن تفسيره حرفيًا تقريبًا: " نموذج هذه السيارة (الذي نقوم بإنشائه الآن) هو وسيطة النموذج التي تم تمريرها إلى المُنشئ. إن maxSpeed لهذه السيارة (التي نقوم بإنشائها) هو وسيطة maxSpeed التي تم تمريرها إلى البناء." وهذا ما يحدث:
public class Car {
String model;
int maxSpeed;
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
public static void main(String[] args) {
Car bugatti = new Car("Bugatti Veyron", 378);
System.out.println(bugatti.model);
System.out.println(bugatti.maxSpeed);
}
}
إخراج وحدة التحكم: Bugatti Veyron 378 قام المنشئ بتعيين القيم المطلوبة بشكل صحيح. ربما لاحظت أن المنشئ يشبه إلى حد كبير الطريقة العادية! اذا هي كذلك. المنشئ هو في الواقع طريقة، ولكن مع ميزات محددة :) تمامًا كما هو الحال مع الطرق، قمنا بتمرير الوسائط إلى المنشئ الخاص بنا. وكما هو الحال مع استدعاء التابع، فإن استدعاء المنشئ لن يعمل إلا إذا قمت بتحديده:
public class Car {
String model;
int maxSpeed;
public Car(String model, int maxSpeed) {
this.model = model;
this.maxSpeed = maxSpeed;
}
public static void main(String[] args) {
Car bugatti = new Car(); // Error!
}
}
يمكنك أن ترى أن المنشئ حقق ما كنا نحاول تحقيقه. الآن لا يمكنك إنشاء سيارة بدون سرعة أو طراز! التشابه بين المنشئين والأساليب لا ينتهي هنا. تمامًا مثل الأساليب، يمكن تحميل المُنشئين بشكل زائد. تخيل أن لديك قطتين أليفتين في المنزل. لقد حصلت على واحدة منهم كقطة صغيرة. لكن الثانية التي أخذتها من الشارع عندما كانت قد نمت بالفعل، ولا تعرف عمرها بالضبط. في هذه الحالة، نريد أن يكون برنامجنا قادرًا على إنشاء نوعين من القطط: تلك التي تحمل اسمًا وعمرًا (للقطة الأولى)، وتلك التي تحمل اسمًا فقط (للقطة الثانية). لهذا سوف نقوم بتحميل المنشئ بشكل زائد:
public class Cat {
String name;
int age;
// For the first cat
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
// For the second cat
public Cat(String name) {
this.name = name;
}
public static void main(String[] args) {
Cat smudge = new Cat("Smudge", 5);
Cat streetCatNamedBob = new Cat("Bob");
}
}
بالإضافة إلى المنشئ الأصلي بمعلمتي "الاسم" و"العمر"، أضفنا واحدًا آخر بمعلمة اسم فقط. تمامًا بنفس الطريقة التي قمنا بها بتحميل الأساليب بشكل زائد في الدروس السابقة. الآن يمكننا إنشاء كلا النوعين من القطط :)
public class Cat {
public static void main(String[] args) {
Cat smudge = new Cat(); // The default constructor is invoked here
}
}
للوهلة الأولى، إنه غير مرئي. لقد خلقنا كائنًا، فماذا في ذلك؟ أين يفعل المنشئ أي شيء هنا؟ لرؤيتها، دعونا نكتب بشكل صريح مُنشئًا فارغًا لفئة Cat . سنعرض بعض العبارات بداخله. إذا تم عرض العبارة، فسيتم استدعاء المنشئ.
public class Cat {
public Cat() {
System.out.println("A cat has been created!");
}
public static void main(String[] args) {
Cat smudge = new Cat(); // The default constructor is invoked here
}
}
إخراج وحدة التحكم: تم إنشاء قطة! هناك التأكيد! المنشئ الافتراضي موجود دائمًا بشكل غير مرئي في فصولك الدراسية. لكن عليك أن تعرف شيئًا آخر عنه. يتم حذف المُنشئ الافتراضي من الفصل الدراسي بمجرد إنشاء مُنشئ باستخدام الوسائط. في الواقع، لقد رأينا بالفعل دليلاً على ذلك أعلاه. وكان في هذا الكود:
public class Cat {
String name;
int age;
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Cat smudge = new Cat(); //Error!
}
}
لم نتمكن من إنشاء قطة بدون اسم وعمر، لأننا أعلنا عن مُنشئ قطة بمعلمات سلسلة و int . أدى هذا إلى اختفاء المُنشئ الافتراضي على الفور من الفصل. لذا تأكد من تذكر أنه إذا كنت بحاجة إلى العديد من المُنشئات في صفك، بما في ذلك مُنشئ بدون وسيطات، فسيتعين عليك الإعلان عنها بشكل منفصل. على سبيل المثال، لنفترض أننا نقوم بإنشاء برنامج لعيادة بيطرية. تريد عيادتنا القيام بأعمال الخير ومساعدة القطط المشردة التي لا تعرف أسمائها وأعمارها. ثم يجب أن يبدو الكود الخاص بنا كما يلي:
public class Cat {
String name;
int age;
// For cats with owners
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
// For street cats
public Cat() {
}
public static void main(String[] args) {
Cat smudge = new Cat("Smudge", 5);
Cat streetCat = new Cat();
}
}
الآن بعد أن كتبنا مُنشئًا افتراضيًا صريحًا، يمكننا إنشاء كلا النوعين من القطط :) كما هو الحال مع أي طريقة، فإن ترتيب الوسائط التي تم تمريرها إلى المُنشئ مهم جدًا. لنقم بتبديل وسيطات الاسم والعمر في مُنشئنا.
public class Cat {
String name;
int age;
public Cat(int age, String name) {
this.name = name;
this.age = age;
}
public static void main(String[] args) {
Cat smudge = new Cat("Smudge", 10); // Error!
}
}
خطا! ينص المنشئ بوضوح على أنه عند إنشاء كائن Cat ، يجب تمرير رقم وسلسلة، بهذا الترتيب. لذا، الكود الخاص بنا لا يعمل. تأكد من تذكر ذلك ووضعه في الاعتبار عند الإعلان عن فصولك الدراسية:
public Cat(String name, int age) {
this.name = name;
this.age = age;
}
public Cat(int age, String name) {
this.age = age;
this.name = name;
}
هذان منشئان مختلفان تمامًا! إذا أردنا التعبير بجملة واحدة عن إجابة السؤال "لماذا أحتاج إلى مُنشئ؟"، فقد نقول: "للتأكد من أن الكائنات تتمتع دائمًا بحالة صالحة". عند استخدام المنشئات، سيتم تهيئة كافة المتغيرات الخاصة بك بشكل صحيح. لن تحتوي برامجك على أي سيارات بسرعة 0 أو أي كائنات أخرى "غير صالحة". فائدتها الرئيسية هي للمبرمج. إذا قمت بتهيئة الحقول يدويًا (بعد إنشاء كائن)، فهناك خطر كبير يتمثل في فقدان شيء ما وإحداث خطأ. لكن هذا لن يحدث مع المُنشئ: إذا فشلت في تمرير كافة الوسائط المطلوبة أو قمت بتمرير أنواع خاطئة من الوسائط، فسيقوم المترجم بتسجيل خطأ على الفور. يجب علينا أيضًا أن نقول بشكل منفصل أنه لا ينبغي عليك وضع منطق برنامجك داخل المُنشئ. هذا هو الغرض من الأساليب. الأساليب هي المكان الذي يجب عليك فيه تحديد جميع الوظائف المطلوبة. دعونا نرى لماذا تعتبر إضافة المنطق إلى المنشئ فكرة سيئة:
public class CarFactory {
String name;
int age;
int carsCount;
public CarFactory(String name, int age, int carsCount) {
this.name = name;
this.age = age;
this.carsCount = carsCount;
System.out.println("Our car factory is called " + this.name);
System.out.println("It was founded " + this.age + " years ago" );
System.out.println("Since that time, it has produced " + this.carsCount + " cars");
System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}
public static void main(String[] args) {
CarFactory ford = new CarFactory("Ford", 115 , 50000000);
}
}
لدينا فئة CarFactory تصف مصنع السيارات. داخل المُنشئ، نقوم بتهيئة جميع الحقول وإدراج بعض المنطق: نعرض بعض المعلومات حول المصنع. يبدو أنه لا يوجد شيء سيء في هذا. البرنامج يعمل بشكل جيد. مخرجات وحدة التحكم: مصنع السيارات الخاص بنا يسمى فورد، وقد تأسس منذ 115 عامًا، ومنذ ذلك الوقت، أنتج 50000000 سيارة في المتوسط، وينتج 434782 سيارة سنويًا، لكننا في الواقع وضعنا منجمًا متأخرًا زمنيًا. وهذا النوع من التعليمات البرمجية يمكن أن يؤدي بسهولة إلى حدوث أخطاء. لنفترض أننا لا نتحدث الآن عن شركة فورد، بل عن مصنع جديد يسمى "أميجو موتورز"، والذي كان موجودًا منذ أقل من عام وأنتج 1000 سيارة:
public class CarFactory {
String name;
int age;
int carsCount;
public CarFactory(String name, int age, int carsCount) {
this.name = name;
this.age = age;
this.carsCount = carsCount;
System.out.println("Our car factor is called " + this.name);
System.out.println("It was founded " + this.age + " years ago" );
System.out.println("Since that time, it has produced " + this.carsCount + " cars");
System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
}
public static void main(String[] args) {
CarFactory ford = new CarFactory("Amigo Motors", 0 , 1000);
}
}
مخرجات وحدة التحكم: يُسمى مصنع السيارات الخاص بنا Amigo Motors Exception في مؤشر الترابط "الرئيسي" java.lang.ArithmeticException: / بواسطة صفر تم تأسيسه منذ 0 عام ومنذ ذلك الوقت، أنتج 1000 سيارة في CarFactory.System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
هنا تقوم بعملية حسابية وتقسيم عدد السيارات المنتجة على عمر المصنع. وبما أن مصنعنا جديد (أي عمره 0 سنة)، فإننا نقسمه على 0، وهو ما لا يمكننا القيام به في الرياضيات. ونتيجة لذلك، ينتهي البرنامج مع حدوث خطأ.
GO TO FULL VERSION