سوالات رایج جاوا که یک برنامهنویس باید بداند

سوالات رایجی در جاوا وجود دارند که مکررا پرسیده میشوند. حتی اگر شما پاسخ را بدانید ارزش این را دارد که فهم عمیقتری از این ابهامات و سوالات به دست آورید. در ادامه پرامتیازترین سوال بر مبنای stackoverflow را بررسی کرده و اشارهای به چند سوال مهم دیگر در این سایت خواهیم داشت.
سوال:
علت اختلاف سرعت پردازش آرایه مرتب نسبت به آرایه غیر مرتب چیست؟
برای درک سوال مثال زیر را با حذف یا حضور خط ۱۷ اجرا کنید.
import java.util.Arrays; import java.util.Random; public class Main { public static void main(String[] args) { // Generate data int arraySize = 32768; int data[] = new int[arraySize]; Random rnd = new Random(0); for (int c = 0; c < arraySize; ++c) data[c] = rnd.nextInt() % 256; // !!! With this, the next loop runs faster Arrays.sort(data); // Test long start = System.nanoTime(); long sum = 0; for (int i = 0; i < 100000; ++i) { // Primary loop for (int c = 0; c < arraySize; ++c) { if (data[c] >= 128) sum += data[c]; } } System.out.println((System.nanoTime() - start) / 1000000000.0); System.out.println("sum = " + sum); } }
پاسخ کوتاه:
این مساله در یک واژه به پیشبینی انشعاب برمیگردد. وقتی آرایه مرتب است چک کردن data[c]>=128 برای بخشی از مقادیرغلط و برای سایر مقادیر صحیح است. پیشبینی آن کار سادهای است. وقتی آرایه نامرتب است هزینه انشعابات را نیز باید پرداخت کنید.
پاسخ جامع:
برای درک پیشبینی انشعاب یک مثال مطرح میکنیم.
یک تقاطع روی ریلهای راهآهن را در نظر بگیرید.
فرض کنید شما سوزنبانی هستید که صدای قطاری را میشنوید و هیچ ایدهای ندارید که قطار از کدام مسیر میخواهد حرکت کند. تصور کنید هیچ راه ارتباطی پیش از رسیدن قطار با راننده آن ندارید. پس شما آن را متوقف کرده و از لکوموتیوران سوال میکنید و بعد از انتخاب مسیر درست، قطار مجددا حرکت خود را آغاز میکند.
قطارها سنگین هستند و اینرسی زیادی دارند و توقف و حرکت مجدد آنها زمان و انرژی زیادی میگیرد.
اما اگر مسیر حرکت قطار را حدس میزدید:
- اگر حدستان درست بود، قطار به حرکت خود ادامه میداد
- اگر حدستان غلط بود، قطار متوقف میشد تا مسیر درست انتخاب شده و مجددا حرکت کند.
حال اگر اغلب مواقع حدستان درست میبود قطار نیازی به توقف نداشت ولی اگر اغلب مواقع حدستان اشتباه میبود لازم به توقف و حرکت مجدد و اتلاف زمان میشد.
حال یک عبارت شرطی را در نظر بگیرید. فرض کنید شما یک پردازنده هستید و یک انشعاب میبینید که هیچ ایدهای ندارید که از کدام مسیر حرکت صورت میگیرد. پس شما اجرا را متوقف کرده و منتظر اتمام دستورات قبل میشوید سپس مسیر درست را انتخاب میکنید.
پردازندههای جدید پیچیده هستند و pipelineهای طولانی زیادی دارند پس این توقف و اجرای مجدد زمان زیادی از آنها میگیرد.
اما اگر مسیر حدس زده میشد
- اگر حدس درست بود اجرا ادامه پیدا میکرد
- اگر حدس غلط بود لازم بود که pipeline در پردازنده flush شده و به انشعاب roll back صورت میگرفت و مسیر درست انتخاب و مجدده اجرا صورت میگرفت.
اگر حدس اغلب مواقع درست میبود اجرا لازم به توقف نبود. در غیراینصورت زمان زیادی صرف توقف اجرا، roll back و اجرای مجدد میشد.
در مثال بالا مساله اصلی سر شرط زیر است:
if (data[c] >= 128) sum += data[c];
در صورتی که آرایه مرتب باشد تقریبا نیمه اول آرایه وارد شرط نشده و بقیه وارد آن میشوند. این طوری به ترتیب انشعاب به یک مسیر یکسان هدایت میشود و برای predictor بسیار خوب عمل میکند.
یک حقه
در صورتی که کامپایلر انشعابها را به صورت یک حرکت شرطی بهینه نکند میتوان خوانایی کد را فدای کارایی آن کرد و کد زیر را جایگزین عبارت شرط کرد:
int t = (data[c] - 128) >> 31; sum += ~t & data[c];
به این شکل انشعاب حذف شده و تعدادی عملیات بیتی جایگزین میشود.
نتایج اجرا روی JDK7 روی netbeans – x64 به شکل زیر است:
// Branch - Random seconds = 10.93293813 // Branch - Sorted seconds = 5.643797077 // Branchless - Random seconds = 3.113581453 // Branchless - Sorted seconds = 3.186068823
که بعد از جایگزینی عملیات های بیتی مرتب بودن یا نبودن آرایه تفاوات چندانی در سرعت اجرا ایجاد نمیکند.
پس به طور کلی در یک قانون سرانگشتی توصیه میشود که از انشعابات وابسته به دادهها در حلقههای مهم برنامههای خود جلوگیری کنید.
از دیگر سوالات رایج جاوا:
نحوه مقایسه رشتهها در جاوا:
یا به طور کلیتر نحوه مقایسه محتویات اشیا در جاوا. اگر برای اولین بار از جاوا استفاده بکنید مسلما از اینکه عملگر == برای رشتهها کاری که مدنظرتان است را انجام نمیدهد متعجب خواهید شد.
Is Java Pass by Reference or Pass by Value?
Why does 128 == 128 return false but 127 == 127 return true?
چگونه از تکرار پیاپی != null در کد جلوگیری کنیم:
چک کردن مقدار null میتواند خستهکننده باشد. در FindBugs و Intellij حاشیهنویسی (annotation) @NotNull وجود دارد که میتواند کمک کننده باشد. هم اکنون Optional هم میتواند به کمکمان بیاید:
به جای کد زیر:
if (a != null && a.getB() != null && a.getB().getC() != null) { a.getB().getC().doSomething(); }
از کد زیر استفاده میکنیم:
Optional.ofNullable(a) .map(A::getB) .map(B::getC) .ifPresent(C::doSomething);
دیگر سوالات مفید:
چه زمانی به جای ArrayList از LinkedList استفاده کنیم
تفاوت بین public, default, protected و private
چگونه چک کنیم که یک آرایه حاوی یک عنصر است یا خیر
چرا به جای extend Thread باید Runnable را پیادهسازی(implement) کنیم؟
آیا همیشه finally اجرا میشود؟
چگونه یک رشته را به Enum تبدیل کنیم
اگر شما در هر یک از مباحث گفته شده یا سایر موارد ابهام و پرسشی داشتید میتوانید در قسمت نظرات مطرح کنید تا در همین بخش آنها را مفصلا بررسی نماییم.
منابع:
با تشکر از مطالب مفیدتون
درباره مساله اول:
بازه اعداد آرایه data بین 255- و 255 هستش و نه بین 0 و 255. بنابراین شرط data[c] >= 128 برای سه چهارم آرایه غلط و برای یکچهارم بقیه صحیح است. ولی شما گفتید: “برای نیمه اول غلط و برای بقیه صحیح است”
سلام
منظور از نیمه، نصف آرایه نیست بلکه بخشی از آن است. برای ابهامزدایی در متن نیز اصلاح میگردد.
با تشکر