JavaRush /وبلاگ جاوا /Random-FA /تخصیص و مقداردهی اولیه در جاوا
Viacheslav
مرحله

تخصیص و مقداردهی اولیه در جاوا

در گروه منتشر شد

معرفی

هدف اصلی برنامه های کامپیوتری پردازش داده ها است. برای پردازش داده ها باید به نحوی آن را ذخیره کنید. من پیشنهاد می کنم درک کنیم که چگونه داده ها ذخیره می شوند.
تخصیص و مقداردهی اولیه در جاوا - 1

متغیرها

متغیرها محفظه هایی هستند که هر داده ای را ذخیره می کنند. بیایید به آموزش رسمی اوراکل نگاه کنیم: اعلام متغیرهای عضو . طبق این آموزش، چندین نوع متغیر وجود دارد:
  • فیلدها : متغیرهای اعلام شده در کلاس.
  • متغیرهای محلی : متغیرهای یک متد یا بلوک کد.
  • پارامترها : متغیرهای موجود در اعلان متد (در امضا).
همه متغیرها باید یک نوع متغیر و یک نام متغیر داشته باشند.
  • نوع یک متغیر نشان می دهد که متغیر چه داده هایی را نشان می دهد (یعنی چه داده هایی را می تواند ذخیره کند). همانطور که می دانیم، نوع یک متغیر می تواند ابتدایی (اولیه ) یا شیء باشد ، نه اولیه (Non-primitive). با متغیرهای شی، نوع آنها توسط یک کلاس خاص توصیف می شود.
  • نام متغیر باید با حروف بزرگ نوشته شود. می توانید اطلاعات بیشتری در مورد نامگذاری در " متغیرها: نامگذاری " بخوانید.
همچنین، اگر یک متغیر سطح کلاس، i.e. یک فیلد کلاس است، می توان یک اصلاح کننده دسترسی برای آن تعیین کرد. برای جزئیات بیشتر به کنترل دسترسی به اعضای یک کلاس مراجعه کنید .

اعلام متغیر

بنابراین، ما به یاد می آوریم که یک متغیر چیست. برای شروع کار با یک متغیر، باید آن را اعلام کنید. ابتدا اجازه دهید به یک متغیر محلی نگاه کنیم. به جای IDE، برای راحتی، از راه حل آنلاین از tutorialspoint استفاده می کنیم: Online IDE . بیایید این برنامه ساده را در IDE آنلاین آنها اجرا کنیم:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
بنابراین، همانطور که می بینید، ما یک متغیر محلی با نام numberو نوع اعلان کرده ایم int. دکمه "Execute" را فشار می دهیم و خطا را دریافت می کنیم:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
چی شد؟ ما یک متغیر را اعلام کردیم، اما مقدار آن را مقداردهی اولیه نکردیم. شایان ذکر است که این خطا در زمان اجرا (یعنی نه در زمان اجرا)، بلکه در زمان کامپایل رخ داده است. کامپایلر هوشمند بررسی کرد که آیا متغیر محلی قبل از دسترسی به آن مقداردهی اولیه می شود یا خیر. از این رو، گزاره‌های زیر از این موضوع به دست می‌آید:
  • متغیرهای محلی فقط باید پس از مقداردهی اولیه قابل دسترسی باشند.
  • متغیرهای محلی مقادیر پیش فرض ندارند.
  • مقادیر متغیرهای محلی در زمان کامپایل بررسی می شوند.
بنابراین، به ما گفته می شود که متغیر باید مقداردهی اولیه شود. مقداردهی اولیه یک متغیر، تخصیص یک مقدار به یک متغیر است. پس بیایید بفهمیم که چیست و چرا.

راه اندازی یک متغیر محلی

مقداردهی اولیه متغیرها یکی از پیچیده ترین موضوعات در جاوا است، زیرا... ارتباط بسیار نزدیکی با کار با حافظه، پیاده سازی JVM، مشخصات JVM و سایر موارد به همان اندازه ترسناک و فریبنده دارد. اما می توانید سعی کنید حداقل تا حدودی آن را کشف کنید. بیایید از ساده به پیچیده برویم. برای مقداردهی اولیه متغیر، از عملگر انتساب استفاده می کنیم و خط کد قبلی را تغییر می دهیم:
int number = 2;
در این گزینه هیچ خطایی وجود نخواهد داشت و مقدار روی صفحه نمایش داده می شود. در این مورد چه اتفاقی می افتد؟ بیایید سعی کنیم استدلال کنیم. اگر بخواهیم مقداری را به یک متغیر اختصاص دهیم، می‌خواهیم آن متغیر مقداری را ذخیره کند. معلوم می شود که مقدار باید در جایی ذخیره شود، اما کجا؟ روی دیسک؟ اما این بسیار کند است و ممکن است محدودیت هایی را برای ما ایجاد کند. به نظر می رسد که تنها جایی که می توانیم به سرعت و کارآمد داده ها را "اینجا و اکنون" ذخیره کنیم، حافظه است. این بدان معنی است که ما باید مقداری فضا در حافظه اختصاص دهیم. درست است. هنگامی که یک متغیر مقدار دهی اولیه می شود، در حافظه اختصاص داده شده به فرآیند جاوا که برنامه ما در آن اجرا می شود، فضایی برای آن اختصاص داده می شود. حافظه اختصاص داده شده به یک فرآیند جاوا به چندین ناحیه یا ناحیه تقسیم می شود. اینکه کدام یک از آنها فضا را اختصاص می دهد بستگی به نوع تعریف متغیر دارد. حافظه به بخش های زیر تقسیم می شود: Heap، Stack و Non-Heap . بیایید با حافظه پشته شروع کنیم. پشته به صورت پشته ترجمه می شود (مثلاً پشته ای از کتاب). این یک ساختار داده LIFO (آخرین ورود، اولین خروج) است. یعنی مثل یک پشته کتاب. وقتی کتاب‌ها را به آن اضافه می‌کنیم، آن‌ها را روی آن قرار می‌دهیم، و وقتی آنها را برداریم، کتاب بالایی (یعنی کتابی که اخیراً اضافه شده است) را می‌گیریم. بنابراین، ما برنامه خود را راه اندازی می کنیم. همانطور که می دانیم یک برنامه جاوا توسط یک JVM یعنی یک ماشین مجازی جاوا اجرا می شود. JVM باید بداند که اجرای برنامه از کجا باید شروع شود. برای انجام این کار، یک روش اصلی را اعلام می کنیم که به آن "نقطه ورودی" می گویند. برای اجرا در JVM، یک رشته اصلی (Thread) ایجاد می شود. هنگامی که یک رشته ایجاد می شود، پشته خود را در حافظه اختصاص می دهد. این پشته از قاب تشکیل شده است. هنگامی که هر متد جدید در یک رشته اجرا می شود، یک فریم جدید برای آن اختصاص داده می شود و به بالای پشته اضافه می شود (مانند یک کتاب جدید در یک پشته کتاب). این قاب حاوی ارجاعاتی به اشیا و انواع اولیه خواهد بود. بله، بله، int ما در پشته ذخیره می شود، زیرا... int یک نوع ابتدایی است. قبل از تخصیص یک فریم، JVM باید بفهمد چه چیزی را در آنجا ذخیره کند. به همین دلیل است که ما خطای "متغیر ممکن است مقداردهی نشده باشد" را دریافت می کنیم، زیرا اگر مقداردهی اولیه نشود، JVM نمی تواند پشته را برای ما آماده کند. بنابراین، هنگام کامپایل یک برنامه، یک کامپایلر هوشمند به ما کمک می کند از اشتباه کردن و شکستن همه چیز جلوگیری کنیم. (!) برای وضوح، من یک مقاله فوق‌العاده را توصیه می‌کنم : « جاوا Stack and Heap: Java Memory Allocation Tutorial ». این به یک ویدیوی به همان اندازه جالب پیوند دارد:
پس از اتمام اجرای یک متد، فریم های اختصاص داده شده برای این متدها از پشته thread حذف می شوند و به همراه آنها حافظه اختصاص داده شده برای این فریم با تمام داده ها پاک می شود.

