JavaRush /وبلاگ جاوا /Random-FA /تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا...

تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا. قسمت 15

در گروه منتشر شد
سلام سلام! یک توسعه دهنده جاوا چقدر باید بداند؟ شما می توانید برای مدت طولانی در مورد این موضوع بحث کنید، اما حقیقت این است که در مصاحبه با تئوری به حداکثر می رسد. حتی در آن حوزه های دانشی که فرصت استفاده از آنها را در کار خود نخواهید داشت. تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 1خوب، اگر مبتدی هستید، دانش تئوری شما بسیار جدی گرفته می شود. از آنجایی که هنوز هیچ تجربه و دستاوردهای بزرگی وجود ندارد، تنها چیزی که باقی می ماند بررسی قدرت پایگاه دانش است. امروز با بررسی محبوب ترین سوالات مصاحبه برای توسعه دهندگان جاوا به تقویت این پایه ادامه خواهیم داد. بیا پرواز کنیم!

جاوا هسته

9. تفاوت بین اتصال استاتیک و پویا در جاوا چیست؟

من قبلا در این مقاله در سوال 18 در مورد چندشکلی ایستا و پویا به این سوال پاسخ داده ام، توصیه می کنم آن را بخوانید.

10. آیا می توان از متغیرهای خصوصی یا محافظت شده در یک رابط استفاده کرد؟

نه نمی توانی. زیرا هنگامی که یک رابط را اعلام می کنید، کامپایلر جاوا به طور خودکار کلمات کلیدی عمومی و انتزاعی را قبل از متدهای رابط و کلمات کلیدی عمومی ، ثابت و نهایی را قبل از اعضای داده اضافه می کند. در واقع، اگر خصوصی یا محافظت شده را اضافه کنید ، یک تضاد ایجاد می‌شود و کامپایلر از اصلاح‌کننده دسترسی با این پیام شکایت می‌کند: «تغییرکننده «<sected modifier» در اینجا مجاز نیست.» چرا کامپایلر عمومی ، استاتیک و نهایی را اضافه می‌کند. متغیرها در رابط؟ بیایید آن را بفهمیم:
  • عمومی - رابط به مشتری اجازه می دهد تا با شی تعامل داشته باشد. اگر متغیرها عمومی نبودند، مشتریان به آنها دسترسی نداشتند.
  • static - رابط ها را نمی توان ایجاد کرد (یا به عبارت بهتر، اشیاء آنها)، بنابراین متغیر ثابت است.
  • نهایی - از آنجایی که رابط برای دستیابی به 100٪ انتزاع استفاده می شود، متغیر شکل نهایی خود را دارد (و تغییر نخواهد کرد).

11. Classloader چیست و چه کاربردی دارد؟

Classloader - یا Class Loader - بارگذاری کلاس های جاوا را فراهم می کند. به طور دقیق تر، بارگیری توسط فرزندان آن - لودرهای کلاس خاص تضمین می شود، زیرا ClassLoader خود انتزاعی است. هر بار که یک فایل .class بارگذاری می شود، برای مثال، پس از فراخوانی یک سازنده یا متد استاتیک از کلاس مربوطه، این عمل توسط یکی از فرزندان کلاس ClassLoader انجام می شود . وارثان سه نوع هستند:
  1. Bootstrap ClassLoader یک لودر پایه است که در سطح JVM پیاده سازی شده است و هیچ بازخوردی از محیط زمان اجرا ندارد، زیرا بخشی از هسته JVM است و با کد بومی نوشته شده است. این لودر به عنوان والد سایر نمونه های ClassLoader عمل می کند.

    عمدتاً مسئول بارگیری کلاس های داخلی JDK، معمولاً rt.jar و دیگر کتابخانه های اصلی واقع در دایرکتوری $JAVA_HOME/jre/lib است . پلتفرم های مختلف ممکن است پیاده سازی های متفاوتی از این کلاس لودر داشته باشند.

  2. Extension Classloader یک Extension loader است که از نسل پایه لودر است. از بارگیری پسوند کلاس های پایه استاندارد جاوا مراقبت می کند. بارگیری شده از دایرکتوری افزونه های JDK، معمولاً $JAVA_HOME/lib/ext یا هر فهرست دیگری که در ویژگی سیستم java.ext.dirs ذکر شده است (از این گزینه می توان برای کنترل بارگیری افزونه ها استفاده کرد).

  3. System ClassLoader یک لودر سیستمی است که در سطح JRE پیاده سازی شده است که وظیفه بارگذاری تمام کلاس های سطح برنامه در JVM را بر عهده دارد. فایل های موجود در متغیر محیط کلاس -classpath یا -cp گزینه خط فرمان را بارگیری می کند.

تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 2لودرهای کلاس بخشی از زمان اجرا جاوا هستند. لحظه ای که JVM یک کلاس را درخواست می کند، کلاس بارگذار سعی می کند کلاس را پیدا کند و با استفاده از نام کاملاً واجد شرایط کلاس، تعریف کلاس را در زمان اجرا بارگذاری کند. متد ()java.lang.ClassLoader.loadClass مسئول بارگذاری تعریف کلاس در زمان اجرا است. سعی می کند یک کلاس را بر اساس نام کامل آن بارگذاری کند. اگر کلاس هنوز بارگذاری نشده باشد، درخواست را به بارگذار کلاس والد محول می کند. این فرآیند به صورت بازگشتی رخ می دهد و به شکل زیر است:
  1. System Classloader سعی می کند کلاس را در حافظه پنهان خود پیدا کند.

    • 1.1. اگر کلاس پیدا شد، بارگیری با موفقیت کامل شد.

    • 1.2. اگر کلاس پیدا نشد، بارگیری به Extension Classloader محول می شود.

  2. Extension Classloader سعی می کند کلاس را در حافظه پنهان خود پیدا کند.

    • 2.1. اگر کلاس پیدا شد، با موفقیت کامل می شود.

    • 2.2. اگر کلاس پیدا نشد، بارگیری به کلاس لودر Bootstrap واگذار می شود.

  3. Bootstrap Classloader سعی می کند کلاس را در حافظه پنهان خود پیدا کند.

    • 3.1. اگر کلاس پیدا شد، بارگیری با موفقیت کامل شد.

    • 3.2. اگر کلاس پیدا نشد، Bootstrap Classloader زیربنایی سعی می کند آن را بارگیری کند.

  4. در صورت بارگیری:

    • 4.1. موفقیت آمیز - بارگیری کلاس به پایان رسید.

    • 4.2. در صورت عدم موفقیت، کنترل به Extension Classloader منتقل می شود.

  5. 5. Extension Classloader سعی می کند کلاس را بارگیری کند و در صورت بارگیری:

    • 5.1. موفقیت آمیز - بارگیری کلاس به پایان رسید.

    • 5.2. اگر خراب شود، کنترل به System Classloader منتقل می شود.

  6. 6. System Classloader سعی می کند کلاس را بارگیری کند و در صورت بارگیری:

    • 6.1. موفقیت آمیز - بارگیری کلاس به پایان رسید.

    • 6.2. با موفقیت عبور نکرد - یک استثنا ایجاد شد - ClassNotFoundException.

موضوع کلاس لودرها بسیار گسترده است و نباید از آن غافل شد. برای آشنایی بیشتر با آن، به شما توصیه می‌کنم این مقاله را بخوانید ، و ما معطل نمی‌شویم و ادامه می‌دهیم.

12. مناطق داده زمان اجرا چیست؟

Run-Time Data Ares - مناطق داده زمان اجرا JVM. JVM برخی از مناطق داده زمان اجرا مورد نیاز در طول اجرای برنامه را تعریف می کند. برخی از آنها با شروع JVM ایجاد می شوند. برخی دیگر thread-local هستند و تنها زمانی ایجاد می شوند که نخ ایجاد شود (و زمانی که نخ از بین برود از بین می روند). مناطق داده زمان اجرا JVM به شکل زیر است: تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 3
  • PC Register محلی برای هر رشته است و حاوی آدرس دستور JVM است که رشته در حال اجرا است.

  • JVM Stack یک ناحیه حافظه است که به عنوان ذخیره سازی برای متغیرهای محلی و نتایج موقت استفاده می شود. هر نخ دارای پشته مجزای خود است: به محض پایان یافتن نخ، این پشته نیز از بین می رود. شایان ذکر است که مزیت پشته نسبت به هیپ عملکرد است، در حالی که heap مطمئناً دارای مزیت در مقیاس ذخیره سازی است.

  • Native Method Stack - یک ناحیه داده در هر رشته که عناصر داده را، مشابه پشته JVM، برای اجرای روش های بومی (غیر جاوا) ذخیره می کند.

  • Heap - توسط همه thread ها به عنوان یک فروشگاه استفاده می شود که حاوی اشیاء، ابرداده های کلاس، آرایه ها و غیره است که در زمان اجرا ایجاد می شوند. این ناحیه هنگام راه اندازی JVM ایجاد می شود و با خاموش شدن از بین می رود.

  • ناحیه روش - این ناحیه زمان اجرا برای همه رشته ها مشترک است و هنگام شروع JVM ایجاد می شود. این ساختارها را برای هر کلاس ذخیره می کند، مانند Runtime Constant Pool، کد سازنده ها و متدها، داده های متد و غیره.

13. شیء تغییرناپذیر چیست؟

در این قسمت از مقاله در سوالات 14 و 15 پاسخی برای این سوال وجود دارد پس بدون اتلاف وقت نگاهی بیندازید.

14. کلاس String چه ویژگی خاصی دارد؟

پیش از این در تجزیه و تحلیل، ما بارها و بارها در مورد ویژگی های خاصی از String صحبت کردیم (بخش جداگانه ای برای این وجود داشت). حال بیایید ویژگی های String را خلاصه کنیم :
  1. این شیء محبوب ترین در جاوا است و برای اهداف مختلفی استفاده می شود. از نظر فراوانی استفاده حتی نسبت به انواع ابتدایی هم کم ندارد.

  2. یک شی از این کلاس را می توان بدون استفاده از کلمه کلیدی جدید ایجاد کرد - مستقیماً از طریق نقل قول String str = "string"; .

  3. رشته یک کلاس غیرقابل تغییر است : هنگام ایجاد یک شی از این کلاس، داده های آن قابل تغییر نیستند (زمانی که + "رشته دیگری" را به یک رشته خاص اضافه کنید، در نتیجه یک رشته جدید و سوم دریافت خواهید کرد). تغییرناپذیری کلاس String آن را ایمن می کند.

  4. کلاس String نهایی شده است (دارای اصلاح کننده نهایی )، بنابراین نمی توان آن را به ارث برد.

  5. رشته دارای حوضچه رشته های خاص خود است، ناحیه ای از حافظه در پشته که مقادیر رشته ای را که ایجاد می کند ذخیره می کند. در این قسمت از سریال در سوال 62 استخر رشته را توضیح دادم.

  6. جاوا دارای آنالوگ های String است که برای کار با رشته ها نیز طراحی شده است - StringBuilder و StringBuffer ، اما با این تفاوت که آنها قابل تغییر هستند. در این مقاله می توانید اطلاعات بیشتری در مورد آنها بخوانید .

تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 4

15. کوواریانس نوع چیست؟

برای درک کوواریانس به یک مثال نگاه می کنیم. فرض کنید یک کلاس حیوانات داریم:
public class Animal {
 void voice() {
   System.out.println("*тишина*");
 }
}
و چند کلاس سگ که آن را گسترش می دهد :
public class Dog extends Animal {

 @Override
 public void voice() {
   System.out.println("Гав, гав, гав!!!");
 }
}
همانطور که به یاد داریم، می توانیم به راحتی اشیایی از نوع وارث را به نوع والد اختصاص دهیم:
Animal animal = new Dog();
این چیزی جز چندشکلی نخواهد بود. راحت، انعطاف پذیر نیست؟ خوب، در مورد لیست حیوانات چطور؟ آیا می‌توانیم فهرستی با یک حیوان عمومی فهرستی با اشیاء سگ ارائه دهیم ؟
List<Dog> dogs = new ArrayList<>();
List<Animal> animals = dogs;
در این صورت خط اختصاص دادن لیست سگ ها به لیست حیوانات با خط قرمز مشخص می شود. کامپایلر این کد را پاس نمی کند. علیرغم اینکه این انتساب کاملاً منطقی به نظر می رسد (به هر حال، ما می توانیم یک شی Dog را به متغیری از نوع Animal اختصاص دهیم )، نمی توان آن را انجام داد. این به این دلیل است که اگر مجاز بود، می‌توانستیم یک شی حیوانی را در لیستی قرار دهیم که در ابتدا قرار بود سگ باشد، در حالی که فکر می‌کردیم فقط سگ‌ها را در لیست داریم . و سپس، برای مثال، از متد get() برای برداشتن یک شی از لیست سگ‌ها استفاده می‌کنیم ، با این تصور که یک سگ است، و برخی از متدهای شی Dog را روی آن فراخوانی می‌کنیم، که Animal ندارد . و همانطور که می دانید، این غیرممکن است - یک خطا رخ خواهد داد. اما، خوشبختانه، کامپایلر این خطای منطقی را با اختصاص فهرستی از فرزندان به لیستی از والدین (و بالعکس) از دست نمی دهد. در جاوا، شما فقط می توانید اشیاء لیست را به متغیرهای لیست با کلیات منطبق اختصاص دهید. به این حالت تغییرپذیری می گویند. اگر می توانستند این کار را انجام دهند، آن را کوواریانس می نامند. یعنی کوواریانس در صورتی است که بتوانیم یک شی از نوع ArrayList<Dog> را روی متغیری از نوع List<Animal> قرار دهیم . معلوم می شود که کوواریانس در جاوا پشتیبانی نمی شود؟ مهم نیست که چگونه است! اما این کار به روش خاص خود انجام می شود. طراحی برای چه استفاده می شود ؟ گسترش حیوان . با یک کلی از متغیری که می‌خواهیم شی لیست را روی آن تنظیم کنیم، با یک ژنریک از نسل قرار می‌گیرد. این ساختار عمومی به این معنی است که هر نوع که از نسل نوع Animal باشد انجام خواهد داد (و نوع Animal نیز تحت این تعمیم قرار می گیرد). به نوبه خود، Animal می تواند نه تنها یک کلاس، بلکه یک رابط نیز باشد (فریب کلمه کلیدی extensions را نخورید ). ما می توانیم تکلیف قبلی خود را به این صورت انجام دهیم: تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 5
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
در نتیجه، در IDE خواهید دید که کامپایلر از این ساخت شکایت نخواهد کرد. بیایید عملکرد این طرح را بررسی کنیم. فرض کنید روشی داریم که باعث می شود همه حیواناتی که به آن منتقل می شوند صدا ایجاد کنند:
public static void animalsVoice(List<? extends Animal> animals) {
 for (Animal animal : animals) {
   animal.voice();
 }
}
بیایید لیستی از سگ ها را به او بدهیم:
List<Dog> dogs = new ArrayList<>();
dogs.add(new Dog());
dogs.add(new Dog());
dogs.add(new Dog());
animalsVoice(dogs);
در کنسول خروجی زیر را خواهیم دید:
ووف ووف ووف!!! ووف ووف ووف!!! ووف ووف ووف!!!
این بدان معناست که این رویکرد به کوواریانس با موفقیت کار می کند. اجازه دهید توجه داشته باشم که این ژنریک در لیست گنجانده شده است ؟ گسترش Animal ما نمی توانیم داده های جدیدی از هر نوع وارد کنیم: نه نوع سگ و نه حتی نوع حیوان :
List<Dog> dogs = new ArrayList<>();
List<? extends Animal> animals = dogs;
animals.add(new Dog());
dogs.add(new Animal());
در واقع، در دو خط آخر کامپایلر درج اشیاء را به رنگ قرمز برجسته می کند. این به این دلیل است که ما نمی توانیم صد در صد مطمئن باشیم که کدام لیست از اشیاء از چه نوع به لیست با داده توسط < عمومی اختصاص داده می شود؟ گسترش حیوان> . من همچنین می خواهم در مورد ضد واریانستجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 6 صحبت کنم ، زیرا معمولاً این مفهوم همیشه با کوواریانس همراه است و به عنوان یک قاعده از آنها در مورد آنها با هم سؤال می شود. این مفهوم تا حدودی مخالف کوواریانس است، زیرا این ساختار از نوع وارث استفاده می کند. فرض کنید فهرستی می‌خواهیم که بتوان فهرستی از نوع اشیاء را که اجداد شی Dog نیستند به آن اختصاص داد . با این حال، ما از قبل نمی دانیم که چه نوع خاصی از آنها خواهد بود. در این مورد، ساخت فرم ? سگ فوق العاده ، که همه انواع آن مناسب هستند - اجداد کلاس سگ :
List<Animal> animals = new ArrayList<>();
List<? super Dog> dogs = animals;
dogs.add(new Dog());
dogs.add(new Dog());
ما می توانیم با خیال راحت اشیایی از نوع Dog را با چنین عمومی به لیست اضافه کنیم ، زیرا در هر صورت تمام روش های پیاده سازی شده هر یک از اجداد خود را دارد. اما ما نمی‌توانیم یک شی از نوع Animal اضافه کنیم ، زیرا هیچ اطمینانی وجود ندارد که در داخل اشیایی از این نوع وجود داشته باشد و مثلاً Dog وجود ندارد . از این گذشته، ما می توانیم از یکی از عناصر این لیست متدی از کلاس Dog را درخواست کنیم که Animal آن را نخواهد داشت . در این صورت یک خطای کامپایل رخ خواهد داد. همچنین، اگر بخواهیم روش قبلی را پیاده سازی کنیم، اما با این کلی:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Dog dog : dogs) {
   dog.voice();
 }
}
یک خطای کامپایل در حلقه for دریافت می‌کنیم ، زیرا نمی‌توانیم مطمئن باشیم که لیست بازگشتی حاوی اشیایی از نوع Dog است و آزادانه از روش‌های آن استفاده می‌کند. اگر در این لیست متد dogs.get(0) را فراخوانی کنیم. - یک شی از نوع Object دریافت خواهیم کرد . یعنی برای اینکه متد () animalsVoice کار کند ، حداقل باید دستکاری‌های کوچکی را با محدود کردن داده‌های نوع اضافه کنیم:
public static void animalsVoice(List<? super Dog> dogs) {
 for (Object obj : dogs) {
   if (obj instanceof Dog) {
     Dog dog = (Dog) obj;
     dog.voice();
   }
 }
}
تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 7

16. چگونه متدهایی در کلاس Object وجود دارد؟

در این قسمت از مجموعه، در بند 11، قبلاً به این سؤال پاسخ داده ام، بنابراین به شما توصیه اکید می کنم که اگر هنوز این کار را نکرده اید، آن را بخوانید. این جایی است که ما برای امروز تمام می کنیم. در قسمت بعدی می بینمت! تجزیه و تحلیل پرسش و پاسخ از مصاحبه برای توسعه دهنده جاوا.  قسمت 15 - 8
سایر مواد این سری:
نظرات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION