دانستنی‌ها

ده چیزی که در مورد جاوا نمی‌دانستید!

شما از کسانی هستید که از زمانی که  شئ‌گرایی رونق داشت جاوا را می‌شناختید، از زمانی که به نام Oak صدا زده می‌شد و از هنگامی که طرفداران c++i هیچ شانسی برای پیشرفت جاوا قائل نمی‌شدند با آن کار می‌کردید؟
حتی اگر شما از ابتدا با جاوا آشنا بودید و با آن کار می‌کردید، قول می‌دهم که بیش از نیمی از موارد زیر را نمی‌دانید.
پس بیایید با ۱۰ شگفتی از عملکرد داخلی جاوا آشنا شویم.

 ۱-چیزی به نام checked Exception وجود ندارد
درست است! JVM اصلا چنین چیزی نمی‌شناسد و فقط زبان جاوا آن را می‌داند.
امروز همه موافق هستند که checked Exceptionها اشتباه بودند. هیچ زبان دیگری بعد از جاوا از chekced Exception استفاده نکرده است و حتی در جاوا ۸ در APIهای استریم‌ها وجود ندارند.
برای اثبات عدم شناخت JVM از این خطاها کد زیر را امتحان کنید:

نه تنها این کد کامپایل می‌شود بلکه خطای SQLException هم پرتاب می‌کند بدون آنکه نیاز به بلوک try/catch یا جمله throws Exception در امضای تابع باشد!

۲- می‌توان تابع‌هایی که تنها در نوع خروجی متفاوت هستند را overload کرد.

کامپایل نمی‌شود درسته؟

زبان جاوا اجازه نمی‌دهد که دو تابع در یک کلاس با نام یکسان بدون تفاوت در نوع خروجی یا عبارت throws تعریف شوند.

اگر جاواداک مربوط به کلاس Class.getMethod(String, class…)i را چک کنیم نوشته شده:
“توجه کنید که ممکن است بیش از یک متد در یک کلاس مطابقت داشته باشد چرا که جاوا اجازه تعریف توابع با امضای یکسان و نوع خروجی متفاوت را به یک کلاس نمی‌دهد اما JVM این‌گونه نیست. این مساله انعطاف پذیری را در ماشین مجازی افزایش می‌دهد که بتواند قابلیت‌های مختلف زبان را پیاده‌سازی کند. برای مثال covariant returns می‌توانند با توابع bridge پیاده شوند. توابع bridge و توابع override شده یک امضا ولی خروجی‌های مختلف خواهند داشت.”
پس اگر کد زیر را بنویسیم داریم:

کد تولید شده در بایت کد کلاس Child را بررسی می‌کنیم.

بنابراین T تنها یک Object در بایت کد است. تابع bridge ساخته‌شده توسط کامپایلر تولید می‌شود چرا که نوع خروجی در امضای تابع Parent.x() ممکن است انتظار برود در بعضی از نقاط فراخوانی Object باشد. اضافه کردن generic بدون چنین توابع bridgeای برای binary comaptible بودن امکان پذیر نمی‌شد. بنابراین تغییر JVM برای اجازه دادن به چنین قابلیت‌هایی راحت‌تر بود. راه‌حل هوشمندانه‌ای بود، نه؟!

۳- تمام این‌ها آرایه‌های دوبعدی هستند!

بله درست است، حتی اگر ذهن شما نتواند سریع پردازش کند که تمام عبارات فوق یکسان هستند. مشابه کد زیر:

فکر می‌کنید احمقانه است؟ حال تصور کنید ویژگی type annotation در جاوا ۸ را به همراه آن به کار ببریم. تعداد حالات بی‌شمار خواهد شد!

۴- شما عبارات شرطی را درک نمی‌کنید
فکر می‌کنید که وقتی از عبارات شرطی استفاده می‌کنید همه چیز را درمورد آن می‌دانید؟ خب لازم است که بگوییم این طور نیست! اغلب شما فکر می‌کنید که کد

و کد زیر

یکسان هستند…خیر! بگذارید تست کنیم:

خروجی برنامه به شکل زیر خواهد بود:

عملگر شرط “هر وقت لازم بداند” نمایش نوع‌داده عددی را به کار می‌گیرد. شما از برنامه زیر انتظار خواهید داشت که nullPointerException پرتاب کند؟

۵- شما حتی از عملگر انتساب ترکیبی هم چیزی نمی‌دانید!
دو خط کد زیر را در نظر بگیرید:

ظاهرا باید معادل باشند، درسته؟ اما نیستند! در JLS آمده است.

عملگر انتساب مرکب به فرم E1 op= E2 معادل است با E1 = (T)((E1) op(E2))i که در آن T از نوع E1 است. به جز در مواردی که E1 تنها یک بار ارزیابی می‌شود.
این مساله خیلی زیباست مثال‌های زیر را ببینید:

یا

یا

یا

حالا واقعا چقدر مفید خواهد بود؟ بریم کاراکترهای توی کدمون را *= کنیم….!

۶- اعداد رندوم
حالا یک معمای بزرگ‌تر. به پاسخ نگاه نکنید ببینید می‌توانید خودتان کشف کنید. وقتی برنامه زیر را اجرا می‌کنیم:

“گاها” خروجی زیر را دریافت می‌کنیم:

چطور ممکن است؟
.
.
.
.
.
راه حل اینجاست (به Integer cache overriding در JDK با استفاده از reflection و سپس استفاده از auto-boxing , auto_unboxing برمی‌گردد)

۷- GOTO
این یکی از موارد مورد علاقه من است. جاوا GOTO دارد! این کد را بنویسید.

نتیجه زیر را در برخواهد داشت!

به این دلیل است که جاوا کلید واژه استفاده نشده GOTO دارد.

اما نکته جالب کار این است که شما خودتان می‌توانید goto را تعریف کنید
پرش به جلو:

در بایت کد

پرش به عقب

در بایت کد

۸- جاوا برای نوع داده‌ها اسم مستعار دارد
در زبان‌های دیگر به سادگی می‌توان اسم مستعار تعریف کرد:

که از این به بعد به جای set به راحتی می‌توان از People استفاده کرد.

در جاوا در سطوح بالا نمی‌توانیم اسم مستعار برای نوع داده‌ها تعریف کنیم اما می‌توانیم این کار را در اسکوپ یک کلاس یا متد انجام دهیم. فرض کنید ما از نامگذاری‌های Integer, Long, … خوشمان نمی‌آید و می‌خواهیم اسامی کوتاه‌تری مثل I, L, … را داشته باشیم. ساده است:

در اسکوپ کلاس Test بالا، I اسم مستعاری برای Integer و در اسکوپ تابع x این کلاس L اسم مستعاری برای Long شده است. پس می‌توان متد بالا را به شکل زیر فراخوانی کرد:

این تکنیک‌ها البته جدی نیستند. در این جا Integer, Long هر دو نوع داده‌های نهایی هستند و این یعنی I, L واقعا اسم مستعارند اما اگر این طور نبود و مثلا با نوع داده Object سروکار داشتیم آن وقت از یک generic ساده استفاده کرده بودیم.

۹- بعضی از روابط بین نوع‌داده‌ها مشخص نیست!
این مورد می‌تواند اندکی ترسناک باشد پس یک لیوان چایی بریزید و تمرکز کنید. این دو نوع را در نظر بگیرید:

نوع داده‌های C, D چه معنی می‌دهند؟
این‌ها تقریبا بازگشتی هستند مشابه شیوه‌ای که java.lang.Enum بازگشتی است در نظر بگیرید:

با تعریف فوق پیاده‌سازی enum تنها یک معادل نحوی برای ساده‌سازی خواهد بود:

با در نظر داشتن این مساله به مثال خودمان برمی‌گردیم. آیا تکه کد زیر کامپایل خواهد شد؟

سوال سختی است!
آیا C یک زیرنوع از Type<? super C>i است؟

یا آیا D یک زیر نوع از Type<? super D<Byte>>i است؟

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

۱۰- اشتراک نوع‌داده‌ها
یک ویژگی عجیبی که جاوا دارد اشتراک نوع‌داده‌هاست. می‌توان نوع‌داده‌ای تعریف کرد که در حقیقت اشتراک دو نوع داده دیگر است. برای مثال:

هر نوع‌داده‌ای که قرار است به جای T استفاده شود باید هر دو کلاس Serializable , Cloneable را پیاده‌سازی کرده باشد مثلا String اینطور نیست ولی Date می‌تواند باشد:

این ویژگی در جاوا ۸ هم استفاده شده که می‌توان نوع‌داده‌ها را به اشتراک نوع‌داده‌های خاص منظوره تبدیل کرد. اما این به چه دردی می‌خورد؟ حقیقتا این اصلا به درد بخور نیست! اما اگر بخواهیم یک lambda expression را به چنین نوع داده‌ای تبدیل کنیم راه دیگری نداریم. فرض کنید محدودیت‌های نوع‌داده مسخره زیر را روی تابع خود داشته باشید:

شما یک Runnableای می‌خواهید که Serializable هم باشد برای شرایطی که هم می‌خواهید آن را در جایی دیگر اجرا کنید و هم روی سیم بفرستید. lambda و Serialization اندکی مشابه اند!
شما می‌توانید یک lambda expression را serialize کنید اگر نوع‌داده مقصد و آرگومان‌های آن serializable بودند.
اما اگر این هم درست باشد به طور اتوماتیک serializable را پیاده‌سازی نمی‌کنند. برای تبدیل آن‌ها به آن نوع‌داده لازم است تبدیل صورت گیرد. اما وقتی فقط به Serializable تبدیل انجام شود.

آن‌گاه lambda دیگر runnable نخواهد بود. بنابراین باید تبدیل به هردو نوع‌داده صورت گیرد.

جاوا زبان پر رمز و رازی است. شما چه عجایب دیگری از آن تا به حال کشف کرده‌اید؟

منابع:

javacodegeek
wikipedia
stackoverflow

نوشته های مشابه

یک دیدگاه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا