JavaRush /בלוג Java /Random-HE /הקצאה ואתחול ב-Java
Viacheslav
רָמָה

הקצאה ואתחול ב-Java

פורסם בקבוצה

מבוא

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

משתנים

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

הצהרת משתנה

אז, אנחנו זוכרים מהו משתנה. כדי להתחיל לעבוד עם משתנה, צריך להצהיר עליו. ראשית, בואו נסתכל על משתנה מקומי. במקום IDE, מטעמי נוחות, נשתמש בפתרון המקוון מ-tutorialspoint: IDE מקוון . בואו נריץ את התוכנית הפשוטה הזו ב-IDE המקוון שלהם:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
אז, כפי שאתה יכול לראות, הכרזנו על משתנה מקומי עם שם numberוסוג int. אנו לוחצים על כפתור "ביצוע" ומקבלים את השגיאה:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
מה קרה? הכרזנו על משתנה, אך לא אתחלנו את ערכו. ראוי לציין ששגיאה זו לא התרחשה בזמן הביצוע (כלומר, לא בזמן ריצה), אלא בזמן הקומפילציה. המהדר החכם בדק אם המשתנה המקומי יאתחל לפני הגישה אליו או לא. לכן, ההצהרות הבאות נובעות מכך:
  • יש לגשת למשתנים מקומיים רק לאחר אתחולם;
  • למשתנים מקומיים אין ערכי ברירת מחדל;
  • הערכים של משתנים מקומיים נבדקים בזמן הקומפילציה.
אז נאמר לנו שיש לאתחל את המשתנה. אתחול משתנה הוא הקצאת ערך למשתנה. אז בואו נבין מה זה ולמה.

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

אתחול משתנים הוא אחד הנושאים הקשים ביותר בג'אווה, מכיוון ש... קשור מאוד לעבודה עם זיכרון, יישום JVM, מפרט JVM ועוד דברים מפחידים ומסובכים לא פחות. אבל אתה יכול לנסות להבין את זה לפחות במידה מסוימת. בואו נעבור מפשוט למורכב. כדי לאתחל את המשתנה, נשתמש באופרטור ההקצאה ונשנה את השורה בקוד הקודם שלנו:
int number = 2;
באפשרות זו, לא יהיו שגיאות והערך יוצג על המסך. מה קורה במקרה הזה? בואו ננסה לנמק. אם אנחנו רוצים להקצות ערך למשתנה, אז אנחנו רוצים שהמשתנה הזה יאחסן ערך. מסתבר שהערך חייב להיות מאוחסן איפשהו, אבל איפה? על דיסק? אבל זה מאוד איטי ועלול להטיל עלינו מגבלות. מסתבר שהמקום היחיד שבו אנחנו יכולים לאחסן נתונים במהירות וביעילות "כאן ועכשיו" הוא הזיכרון. זה אומר שאנחנו צריכים להקצות קצת מקום בזיכרון. זה נכון. כאשר משתנה מאותחל, יוקצה עבורו מקום בזיכרון שהוקצה לתהליך ה-java שבתוכו תבוצע התוכנית שלנו. הזיכרון המוקצה לתהליך Java מחולק למספר אזורים או אזורים. מי מהם יקצה שטח תלוי באיזה סוג המשתנה הוכרז. הזיכרון מחולק לחלקים הבאים: ערימה, ערימה ו-Non-Heap . נתחיל עם זיכרון מחסנית. מחסנית מתורגמת כמחסנית (לדוגמה, ערימת ספרים). זהו מבנה נתונים LIFO (נכנס אחרון, יוצא ראשון). כלומר, כמו ערימה של ספרים. כשאנחנו מוסיפים לו ספרים, אנחנו שמים אותם למעלה, וכשאנחנו לוקחים אותם, אנחנו לוקחים את העליון (כלומר זה שהתווסף לאחרונה). אז, אנחנו משיקים את התוכנית שלנו. כפי שאנו יודעים, תוכנית Java מבוצעת על ידי JVM, כלומר, מכונה וירטואלית של Java. ה-JVM חייב לדעת היכן הפעלת התוכנית צריכה להתחיל. לשם כך, אנו מכריזים על שיטה עיקרית, הנקראת "נקודת הכניסה". לביצוע ב-JVM, נוצר שרשור ראשי (Thread). כאשר חוט נוצר, הוא מוקצה מחסנית משלו בזיכרון. מחסנית זו מורכבת ממסגרות. כאשר כל שיטה חדשה מבוצעת בשרשור, תוקצה לה מסגרת חדשה ותתווסף לראש הערימה (כמו ספר חדש בערימת ספרים). מסגרת זו תכיל הפניות לאובייקטים ולטיפוסים פרימיטיביים. כן, כן, ה-int שלנו יאוחסן על הערימה, כי... int הוא טיפוס פרימיטיבי. לפני הקצאת מסגרת, ה-JVM חייב להבין מה לשמור שם. מסיבה זו נקבל את השגיאה "ייתכן שהמשתנה לא היה אתחול", כי אם הוא לא מאותחל, אז ה-JVM לא יוכל להכין עבורנו את המחסנית. לכן, בעת קומפילציה של תוכנית, מהדר חכם יעזור לנו להימנע מטעויות ולשבור הכל. (!) למען הבהירות, אני ממליץ על מאמר סופר-דופר : " Java Stack and Heap: Java Memory Allocation Tutorial ". זה מקשר לסרטון מגניב לא פחות:
לאחר השלמת ביצוע שיטה, הפריימים שהוקצו לשיטות אלו יימחקו מהמחסנית של השרשור, ויחד איתם יימחק הזיכרון שהוקצה למסגרת זו עם כל הנתונים.

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

