היום בתרגול ממשקים כללים בסיסיים o מימוש מספר ממשקים o דוגמת ממשק כחוזה o הורשה כללים בסיסיים o דריסה o instanceof אופרטור o תרגול 01: ממשקים והורשה interfaces ממשקים - כללי: הממשק אינו מחלקה, הוא מייצג רעיון מופשט. מבחינת המתכנת, הממשק הוא הצהרת כוונות או הבטחה שצריך למלא. ממשק קובע את הפונקציונליות המשותפת לכול המחלקות הממשות אותו. הממשק מתמצת\מאפיין את התכונות של המושג, וניתן לממש אותו בדרכים שונות. כל מחלקה המממשת ממשק מסוים צריכה לעמוד בתנאים אלה. שימושים: ממשקים מאפשרים תכנות ברמת מופשטות )אבסטרקציה( גבוהה ותכנון מוקדם של עבודת תכנות רבת היקף. כך ניתן לחלק מטלה גדולה למרכיבים ולעבוד על כל רכיב באופן עצמאי. הממשק )interface( הינו כלי ב- Java למימוש עיקרון ההפרדה בין הכרזה למימוש. המשתמש במחלקה המממשת ממשק אינו יכול לדעת את פרטי המימוש של השיטות ואפילו רצוי שלא יצטרך לחשוב עליהם כדי שיוכל להתרכז במשימה שלפניו ולטפל בה ברמת מופשטות מתאימה. לדוגמא: כאשר רוצים לממש מערכת גדולה אשר בה כל תת-מערכת נכתבת ע"י גורם אחר. במצב כזה מגדירים הגורמים )המתכנתים( את הממשק של כל תת-מערכת, בכדי שהאינטגרציה בין תת-המערכות תתבצע ללא בעיה. במקרה הנ"ל כל תת מערכת צריכה לדעת מהו הממשק של תת-המערכות אליהן היא אמורה להתחבר בעוד שהמימוש אינו חשוב. כללים בסיסיים: הצהרה על ממשק היא דומה להצהרה על מחלקה, רק שבמקום class נשתמש במילה השמורה,interface למשל: <שם הממשק> public interface { >רשימת קבועים< <רשימת כותרות של השיטות> 0.ממשקים מתארים שיטות )ציבוריות( ללא ישומן. 0
2. הגדרת שיטה כפרטית בממשק היא טעות קומפילציה. 3. כל שיטה ללא רמת גישה בממשק היא ציבורית. בכדי לא להתבלבל, ניתן להגדיר את השיטות תמיד כציבוריות. 4. כל השדות בממשק הם ציבוריים ובלתי ניתנים לשינוי גם אם לא הוגדרו כך. 5.ממשק, בדומה למחלקה, מגדיר טיפוס. שדות, משתנים ופרמטרים יכולים להיות מוגדרים להיות מסוג ממשק. למשל, נניח ש Predator )בעברית: טורף( הוא שם של ממשק, אזי ניתן להצהיר על משתנה x להיות מסוג :Predator Predator x; 6. לא ניתן ליצור אובייקט מממשק. למשל: x = new Predator(); // will not compile!!! 7. מחלקה שמממשת ממשק צריכה לממש את כל שיטותיו. ניתן להצהיר על מחלקה להיות מממשת ממשק כך: <שם ממשק> implements <שם מחלקה> public class 8. כאמור מחלקה כזו צריכה לממש את כל שיטות הממשק )אחרת הקומפיילר צועק(. ניתן להגדיר מחלקות שמיישמות רק חלק מן השיטות בממשק. אלו יהיה מחלקות אבסטרקטיות, שלא ניתן ליצור מהן מופע, ועליהן נלמד בהמשך הקורס. 9. מחלקה יכולה לממש יותר מממשק אחד! )פרוט בהמשך( דוגמה: public interface Predator { boolean chaseprey(prey p); void eatprey(prey p); public class Shark implements Predator { public boolean chaseprey(prey p) { // code to chase prey p (specifically for a shark) return swimafterprey(p); public void eatprey (Prey p) { // code to eat prey p (specifically for a shark) bite(p); swallow(p); 2
public class Tiger implements Predator { public boolean chaseprey(prey p) { // code to chase prey p (specifically for a tiger) return runafterprey(p); public void eatprey (Prey p) { // code to eat prey p (specifically for a tiger) chew(p); swallow(p); כריש הוא טורף וגם נמר הוא טורף. בזכות הממשק המשותף אותו הם ממשים, אנחנו יכולים למשל, להחזיק מערך של טורפים ולהפעיל פעולות משותפות על כל אחד מהאיברים במערך. Predator[] preds = new Predator[3]; preds[0] = new Tiger(); preds[1] = new Shark(); preds[2] = new Shark(); Prey froggy = new Frog(); for (int i=0; i<preds.length & froggy.isalive(); i=i+1) { froggy.runaway(); if (preds[i].chaseprey(froggy)) preds[i].eatprey(froggy); בקטע הקוד הנ"ל ניתן לראות שיצרנו גם טרף צפרדע, שנרדף ע"י הכרישים והנמר. מיד נראה מה טרף מסוגל לבצע, כלומר כיצד הוגדר ממשק של טרף Prey.interface אך קודם נראה דוגמה כיצד ניתן לקבל טורף וטרף כפרמטר לפונקציה: public static void simulatechase(predator predat, Prey prey) { prey.runaway(); if (predat.chaseprey(prey)) predat.eatprey(prey); אנחנו יכולים להפעיל את הפעולות שטורף וטרף יודעים לעשות, למרות שאיננו יודעים איזה טורף או טרף קיבלנו בקריאה לפונקציה. Shark sharky = new Shark(); Frog kermit = new Frog(); simulatechase(sharky, kermit); 3
ניתן, כמובן, להוסיף פעולות )שיטות( ומצב )שדות( למחלקות השונות, ללא קשר לממשק אותן ממשות. לדוגמה: public class Shark implements Predator { private String name; private int numofteeth; public Shark(String name) { this.name = name; //3000-3999 teeth numofteeth = 3000 + (int)(math.random()*1000); public boolean chaseprey(prey p) { return swimafterprey(p); public void eatprey(prey p) { bite(p); swallow(p); private void swallow(prey p) {... p.die(); public int getnumofteeth() { return numofteeth; public void swimforfun() {...... אנחנו רואים כי "טרף" הוא גם כן תאור כללי של יצור, המסוגל לבצע מספר פעולות בסיסיות. נבנה לו אם כן ממשק: public interface Prey { public boolean isalive(); public void die(); public void runaway(); 4
וכבר ראינו דוגמה למחלקה שעשויה לממש "טרף": public class Frog implements Prey { private boolean living; public Frog() { living = true; public boolean isalive() { return living; public void die() { living = false; public void runaway() {... מימוש מספר ממשקים ניתן לממש יותר מממשק אחד. המחלקה המממשת תצטרך לממש את השיטות מכל הממשקים. מימוש מספר ממשקים מתבצע באופן הבא: <שם ממשק < n,,<שם ממשק < 2,<שם ממשק < 0 implements <שם מחלקה> class על המחלקה לממש את הפונקציות של n הממשקים. אם מחלקה מיישמת שני ממשקים בעלי שיטה בעלת שם זהה נניח off אז: אם לשתי ה off יש חתימה שונה אז המחלקה מיישמת חייבת לממש את שתיהן. אם לשתי ה off יש אותה חתימה ואותו טיפוס מוחזר אז המחלקה מיישמת רק off אחד. - טעות קומפילציה. )לא ניתן לממש אם לשתי ה off יש אותה חתימה אך טיפוס מוחזר שונה את שני הממשקים יחד(. עד עתה חשבנו על צפרדע כטרף. אבל ניתן לחשוב עליו גם כטורף )זבובים יהיו הטרף שלו במקרה זה( public class Frog implements Prey, Predator {... public boolean chaseprey(prey p) {... public void eatprey (Prey p) {... 5
Shark sharky = new Shark(); Frog kermit = new Frog(); Fly bzzit = new Fly(); כעת הצפרדע יכולה לבצע את שני התפקידים: simulatechase(sharky, kermit); if(kermit.isalive()) simulatechase(kermit, bzzit); sharky.swimforfun(); למה הקוד הזה מתקמפל? הרי הפונקציה simulatechase מקבלת "טורף" ו"נטרף" לא כריש או צפרדע או זבוב. הסיבה נעוצה בעובדות שכריש הוא טורף, צפרדע היא טרף, צפרדע היא גם טורף, וזבוב הוא טרף. ממשק יכול לרשת מממשק אחר, כלומר להרחיב אותו. נדבר על הורשה בהמשך התרגול. 6
ממשק כחוזה נגדיר ממשק ל"זוג סדור". בזוג סדור יש שני איברים: איבר ראשון ואיבר שני. ניתן להבחין בין שניהם. נרצה יכולת ליצור זוג סדור, ואחר כך לגשת לכל איבר. כמו כן נוסיף את הפעולה equals שתשווה את הזוג הסדור לזוג סדור אחר. יש מספר דרכים לממש זוג סדור. למשל בעזרת שני משתנים, first ו- second, או בעזרת מערך של בגודל 2. נשים לב כי השיטות unorderedequals, orderedequals מקבלת זוג סדור אחר, ואיננו יודעים כיצד הוא ממומש )שני משתנים, מערך או אחרת(. כל שאנו יכולים לעשות הוא לגשת לשיטות שהחוזה )הממשק( OrderedPair מספק לנו. public interface OrderedPair { public Object getfirst(); public Object getsecond(); public boolean orderedequals(orderedpair another); public boolean unorderedequals(orderedpair another); public class PairAsFields implements OrderedPair { private Object first, second; public PairAsFields(Object x, Object y){ first = x; second = y; public Object getfirst() { return first; public Object getsecond() { return second; public boolean orderedequals(orderedpair another) { return first.equals(another.getfirst()) && second.equals(another.getsecond()); public boolean unorderedequals(orderedpair another) { return (getfirst().equals(another.getfirst()) && getsecond().equals(another.getsecond())) (getfirst().equals(another.getsecond()) && getsecond().equals(another.getfirst())); 7
public class PairAsArray implements OrderedPair { private Object[] pair; public PairAsArray(Object a, Object b) { pair = new Object[2]; pair[0] = a; pair[1] = b; public Object getfirst() { return pair[0]; public Object getsecond() { return pair[1]; public boolean orderedequals(orderedpair another) { return pair[0].equals(another.getfirst()) && pair[1].equals(another.getsecond()); public boolean unorderedequals(orderedpair another) { return (getfirst().equals(another.getfirst()) && getsecond().equals(another.getsecond())) (getfirst().equals(another.getsecond()) && getsecond().equals(another.getfirst())); 8
הורשה באופן כללי אובייקטים מתוארים על ידי מחלקות.)classes( המחלקה מתארת את מרחב המצבים ואת ההתנהגויות האפשריות לאובייקט. לדוגמה, מחלקה המגדירה אופניים תגדיר אובייקטים בעלי שני גלגלים, כידון ופדלים. אולם, קיימים סוגים רבים של אופניים ונרצה להבדיל בין הסוגים השונים. מערכות מונחות עצמים מאפשרות להגדיר מחלקות בעזרת מחלקות אחרות. למשל אופני הרים, אופני מרוץ ואופניים עם גלגלי עזר הן סוג של אופניים. במונחי object-oriented אופני הרים, אופני מרוץ ואופניים עם גלגלי עזר הן תתי מחלקות )subclasses( של מחלקת אופניים.)class( באותו אופן, מחלקת האופניים היא מחלקת אב של )superclass( של מחלקות אופני הרים ואופני מרוץ. אופניים אופני הרים אופני מרוץ אופניים עם גלגלי עזר כל תת מחלקה יורשת ממחלקת האב שלה התנהגות ומצב. לכל מחלקות האופניים קיימים מאפייני מצב משותפים כמו מהירות, צבע, והתנהגויות משותפות כמו עצירה, נסיעה והחלפת הילוכים. תתי מחלקות יכולות להוסיף שדות ושיטות משלהן, למשל: אופני הרים יכולות לנסוע בשטח לאופני מרוץ הילוכים מיוחדים. לאופניים עם גלגלי עזר יש גלגלי עזר, וכו'. ככל שנוסיף התנהגויות ומשתני מצב נהפוך את המחלקה לספציפית יותר )כללית פחות(. ככל שיורדים בעץ ההורשות, כך המחלקה יותר ספציפית. לדוגמה אופני מרוץ מגדירים קבוצה מדויקת יותר מאשר אופניים כלליים. תתי מחלקות יכולות לדרוס את השיטות אותן הן יורשות. למשל שיטת החלפת הילוכים אשר קיימת במחלקה אופניים, תהיה שונה עבור אופניים עם גלגלי עזר, אשר אין להם הילוכים, או במחלקת אופני מרוץ אשר יש להם הילוכים מיוחדים. 9
הורשה אינה מוגבלת לרמה אחת. ניתן להגדיר תת מחלקות עבור תת המחלקות וכן הלאה. בשורש עץ ההורשות ב- Java יושבת המחלקה Object אשר כל המחלקות האחרות יורשות ממנה באופן ישיר או עקיף. לכן, ההתנהגויות המוגדרות במחלקה Object משותפות לכל האובייקטים ב- Java. כללים בסיסיים: מחלקה יכולה להרחיב רק מחלקה אחת. מחלקה B תרחיב את A אם נכון לומר "B הוא סוג של A". אובייקט מרחיב )מטיפוס B( מכיל בתוכו את האובייקט המורחב )מטיפוס A(. ניתן ליצור אובייקט מטיפוס B מבלי ליצור במפורש אובייקט מטיפוס A. אם B מרחיבה )יורשת( את A אזי A היא ה parent class או ה super class של B..A של child class או subclass היא ה B B יורשת את כל השיטות והמשתנים שאינם private ב- A מלבד הבנאים. לא ניתן לגשת בתוך B לשיטות או שדות פרטיים ב- A על אף שהם קיימים..0.2.3.4.5 class A { private int anum; class B extends A { private int bnum; public B() { anum=0; // compilation error bnum = 0; בנאים אינם עוברים בהורשה! לכן יש להגדיר בנאים חדשים. כשיוצרים אובייקט חדש מסוג B, הבנאי של מחלקה A חייב להיקרא. ניתן לקרוא לו באופן מפורש, אחרת נקרא באופן אוטומטי הבנאי הריק של A..6.7 קריאה מפורשת לבנאי של superclass נעשית ע"י.super( ) יש לבצע קריאה זו בשורה הראשונה של הבנאי של מחלקה B. super הינה מלה שמורה בשפת,Java אשר מייצגת את מחלקת האב. המחשב מזהה לאיזה מבין הבנאים 01
של מחלקת האב לקרוא לפי רשימת הארגומנטים אשר מופיעה אחרי.super class A { private int anum; public A(int n) { anum = n; class B extends A { private int bnum; public B() { super(0); //calls the constructor of A bnum = 0; נשים שאם הגדרנו במחלקת האב בנאים, אך אף אחד מהם אינו בנאי ברירת-המחדל, האוטומטית לבנאי ברירת-המחדל )אשר אינו קיים( תגרור שגיאת קומפילציה. הקריאה מומלץ כי בבנאי של מחלקה מרחיבה תהיה קריאה מפורשת לבנאי של המחלקה המורחבת. class A { private int anum; public A(int n) { anum = n; class B extends A { private int bnum; public B() { //compilation error no default constructor for A bnum = 0; 00
קריאה ל super חייבת להיות השורה הראשונה בבנאי של B class A { private int anum; public A(int n) { anum = n; class B extends A { private int bnum; public B() { bnum = 0; //compilation error can t find A() super(0);.8 דריסה - Overriding כאשר מחלקת בן מגדירה פונקציה שאינה private בעלת חתימה זהה לפונקציה המוגדרת במחלקת האב שלה נאמר כי השיטה של מחלקת הבן "דורסת" את שיטת האב. כאשר מדובר בדריסה, הטיפוס של האינסטנס (instance) הטיפוס הנפנה- מגדיר איזו פונקציה תופעל. במקרים אחרים מופעלת הפונקציה לפי הרפרנס.(reference) הטיפוס הפונה.9 class A { private int anum; public A(int n) { anum = n; public void inc(int n){ anum = anum + n; class B extends A{ private int bnum; public B() { super(0); bnum = 0; public void inc(int amount){ // overrides inc(int) of A super.inc(amount); //increment anum bnum += amount; //increment bnum 02
לא ניתן לדרוס שיטה עם חתימה זהה וטיפוס החזרה שונה, (זה חוקי אם טיפוס ההחזרה הוא אובייקט ויורש מטיפוס ההחזרה של השיטה הנדרסת, אך לא רצוי בכל מקרה(: class B extends A{ private int bnum; public B() { super(0); bnum = 0; public int inc(int n){ // Compilation error: // cannot override int inc(int) super.inc(n); bnum += n; return bnum; 01. super - אם רוצים לקרוא באופן מפורש לפונקציה של האב ניתן להשתמש במילה.super נראה דוגמאות בהמשך. 00. שימו, המונחים דריסה (overriding) והעמסה (overloading) הם שני מושגים שונים: - העמסה היא כאשר ישנן מספר הגדרות לפונקציה בעלת אותו שם אך עם פרמטרים שונים באותה המחלקה. - דריסה היא הגדרה של שתי שיטות בעלות חתימה זהה, אחת במחלקת האב ואחת במחלקת הבן. - העמסה מאפשרת פעולה דומה עבור data שונה. - דריסה מאפשרת לבצע פעולה דומה עבור טיפוס אובייקט שונה. 02. ניתן ליצור הירארכיה בעזרת הורשה, כך שמחלקת בן של מחלקה אחת יכולה להיות מחלקת אב של מחלקה אחרת. Book Dictionary Cookbook Novel Mystery Romance 03
03. כל מחלקה מרחיבה בצורה ישירה או עקיפה את המחלקה.Object כל מחלקה שקיימת ב Java או שניצור בעצמנו ואשר אינה מרחיבה מחלקה אחרת, יורשת באופן אוטומטי את המחלקה.Object וכך המחלקה Object היא בעצם השורש בהירארכית הירושה. )המחלקה Object מוגדרת תחת.)java.lang Object Book 04. המימושים הדיפולטיביים )ברירות המחדל, מן המילה )default של השיטות tostring, equals ועוד מוגדרים במחלקה Object עם החתימות הבאות: public String tostring() public boolean equals(object obj) ולכן שיטות אלו נורשות אוטומטית ע"י מחלקה שאנו מגדירים כלומר, אם נגדיר עבור מחלקה A את השיטה הבאה: other) public boolean equals(a לא ביצענו דריסה של השיטה equals של מחלקת האב,Object מאחר והחתימה שונה. 04
.05 אופרטור :instanceof זהו אופרטור המחזיר true אם ורק אם האובייקט משמאלו הוא מופע )instance( של המחלקה מימינו של האופרטור. דוגמאות: class Cat{ interface ShowAnimal { class Siamese extends Cat implements ShowAnimal{ public class InstanceofExample { public static void main(string[] args) { Cat mitzi = new Siamese(); Cat myaoo = new Cat(); System.out.println(myaoo instanceof Cat); //true System.out.println(mitzi instanceof Cat); //true System.out.println(myaoo instanceof "Cat"); //Compilation error System.out.println(mitzi instanceof Siamese); //true System.out.println(myaoo instanceof Siamese); //false System.out.println(mitzi instanceof ShowAnimal); //true System.out.println(mitzi instanceof Object); //true System.out.println(mitzi instanceof myaoo); //Compilation error System.out.println(!mitzi instanceof Cat); //Compilation error: operator! cannot be applied to Cat System.out.println(!(mitzi instanceof Cat)); //false Cat pitzi = null; System.out.println(pitzi instanceof Cat); System.out.println(null instanceof String); //false //false 05