سوالی که ۴ نفر از هر ۵ توسعهدهنده نتوانستند پاسخ دهند
از چند ماه گذشته سایت Java Deathmatch یک بازی کوچک معماگونه برای توسعهدهندگان جاوا راهاندازی کرده است و از آن زمان تاکنون بیش از ۲۰۰۰۰ توسعهدهنده آن را امتحان کردهاند. بعد از جمعآوری آمار مربوط به این بازی اطلاعات جالبی از آن استخراج شده و در ادامه سوالی که سختترین سوال این بازی بوده است را بررسی میکنیم.
به این سوال فقط ۲۰ درصد شرکت کنندگان توانستهاند پاسخ دهند. یعنی اگر شما یک جواب را به صورت تصادفی انتخاب کنید شانس بیشتری دارید تا اینکه جواب درست را بیابید!
برای پاسخگویی به هر معمای این بازی حداکثر ۹۰ ثانیه به شما فرصت داده میشود. شما نیز ابتدا سعی کنید در این زمان خود را محک بزنید و در ادامه با ما در بررسی جواب آن همراه شوید:
ابتدا سؤال را ببینید |
RuntimeException و SQLException هر دو از کلاس Exception ارث بری میکنند. درحالیکه RuntimeException چکنشده است اما SQLException چکشده است.
(تمامی فرزندان کلاس Exception به جز RuntimeException چکشده هستند یعنی کامپایلر شما را مجبور میکند که آن را catch کنید یا در امضای تابع خود تعریف کنید)
generic در جاوا مجسمیافته (reified) نیست، به این معنا که در زمان کامپایل اطلاعات نوعدادهی generic گم میشود و مشابه شرایطی خواهد بود که کد با استفاده از محدودهی نوعداده یا Object جایگزین شده باشد. این چیزی است که پاک کردن نوعداده نامیده میشود.
در یک نگاه سطحی انتظار میرود در خط ۷ام خطای زمان کامپایل داشته باشیم چرا که SQLException به RuntimeException تبدیل نمیشود. اما این اتفاق نمیافتد و نوعدادهی T با Exception جایگزین میشود و داریم:
throw (Exception) t; // t is also an Exception
از آنجایی که pleaseThrow یک Exception میپذیرد و T جایگزین Exception شده است پس این تبدیل حذف میشود گویا اصلاً وجود نداشته است. این مساله در بایت کد نیز قابل مشاهده است:
private pleaseThrow(Ljava/lang/Exception;)V throws java/lang/Exception L0 LINENUMBER 8 L0 ALOAD 1 ATHROW L1 LOCALVARIABLE this LTemp; L0 L1 0 // signature LTemp<tt;>; // declaration: Temp LOCALVARIABLE t Ljava/lang/Exception; L0 L1 1 MAXSTACK = 1 MAXLOCALS = 2
در حالیکه اگر بدون generic بایت کد را بررسی کنیم مشاهده میکنیم که درست قبل از ATHROW داریم:
CHECKCAST java/lang/RuntimeException
حال که پی بردیم تبدیلی صورت نمیگیرد گزینههای دوم و چهارم حذف میشود.
پس یک SQLException پرتاب میشود که انتظار میرود توسط بلوک catch گرفته شده و دنبالهی پشتهی آن دریافت شود. اما اینچنین نیست. در این مورد کامپایلر نیز مثل ما گیج میشود و فکر میکند که بلوک catch قابل دستیافتن نیست و برای اطرافیان از همه جا بیخبر هیچ SQLException ای وجود ندارد. پاسخ صحیح خطا در زمان کامپایل است چراکه کامپایلر انتظار پرتاب شدن SQLException را از بلوک try ندارد.
یک راه حل برای که بفهمیم مشکل چیست و چگونه SQLException پرتاب میشود تغییر بلوک catch به نحوی است که RuntimeException بپذیرد که در این صورت دنبالهی پشتهی واقعی خطا را مشاهده خواهیم کرد که پرتاب یک SQLException را نشان میدهد.
پاسخ شما چه بود؟ آیا شما جزء ۲۰ درصد کسانی بودید که پاسخ درستی به این سوال دادهاند؟
منبع:
https://dzone.com/articles/4-out-of-5-java-developers-failed-to-solve-this-pr?oid=java
سلام و درود برشما و ممنون از مطلب جالبی که گذوشتین. من از شاگردای استاد علی اکبری هستم.
به نظر من این قسمت که گفتین: “در یک نگاه سطحی انتظار میرود در خط ۷ام خطای زمان کامپایل داشته باشیم چرا که SQLException به RuntimeException تبدیل نمیشود. اما این اتفاق نمیافتد و نوعدادهی T با Exception جایگزین میشود” کمی ایراد دارد.
مساله اینه که در زمان کامپایل کامپایلر فقط reference ها رو بررسی میکنه و در نتیجه نمیتونه بدون dynamic binding که مخصوص زمان اجراهست بفهمه که پارامتر t پاس شده به متد از نوع SQLException هست و خطای cast کردن اتفاق نمی افته چون در واقع اصل مطلب اینطوریه:
throw (RuntimeException) t
که t از نوع Exception هست و چون هنوز فرآیند erasure اتفاق نیفتاده از نوع T اطلاع داره و اونو با Exception جایگزین نمیکنه. downCast بالا هم درسته چون Exception به RuntimeException کست میشه.