CodeGym /בלוג Java /Random-HE /בוני ג'אווה
John Squirrels
רָמָה
San Francisco

בוני ג'אווה

פורסם בקבוצה
היי! היום נשקול נושא חשוב מאוד הנוגע לאובייקטים שלנו. בלי להגזים, אנחנו יכולים לומר שתשתמשו בנושא הזה בחיים האמיתיים כל יום! אנחנו מדברים על Java Constructors. זו אולי הפעם הראשונה שאתה שומע את המונח הזה, אבל למעשה כבר השתמשת בבנאים. פשוט לא הבנת את זה :) אנחנו משכנעים את עצמנו בכך מאוחר יותר.

מה זה בכלל קונסטרוקטורים ולמה הם נחוצים?

הבה נבחן שתי דוגמאות.
public class Car {

   String model;
   int maxSpeed;

   public static void main(String[] args) {

       Car bugatti = new Car();
       bugatti.model = "Bugatti Veyron";
       bugatti.maxSpeed = 378;

   }
}
יצרנו את המכונית שלנו, וקבענו את הדגם והמהירות המרבית שלה. אבל לאובייקט המכונית כמובן לא יהיו 2 שדות בפרויקט אמיתי. לדוגמה, הוא עשוי לכלול 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);

   }

}
פלט קונסולה: דגם: Bugatti Veyron. נפח מנוע: 6.3. נפח תא המטען: 0. חומר תא הנוסעים: ריק. רוחב גלגל: 0. נרכש ב-2018 על ידי מר null הקונה שלך, שוויתר על 2 מיליון דולר עבור המכונית, ברור שלא יאהב שיקראו לו " מר null "! אבל ברצינות, השורה התחתונה היא שהתוכנית שלנו יצרה אובייקט בצורה שגויה: מכונית עם רוחב גלגלים של 0 (כלומר ללא גלגלים בכלל), תא מטען חסר, תא נוסעים מחומר לא ידוע, ומעל הכל, בעלים לא מוגדר. . אתה יכול רק לדמיין איך טעות כזו יכולה "להתבטל" כשהתוכנית פועלת! אנחנו צריכים איכשהו להימנע ממצבים כאלה. עלינו להגביל את התוכנית שלנו: בעת יצירת אובייקט Car חדש , אנו רוצים שהשדות, כגון הדגם והמהירות המקסימלית, יצוינו תמיד. אחרת, אנו רוצים למנוע את יצירת האובייקט. בונים מטפלים במשימה זו בקלות. הם קיבלו את שמם מסיבה כלשהי. הקונסטרוקטור יוצר מעין "שלד" מחלקה שכל אובייקט חדש חייב להתאים. מטעמי נוחות, נחזור לגרסה הפשוטה יותר של מחלקת המכוניות עם שני שדות. בהתחשב בדרישות שלנו, הקונסטרוקטור של מחלקת המכוניות ייראה כך:
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 ) החל באות גדולה. בנוסף, הבנאי משמש עם מילת מפתח חדשה עבורך: זה . מילת המפתח זו מיועדת לציון אובייקט מסוים. הקוד בקונסטרוקטור
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!
   }

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

}
בנוסף לבנאי המקורי עם פרמטרים "שם" ו"גיל", הוספנו עוד אחד עם פרמטר שם בלבד. בדיוק באותו אופן שהעמסנו על שיטות בשיעורים קודמים. עכשיו אנחנו יכולים ליצור את שני סוגי החתולים :)
למה אנחנו צריכים קונסטרוקטורים?  - 2
זוכרים שבתחילת השיעור אמרנו שכבר השתמשתם בקונסטרוקטורים מבלי ששמתם לב? התכוונו למה שאמרנו. העובדה היא שלכל מחלקה בג'אווה יש מה שנקרא בנאי ברירת מחדל. זה לא דורש שום טיעונים, אבל זה מופעל בכל פעם שאתה יוצר אובייקט כלשהו מכל מחלקה.
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!
   }
}
לא יכולנו ליצור Cat בלי שם וגיל, כי הכרזנו על בנאי Cat עם פרמטרים של מחרוזת ו- 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 הוא נוסד לפני 0 שנים מאז, הוא ייצר 1000 מכוניות ב-CarFactory. (CarFactory.java:15) ב-CarFactory.main(CarFactory.java:23) התהליך הסתיים עם קוד יציאה 1 בּוּם! התוכנית מסתיימת בסוג של שגיאה בלתי מובנת. אתה יכול לנסות לנחש את הסיבה? הבעיה היא בלוגיקה שהכנסנו לבנאי. ליתר דיוק, השורה הזו:
System.out.println("On average, it produces " + (this.carsCount/this.age) + " cars per year");
כאן אתה מבצע חישוב ומחלק את מספר המכוניות שיוצרו לפי גיל המפעל. ומכיוון שהמפעל שלנו חדש (כלומר הוא בן 0 שנים), אנחנו מחלקים ב-0, מה שאנחנו לא יכולים לעשות במתמטיקה. כתוצאה מכך, התוכנית מסתיימת עם שגיאה.

מה היינו צריכים לעשות?

שים את כל ההיגיון בשיטה נפרדת. בואו נקרא לזה printFactoryInfo() . אתה יכול להעביר אליו אובייקט CarFactory כארגומנט. אתה יכול לשים את כל ההיגיון שם, ובו זמנית לטפל בשגיאות פוטנציאליות (כמו שלנו הכוללות אפס שנים). על טעם ועל ריח. דרושים בנאים כדי להגדיר מצב אובייקט חוקי. יש לנו שיטות להיגיון עסקי. אל תערבבו אחד עם השני. כדי לחזק את מה שלמדת, אנו מציעים לך לצפות בשיעור וידאו מקורס Java שלנו
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION