8

Click here to load reader

ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

  • Upload
    -

  • View
    448

  • Download
    2

Embed Size (px)

DESCRIPTION

חלק מסדרת הרצאות בנושא "תכנות מונחה עצמים בסביבת ג'אווה", והפעם: משתנים מטיפוס מחלקה והמרת טיפוסים.

Citation preview

Page 2: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

Class type Variables -מטיפוס מחלקה יםמשתנ

בהנחה וקיימות ההגדרות הבאות:

public interface InterfaceName {} public class ClassNameA {} public class ClassNameB extends ClassNameA implements InterfaceName {}

המשתנה שמיועד להכיל את הרפרנס לאובייקט יכול להיות מאחד משלושה טיפוסים בלבד:

ממנה אנו יוצרים את האובייקט, זהו הטיפוס הטריוויאלי: מטיפוס המחלקה, הראשון

ClassNameB cnb=new ClassNameB();

וס מחלקה שהמחלקה ממנה ממנה אנו יוצרים את האובייקט, כלומר מטיפ מטיפוס מחלקה שמורישה למחלקה, השני

יוצרים את האובייקט מרחיבה את הגדרתה.

ClassNameA cna=new ClassNameB();

.מיישמתממנה יוצרים את האובייקט מטיפוס ממשק אותו המחלקה, השלישי

InterfaceName in=new ClassNameB();

לאובייקט( במשתנה המיועד לכך, ניתן יהיה לגשת למאפיינים שלו )בהתאם לאחר שנציב את ערך החזרה )הרפרנס

להרשאות גישה( ע"י ציון שם המשתנה, נקודה מפרידה ושם המאפיין )משתנה או מתודה(, לדוגמה כך:

objectName.methodName();

וא נוצר מבלי קשר לאיזה אובייקט יש לשים לב שכל סוג של משתנה יכול לגשת אך ורק למאפיינים שהוגדרו בטיפוס ממנו ה

הוא מחזיק.

Page 3: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

על מנת להבין את הנושא טוב יותר נעבה מעט את הדוגמה האחרונה.

נתון: UMLמתוך תרשים

נוכל להבין שקיימות ההגדרות הבאות:

public interface InterfaceName { public abstract void interfaceMethod(); } public class ClassNameA { int classNameAVar; } public class ClassNameB extends ClassNameA implements InterfaceName { int classNameBVar; public void interfaceMethod () { } }

נעשים שימושים המעידים על קשרי תלות למחלקות והממשק )יצירת אובייקטים וכד'(. Runner-וכי ב

כעת, נניח ויצרנו אובייקט שאת הרפרנס אליו נציב במשתנה מסוג המחלקה ממנה הוא נוצר, כך שתהיה גישה לכל

המאפיינים שהוגדרו במחלקה:

ClassNameB cnb=new ClassNameB();

חדשים, אלא נפיץ את הרפרנס לאובייקט שיצרנו במשתנים מהסוגים האפשריים שהוצגו קודם: מכאן, לא ניצור אובייקטים

ClassNameA cna = cnb; InterfaceName in = cnb;

Page 4: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

אליו מוכל בשלושת טיפוסי המשתנים שהצגנו שהרפרנס ClassNameBמשעשינו זאת, יש בידנו אובייקט מסוג מחלקה

מתייחסים לאותו האובייקט, אך הגישה של כל אחד מהם in, cna, cnbבדוגמה הקודמת, כלומר, שלושת המשתנים

למאפיינים של האובייקט תהיה בהתאם לאילו מאפיינים הוגדרו בכל אחת מהגדרות הטיפוסים מהם הם נוצרו:

InterfaceNameהיות והיא היחידה שמגיעה מהטיפוס ()interfaceMethodיכול "לראות" רק את המתודה inהמשתנה

ממנו הוא נוצר:

in.interfaceMethod();

ממנו הוא ClassNameAהיות והוא היחיד שמגיע מהטיפוס classNameAVarיכול "לראות" רק את המשתנה cnaהמשתנה

נוצר:

cna.classNameAVar=0;

ממנה הוא נוצר, ClassNameBהיות והוא מוגדר במחלקה classNameBVarיכול "לראות" את המשתנה cnbהמשתנה

,ClassNameAהיות והוא יורש מהמחלקה classNameAVarכמו כן, הוא יכול "לראות" את המשתנה

:InterfaceNameהיות והוא מממש את הממשק ()interfaceMethodוהוא יכול "לראות" את המתודה

cnb.interfaceMethod(); cnb.classNameAVar=0; cnb.classNameBVar=0;

המשתנות בהתאם ללוגיקה שעומדת יש לא מעט סיבות להצבת הרפרנס לאובייקט בכל אחד מטיפוסי המשתנים האפשריים

מאחורי כתיבת התכנית, אך כפי שניתן לראות, אחד היתרונות המידיים המתקבלים הוא היכולת "להסתיר" את הגישה לחלק

מהמאפיינים של אובייקט, בהתאם לסוג המשתנה המכיל את הרפרנס אליו.

וצה רחבה, בחינם, ע"מ לעורר מודעות בקרב הקהל ניתן לדמות זאת לחברה המפתחת תוכנה ומעוניינת להפיץ גרסה בתפ

לגבי המוצר שפיתחה. אם הגרסה שתופץ תהה גרסה מלאה, הרי שמעט מאוד אנשים יחליטו לקנות את המוצר היות וכבר

יש להם אותו ביד, מתפקד בצורה מלאה ועל כן, מפיצה החברה גרסה שמוגבלת מבחינת הביצועים שלה, שהוסרו ממנה

ימים או שהוגבל זמן השימוש בה. אותו אדם שנחשף לגרסת הניסיון, מקבל גרסה שדרכה ניתן לראות פיצ'רים מסו

ולהשתמש אך ורק באותם פיצ'רים שנחשפו בידי המפתחים, בדיוק כשם שאותו משתנה מסוג ממשק חושף אך ורק את

ממחלקה אחרת בדוגמה שלנו הפיצ'רים שמוגדרים בו ולא את אלו של המחלקה המשוכללת, זו שמימשה אותו וירשה

)ובהקבלה: גרסת התוכנה המלאה(.

יתרון נוסף הוא היכולת לפנות לאובייקטים מסוגים שונים באותה הדרך )קיבוץ( ובנוסף, אנו נדבר על הנושא בפרק העוסק

בפולימורפיזם.

בדוגמאות שראינו נעשה שימוש לא מודע בהמרת טיפוסים אוטומטית.

Page 5: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

Up/Down Type Casting -המרת טיפוסים למעלה/למטה

היכולת לבצע קאסטינג היא נדבך חשוב מאוד בשפת ג'אווה. יכולת זו מקבלת תרגום ישיר לביצוע משימות מורכבות תוך

שימוש בתחביר פשוט. קאסטינג זה נבדל במהותו מאותן ההמרות בין טיפוסים פרימיטיביים שהכרנו עד כה.

ם למעלה, מדובר על היכולת להתייחס לאובייקט ממחלקה יורשת כאל אובייקט ממחלקה מורישה. המרה בהמרת טיפוסי

מסוג זה יכולה להתבצע בצורה מפורשת, אך זאת רק לשם הבהירות, אין בכך צורך טכני ממשי היות והיא מתבצעת בכל

מבלי בעיות. זאת מתוך כך שהמרה זו נחשבת כבטוחה ומצליחה תמיד –מקרה באופן אוטומטי

המרה למטה לעומת זאת, צריכה להתבצע באופן ידני מפורש וגם אז, תוך מתן תשומת לב לסוג ההמרה שמבצעים היות

והמרה זו פחות טריוויאלית ועלולה לגרור שגיאות קומפילציה ושגיאות ריצה באם בוצעה באופן לא מבוקר.

הנתון: UML-ים העל מנת להבין טוב יותר את הנושא, נביט ראשית בתרש

מתוכו נוכל להבין שקיימות ההגדרות הבאות:

public class Human { String type="Human"; }

public class Person extends Human { }

public class Student extends Person { }

public class Soldier extends Person { }

'(.וכד אובייקטים יצירת) למחלקות תלות קשרי על המעידים שימושים נעשים Runner-ב וכי

Page 6: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

התרשים מתאר גרסה מצומצמת של היררכיית המחלקות של בן אנוש. ניתן בקלות לראות שסטודנט וחייל יורשים מבן אדם

טיפוסים פרימיטיביים, כל וכי בן אדם יורש מבן אנוש. כמו כן, נציין כי בן אנוש יורש מאובייקט, שהרי בג'אווה, מלבד

הטיפוסים יורשים מאובייקט באופן אוטומטי.

:Runnerנביט בהגדרת

public class Runner { public static void main(String[] args) { Student student = new Student(); System.out.println(student.type); Soldier soldier = new Soldier(); System.out.println(soldier.type); } }

.Human-מ typeיהיה כמובן זהה עבור שני האובייקטים, היות ושניהם יורשים את Runnerהפלט שיתקבל בהרצת

)כאמור, מתבצעת באופן אוטומטי( המרה למעלה

public class Runner { public static void main(String[] args) { Student student = new Student(); System.out.println(student.type); Soldier soldier = new Soldier(); System.out.println(soldier.type); Student s = new Student(); System.out.println(s); Person p = s; // Person p = (Person)s; - שקולה ל –המרה למעלה אוטומטית System.out.println(p); /* שיודפסמה Human Human Student@32e0c07c Student@32e0c07c */ } }

בדוגמה ניתן לראות שהמרה למעלה בוצעה מסטודנט לבן אדם, הסיבה שהמרה כזו תתבצע תמיד בלי בעיות היא שסטודנט

UML-סטודנט הוא בן אנוש וכמו כן סטודנט הוא אובייקט, כל זאת ניתן ללמוד מדיאגרמת ההוא אכן בן אדם, כמו כן,

המתארת את היררכיית הירושה.

דבר נוסף שחשוב לשים לב אליו בדוגמה, הוא שסטודנט שמוחזק ע"י משתנה מסוג סטודנט וסטודנט שמוחזק ע"י משתנה

למשתנה שמחזיק באובייקט אין השפעה על האובייקט עצמו. -בר מסוג בן אדם מודפסים באותו האופן. לא במקרה כך הד

מסוג בן אדם, pהמשתנה משמש כמעיין תווית לפיה יהיה ניתן להתייחס לאובייקט בצורה שונה, ובדוגמה, דרך המשתנה

ם ירש מסוג סטודנט, נוכל לפנות אך ורק למשתנים ומתודות שהוגדרו בבן אדם או שבנאד sהמחזיק ברפרנס לאובייקט

מלמעלה, השדות ששייכים למה שמתחת בהיררכיית הירושה ובמקרה של בנאדם, לסטודנט, יוסתרו כל עוד שהפנייה

לאובייקט תיעשה "מלמעלה", ממחלקה מורישה או מממשק שמומש. חשוב לציין שברגע שתיעשה המרה ידנית למטה

מים כד כה.בחזרה למשתנה מטיפוס סטודנט, יחזרו גם כל המאפיינים שהיו חסו

Page 7: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

)כאמור, מתבצעת באופן ידני(המרה למטה

ההמרה למטה צריכה להיעשות בצורה מבוקרת. כשאנחנו עושים קאסטינג למטה עלינו לשאול את עצמנו האם אנחנו

מבצעים המרה חוקית או לא בהתאם למקור המהות של האובייקט.

לאובייקט מסוג סטודנט ולכן המרה למטה בחזרה לטיפוס סטודנט אפשרית וחוקית. החזיק ברפרנס pבדוגמה שהבאנו,

Student s1 = new Student(); System.out.println(s1); Person p = s1; // Person p = (Person)s1; - שקולה ל –המרה למעלה אוטומטית System.out.println(p); Student s2=(Student)p; // ידנית למטה תקינה היות ומחזירים לסטודנט את התכונות שהיו שלו מלכתחילה המרה

(, אך תתאפשר מבחינת קומפילציה, זאת ClassCastExceptionניסיון להמיר למטה לטיפוס מסוג חייל, תניב שגיאת ריצה )

היררכיה מחלקתית, לא נוצר אובייקט, זה היה רק משתנה שהחזיק ברפרנס לאובייקט שמתחתיו מבחינת p-היות ומ

תיאורטית הוא יכול להחזיק או בסטודנט או בחייל )או בבן אדם(, ועל כן, האחריות לחוקיות היא בידי המתכנת, המתכנת

הוא זה שצריך לדעת באיזה אובייקט בפועל מדובר:

Soldier sol=new Soldier(); sol=(Soldier)p;

באופן הבא: instanceofלה השמורה, האופרטור ע"מ לדעת מה מקור האובייקט, ניתן להשתמש במי

Student s3 = new Student(); Human h = s3; // המרה למעלה if(h instanceof Student) { // בדיקה

Student s4 = (Student)h; // המרה למטה תקינה }

טיפוס הוא וננסה להמיר ממנו לטיפוס מדובר בהמרה למטה של טיפוס שכן נוצר ממנו אובייקט, שידוע בוודאות מאיזה אם

(, בדוגמה הבאה ברור שבן אדם הוא לא סטודנט )סטודנט הוא בן אדם(:ClassCastExceptionאחר, נקבל שגיאת ריצה ) Person per=new Person(); Student s5=(Student)per;

"מה מקור האובייקט?" השאלה שצריכה להישאל היא על מנת לפשט את ההחלטה את מי ניתן להמיר למי וממי,

אם סטודנט הוא בן אדם, ניתן להמיר אותו לבן אדם. אם סטודנט הוא בן אנוש, ניתן להמיר אותו לבן אנוש.

האם בן אנוש הוא חייל? האם בן אנוש הוא בן אדם? לא ולכן לא ניתן להמיר אותם.

אני יכול להתייחס אליו גם כאל סטודנט ואז "לדבר" איתו על כל מה האם אני מתייחס לסטודנט הזה כבן אדם? משמע,

שאפשר לדבר עם סטודנט, אך כל עוד שאני מתייחס אליו כאל בן אדם, אני לא יכול לדבר אליו כאל סטודנט, אלא רק על

דברים שכלליים לבני אדם.

Page 8: ג'אווה - תכנות מונחה עצמים - משתנים מטיפוס מחלקה והמרת טיפוסים למעלה/למטה

לת לייצר מתודות כלליות שיכולות לקבל סוגים שונים אחד היתרונות הבולטים של המרת הטיפוסים בשפת ג'אווה, הוא היכו

של אובייקטים כארגומנט. נניח וברצוננו לייצר מתודה שתדע לקבל גם חיילים, גם סטודנטים, גם בני אדם וגם בני אנוש

בצורה גנרית. נביט בהגדרה הבאה:

public void whois(Human h) {

System.out.println("This is a "+h); }

:Humanבאופן זה, נוכל לשלוח למתודה זו רפרנסים לכל אחד מהמחלקות שברמת או במורד ההיררכיה של

whois(new Student()); whois(new Person()); whois(new Human());

ונקבל פלט כזה:

This is a Student@603a3e21 This is a Person@fc519e2 This is a Human@55264c84

אם כן, מה שעובר המרה זה לא האובייקט עצמו, אלא הרפרנס אליו. עד כה, דיברנו על סוגי המשתנים בהם ניתן להציב את

אותו הרפרנס. כעת, נציין את העובדה שאת אותו הרפרנס ניתן להציב ביותר ממשתנה אחד.

לאותו האובייקט, כך גם בנוגע להשמה )=( במקרה זה, כל המשתנים שיכילו ערך זה, יכילו את אותו הרפרנס ובשל כך, יפנו

בין משתני מחלקה שונים, האובייקט עצמו יישאר ללא שינוי, רק הרפרנס יושם כמתבקש במשתנה החדש.

, אך ורק אם הרפרנסים המושווים אמתבאותו האופן, ניתן אם כן לדבר על השוואה )==( בין אובייקטים. השוואה תחזיר ערך

גם אם האובייקטים זהים מבחינת התוכן, אך בעלי שקרת לתוכן עצמו, כלומר, השוואה תחזיר ערך זהים, מבלי התייחסו

רפרנס שונה.

לצורך ביצוע ההשוואה בין equals-, ניתן להשתמש בObjectהיות וכל המחלקות יורשות באופן אוטומטי מהמחלקה

לאופי פעולתו של האופרטור ==, אך יש הבדל בין equalsאובייקטים. אין הבדל בין אופן פעולתה הדפולטיבי של המתודה

, שזו שפה Groovy-טיפ: ניתן לבצע העמסת אופרטורים בהשניים בכך שלא ניתן לבצע העמסת אופרטורים בג'אווה )

, כך equals(, אך כן ניתן לבצע דריסת מתודות ומכאן, שניתן לשנות את אופן פעולתה של דומה לג'אווה JVMמבוססת

שתבצע את ההשוואה לפי התוכן ולא לפי הרפרנס, כפי שהיא עושה בדרך ברירת המחדל:

public boolean equals(Object obj) { /*New logic goes here*/ }

, כלומר, זוהי אינדיקציה לגבי instantiation, באם לא בוצע nullמחדל משתנה מטיפוס מחלקה יקבל את ערך ברירת ה

באופן יזום(. ניסיון לשימוש במשתנה nullהעובדה שמשתנה זה לא מחזיק ברפרנס לאף אובייקט )ניתן גם לבצע הצבה של

, יגרום לשגיאת ריצה.nullמסוג מחלקה המכיל

null=!או null==פרנס לאובייקט או לא ע"י בדיקה האם הוא ניתן לבצע בדיקה האם משתנה מסוג מחלקה מכיל ר