مقداردهی اولیه متغیرهای شی محلی

بیایید دوباره کد خود را به کمی پیچیده تر تغییر دهیم:
public class HelloWorld{

    private int number = 2;

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

}
اینجا چه خواهد شد؟ بیایید دوباره در مورد آن صحبت کنیم. JVM می داند که برنامه را از کجا باید اجرا کند، یعنی. او روش اصلی را می بیند. یک رشته ایجاد می کند، حافظه را برای آن اختصاص می دهد (در نهایت، یک نخ باید داده های مورد نیاز برای اجرا را در جایی ذخیره کند). در این رشته یک فریم برای روش اصلی اختصاص داده شده است. سپس یک شی HelloWorld ایجاد می کنیم. این شی دیگر روی پشته ایجاد نمی شود، بلکه روی پشته ایجاد می شود. زیرا شی یک نوع اولیه نیست، بلکه یک نوع شی است. و پشته فقط یک مرجع به شی در پشته ذخیره می کند (ما باید به نحوی به این شی دسترسی داشته باشیم). در مرحله بعد، در پشته متد main، فریم هایی برای اجرای متد 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 مقداردهی اولیه می شود. اما فیلد شمارش زمانی که کلاس توسط ماشین مجازی جاوا بارگذاری می شود مقداردهی اولیه می شود. بارگذاری کلاس یک موضوع جداگانه است، بنابراین ما آن را در اینجا مخلوط نمی کنیم. فقط ارزش دانستن این نکته را دارد که متغیرهای استاتیک زمانی که کلاس در زمان اجرا شناخته می شود مقداردهی اولیه می شوند. چیز دیگری در اینجا مهمتر است و شما قبلاً متوجه این موضوع شده اید. ما مقدار را در جایی مشخص نکردیم، اما کار می کند. و در واقع. متغیرهایی که فیلد هستند، اگر مقدار مشخصی نداشته باشند، با مقدار پیش فرض مقداردهی اولیه می شوند. برای مقادیر عددی این 0 یا 0.0 برای اعداد ممیز شناور است. برای Boolean این نادرست است. و برای همه متغیرهای نوع شی، مقدار صفر خواهد بود (در ادامه در مورد آن صحبت خواهیم کرد). به نظر می رسد، چرا این چنین است؟ اما چون اجسام در Heap (در پشته) ایجاد می شوند. کار با این ناحیه در Runtime انجام می شود. و ما می توانیم این متغیرها را در زمان اجرا مقداردهی اولیه کنیم، برخلاف پشته که حافظه آن باید قبل از اجرا آماده شود. این نحوه عملکرد حافظه در جاوا است. اما یک ویژگی دیگر در اینجا وجود دارد. این قطعه کوچک زوایای مختلف حافظه را لمس می کند. همانطور که به یاد داریم، یک فریم در حافظه Stack برای متد اصلی اختصاص داده شده است. این قاب یک مرجع به یک شی را در حافظه Heap ذخیره می کند. اما شمارش کجا ذخیره می شود؟ همانطور که به یاد داریم، این متغیر بلافاصله قبل از ایجاد شی در پشته مقداردهی اولیه می شود. این یک سوال واقعا مشکل است. قبل از جاوا 8، یک ناحیه حافظه به نام PERMGEN وجود داشت. با شروع جاوا 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 یک مرجع خاص است که به هیچ جا و به هیچ شیئی اشاره نمی کند. بنابراین، فقط یک متغیر شی می تواند null باشد. دومین چیزی که درک آن مهم است این است که null یک مرجع است. من مرجع نیز وزن خود را دارند. در این مبحث، می توانید سوال stackoverflow را بخوانید: " آیا متغیر تهی به فضایی در حافظه نیاز دارد ".

بلوک های اولیه

هنگام در نظر گرفتن مقداردهی اولیه متغیرها، عدم در نظر گرفتن بلوک های اولیه گناه است. به نظر می رسد این است:
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