۱۹ سوال رایج در مصاحبههای شغلی جاوا

وقتی صحبت از مصاحبه شغلی برای برنامهنویسی جاوا میشود، چند سوال وجود دارد که در بسیاری از موارد تکرار میشوند. اکثر این سوالها راجع به «Multi-threading»، «collection»، «serialization»، کدنویسی و اصول برنامهنویسی شیء گرا است. در هر مصاحبه حداقل یک یا دو سوال مربوط به کدنویسی میآید. در این مقاله 19 مورد از مهمترین سوالات به همراه پاسخهای آنها آمده است تا شما را برای مصاحبههای کاری آماده کند.
این مجموعه، از سوالهای ساده، سخت و انحرافی تشکیل شدهاست؛ مثلا سوال «چرا در جاوا امکان ارثبری چندگانه وجود ندارد؟» یک سوال انحرافی است. اکثر این سوالها در مصاحبه با افراد پر تجربه پرسیده شده است، یعنی افرادی که 3 تا 6 سال تجربه کار با جاوا دارند. مثلا سوال «HashMap در جاوا چگونه کار میکند» یکی از سوالات معروف از افراد با تجربه است.
کتابهایی هم هستند که به برنامهنویسان کمک میکنند در مصاحبههای خود خوب عمل کنند؛ مثلا کتابهای «Cracking the Coding Interview» و «Programming Interviews Exposed: Secrets to Landing Your Next Job» دو مورد از بهترین کتابها در این زمینه هستند. این کتابها بر روی برنامهنویسی و برخی موضوعات مرتبط از جمله ساختمان دادهها، الگوریتمها، دیتابیس، SQL، شبکه و نحوه پاسخدهی به سوالات در مصاحبهها تمرکز دارند. با این مقدمه، به بررسی سوالات بیشتر پرسیده شده در مصاحبههای استخدامی زبان جاوا میپردازیم.
سوال 1: چرا نباید از HashMap در محیطهای multi-thread شده استفاده کنیم؟ چه هنگام متد ()get به یک حلقه بینهایت منجر میشود؟
درواقع بسته به نوع مصرف شما، هیچ مشکلی در این کار وجود ندارد. برای مثال، اگر شما HashMap را در یک ترد تعریف کنید و در سایر تردها فقط آن را فرا خوانی کنید و میبینید که همهچیز به خوبی کار میکند؛ مثلا میتوانید از یک Map برای نگهداری مقادیر مربوط به تنظیمات نرمافزار استفاده کنید.
مشکل اصلی زمانی آغاز میشود که حداقل یکی دیگر از سایر تردها اقدام به بروزرسانی HashMap میکنند و سعی میکنند مقداری را حذف یا اضافه کنند و یا مقداری را تغییر دهند. از آنجایی که عمل ()put میتواند باعث تغییر اندازه و در نتیجه ایجاد حلقه بینهایت میشود. همچنین بهتر است از Hashtable یا ConcurrentHashMap استفاده کنید.
سوال 2: آیا انجام عمل Override برروی متد ()hashcode میتواند منجر به تغییری در عملکرد شود؟
این سوال خوب و مناسبی برای همه است. داشتن یک تابع مربوط به «hash code» که خطایی در کارش وجود داشته باشد، میتواند منجر به پیش آمدن مقدارهای تکراری در HashMap شود که به مرور زمان باعث میشود زمان مورد نیاز برای اضافه کردن شیء به HashMap افزایش پیدا کند.
البته، بعد از جاوا 8، این مقادیر تکراری مانند قبل برروی عملکرد تاثیر نمیگذارند، زیرا در ادامه این لیست به یک درخت دودویی (Binary Tree) تبدیل میشود و عملکرد آن از O)n) به O)logN) تغییر میکند.
سوال 3: آیا تمام مقادیر یک «شیء تغییر ناپذیر» (Immutable Object) باید از نوع «final» تعریف شده باشد؟
اجباری به این کار نیست؛ همانطور که بالاتر اشاره کردیم، شما میتوانید مقادیر را «private» تعریف کنید و تنها از طریق خود سازنده آنها را تغییر دهید. متدی برای تغییر مقادیر آنها نسازید و اگر هم شیء قابل تغییر است، هیچ اشارهای به اعضای آن در سایر بخشهای نرمافزار نکنید.
توجه داشته باشید که تعریف یک متغیر از نوع «final» تنها باعث میشود نتوانید آن مقدار را به مقدار دیگری تغییر دهید، ولی هنوز هم سایر مقادیر شیء که توسط آن مقدار قابل دسترس هستند، امکان تغییر دارند؛ این یک نکته مهم است که شخص مصاحبه کننده دوست دارد آن را بشنود.
سوال 4: متد ()substring که درون رشتهها قرار دارد، چگونه کار میکند؟
این سوال، یکی دیگر از سوالات خوب جاوا است. به صورت خلاصه جواب میتواند این باشد که substring یک شیء جدید درست میکند که شامل بخشی از رشته اصلی است.
این سوال بیشتر به این منظور پرسیده میشود که ببینند آیا برنامهنویس به خطرات مربوط به پر شدن حافظه (که با substring امکان روی دادن آن وجود دارد) آشنایی دارد یا خیر. تا قبل از جاوا 1.7، substring یک مرجع مستقیم به آرایه کاراکتری اصلی را ارائه میداد که یعنی حتی اگر آن زیر رشته شامل 5 کاراکتر بود، امکان داشت مانع حذف شدن یک رشته 1 گیگابایتی از حافظه شود.
این مشکل در جاوا 1.7 رفع شد و دیگر مرجعی به آرایه اصلی نگهداری نمیشود. ولی این تغییر باعث شد زمان ساخت یک زیر رشته کمی بیشتر شود. در گذشته زمان آن O)1) بود، ولی در جاوا 7 میتواند O)n) باشد.
سوال 5: میتوانید یک «ناحیه بحرانی» (Critical Section) برای یک کلاس «Singleton» بنویسید؟
این سوال بسیار مهمی است که معمولا پرسیده میشود و از فرد کاندید انتظار دارد که یک کلاس «singleton» را توسط الگوی «double checked locking» طراحی کند. حواستان باشد که از متغیرهایی از نوع «volatile» استفاده کنید تا کلاستان «thread-safe» باشد.
سوال 6: شرایط خطا را در هنگام نوشتن «stored procedure» یا دسترسی به یک «stored procedure» از طریق جاوا، چگونه مدیریت میکنید؟
این یکی از آن سوالهای سخت جاوا است که تقریبا از همه پرسیده میشود. stored procedureها باید خودشان در صورت بروز خطا، یک «شماره خطا» (error code) برگرداند، ولی اگر این کار را نکردند، گرفتن «SQLException» در کد، تنها راه است.
سوال 7: تفاوت متدهای ()Executor.submit و ()Executor.execute در چیست؟
این سوال روز به روز محبوبتر میشود؛ دلیل آن این است که نیاز به برنامهنویسان جاوایی که توانایی بالا در نوشتن کدهایی که امکان اجرای همزمان را دارند، بالا است. پاسخ این است که ()Executor.submit یک شیء از نوع «Future» را برمیگرداند که میتوان از آن برای دیدن نتیجه کار آن ترد استفاده کرد.
تفاوت اصلی در هنگامی است که میخواهید خطا را مدیریت کنید. اگر خطایی در هر کدام از وظایف (tasks) شما رخ دهد، و اگر از طریق Execute ثبت شده باشد، این خطا به بخش مدیریت خطاهای مدیریت نشده (uncaught exception handler) منتقل میشود (اگر شما هم چیزی برای اینکار تعریف نکرده باشید، به طور پیشفرض یک «Stack trace» از آن در «System.err» ثبت میشود).
اگر وظیفه را به وسیله submit ثبت کرده باشید، هر خطایی که رخ دهد، چه قبلا مدیریت شده باشد یا نشده باشد، در وضعیت نهایی اون وظیفه برگردانده میشود. وظیفهای که توسط submit اجرا و سپس متوقف شده باشد، متد ()Future.get تمام خطاهای آن را درون یک ExecutionException انداخته و نمایش میدهد.
سوال 8: تفاوت الگوهای «factory» و «abstract factory» در چیست؟
«abstract factory» یک مرحله «abstraction» بیشتر ارائه میدهد. فرض کنید که هر «factory» از یک «abstract factory» به وجود آمدهاست و وظیفه ساخت یک سلسله مراتب از آبجکتها را بر اساس نوع «factory» دارد؛ برای مثال یک «abstract factory» که میتواند از «AutomobileFactory» ،«UserFactory» ،«RoleFactory» یا سایر factoryها به وجود آمده باشد. در این حالت، هر factory وظیفه ساخت شیء در همان مجموعه را دارد. در زیر یک نمودار «UML» از الگوهای «factory» و «abstract factory» را مشاهده میکنید:
سوال 9: کلاس «Singleton» چیست؟
«Singleton» یک کلاس در جاوا است که در کل اپلیکیشن جاوا فقط یک شیء از روی آن ساخته میشود؛ برای مثال «java.lang.Runtime» یک کلاس «singleton» است. تا قبل از جاوا 4، ساخت کلاسهای «Singleton» کار سختی بود، ولی پس از معرفی «Enum» در جاوا 5 این کار بسیار ساده شد.
سوال 10: آیا میتوانید کدی برای بررسی یک HashMap در جاوا 4 و جاوا 5 بنویسید؟
برای بررسی و چرخیدن بین مقادیر هر Map در جاوا میتوان از چهار طریق اقدام کرد. دو تا از بهترین روشها استفاده از ()keyset و ()entryset هستند. در روش اول با استفاده از ()keyset کلید را به پیدا کرده و سپس با استفاده از ()get مقدار را به دست میآوریم، ولی این کار کمی زمانبر است.
روش دوم استفاده از ()entrySet و بررسی مقادیر با استفاده از یک حلقه «for each» یا حلقه «while» با استفاده از متد ()Iterator.hasNext است. این روش مناسبتر است، زیرا که در این روش شما هم کلید و هم مقدار شیء را در هنگام بررسی مقدار به دست میآورید و دیگر نیازی به استفاده از متد ()get برای گرفتن مقادیر ندارید. با این کار در صورت داشتن تعداد زیادی از مقادیر مربوط، زمان عملکرد نرمافزار O)n) خواهد بود.
سوال 11: در چه زمان باید ()hashcode و ()equals را «override» کرد؟
در هر جایی که لازم باشد این کار را انجام میدهیم، به ویژه اگر بخواهیم برابری را به جای منطق برابری آبجکتها، بر اساس منطق کار بررسی کنیم. برای مثال دو کارمند که دارای مقدار «emp_id» مساوی باشند، حتی اگر در دو بخش متفاوت از کد ساخته شده باشند، برابر هستند.
علاوه بر این، اگر بخواهیم از این دو مقدار به عنوان کلید در HashMap استفاده کنیم، override کردن این دو متد الزامی میباشد. براساس قراردادی که در جاوا بین equals و hashcode وجود دارد، اگر شما بخواهید equals را override کنید، باید hashcode را نیز override کنید، در غیر اینصورت کلاسهایی که برای عملکرد خود به متد ()equals نیاز دارند، دچار مشکل خواهند شد.
سوال 12: اگر متد ()hashcode را «override» نکنیم، چه اتفاقی میافتد؟
اگر متد equals را override نکنید، قرارداد بین equals و hashcode دیگر کار نمیکند. بر اساس این قرارداد، دو شیء که توسط ()equals برابر شناخته میشوند، باید دارای hashcode یکسان باشند. در این حالت، دوتا شیء ممکن است hashcode متفاوتی را برگردانند و در نتیجه در آن محل ذخیره شوند، که این اتفاق باعث میشود ثبات کلاس HashMap از بین برود، چرا که کلیدهای تکراری نباید در آن ذخیره شوند.
زمانی که شما یک شیء را به وسیله متد put() اضافه میکنید، این متد در تمام شیء Map.Entry که در آن محل قرار دارد میچرخد و اگر آن کلید را پیدا کند، مقدار آن را بروزرسانی میکند. اگر hashcode را override نکرده باشید، این عمل کار نخواهد کرد.
سوال 13: بهتر است فقط نواحی بحرانی متد ()getInstance را همگام کنیم یا تمام متد را؟
پاسخ این سوال، «فقط نواحی بحرانی» است، زیرا اگر تمام متد را قفل کنیم، هربار که کسی آن را صدا بزند باید منتظر باشد، حتی اگر در حال ساخت هیچ شیء جدیدی نباشیم. به عبارتی دیگر، همگام سازی تنها موقعی لازم است که دارید یک شیء میسازید، که این کار هم تنها یک بار صورت میگیرد.
وقتی که شیء ساخته شده است دیگر نیازی به همگام سازی نیست. درواقع، این نحوه کد زدن از نظر عملکرد بسیار ضعیف است زیرا که متدهای همگام شده عملکرد را بین 10 تا 20 برابر کاهش میدهند. در زیر یک نمودار UML از الگوی «Singleton» مشاهده میکنید:
چندین راه برای ساخت یک کلاس «singleton» به صورت «thread-safe» در جاوا وجود دارد که میتوانید برای جواب این سوال یا هر سوال مربوط دیگری به آن اشاره کنید.
سوال 14: در هنگام عمل get()، متدهای ()hashcode و ()equals چه نقشی دارند؟
هنگامی که حرف از hashcode میزنید، احتمالا سوال بعدی که میپرسند این است که hashcode چگونه در HashMap استفاده میشود. هنگامی که یک شیء کلید را ارائه میدهید، ابتدا متد hashcode آن فراخوانی میشود تا محل قرارگیری آن در سطل (Bucket) پیدا شود. با توجه به اینکه هر سطل میتواند چندین لیست داشته باشد، Map.Entry هر کدام آنها توسط متد ()equals مقایسه میشوند تا وجود یا عدم وجود آن کلید در آن مشخص شود.
سوال 15: چگونه از وقوع «بنبست» (deadlock) در جاوا جلوگیری کنیم؟
با شکستن شرط حلقه انتظار میتوان جلوی وقوع بنبست را گرفت. برای اینکار میتوانید یک نظمی در کد قرار دهید که ترتیب فعالیت و باز شدن قفل را مشخص کند.
اگر قفل به یک ترتیب مشخص و ثابتی ایجاد شده و در ترتیب مخالفش باز شود، دیگر شرایطی پیش نمیآید که در آن یک ترد، قفلی را که توسط ترد دیگری مشغول است را اشغال کند.
سوال 16: تفاوت ساخت یک String به صورت ()new و «مستقیم» (literal) در چیست؟
وقتی ما یک رشته را با استفاده از عملگر ()new میسازیم، این مقدار در پشته ساخته میشود، در حالی که رشته ساخته شده به صورت مستقیم، در «String pool» که در بخش «PermGen» در پشته قرار دارد، ساخته میشود.
کد فوق، رشته را در «String pool» قرار نمیدهد. برای قرار دادن این رشته در «String pool» باید به صورت جداگانه متد ()String.intern را فراخوانی کنیم. تنها موقعی که یک رشته را به صورت مستقیم میسازید، جاوا آن را به صورت خودکار در «String pool» قرار میدهد.
سوال 17: شیء تغییر ناپذیر چیست؟ میتوانید یک کلاس تغییر ناپذیر بنویسید؟
کلاسهای تغییر ناپذیر کلاسهایی هستند که پس از ساخت آن، دیگر نمیتوان در آبجکتهای آن تغییری ایجاد کرد. هر تغییری که در یک شیء تغییر ناپذیر ایجاد کنید، یک شیء جدید ساخته میشود. برای مثال String در جاوا یک شیء تغییر ناپذیر است. اکثر کلاسهای تغییر ناپذیر در جاوا از نوع final نیز هستند تا جلوی override کردن آنها در زیر-کلاسها گرفته شود. اگر اعضا را از نوع private نیز تعریف کنید و در هیچجایی جز سازنده آن را تغییر ندهید نیز نتیجه همان است.
این نیز واضح است که هیچوقت نباید مقادیر داخلی یک شیء تغییر ناپذیر را قابل دسترس قرار دهید، به ویژه اگر این شیء شامل یک مقدار تغییرپذیر باشد. اگر یک مقدار تغییر پذیر را از جایی نظیر java.util.Date دریافت کردید، از متد ()clone استفاده کنید تا یک کپی از آن را برای خودتان نگه دارید تا جلوی خطرات ناشی از تغییر مقدار منبع را بگیرید.
همین کار را باید در هنگام برگرداندن یک مقدار به یک عضو تغییرپذیر نیز رعایت کنید. یک کپی از مقدار را برگردانید و هیچوقت یک منبع مستقیمی که توسط کلاس تغییر ناپذیر استفاده میشود را برنگردانید.
سوال 18: سادهترین راه برای بررسی زمان مصرفی یک متد، بدون استفاده از ابزارهای بررسی عملکرد، چیست؟
زمان سیستم را دقیقا قبل از اینکه متد آغاز شود و بعد از اینکه مقدار را برگرداند ثبت کنید. تفاوت زمانی را بررسی کنید تا زمان اجرای متد را به دست آورید؛ برای مثال کد زیر را مشاهده کنید:
توجه داشته باشید که اگر زمان اجرایی متد خیلی کوتاه باشد، ممکن است مقدار صفر را برگرداند. سعی کنید کد را برای متدهایی به کار ببرید که عملیات سنگینتری را برای اجرا دارند.
سوال 19: برای استفاده از یک شیء به عنوان کلید در HashMap، از کدام دو متد باید استفاده کنید؟
برای استفاده از یک شیء به عنوان کلید در HashMap یا Hashtable، باید از متدهای equals و hashcode در جاوا استفاده کنیم.
حال اگر مایل به مطالعه و یادگیری بیشتر در موضوعات مشابه باشید، شاید آموزشهای زیر بتوانند برای شما مفید باشند:
**
Before Java 7, the JVM placed the Java String Pool in the PermGen space, which has a fixed size — it can’t be expanded at runtime and is not eligible for garbage collection.
From Java 7 onwards, the Java String Pool is stored in the Heap space, which is garbage collected by the JVM. The advantage of this approach is the reduced risk of OutOfMemory error because unreferenced Strings will be removed from the pool, thereby releasing memory.