11 minute read

שימו לב - העשרה


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

למה צריך את Java

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

Java היא שפה עילית (high-level), במידה מסוימת

בכל מקרה, תוכנית מחשב שיכולה לקחת כל תיאור מילולי של תוכנת מחשב ולהפוך אותו לקוד מכונה היא עדיין דבר בדיוני למדי (למרות שבשנת 2023 ראינו קפיצה אדירה בתחום הבינה המלאכותית והופיע ChatGPT - התקרבנו לשם, אבל אנחנו עוד לא שם). שפת התכנות הראשונה (כנראה?) הייתה אסמבלי, או בעברית - שפת סף. אסמבלי היא שפה שבה כל שורת קוד מייצגת פקודת מכונה - קוד עם \(n\) שורות אסמבלי יהפוך לקוד מכונה עם \(n\) פקודות. זה מה שהופך את אסמבלי לשפת סף, ובכך היא בתחתיתו של אחת הסקאלות שעליהן ממוקמות שפות התכנות - סקאלת low-level-high-level. שפות שהן low-level הן יותר קרובות למה שהמחשב עושה בפועל, מכילות פחות אבסטרקציה, וככל שהן יותר low-level המתכנת נדרש יותר לחשוב על איך המחשב פועל (לכן, הן גם נקראות “קרובות למתכת”). לעומתן, שפות שהן יותר high-level (שפות עיליות) מאפשרות למתכנת לחשוב בקונספטים אבסטרקטיים יותר, ויותר קרובים לצורת מחשבה אנושית במקום מחשבית, ולכתוב קוד שהוא, בתקווה, יותר קריא מאשר שפות low-level, ויותר קל לכתיבה.

אז איפה שפת Java על הסקאלה הזו? ראשית צריך להבין שהסקאלה היא לא אבסולוטית - אין מספר או שבו ניתן להשוות האם שפה היא עילית יותר ובכמה, ואין לזה יחידת מידה. אין (ככל הידוע לי לפחות) מכשיר מדידה או נוסחה שתסווג את Java כשפה עם של “7.23 הופר” ואת אסמבלי כשפה של “0.1 הופר”. מתכנת פייתון יאמר לנו ש-Java היא שפה low-level יחסית, ומתכנתת C++ תאמר לנו ש-Java היא שפה high-level יחסית. כנראה שהרוב יסכימו על השוואות מסוימות: למשל, אין ממש מקום לטעון שיש שפה שהיא יותר תחתית מאשר אסמבלי (חוץ משפת מכונה או בינארי), ואין ממש מקום לטעון ששפת C היא יותר עילית מאשר Java. על כן, אנחנו יכולים להגיד על Java שהיא עילית יחסית. הכל יחסי כאן.

לאחר שדיברתי קצת על כך ש-Java היא שפה high-level יחסית, יש עוד שלוש תכונות של Java שאני רוצה לדבר עליהן: Java היא שפה מקומפלת, היא שפה אוניברסלית, והיא שפה מונחת עצמים.

Java היא שפה מקומפלת

סליחה, לא יכולתי להתאפק

(סליחה, לא יכולתי להתאפק)

Java היא שפה מקומפלת. הזכרתי קודם שקומפיילר (מהדר) הוא תוכנה שלוקחת קוד בשפת תכנות וממירה אותו לשפת מכונה. אם שפות עוברות קומפילציה (הידור), אז האם לא כל שפת תכנות היא מקומפלת? לא בדיוק. קומפיילר קורא את כל התוכנית ופולט קוד מכונה. יש עוד קטגוריה של שפות - שפות אינטרפר (מפרש). אינטרפטר הוא דומה לקומפיילר - הוא מקבל קוד בשפת תוכנית ומאפשר להריץ אותו - אבל אינטרפרטר קורא את הקוד ומבצע אותו מיד, שורה אחרי שורה. מפרש קורא שורה - מבין אותה - מבצע אותה, ורק אז ממשיך לשורה הבא. אני מפשט קצת (ובכלל, לפשט דברים זה חלק גדול בתכנות), אבל זה בערך ההבדל, או לכל הפחות, התיאור הזה טוב מספיק בשביל ההקשר הנוכחי. בתאוריה, אינטרפטרים (לפחות מסוימים) יכולים לקבל קוד שמתחיל בשורות קוד הגיוניות ותקינות, ואחריהן ג’יבריש מוחלט (מהסוג שנוצר כאשר החתול מחליט לפסוע על המקלדת שלנו כאילו מדובר ברצפז’/1גקסעהטאה4לםפצלוו]ץ5985) - והוא יבצע את השורות ההגיוניות, ורק לאחר מכן יגיד, “רגע, משהו פה לא בסדר” ויפסיק ויצעק עלינו.

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

Java היא אוניברסלית (וגם, שיקרתי קצת קודם)

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

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

וזו קצת בעיה, כי אם נרצה קוד למחשבים שונים, נרצה לכתוב תוכנה חדשה. זה אומר שאם אנחנו כותבים קוד עבור עצמנו, לא נוכל לשדרג את המחשבים שבהם אנחנו משתמשים בלי לשכתב את התוכנה, ואם נרצה לכתוב קוד עבור לקוחות אז נצטרך קוד שונה לכל לקוח. על כן, הומצא הקונספט של שפה אוניברסלית - שפה אחת לכל ארכיטקטורה שנרצה להשתמש בה. איך נריץ על ארכיטקטורות שונות? פשוט - יהיו לנו מהדרים שונים לכל ארכיטקטורה. את הקוד נכתוב בשפה אוניברסלית, ואז, לכל ארכיטקטורה שנרצה להריץ את התוכנה שלנו עליה, יהיה לנו מהדר, ונזין לתוכו את הקוד בשפה האוניברסלית שלנו, ונקבל קוד מכונה המתאים למכונה הזו. שפת התכנות הראשונה שעשתה זאת הייתה, ככל הידוע לי, COBOL - Common Business Oriented Language, שנוצרה ביוזמת מערכת הבטחון האמריקאית.

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

היה יכול להיות נחמד אם הייתה ארכיטקטורה אחת, שיש לכולם, אבל זה לא הולך לקרות. אז הרעיון של Java הוא שלכולם כן תהיה ארכיטקטורה אחת - סוג אחד של מכונה - אבל המכונה הזו, תהיה מכונה וירטואלית. המכונה הוירטואלית של Java, ה-Java Virtual Machine, היא תוכנית שמותקנת על מחשב, ו”מעמידה פנים” שהיא מחשב בפני עצמו. הרעיון הזה לא ייחודי לJava - הוא קיים מקטן ועד גדול. בין השאר, על מחשב בינוני היום אפשר להתקין מכונה וירטואלית שתריץ מערכת הפעלה שנועדה למחשב עצמאי בפני עצמה. אני למשל, את שורות אלה, כותב במחשב ה-Windows שלי, ועליו יש לי מכונה וירטואלית ב-Virtualbox שמריצה מערכת הפעלה ממשפחת Linux - זה שימושי לדברים מסוימים, כמו למשל אם אני רוצה להשתמש בתוכנות שנכתבו עבור מחשבי Linux, או סתם תוכנות שאני לא סומך עליהן לחלוטין ומעדיף להריץ אותן על מחשב שאם הן מכילות קוד זדוני, לא יהיה הרבה נזק שהן יוכלו בגלל לגרום אפילו אם ינסו.

מכונה וירטואלית - עולמות בתוך עולמות

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

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

אז לJava יש מכונה וירטואלית - המכונה הוירטואלית של Java - הJVM. המהדר של Java מקבל קוד בשפת Java, ופולט קוד מכונה עבור המכונה הוירטואלית של Java, ולא עבור המחשב שלנו. הקוד הזה נקרא byte-code. כשאנחנו מריצים תוכנית Java מקומפלת, אנחנו מריצים אותה על המכונה הוירטואלית של Java, שהיא בעצמה תוכנה שרצה על המחשב שלנו, שכתובה בשפת מכונה עבור המחשב הספציפי שלנו.

על כן, ה-JVM, המכונה הוירטואלית של Java, היא תוכנית בפני עצמה. המפתחים שיוצרים את שפת Java, בחברת Oracle (במקור השפה הייתה של חברת Sun) כתבו את התוכנה הזו, והידרו אותה עבור הארכיטקטורה של המחשב שלנו, ולכל ארכיטקטורה שהם רק יכלו, כדי שכל אחד יוכל להוריד מהאתר שלהם את המכונה הוירטואלית של Java ולהתקין אותה על המחשב שלו. גם המהדר של Java הוא תוכנה בפני עצמו, כזו שמטרתה לקבל קוד Java ולהוציא byte-code.

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

Java היא שפה מונחת עצמים

Java נמצאת במשפחה של שפות מונחות עצמים. תכנות מונחה עצמים הוא גישה תכנותית וגם צורת מחשבה. הצורה שבה בני אדם חווים את העולם, היא צורה שחושבת בעצמים. הכלים שבהם אנחנו משתמשים הם עצמים - עצמים שמורכבים מעצמים אחרים, ויש להם שימושים - דברים שאנחנו עושים איתם. לפעמים בעזרת העצמים האלה אנחנו משנים עצמים אחרים - לפעמים העצמים האלה עושים את פעולתם על ידי עצמים אחרים שמהם הם מורכבים.

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

בעיניי, Java היא הבחירה הטובה ביותר לשפת תכנות ראשונה ללמוד, וזאת כיוון שהיא שפה שהתכנות מונחה-העצמים הוא במרכז שלה, יותר משפות אחרות. יש הרבה שפות תכנות מונחות עצמים, אבל כנראה שבJava הן מהוות חלק משמעותי יותר מאשר בהרבה שפות אחרות. בנוסף, מבחינת סקאלת העיליות-תחתיות שהזכרנו קודם, Java היא, מבין השפות מונחות העצמים, במקום טוב באמצע. פייתון נחשבת על פי רוב עילית יותר ממנה, והיא שפה נפוצה למדי; C++ היא שפה שנחשבת על פי רוב תחתית יותר מJava, וגם היא נפוצה למדי. חוץ מזה, גם Java היא נפוצה למדי: כאשר תתקינו את Java, תוכנת ההתקנה כנראה תתגאה בפניכם ש”יותר משלושה מיליארד מכשירים מריצים את Java”, והמספר הזה היה המספר שתוכנת ההתקנה מציגה כבר שנים, והמספר האמיתי הוא כנראה כבר ממזמן הרבה יותר מזה.

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

מהו תכנות מונחה עצמים? אני נכנס לזה לעומק בהמשך - כאמור, בעיניי תכנות מונחה עצמים הוא הליבה של Java. נגיע לזה. אבל אני עדיין מרגיש שכדאי להגיד משהו בקצרה, אז על קצה המזלג: אני חושב על “עצמים” בתור “דברים” - אם יש קונספט, כמו למשל “אלגוריתם” או “מערכת ברובוט” או “קובץ” או “רובוט” וכו’ וכו’, אנחנו ניצור לו (או שכבר יהיה לנו) מה שנקרא “מחלקה” - קוד שמגדיר את הקונספט הזה ומה הוא יכול לעשות ואיך, והרבה מזה בעזרת מחלקות אחרות ומה שהן יכולות לעשות. למשל, מעלית ברובוט תהיה מחלקה, היא תשתמש במחלקה של מנוע ואולי גם במחלקות של חיישנים מסוימים, והיא תגדיר שהמעלית יכולה לעלות ולרדת ולעצור, ואיך. בצורה הזו אפשר לכתוב קוד שקל לכתוב ולקרוא (בתקווה), שקל להתמצא בו.

נקודות עיקריות

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

Comments