ראשי
פרק 1
פרק 2
פרק 3
פרק 4
פרק 5
פרק 6
פרק 7
פרק 8

פרק 6 מחלקות ועצמים - הרחבה


תוכן הפרק:

מודל ההכלה
חריגות
מחלקות אוסף
קלט / פלט
סדרתיות



מודל ההכלה - מחלקות פנימיות

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

  • מחלקות סטטיות.
  • מחלקות חברות.
  • מחלקות מקומיות.
  • מחלקות אנונימיות.

מחלקה סטטית

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

מחלקות סטטיות

יצרנו שתי מחלקות, כאשר מחלקת האב מכילה את מחלקת הבן, ואולם - אין קשר בין המחלקות, וכל משתנה x מסוג int בכל אחת מהמחלקות הוא משתנה בפני עצמו.
נשתמש בעצמים מסוג המחלקה כך:

שימוש במשתנים במחלקות סטטיות

כלומר, יצירת עצם מסוג מחלקת Son מתבצעת תוך שימוש במרחב השמות של מחלקת Father, ש Son מוכלת בתוכה.

מחלקה חברה

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

מחלקת Father
והקריאה מהמחלקה הראשית:
מחלקת FriendClass
הפלט של התוכנית יהיה:
a =3
a =2
a =1

מאחר וא"א ליצור עצם של מחלקה פנימית בצורה חופשית, אלא רק כחלק מעצם של המחלקה שמכילה אותה, יצירת העצם של מחלקה פנימית תעשה ע"י קריאה לעצם שכבר יצרנו מסוג המחלקה המכילה, ודרכו ניצור את העצם החדש של המחלקה המוכלת.
לכן יצירת עצם מסוג Son היתה רק על גבי העצם f מסוג Father, שכבר יצרנו.
בקריאה לשמות המשתנים בפונקציה out, מאחר והיו למשתנים שמות דומים קראנו להם ע"י שימוש בשם המחלקה שהם שייכים לה ושימוש במצביע העצמי (this).

מחלקה מקומית

דומה למחלקה חברה, אבל מוגדרת בתוך הגדרת פונקציה כלשהי במחלקה הראשית.
דוגמא:

מחלקה מקומית

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

מחלקה אנונימית

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

דוגמא:

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

מחלקת MouseApp
המחלקה המאזינה:
מחלקת myMouseAdapter
עכשיו נבנה את התוכנית המקבילה תוך שימוש במחלקה אנונימית:
מחלקת FuncMouseApp

הגדרנו את המחלקה המאזינה כתת מחלקה אנונימית בתוך מחלקת האם.

הערות חשובות

  • במחלקה פנימית אי אפשר להגדיר פונקציות או תכונות סטטיות.
  • מחלקה חברה תקבל שם שונה משם המחלקה המכילה.
  • בזמן ההידור יבנה קובץ class מיוחד למחלקה המוכלת. שמו יהיה: המחלקה המכילה $ המחלקה המוכלת. לדוג': Father$Son.class.


למעלה

חריגות

הטיפול בחריגות - שגיאות, הוא אחד הדברים החשובים בכתיבת תוכנית. בכל הרצת תוכנית עלולות להתרחש שגיאות מסוגים שונים: שגיאות זיכרון, שגיאות מתמטיות (חילוק ב 0 למשל), ועוד.
בכל שגיאה בג'אווה חייבים לטפל. קריאה לפונקציה שעלולה להחזיר שגיאה בלי לטפל בשגיאה הינה טעות הידור!!
ישנן שתי אפשריות טיפול בקריאה לפונקציה שעלולה להחזיר חריגה.

  1. זריקת החריגה - הפונקציה תצהיר בהתחלה שהיא מתעלמת מטיפול בשגיאות, ע"י שימוש בהצהרת throws Exception. לדוגמא:
    זריקת חריגה

    הפונקציה תטפל בחריגות ע"י זריקתן.

  2. תפיסת החריגה - הפונקציה תיכתב בתוך בלוק try והחריגות תתפסנה ע"י בלוקים מיוחדים מסוג catch, שיטפלו בשגיאות מסוג מסוים שיוכרז בהם. לדוגמא:
    תפיסת חריגה

    כל החריגות שתיווצרנה בפונקציות שכתובות בתוך בלוק ה try תתפסנה בפונקציות ה catch. שגיאות מסוג קלט/פלט, תתפסנה ע"י הפונקציה הראשונה, שאר השגיאות תתפסנה ע"י הפונקציה השניה, שתופסת עצמים מסוג חריגה כלשהי.

    • קיים בלוק נוסף, מסוג finally, שנכתב בסוף התוכנית אחרי בלוקי ה catch. הבלוק הזה יבוצע תמיד, ללא קשר אם נזרקה שגיאה או לא.

בלוק finally

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

  • Error - שגיאה חמורה. בד"כ מסיימת את ריצת התוכנית.
  • Exception - חריגה, שגיאה קלה. בד"כ תטופל והתוכנית תמשיך לרוץ.