בואו נשנה שוב את הקוד שלנו לקצת יותר מסובך:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
מה יקרה כאן? בואו נדבר על זה שוב. ה-JVM יודע מאיפה הוא צריך להפעיל את התוכנית, כלומר. היא רואה את השיטה העיקרית. הוא יוצר שרשור, מקצה לו זיכרון (אחרי הכל, שרשור צריך לאחסן את הנתונים הדרושים לביצוע איפשהו). בשרשור זה מוקצים מסגרת לשיטה הראשית. לאחר מכן אנו יוצרים אובייקט HelloWorld. האובייקט הזה כבר לא נוצר על הערימה, אלא על הערימה. כי אובייקט הוא לא טיפוס פרימיטיבי, אלא סוג אובייקט. והמחסנית תאחסן רק התייחסות לאובייקט בערימה (אנחנו חייבים איכשהו לגשת לאובייקט הזה). לאחר מכן, בערימה של השיטה הראשית, יוקצו מסגרות לביצוע שיטת println. לאחר ביצוע השיטה הראשית, כל המסגרות יושמדו. אם המסגרת תושמד, כל הנתונים יושמדו. חפץ החפץ לא יושמד מיד. ראשית, ההתייחסות אליו תושמד וכך אף אחד לא יתייחס יותר לאובייקט האובייקט והגישה לאובייקט זה בזיכרון לא תתאפשר יותר. ל-JVM חכם יש מנגנון משלו לכך - אספן אשפה (אספן אשפה או בקיצור GC). לאחר מכן הוא מסיר מהזיכרון אובייקטים שאף אחד אחר לא מתייחס אליהם. תהליך זה תואר שוב בקישור שניתן לעיל. יש אפילו סרטון עם הסבר.

אתחול שדות

אתחול של שדות שצוינו במחלקה מתרחשת בצורה מיוחדת, תלוי אם השדה סטטי או לא. אם לשדה יש ​​את מילת המפתח static, אז השדה הזה מתייחס למחלקה עצמה, ואם המילה static לא צוינה, אז השדה הזה מתייחס למופע של המחלקה. בואו נסתכל על זה עם דוגמה:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
בדוגמה זו, השדות מאותחלים בזמנים שונים. שדה המספר יאתחל לאחר יצירת אובייקט המחלקה HelloWorld. אבל שדה הספירה יאתחל כאשר המחלקה תיטען על ידי המכונה הוירטואלית של Java. טעינת הכיתה היא נושא נפרד, אז לא נערבב את זה כאן. רק כדאי לדעת שמשתנים סטטיים מאותחלים כאשר המחלקה הופכת ידועה בזמן הריצה. משהו אחר חשוב כאן יותר, וכבר שמתם לב לזה. לא ציינו את הערך בשום מקום, אבל זה עובד. ואכן. משתנים שהם שדות, אם אין להם ערך שצוין, הם מאותחלים עם ערך ברירת מחדל. עבור ערכים מספריים זה 0 או 0.0 עבור מספרי נקודה צפה. עבור בוליאני זה שקר. ולכל משתני סוג האובייקט הערך יהיה null (נדבר על זה מאוחר יותר). נראה, מדוע זה כך? אלא בגלל שאובייקטים נוצרים בערימה (בערימה). העבודה עם אזור זה מתבצעת ב-Runtime. ונוכל לאתחל את המשתנים הללו בזמן ריצה, בניגוד למחסנית, שעבורה יש להכין את הזיכרון לפני הביצוע. כך עובד הזיכרון ב-Java. אבל יש כאן עוד תכונה אחת. היצירה הקטנה הזו נוגעת בפינות שונות של הזיכרון. כזכור, מסגרת מוקצה בזיכרון Stack עבור השיטה הראשית. מסגרת זו מאחסנת הפניה לאובייקט בזיכרון הערימה. אבל איפה נשמרת הספירה אז? כזכור, משתנה זה מאותחל מיד, לפני יצירת האובייקט בערימה. זו שאלה ממש מסובכת. לפני Java 8, היה אזור זיכרון שנקרא PERMGEN. החל מ-Java 8, אזור זה השתנה ונקרא METASPACE. בעיקרו של דבר, משתנים סטטיים הם חלק מהגדרת המחלקה, כלומר. המטא נתונים שלו. לכן, הגיוני שהוא מאוחסן במאגר המטא נתונים, METASPACE. MetaSpace שייך לאותו אזור זיכרון Non-Heap והוא חלק ממנו. כמו כן, חשוב לקחת בחשבון כי נלקח בחשבון סדר ההכרזה על משתנים. לדוגמה, יש שגיאה בקוד זה:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

מה זה ריק

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

בלוקים של אתחול

כאשר בוחנים אתחול של משתנים, יהיה זה חטא לא לשקול את בלוקי האתחול. זה נראה כמו זה:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

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

סיכום

אני מקווה שסקירה קצרה זו הצליחה לספק קצת תובנה לגבי איך זה עובד ומדוע. #ויאצ'סלב
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION