ماذا تعرف عن ال Project Coin
المشروع Coin هو مشروع مفتوح المصدر بدء منذ 2009 وهو جزء أساسي من لغه جافا 7 و النسخه القادمة 8، والفكرة من وراء هذا المشروع هو تقديم التغييرات الصغيرة في اللغه حتى يتم اعتمادها، قدم لهذا المشروع أكثر من 70 مقترح وتمت الموافقه على 6 منها وهي تعتبر من التغييرات الموجودة في النسخه 7.
قبل أن نطرح هذه التغييرات من المهم معرفة الجهد المبذول لأي خاصية تقدم جديدة في الجافا، حقيقة خلال تطوير جافا 7 قدمت الكثير من الاراء حول بعض المزايا الجديدة في اللغه، ولكن المشكلة أن بعض الميزات تحتاج لعمل وجهد أكبر من ذي بعض الميزات ، وبشكل عام المخطط التالي يوضع مقدار الجهد والوقت المطلوب للقيام بالعمل، ولو لاحظنا تقديم مكتبة او العمل عليها اسهل بكثير من تقديم ميزة ضمن منصه الجافا.
وبشكل عام يجب اختيار الطريق الأسهل الذي يؤدي للحل، فمثلاً اذا كان يمكن عمل التغييرات الجديدة في مكتبة فهذا ما يفضل عمله، لكن بالطبع ليست جميع المزايا يمكن تطبيقها كمكتبة لأنها قد تحتاج ان تطبق داخلياً على مستوى اللغه او المنصه.
التصنيف اعلاه يمكن أن تضع التغييرات الجديدة فيه ، مثلاً احد التغييرات الجديدة (التي سنتناولها بعد قليل) وهو السماح بال Underscore Number سيتم تصنيفه على انه Syntactic Sugar ، ومثلاً التغيير الجديد TWR يمكن تصنيفه تحت ال Small new language feature، لو نظرنا في السابق في جافا 5 عندما قدمت ال Annotations يمكن تصنيفها ضمن ال Class file format change، في جافا 7 هناك ايضاً ال InvokeDynamic وهو يقع ضمن ال New JVM Feature.
(للمعلومية كلمة Syntactic Sugar تعني ان الSyntax مكرر أي انها موجودة في اللغه ولكن الان وجد بشكل جديد اسهل بدلاً من الSyntax الأول، وفي الغالب ال Syntactic Sugar يتم حذفها اثناء عمل الترجمة بواسطة المترجم ويقوم بارجاعها الى شكلها القديم أو الSyntax الأول، وكما تلاحظ أن تقديم Syntactic Sugar في اللغه سهل لأنه يتطلب فقط التعديل في المترجم).
مشروع ال Coin في الغالب يحتوي على ال Syntactic Sugar الى الوصول للتغيرات الصغيرة باللغه Small new language features. حان الوقت لالقاء نظره على تلكم التغيرات في جافا 7.
التغييرات في ال Project Coin
كما ذكرنا قدم هذا المشروع 6 ميزات جديدة في جافا 7، حيث سمح باستخدام ال String في جملة switch ، وقدم طريقة جديدة لكتابه الأرقام في الكود Number Literal، وحسن طريقة التعامل مع الException ، ايضاً الآن بامكانك غلق المصادر Resource بدون الحاجة لاستدعاء close من خلال الميزة الجديدة try-with-resource، والميزة الجديدة في تعريف Generic وهي الDiamond syntax، وأخيراً قام بحل بعض التحذيرات التي يصدرها المترجم عندما تتعامل مع Varargs (المعاملات التي تستقبل عدد غير محدد من القيم) ..
سوف نتناول الآن هذه التغييرات بشيء من التفصيل، مع تقديم السبب الذي ادى لظهور هذه المتغيرات (سواء بكتابة ذلك أو من خلال الأمثلة المطروحة ) ، ودعنا نبدأ بطريقة استخدام النصوص داخل جملة ال switch.
التغيير الأول String in Switch
كما نعلم ان جملة الswitch تسهل لنا كتابة الشروط المتعددة بدون استخدام if-else-if else القبيحة المنظر ، مثل الكود التالي نريد معرفة اليوم الحالي :
public void printDay(int dayOfWeek) { switch (dayOfWeek) { case 0: System.out.println("Saturday"); break; case 1: System.out.println("Sunday"); break; case 2: System.out.println("Monday"); break; case 3: System.out.println("Tuesday"); break; case 4: System.out.println("Wednesday"); break; case 5: System.out.println("Thursday"); break; case 6: System.out.println("Friday"); break; default: System.err.println("Error!"); break; } }
في السابق ايام جافا 6 وما قبلها هذه القيمة المستخدمه داخل جملة الswitch يجب أن تكون من الأنواع (byte, int, short, char) أو Enum. الان في جافا 7 تستطيع استخدام الString اضافة للأنواع القديمة وبالتالي يمكن أن يكون شكل جملة الSwitch الآن:
public void printDay(String dayOfWeek) { switch (dayOfWeek) { case "Saturday": System.out.println("Study Java"); break; case "Sunday": System.out.println("Read Books"); break; case "Monday": System.out.println("Swimming"); break; case "Tuesday": System.out.println("Play Football"); break; case "Wednesday": System.out.println("Write Blog"); break; case "Thursday": System.out.println("Coding"); break; case "Friday": System.out.println("Sleeping"); break; default: System.out.println("Error: '"+ dayOfWeek +"' is not a day of the week"); break; } }
كما تلاحظ لم يتغير اي شيء في تركيبه الجملة Switch ، وهكذا اغلب تغييرات الCoin Project فقد تقدم تغيير بسيط لكن هذا يسهل لك البرمجة في جافا..
التغيير الثاني وهو تحسين كتابة ال Numeric Literals
هناك الكثير من الاقتراحات التي قدمت لهذا المشروع ، ولكن ما تم اختياره هو اثنان فقط، الأول يسمح لك بكتابة الأرقام من خلال كتابتها بالترميز الثنائي Binary Literals، والثاني يسمح لك باستخدام الشرطة السفلية Underscore للفصل بين الأرقام وذلك لتسهل قرائتها Improve Readability.
هذه ليست تغييرات قد تفيدك في برمجتك اليومية، ولكن قد تفيد المبرمجين الذين يعملوا في مكتبات التشفير أو بروتوكلات الشبكات أو حتى يمكن يعمل على مستوى البت والعمليات التي فيه ، بمعنى هي مفيدة لل Low Level Programmers، ولنستعرضها الآن:
في السابق كنا لكي نكتب الأرقام الثنائيه نحتاج ان نستدعي الدالة parseInt ونمرر لها الترميز المطلوب:
int x = Integer.parseInt("1100110", 2);
هذا الأمر مزعج حقيقة، لأنك تحتاج ان تتذكر هذه الدالة وتتذكر المعاملات بها، ويجب أن لا تخطئ في كتابة الرقم الثنائي والا ستحصل على RuntimeException، والأهم من ذلك كله هو الأداء Performance لأنك تقوم باستدعاء دالة كل مرة تود كتابه رقم الثنائي، وهذا يعني ان عملية كتابة رقم ثنائي لن تكون مثل بقية الثوابت (يتعرف عليها المترجم Compile-Time Constant) ولكنها سوف تعامل مثل بقية العمليات وقت التشغيل runtime expression (هذا يعني أيضاً أنك لن تستطيع استخدامها داخل جملة ال Switch )..
الأن في جافا، فقط ادخل 0b قبل كتابة الرقم ، بهذا الشكل:
int x = 0b1100110;
هذا التغيير البسيط حل لنا المشاكل السابقة، واذا كنت تتعامل مع البايتات بشكل كثير في برنامجك فسوف تسعد بهذه الاضافه بكل تأكيد .. وهناك الاضافه الأخرى وهي مفيدة لأي مبرمج يكتب ارقام طويلة وهي استخدام الUnderscore.
فكما تعلم أن قرائه الأرقام الطويلة امر قد يعرضك للأخطاء لذلك في حياتنا العادية نجد ان الأرقام الطويلة في البنوك يتم فصلها بفواصل (مثلاً رصيدك في البنك هو 10,000,000 ريال، ورقم هاتف هو 454-554-54 وهكذا الفصل يسهل القرائه). ولسوء الحظ لا يمكن ان نستخدم الفاصله ولا ال – في الفصل لأنها رموز لها اكثر من معنى في البرمجة، لذلك تم استعارة استخدام الفاصلة من لغه روبي وبالتالي يمكنك الأن كتابه رقمك بهذا الشكل:
int x = 10000000; int newX = 10_000_000; // its more readable // we can use also on other types long anotherLong = 2_147_483_648L; int bitPattern = 0b0001_1100__0011_0111__0010_1011__1010_0011
التغيير الثالث وهو تحسين التعامل مع ال Exceptions
هنا لدينا تحسينين ( ال Multicatch وال final rethrow) ، ولننظر لمثال يوضح كيف ستساعدنا التحسين الأول، المثال يقوم بقرائه من ملف ويقوم بعمل Parse ويقرأ الConfiguration ، وفيه العديد من الExceptions التي تم معالجتها:
public Configuration getConfig(String fileName) { Configuration cfg = null; try { String fileText = getFile(fileName); cfg = verifyConfig(parseConfig(fileText)); } catch (FileNotFoundException fnfx) { System.err.println("Config file '" + fileName + "' is missing"); } catch (IOException e) { System.err.println("Error while processing file '" + fileName + "'"); } catch (ConfigurationException e) { System.err.println("Config file '" + fileName + "' is not consistent"); } catch (ParseException e) { System.err.println("Config file '" + fileName + "' is malformed"); } return cfg; }
هذه الExceptions قد تحدث لأسباب عديدة (الملف غير موجود، الملف تم حذفه اثناء قرائته، الملف له format غير صحيحه، الملف يحتوى معلومات خاطئة) . ولو نظرنا لنوعيه هذه الأسباب يمكن أن نقول المشكلة أما أن الملف غير موجود أو أن فورمات غير صحيحة. وهنا جافا 7 قالت أنه من الأفضل اختصار هذه الExceptions لهذه المسببين الرئيسين ، وبالتالي اختصرنا المشكلة ل “الملف غير موجود” أو “الملف غير صالح” :
public Configuration getConfig(String fileName) { Configuration cfg = null; try { String fileText = getFile(fileName); cfg = verifyConfig(parseConfig(fileText)); } catch (FileNotFoundException|ParseException|ConfigurationException e) { System.err.println("Config file '" + fileName + "' is missing or malformed"); } catch (IOException iox) { System.err.println("Error while processing file '" + fileName + "'"); } return cfg; }
المتغير e الموجود في جملة الcatch لن تعرف نوعه وقت ترجمة التطبيق، ونوعه سيتم معرفته وقت حدوث الException المعين، هكذا الاختصار سهل البرنامج وايضاً سهل على المبرمج التعامل مع الExceptions (بدلاً من تكرار الكود لكل من الExceptions عندما يكونوا متفرقين).
بالنسبة للتحسين الثاني (ال Final Rethrow) فبعض الأحيان تجد أنك عندما تقوم بمعالجة الException في جملة الCatch فانك تريد ايضاً بعمل throw لهذا الException للأعلى، وبالتالي قد تقوم بعمل مثل هذا الكود:
try { doSomethingWhichMightThrowIOException(); doSomethingElseWhichMightThrowSQLException(); } catch (Exception e) { ... throw e; }
المشكلة أنك عندما تقوم بعمل throw في الأعلى سوف تقوم بعمله ولكن للنوع Exception (وليس للنوع الحقيقي الذي حدث وهو اما IOException أو SQLException) ، الآن في جافا 7 من خلال كلمة final تضعها للنوع في جملة ال Exception فعندما يحدث ذلك الException وتقوم بعمل throw فالنوع سوف يكون النوع الحقيقى وليس الأب لهم Exception:
try { doSomethingWhichMightThrowIOException(); doSomethingElseWhichMightThrowSQLException(); } catch (final Exception e) { ... throw e; }
بالاضافه لهذه التحسينات في الException Handling نجد أن جافا 7 ايضاً قد قدمت تحسينات في ال Resource Management كما سوف نوضحها فيما يلي.
التغيير الرابع Try-with-Resources (وأختصاراً TWR)
فكرة هذه التغيير سهله الشرح حيث انك ستحدد التعامل مع المصادر (ولنقل ملف) محددة بScope معين (بلوك في الكود) وبمجرد انتهاء مجال هذا الScope (الخروج خارج البلوك) سيتم اغلاق المصدر مباشره بشكل تلقائي.
فكرة التغيير كانت سهله ولكنها مهمه حقاً لأننا كثيراً ما ننسى اغلاق ال Resources أو حتى نغلقها بشكل خاطئ (حتى انه هناك بعض الأكواد الموجودة في ال JDK وجد بها أخطاء قدمت عندما اقترح هذا التغيير). لنأخذ مثالاً على القرائه من رابط URL والكتابه في ملف (في جافا 6 وما قبلها) ، يمكن أن نقوم ب:
InputStream is = null; try { is = url.openStream(); OutputStream out = new FileOutputStream(file); try { byte[] buf = new byte[4096]; int len; while ((len = is.read(buf)) >= 0) out.write(buf, 0, len); } catch (IOException iox) { } finally { try { out.close(); } catch (IOException closeOutx) { } } } catch (FileNotFoundException fnfx) { } catch (IOException openx) { } finally { try { if (is != null) is.close(); } catch (IOException closeInx) { } }
هل لاحظ جمل ال close ، المشكلة هي انك عندما تتعامل مع ال External Resources فهناك امور عدة يمكن ان يحصل بها أخطاء (مثلاً الInputStream قد لا يستطيع فتح الرابط أو القرائه منه أو حتى اغلاقه جيداً، الFile قد لا يفتح أو انك لا تستطيع الكتابه فيه أو حتى اغلاقه جيداً) أو حتى مجموعه من الأخطاء بأكثر من سبب وهو الأكثر صعوبة للحل,,
لذلك قدم هذا التغيير، فهل أقل عرضه للأخطاء Less error-prone ، نشاهد المثال بعد تحويله لجافا 7:
try (OutputStream out = new FileOutputStream(file); InputStream is = url.openStream() ) { byte[] buf = new byte[4096]; int len; while ((len = is.read(buf)) > 0) { out.write(buf, 0, len); } }
اعتقد أن هذا التغيير مفيد وحتى للمقروئية فالكود أصبح أسهل وأقل في عدد الأسطر، لمبرمجي السي# هذا الأمر موجود لديهم باستخدام using، وكما نلاحظ أعلاه أننا لا نقوم بكتابة الclose بل سيتم ذلك بشكل تلقائي,,
عليك أن تكون حريصاً عندما تستخدم هذه الميزة لأنه قد لا يتم اغلاق الresource في حالة واحدة، وهي عندما تقوم بعمل chaning للstreams ويحصل خطأ اثناء ذلك، مثلاً الكود التالي لن يغلق الFileInputStream اذا حدث خطأ في انشاء ال ObjectInputStream (في حال كان الملف موجود someFile.bin ولكنه غير صالح لكي يكون ObjectInput ):
try ( ObjectInputStream in = new ObjectInputStream(new FileInputStream("someFile.bin")) ) { ... }
لذلك لا تقوم بعمل التداخل في انشاء الكائنات ، وقم بعمل كل كائن على حده ، وهذه هي الطريقة الصحيحه والتي تضمن الاغلاق الصحيح :
try ( FileInputStream fin = new FileInputStream("someFile.bin"); ObjectInputStream in = new ObjectInputStream(fin) ) { ... }
داخلياً كل الكلاسات التي تقوم بهذه الميزة (الاغلاق تلقائياً) قامت بعمل implements لل AutoCloseable وهو interface يقوم اي كلاس باعادة الدوال بداخله عندما يريد تطبيق هذه الميزه TWR، الكثير من الكلاسات في جافا 7 تقوم بعمل implements لل AutoCloseable . وعلى كل النصيحة العامة للمبرمجين هي استخدام ال TWR بقدر الامكان متى توفرت في الكلاس وهكذا ستنهي كثير من المشاكل المتعلقة بعمليات الاغلاق.
أذا أردت المزيد عن هذه المقترحات يمكنك زيارة الMailing List ومشاهدة النقاشات حولها، مثلاً المقترح TWR قدم هنا:
http://mail.openjdk.java.net/pipermail/coin-dev/2009-February/000011.html
صاحب المقترح هو الخبير جوثا بلوك (صاحب كتاب Effective Java الغني عن التعريف).
التغيير الخامس Diamond Syntax:
هذا التغيير يريحك من كتابة الأنواع الكثيرة عندما تتعامل مع الGeneric، مثلاً لديك الكثير من المستخدمين الذين تعرفهم بواسطة id (رقم صحيح) وكل منهم لديه معلومات خاصه به تكون بشكل lookup (اسم وقيمه) ، في جافا 6 وما قبلها (5 فقط لأن 4 لم تكن موجودة هذه الميزه) ستقوم ب:
Map<Integer, Map<String, String>> usersLists = new HashMap<Integer, Map<String, String>>();
بالتأكيد متعبه في القرائة والكتابه ، الآن في جافا 7 استخدم الصيغة المختصرة :
Map<Integer, Map<String, String>> usersLists = new HashMap<>();
وسيقوم المترجم من تلقاء نفسه بمعرفة النوع من خلال التصريح اللى في الجهه اليمني. وسميت هذه الميزة ب (Diamond Syntax) والسبب انها بعد الاختصار تشبه المجوهره.
أخر مزايا هذا المشروع هو حذف بعض التحذيرات التي تصدر من المترجم عندما تستخدم ال Varargs ، لنتحدث عنها الآن..
التغيير السادس Simplified Varargs:
اذا كنت تعلم ان جافا منذ الاصدار دعم الدوال التي تستقبل عدد غير معروف من المعاملات (تسمى Variable Arguments) المثال التالي يقوم بطباعه كل النصوص التي ترسل ويمكن ارسال اي عدد من النصوص:
import java.util.*; import java.io.*; public class Demo { public static void main(String [] args) { printAll("Wajdy", "Essam", "is" , "Coder"); } public static void printAll(String... args) { for(String arg: args) { System.out.println(arg); } } }
داخلياً هذه القيم يتم تعبئتها في مصفوفة وبالتالي يمكنك عمل iteration عليها، المشكلة التي تواجهنا في السابق هو صدور التحذيرات من المترجم اذا كان نوع هذه المعاملات هو Non-Reifiable لأنه سوف يؤدي الى عمليات غير آمنه، الأنواع التي تسمى Reifiable هي الأنوع التي لا تفقد اي معلومات بعد عملية ال Type-Errasure وقت الترجمة ، وهذه الأنواع هي كما يلي (البقية تعتبر Non-Reifiable) :
اذا كان النوع ليس Generic مثلاً String ، اذا كان النوع يستخدم الUnbounded Wildcard مثلاً List<?>، واذا كان النوع هو raw type مثل java.util.List ، والأنواع الPrimitve تعتبر ايضاً Reifiable ، وايضاً المصفوفات التي تكون من أنواع Reifiable تكون هي كذلك مثلاً مصفوفة من String.
مثال ، لو شغلت المثال التالي ومررت المعامل -Xlint:unchecked وقت الترجمة ستجد التحذير :
import java.util.*; import java.io.*; public class Demo{ public static void main(String [] args) { printAll(new ArrayList<String>(), new LinkedList<String>()); } public static void printAll(List<String> ... args){ for(List<String> arg:args){ System.out.println(arg); } } }
رسالة التحذير:
Demo.java:6: warning: [unchecked] unchecked generic array creation of type java.
util.List<java.lang.String>[] for varargs parameter
printAll(new ArrayList<String>(), new LinkedList<String>());
^
1 warning
لكن اذا كنت متأكد من ما تفعل فيمكنك في جافا 7 استخدام @SafeVarargs حتى توقف هذه التحذيرات:
import java.util.*; import java.io.*; public class Demo{ public static void main(String [] args) { printAll(new ArrayList<String>(), new LinkedList<String>()); } @SafeVarargs public static void printAll(List<String> ... args){ for(List<String> arg:args){ System.out.println(arg); } } }
الى هنا نكون قد استعرضنا جميع الاضافات التي حصلت في جافا 7 في المشروع Coin ، ونصيحتنا هي ان تبدأ باستخدام هذه الميزات من الآن ، وبالنسبه لي بدأت بالتعود على الException بالطريقة الجديدة و ال TWR و ال Diamond Syntax.
بسم الله الرحمن الرحيم
فى كتابة عنوان التدوينة افضل لو كتبتها ماذا اضاف project coin فى جافا 7
اكمل اخى الفاضل متابعيين بأذن الله تعالى
جزاكم الله خير
نعم هكذا افضل ، بارك الله فيكم