מחלקת האם של שתי המחלקות היא מחלקת Throwable שיש לה פונקציות אבחון כמו ( )printStackTrace - שמחזירה את מחסנית הקריאות, ( )getMessage, שתיתן תיאור של החריגה שהתגלתה, ועוד.

אמנם אמרנו שאין להשאיר חריגות לא מטופלות, אבל יש שגיאות שהמכונה המדומה (JVM) תטפל בהן בזמן הריצה. למשל:

  • חריגה מגבולות מערך.
  • כשלון הקצאת זכרון.
  • גלישה ממחסנית הקריאות.
  • נסיון הצבעה למצביע שערכו null.

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


למעלה

מחלקות אוסף - Collection class

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

  • Vector - מערך ללא הגבלת גודל.
  • Stack - מחסנית.
  • Hashtable - טבלה שמקשרת מפתח מסוים לערך מסוים אחר (למשל טבלה שתקשר את אותיות הא"ב עם הערך המספרי שלהם).

וקטור - Vector

מחלקת וקטור מיועדת למערך הגדל לפי הצורך.
הפונקציות הבסיסיות של המחלקה הן:

    • ( )addElement - הוספת איבר למערך.
    • ( )elementAt - קבלת איבר שנמצא במקום מסוים במערך.
    • ( )setElementAt - הכנסת איבר למקום מסוים במערך.
    • ( )indexOf - החזרת המקום של איבר מסוים במערך (אם קיים).
    • ( )removeAllElements - איפוס הווקטור.

דוגמא:

;( )Vector vec = new Vector
;("vec.addElement("sunday
;("vec.addElement("monday
. . .
;("vec.addElement("saturday
;(System.out.println(vec

התוכנית תדפיס את ימי השבוע.

טבלה - Hashtable

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

    • ( )put - הוספת מפתח + ערך נוסף לטבלה.
    • ( )get - קבלת ערך בעזרת מפתח.
    • ( )remove - הסרת מפתח בטבלה.
    • ( )size - קבלת מספר האיברים בטבלה.

דוגמא:

;( )Hashtable numerology = new Hashtable
;((numerology.put("a" ,new Integer(1
;((numerology.put("b" ,new Integer(2
;((numerology.put("c" ,new Integer(3
. . .
;Integer letter
;("letter = (Integer)numerology.get(“x

בנינו טבלה של ערכי האותיות בגימטריה.
מדוע השתמשנו ב Integer ?
מאחר והטבלה מקבלת רק עצמים - ולא משתנים - נאלצנו במקום להשתמש במשתנה מסוג int להשתמש בעצם מסוג Integer, שהיא המחלקה העוטפת את int (בהתאם יש מחלקות עוטפות גם לשאר סוגי המשתנים הבסיסיים).

מחסנית - Stack

מחלקת מחסנית - עיקרון last in first out) lifo).
הפונקציות הבסיסיות של המחלקה הן:

    • ( )push - דחיפת איבר חדש למחסנית.
    • ( )pop - הוצאת איבר מהמחסנית.
    • ( )peek - "הצצה" מהו האיבר בראש המחסנית.
    • ( )search - חיפוש איבר במחסנית.
    • ( )empty - בדיקה אם המחסנית ריקה.

דוגמא:

;( )Stack sta = new Stack
;("sta.push("red
;("sta.push("green
;("sta.push("blue
;("sta.push("yellow
(( )while (!sta.empty
;(( )System.out.println(sta.pop  

התוכנית תדפיס את תוכן המחסנית.


למעלה

קלט / פלט

לטיפול בפלט/קלט התקני (כלומר למסך וממנו), השתמשנו עד כה ב 3 עצמים של המחלקה System, שהיא מחלקת המערכת בג'אווה, ותפקידה לטפל במאפייני המערכת.

  1. out - לטיפול בפלט התקני.
  2. in - לטיפול בקלט התקני.
  3. err - לטיפול בשגיאות.

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

  • סוג המידע - בתים/תוים.
  • מה מתבצע - מ/לאיזה מדיה הק/פ מגיע (מקלדת זכרון קבצים וכו'), ומהן הפעולות שמתבצעות בק/פ.

זרמי ק/פ עפ"י יצוג מדיה

יעד המידע זרמי בתים זרמי תווים
זכרון - כתיבה וקריאה לזיכרון וממנו ByteArrayInputStream
ByteArrayOutputStream
StringBufferInputStream
CharArrayReader
CharArrayWriter
StringReader
StringWriter
צינור PIPE - העברת נתונים מתוכנית אחת לאחרת דרך צינור נתונים PipedInputStream
PipedOutputStream
PipedReader
PipedWriter
קובץ - קריאה מתוך קובץ וכתיבה אליו FileInputStream
FileOutputStream
FileReader
FileWriter

זרמי ק/פ המבצעים עיבוד על הנתונים

תהליך זרמי בתים זרמי תווים
הדפסה PrintStream PrintWriter
באפר - כתיבה וקריאה דרך באפר BufferedInputStream
BufferedOutputStream
BufferedReader
BufferedWriter
המרת נתונים - קריאת נתונים והמרתם לטיפוסי ג'אווה בסיסים DataInputStream
DataOutputStream
 
סדרות - כתיבת וקריאת עצמים מ/אל זרם הק/פ. ObjectInputStream
ObjectOutputStream
 
דחיסה - דחיסת נתונים בפורמט zip ו gzip. ZipInputStream
ZipOutputStream
GzipInputStream
GzipOutputStream
 

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

DataInputStream dis = new DataInputStream(new BufferedInputStream(new
;(((" FileInputStream("file.txt
ולחילופין, לשם שמירת נתונים בקובץ, נגדיר עצם כזה:
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(new
;((("FileOutputStream("file.txt

פלט פשוט ע"י printWriter

הכרנו כבר את עצם הפלט התקני System.out. אפשר להעמיס אותו, בעזרת שימוש בפונקציה ()print.
נראה דוגמא:

העמסת System.out

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

העמסת System.out (המשך)

עכשיו התוכנית תדפיס את הפלט לקובץ file.txt.

קלט

נסביר קודם את המחלקה StringTokenizer.

StringTokenizer

StringTokenizer היא מחלקה שמקבלת מחרוזת ומפרקת אותה לתתי מחרוזות תוך התעלמות מרווחים וטאבים (פירסינג).

;(StringTokenizer st = new StringTokenizer (str, delim, true

הסבר:
העצם st מקבל 3 פרמטרים:

  • str - מחרוזת לפירוק
  • delim - מפרידים.
    למשל: "," ";" וכיו"ב. כלומר, המחרוזת str תחולק לחלקים, תתי מחרוזות, עפ"י המפריד שנבחר (אם בחרנו פסיק, המחלקה תחלק את המחרוזת עפ"י הפסיקים שיש בה). ניתן להגדיר כמה מפרידים ואז הם יכתבו ברצף בתור הפרמטר delim. למשל: ";,." יגרום לחלוקת המחרוזת str עפ"י פסיקים, נקודה-פסיק, או נקודה.
  • true/false - ערך true יחזיר גם את המפרידים (כלומר את הפסיק למשל) בתור תת מחרוזת.

פונקציות במחלקה

  • ( )st.nextToken - תחזיר את חלק המחרוזת הבא. היא יכולה לקבל מפריד מסוים, ואז תחלק את החלק הבא רק עפ"י המפריד הספציפי, ולא עפ"י המפרידים שהועברו בבנאי.
  • ( )st.hasMoreToken - מחזיר true/false אם הסתיימה חלוקת המחרוזת.

קלט ע"י BufferedReader

המחלקה BufferedReader משמשת לקבלת קלט. בתוכה קיימת פונקציה נוחה לשימוש בשם ( )readLine. לעומת זאת, אין למחלקה פונקציות לטיפול בקלט של טיפוסים בסיסיים כמו int למשל, ולא ניתן לדלג על הרווחים או על הטאבים. לכן נקלוט בעזרת עצם מהמחלקה ואז ננתח בעזרת ה StringTokenizer.
דוגמא
לתוכנית שתקרא מהקובץ file.txt:

מחלקת BufferAndToken

התוכנית קוראת מהקובץ ומדפיסה את המחרוזת הראשונה בכל שורה על המסך.

שימוש במחלקה System

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

(setIn(InputStream
(setOut(PrintStream
(setErr(PrintStream
לדוגמא:
;(("System.setIn(new FileInputStream("file.txt
;(("System.setOut(new FileOutputStream("file.txt

הקלט והפלט של פונקציות המערכת ישלח לקובץ file.txt.


למעלה

סדרתיות - שמירת עצמים ע"ג מדיות - serialization

לשמירה של מצב נתון של עצמים על מדית אחסון (למשל דיסק קשיח) משתמשים באחסון סדרתי.
נשתמש בפונקציות מיוחדות:

  • ( )writeObject - שמחלקת ObjectOutputStream לכתיבה למדיה.
  • ( )readObject - שמחלקת ObjectInputStream לקריאה מהמדיה.

שמירה תעשה בשלבים הבאים:

  1. פתיחת קובץ לכתיבה.
  2. פתיחת זרם ObjectOutputStream על גבי הקובץ שפתחנו.
  3. קריאה לפונקציה ( )writeObject לכל עצם שמיועד לשמירה.
  4. סגירת הקובץ.

שיחזור יעשה בשלבים הבאים:

  1. פתיחת קובץ לקריאה.
  2. פתיחת זרם ObjectInputStream על גבי הקובץ שפתחנו.
  3. קריאה לפונקציה ( )readObject לכל עצם שמיועד לשמירה.
  4. סגירת הקובץ.

העצם עצמו, שנרצה לשמור על מדיית האחסון, יהיה חייב לממש את הממשק Serializable, שאינו כולל שום פונקציות למימוש אבל מציין שהעצם ניתן לסידרו·ת. שדה ספציפי בתוך עצם, שלא נרצה לשמור, נציין בעזרת המילה השמורה transient.



למעלה