مدخل إلى جافا المدخلات والمخرجات النصية في جافا


رضوى العربي

لقد رأينا مدى سهولة اِستخدَام دوال (functions) مثل System.out.print و System.out.println لعَرْض نص معين للمُستخدِم، ولكن لا يُمثِل ذلك سوى جزءًا صغيرًا من موضوع مُخْرَجات النصوص. تَعتمِد غالبية البرامج عمومًا على البيانات التي يُدخِلها المُستخدِم أثناء زمن تَشْغِيل البرنامج لذا لابُدّ من فهم الكيفية التي ينبغي أن نَتَعامَل على أساسها مع كُلًا من المُدْخَلات والمُخْرَجات. سنشرح خلال هذا القسم كيف يُمكِننا قراءة البيانات المُدْخَلة من قِبَل المُستخدِم كما سنناقش المُخْرَجات على نحو أكثر تفصيلًا، وأخيرًا سنتناول اِستخدَام الملفات كوسيلة للمُدْخَلات والمُخْرَجات.

الخرج البسيط والخرج المنسق

تُعدّ الدالة System.out.print(x)‎ واحدة من أبسط دوال الخَرْج حيث تَستقبِل مُعاملًا x عبارة عن قيمة أو تعبير (expression) من أي نوع. إذا لم تَكُن قيمة المُعامل x المُمرَّرة سِلسِلةً نصية من النوع String، فإنها ستُحوَّل أولًا إلى قيمة من النوع String ثم ستُرسَل إلى مقصد خَرْج (output destination) يُعرَف باسم "الخَرْج القياسي (standard output)" مما يَعنِي أنها ستُعرَض للمُستخدِم. لاحِظ أنه في حالة برامج واجهة المُستخدِم الرسومية (GUI)، ستُرسَل تلك السِلسِلة إلى مكان لا يُحتمَل للمُستخدِم أن يراه. بالإضافة إلى ذلك، يُمكِننا أيضًا أن نُعيد توجيه الخَرْج القياسي (standard output) لكي يَكْتُب إلى مقصد خَرْج (output destination) مختلف، ولكن فيما هو مُتَعلِّق بالبرنامج التالي، سيتولَّى الكائن System.out مسئولية عَرْض النص للمُستخدِم.

تُرسِل الدالتان System.out.println(x)‎ و System.out.print نفس النص إلى الخَرْج، ولكن تُضيف الأولى سطرًا جديدًا (line feed) إلى نهاية النص أي سيُعرَض الخَرْج التالي بسطر جديد. يُمكِنك أيضًا أن تَستخدِم الدالة System.out.println()‎ بدون أية مُعاملات (parameter) لإخراج سطر جديد أي يُكافِئ استدعاء الدالة System.out.println(x)‎ ما يلي:

System.out.print(x);
System.out.println();

ربما تَكُون قد لاحظت أن System.out.print تُخرِج أعدادًا حقيقية مُكوَّنة من عدد أرقام مناسب بَعْد العلامة العشرية، فمثلًا، تُخرِج العدد π كالتالي "3.141592653589793" أما الأعداد المُمثِلة للنقود فتُخرِجها على الصورة التالية "1050.0" أو "43.575". قد تُفضِّل إِخراج تلك الأعداد بهيئة مختلفة مثل "3.14159" و "1050.00" و "43.58"، وعندها ينبغي أن تَستخدِم ما يُعرَف باسم "الخَرْج المُنسَّق (formatted output)" والذي سيُساعدك على التَحكُّم بكيفية طباعة الأعداد الحقيقية وغيرها من القيم الآخرى. تَتَوفَّر العديد من خيارات التنسيق سنُناقِش هنا أبسطها وأكثرها شيوعًا.

يُمكِنك اِستخدَام الدالة System.out.printf للحصول على خَرْج مُنسَّق. يُعدّ الاسم printf اختصارًا للكلمتين "print formatted" أي "اِطبَع بصورة مُنسَّقة"، وهو في الواقع مأخوذ من لغتي البرمجة C و C++‎. تَستقبِل الدالة System.out.printf مُعاملًا (parameter) واحدًا أو أكثر بحيث يكون مُعاملها الأول عبارة عن سِلسِلة نصية من النوع String تُحدِّد صياغة الخَرْج (output format)، ويُطلَق عليها اسم "صِيْغة سلسلة التنسيق (Format String)" أما بقية المُعاملات فتُخصِّص القيم المطلوب إرسالها للخَرْج. تَطبَع التَعليمَة التالية عددًا بصياغة تتناسب مع قيمة نقدية بالدولار بحيث يُمثِل amount مُتْغيِّرًا من النوع double:

System.out.printf( "%1.2f", amount );

تتكوَّن أي صِيْغة سلسلة تنسيق (format string) من "مُحدِّد تنسيق (format specifiers)" واحد أو أكثر بحيث يكون كُلًا منها مسئولًا عن صيغة الخَرْج لقيمة واحدة تُمرَّر عبر مُعامل آخر إضافي. في المثال أعلاه، احتوت صيغة سلسلة التنسيق على مُحدِّد تنسيق واحد هو ‎%1.2f. يبدأ أي مُحدِّد تنسيق عمومًا بعلامة نسبة مئوية % وينتهي بحرف أبجدي يُحدِّد نوع الخَرْج المطلوب كما قد يَتَضمَّن معلومات تنسيق آخرى تُكْتَب بينهما، فمثلًا، ها هي بعض من مُحدِّدات التنسيق المُحتمَلة: ‎%d و ‎%12d و ‎%10s و ‎%1.2f و ‎%1.8g. يُشير الحرف "d" بمُحدِّديّ التنسيق ‎%d و ‎%12d إلى أن القيمة المطلوب كتابتها عبارة عن عدد صحيح (integer) أما "12" بالمُحدِّد الثاني فتُشير إلى أقل عدد من الخانات ينبغي للخَرْج أن يحتلّه، فمثلًا، إذا تَطلَّب إخراج عدد صحيح عدة خانات عددها أقل من ١٢، ستُضَاف خانات فارغة إضافية إلى مُقدمة ذلك العدد لكي يُصبِح مجموع الخانات بالنهاية مساويًا لأقل عدد مُمكِن أي ١٢. يُقال عندها أن الخَرْج "وَاقِع على مُحاذاة اليمين بحقل طوله ١٢" أما إذا تطلَّب إخراجه عددًا أكبر من ١٢ خانة، ستُطبَع جميع الأرقام بدون أي خانات إضافية. لاحِظ أن مُحدِّديّ التنسيق ‎%d و ‎%1d لهما نفس المعنى أي إخراج القيمة بهيئة عدد صحيح بأي عدد ضروري من الخانات. يُعدّ الحرف "d" اختصارًا لكلمة "decimal" كما تستطيع اِستخدَام الحرف "x" بدلًا منه لإِخراج عدد صحيح بصياغة ست عشريّة (hexadecimal).

يُمكِنك أيضًا اِستخدَام الحرف "s" بنهاية مُحدِّد تنسيق (format specifier) مع أي قيمة من أي نوع، مما يَعنِي ضرورة إخراج تلك القيمة بصيغتها الافتراضية كما لو أننا لم نَستخدِم خرجًا مُنسَّقًا من الأساس، فمثلًا، يُمكننا أن نُخصِّص مُحدِّد تنسيق مثل ‎%20s بحيث يُمثِل العدد "20" أقل عدد ممكن من المحارف. يُعدّ الحرف "s" اختصارًا لكلمة "string" ويُمكِن استخدامه للقيم من النوع String أو مع أي قيم من أي نوع آخر بحيث تُحوَّل تلك القيم إلى النوع String بالطريقة المُعتادة.

تُعدّ مُحدِّدات تنسيق القيم من النوع double أكثر تعقيدًا بقليل. يُستخدَم الحرف "f" بمُحدِّد تنسيق مثل ‎%1.2f لإخراج عدد بصيغة عدد عشري مُكوَّن من فاصلة عائمة (floating-point form) أي يحتوي على عدة أرقام بعد العلامة العشرية. يُخصِّص العدد "2" بذلك المُحدِّد عدد الأرقام المُستخدَمة بَعْد العلامة العشرية أما العدد "1" فيُخصِّص أقل عدد من المحارف يلزم إخراجه. تعني القيمة "1" بذلك المَوضِع إمكانية إخراج أي عدد ضروري من المحارف. كمثال آخر، يُخصِّص المُحدِّد ‎%12.3f صيغة عدد عشري مُكوَّن من ثلاثة أرقام بَعْد العلامة العشرية بحيث يُطبَع على محاذاة اليمين داخل حقل طوله ١٢. بالنسبة لكُلًا من الأعداد الكبيرة جدًا والصغيرة جدًا، فينبغي أن تُكْتَب بصيغة أسية (exponential format) مثل 6.00221415e23 وهو ما يُمثِل حاصل ضرب 6.00221415 في ١٠ مرفوعة للأس ٢٣. يُخصِّص مُحدِّد صيغة مثل ‎%15.8e خَرْجًا بصيغة أُسية بحيث تُشير "8" إلى عدد الأرقام المُستخدَمة بَعْد العلامة العشرية. لاحِظ أنه إذا كنت تَستخدِم "g" بدلًا من "e"، فسيَكوُن الخرج بصيغة أُسية فقط للقيم الصغيرة جدًا والكبيرة جدًا أما القيم الآخرى فستُعرَض بصيغة عدد عشري كما ستُشير القيمة "8" بمُحدِّد صيغة مثل ‎%1.8g إلى عدد الأرقام الكلي بما في ذلك كُلًا من الأرقام قَبْل وبَعْد العلامة العشرية.

بالنسبة لخرج القيم العددية، فيُمكِنك أن تُضيف فاصلة , إلى مُحدِّد الصيغة لتقسيم أرقام العدد إلى مجموعات مما يُسهِل من قراءة الأعداد الكبيرة. بالولايات المتحدة الأمريكية، تَتَكوَّن كل مجموعة من ثلاثة أرقام يَفصِل بينها محرف فاصلة، فمثلًا إذا كانت قيمة x تُساوِي واحد بليون، فسيَكُون الخَرْج الناتج عن System.out.printf("%,d",x)‎ هو 1,000,000,000. بدول آخرى، قد يختلف المحرف الفاصل وكذلك عدد الأرقام بكل مجموعة. لابُدّ من كتابة الفاصلة ببداية مُحدِّد الصيغة (format specifier) أي قبل تَخْصِيص عَرْض الحقل (field width) كالتالي ‎%,12.3f. إذا أردت محاذاة الخرج إلى اليسار بدلًا من اليمين، يمكنك إضافة علامة سالبة إلى بداية مُحدِّد الصيغة كالتالي ‎%-20s.

إلى جانب مُحدِّدات الصيغة (format specifiers)، قد تَتَضمَّن صيغ سَلاسِل التنسيق (format string) المُمرَّرة إلى الدالة printf محارفًا آخرى عادية تُنسَخ إلى الخَرْج كما هي. يَكُون ذلك مناسبًا لتَضْمِين بعض القيم بمنتصف سِلسِلة نصية. فلنَفْترِض مثلًا أن كُلًا من x و y عبارة عن مُتْغيِّرات (variables) من النوع int، يُمكِننا إذًا كتابة التالي:

System.out.printf("The product of %d and %d is %d", x, y, x*y);

عندما تُنفَّذ تلك التَعْليمَة، فستَحِلّ قيمة x محلّ أول حُدوث لمُحدِّد الصيغة ‎%d بالسِلسِلة النصية (string) بينما ستَحِلّ قيمة y محلّ الحُدوث الثاني، وأخيرًا ستَحِلّ قيمة التعبير x*y محلّ الحُدوث الثالث. فمثلًا، قد يَكُون الخَرْج كالتالي "The product of 17 and 42 is 714" (علامتي الاقتباس ليست ضِمْن الخَرْج).

إذا أردت أن تُخرج علامة نسبة مئوية، فينبغي أن تَكْتُب مُحدِّد الصيغة %% بصيغة سِلسِلة التنسيق (format string) أما إذا كنت تريد أن تُخرِج سطرًا جديدًا، فاستخدِم مُحدِّد الصيغة ‎%n. بالمثل من السَلاسِل النصية العادية، يُمكِنك أيضًا أن تَستخدِم خطًا مائلًا عكسيًا \ لإخراج محارف خاصة كعلامة الاقتباس المزدوجة أو المحرف "tab".

مثال لمدخل نصي

اعتادت الجافا لسبب غَيْر مفهوم أن تُصعِب من قراءة البيانات المُدْخَلة من قِبَل مُستخدِم البرنامج. كما ذكرنا من قبل، يتَوفَّر الكائن System.out بصورة مُسْبَقة ويُستخَدم لعَرْض الخَرْج للمُستخدِم حيث يَتَضمَّن البرنامج الفرعي (subroutine)‏ System.out.print لذلك الغرض. يَتَوفَّر أيضًا الكائن System.in لأغراض قراءة البيانات المُدْخَلة من قِبَل المُستخدِم، ولكنه يُوفِّر خدمات إِدْخَال بدائية جدًا تَتَطلَّب بعض المهارات البرمجية المُتقدمة بالجافا لاستخدامه بكفاءة.

قدمت الجافا منذ إصدارها الخامس الصَنْف Scanner مما سَهَّل قليلًا من عملية قراءة البيانات المُدْخَلة من قِبَل المُستخدِم، ولكنه يَتطلَّب بعض المعرفة بالبرمجة كائنية التوجه (object-oriented programming) لكي تَتَمكَّن من اِستخدَامه، لذلك ربما لا يَكُون الحل الأمثل حاليًا بينما ما نزال نبدأ خطواتنا الأولى. بالإضافة إلى ذلك، قدمت الجافا منذ إصدارها السادس الصَنْف Console لأغراض التواصل مع المُستخدِم، ولكنه يُعاني هو الآخر من بعض المشاكل، فهو أولًا غَيْر متاح دائمًا للاِستخدَام كما أنه قادر على قراءة السَلاسِل النصية (strings) فقط وليس الأعداد. يرى الكاتب أن الصَنْفين Scanner و Console لا يقومان بالأشياء على نحوٍ صحيحٍ تمامًا، لذلك فإنه سيَعتمِد على صَنْف خاص TextIO عَرَّفه بنفسه، ولكن سنتعرَّض أيضًا إلى الصَنْف Scanner بنهاية هذا القسم في حال أردت أن تبدأ باِستخدَامه.

يُمكِنك أن تُنشِئ أصنافًا (classes) جديدة تَتَضمَّن برامجًا فرعيةً (subroutines) غَيْر مُتاحة بالجزء القياسي من لغة الجافا. بمُجرّد إِنشائك لصنف جديد، يُمكِنك أن تَستخدِم البرامج الفرعية (subroutines) المُعرَّفة بداخله بنفس الكيفية التي تَستخدِم بها البرامج المبنية مُسْبَقًا (built-in). على سبيل المثال، يَحتوِي الصَنْف TextIO الذي عرَّفه الكاتب على برامج فرعية (subroutines) لقراءة أي مُدْخَلات يَكْتُبها المُستخدِم من خلال كائن الدَخْل القياسي (standard input)‏ System.in وذلك بدون الحاجة لأي مَعرِفة مُتقدمة بالجافا والتي يَتَطلَّبها كُلًا من اِستخدَام الصنف Scanner والاِستخدَام المباشر للكائن System.in.

لاحِظ أن الصَنْف TextIO مُعرَّف بـ"حزمة (package)" اسمها textio مما يَعنِي وقوع الملف TextIO.java بمجلد اسمه textio كما يَعنِي أنه من الضروري لأي برنامج يَرغَب باِستخدَام الصَنْف TextIO من أن "يستورد (import)" ذلك الصَنْف من الحزمة textio أولًا بكتابة مُوجِّه الاستيراد import التالي:

import textio.TextIO;

لابُدّ أن يأتي الموجِّه بالأعلى قَبْل عبارة public class المُستخدَمة لبدء البرنامج. لاحِظ أن غالبية أصناف الجافا القياسية (standard classes) مُعرَّفة ضِمْن حزم (packages) ينبغي اِستيرادها (import) بنفس الطريقة إلى البرامج التي تَستخدِمها.

لكي تَتَمكَّن من اِستخدَام الصَنْف TextIO ضِمْن برنامج، لابُدّ أن تَتَأكَّد أولًا من تَوفُّر الصَنْف (class) بذلك البرنامج. يختلف ما يَعنِيه ذلك بحسب بيئة برمجة الجافا (Java programming environment) المُستخدَمة، ولكن ينبغي عمومًا أن تُضيف المجلد textio -الذي يحتوي على الملف TextIO.java- إلى مجلد البرنامج الرئيسي. اُنظر القسم ٢.٦ لمزيد من المعلومات عن كيفية اِستخدَام الصَنْف TextIO.

تُعدّ برامج (routines) الإِدْخَال المُعرَّفة بالصَنْف TextIO أعضاء دوال ساكنة (static member functions) ضِمْن ذلك الصنف (ناقشنا ما يعنيه ذلك بالقسم السابق). إذا كنت تَرغَب بكتابة برنامج يَقرأ عددًا صحيحًا مُدْخَلًا من قِبَل المُستخدِم، فيُمكِنك إذًا أن تَستخدِم الصَنْف TextIO حيث يَتَضمَّن عضو دالة ساكن مُعرَّف خصيصًا لذلك الغرض اسمه هو getlnInt. نظرًا لأن تلك الدالة (function) مُعرَّفة ضِمْن الصَنْف TextIO، فلابُدّ إذًا أن تَستخدِم الاسم TextIO.getlnInt للإشارة إليها ضِمْن البرنامج. لا تَستقبِل تلك الدالة أية مُعاملات (parameters)، لذا يَكُون استدعائها على الصورة TextIO.getlnInt()‎. يُعيد ذلك الاستدعاء قيمة من النوع int تُمثِل القيمة التي أَدْخَلها المُستخدِم، والتي ستَرغَب عادةً في اِستخدَامها بشكل أو بآخر لذا سنُسنِدها إلى مُتْغيِّر (variable). بِفَرض أن userInput عبارة عن مُتْغيِّر (variable) من النوع int صَرَّحنا عنه بتَعْليمَة تّصْرِيح (declaration statement) مثل int userInput;‎، نستطيع إذًا أن نُسنِد إليه القيمة المُعادة باِستخدَام تَعْليمَة الإِسْناد (assignment statement) التالية:

userInput = TextIO.getlnInt();

عندما يُنفِّذ الحاسوب التَعْليمَة بالأعلى، فإنه سينتظر إلى أن يُدْخِل المُستخدِم قيمة من النوع العددي الصحيح أي لابُدّ أولًا أن يُدْخِل المُستخدِم عددًا ثم يَضغَط على مفتاح العودة إلى بداية السطر (return) لكي يُكمِل البرنامج عمله. ستُعيد الدالة (function) المُستدعَاة بالأعلى القيمة التي أَدْخَلها المُستخدِم، والتي ستُخزَّن بالمُتْغيِّر userInput. يَستخدِم البرنامج التالي الدالة TextIO.getlnInt لقراءة العدد المُدْخَل من قِبَل المُستخدِم ثم يَعرِض مربع ذلك العدد (لاحِظ مُوجّه الاستيراد [import] بأول سطر):

import textio.TextIO;

public class PrintSquare {

     public static void main(String[] args) {

        int userInput;  // العدد الذي أدخله المستخدم
        int square;     // قيمة مربع العدد الذي أدخله المستخدم

        System.out.print("Please type a number: ");
        userInput = TextIO.getlnInt();
        square = userInput * userInput;

        System.out.println();
        System.out.println("The number that you entered was " + userInput);
        System.out.println("The square of that number is " + square);
        System.out.println();

     } // ‫نهاية main()

} // نهاية الصنف‫ PrintSquare

يَعرِض البرنامج بالأعلى الرسالة النصية "Please type a number:‎" عند تَشْغِيله ثم يَنتظِر إلى أن يَتَلقَّى إجابة مُكوَّنة من عدد يتبعه محرف العودة إلى بداية السطر (carriage return). لاحِظ أنه من الأفضل دومًا لأي برنامج أن يَطبَع سؤالًا للمُستخدِم قبلما يُحاوِل أن يقرأ أي قيم مُدْخَلة؛ لأنه في حالة غياب ذلك، قد لا يَتَمكَّن المُستخدِم من معرفة نوع القيمة التي ينتظرها الحاسوب بل إنه قد لا يَتَمكَّن حتى من ملاحظة كَوْن البرنامج ينتظره لأن يُدْخِل قيمة في العموم.

دوال الإدخال بالصنف TextIO

يَتَضمَّن الصَنْف TextIO تشكيلة متنوعة من الدوال (functions) لقراءة الأنواع (types) المختلفة التي يُمكِن للمُستخدِم أن يُدْخِلها. تَستعرِض القائمة التالية عددًا من تلك الدوال:

j = TextIO.getlnInt();     // ‫اقرأ قيمة من النوع int
y = TextIO.getlnDouble();  // ‫اقرأ قيمة من النوع double
a = TextIO.getlnBoolean(); // ‫اقرأ قيمة من النوع boolean
c = TextIO.getlnChar();    // ‫اقرأ قيمة من النوع char
w = TextIO.getlnWord();    // ‫اقرأ كلمة واحدة كقيمة من النوع String
s = TextIO.getln();        // ‫اقرأ سطرًا مدخلًا بالكامل كقيمة من النوع String

لكي تُصبِح أي من تَعْليمَات الإِسْناد (assignment) بالأعلى صالحة، ينبغي أولًا أن تُصرِّح (declare) عن المُتْغيِّر الموجود على جانبها الأيسر كما ينبغي للمُتْغيِّر أن يَكُون من نفس النوع المُعاد (return type) من الدالة على الجانب الأيمن. لا تَستقبِل تلك الدوال أية مُعاملات (parameters)، وتُعيد قيم تُمثِل ما أَدْخَله المُستخدِم أثناء تَشْغِيل البرنامج، والتي ينبغي أن نُسنِدها (assign) إلى مُتْغيِّرات (variable) حتى نَتَمكَّن من اِستخدَامها ضِمْن البرنامج، حيث سنَتَمكَّن عندها من اِستخدَام أسماء تلك المُتْغيِّرات للإشارة إلى مُدْخَلات المُستخدِم.

عندما تَستدعِي واحدة من تلك الدوال (functions)، فإنها ستُعيد دومًا قيمة صالحة من النوع المُحدَّد، ففي حالة أَدْخَل المُستخدِم قيمة غَيْر صالحة كمُدْخَل أي إذا طلب البرنامج قيمة من النوع int ولكن المُستخدِم أَدْخَل محرفًا (character) غَيْر عددي أو أَدْخَل عددًا يَقَع خارج النطاق المسموح به للقيم التي يُمكِن تَخْزِينها بمُتْغيِّر (variable) من النوع int، فسيَطلُب الحاسوب من المُستخدِم أن يُعيد إِدْخَال القيمة وسيتصرف كما لو أنه لمْ يَر القيمة غَيْر الصالحة نهائيًا. تَسمَح الدالة TextIO.getlnBoolean()‎ للمُستخدِم بكتابة أي من القيم التالية: true و false و t و f و yes و no و y و n و 1 و 0 كما تَسمَح باِستخدَام كُلًا من الأحرف الأبجدية الكبيرة (upper case) والصغيرة (lower case). في جميع الأحوال، يُفسَّر مُدْخَل المُستخدِم على أساس كَوْنه مُكافِئ للقيمة true أو للقيمة false. عادةً ما تُستخدَم الدالة TextIO.getlnBoolean()‎ لقراءة إجابة المُستخدِم على أسئلة "نعم أم لا".

يُوفِّر الصَنْف TextIO دالتي إِدْخَال (input functions) تُعيد كلتاهما قيمة من النوع String هما: getlnWord()‎ و getln()‎. تُعيد الأولى getlnWord()‎ سِلسِلة نصية (string) مُكوَّنة من محارف (characters) فقط بدون أي فراغات (spaces)، حيث تَتَخطَّى عند اِستدعَائها أي فراغ (spaces) أو محرف عودة إلى بداية السطر (carriage return) يُمكِن أن يَكُون المُستخدِم قد أَدْخَله ثم تبدأ بقراءة المحارف إلى أن تَصِل إلى أول فراغ أو أول محرف عودة إلى بداية السطر، وعندها تُعيد سِلسِلة نصية من النوع String مُكوَّنة من المحارف المقروءة. في المقابل، تُعيد دالة الإِدْخَال الثانية getln()‎ سِلسِلة نصية (string) مُكوَّنة من أية محارف أَدْخَلها المُستخدِم بما في ذلك أي فراغات إلى أن تَصِل إلى محرف عودة إلى بداية السطر (carriage return) أي أنها تَقرَأ سطرًا كاملًا من النص المُدْخَل. في حين يقرأ الحاسوب محرف العودة إلى بداية السطر (carriage return) إلا أنه لا يَعُدّه جزءًا من السِلسِلة النصية المُدْخَلة حيث يُهمله الحاسوب بَعْد قراءته. لاحِظ أنه إذا اِكتفَى المُستخدِم بالضغط على محرف العودة إلى بداية السطر بدون أن يَكْتُب أي شيء قبلها، ستُعيد عندها الدالة TextIO.getln()‎ سِلسِلة نصية فارغة "" لا تَحتوِي على أية محارف نهائيًا.

لا تتخطَّى دالة الإِدْخَال TextIO.getln()‎ أية فراغات أو محارف نهاية السطر قبل قراءة القيمة المُدْخَلة. يختلف ذلك عن دوال الإِدْخَال الآخرى getlnInt()‎ و getlnDouble()‎ و getlnBoolean()‎ و getlnChar()‎ والتي تُشبه الدالة getlnWord()‎ فيما يَتَعلَّق بتخطّيها لأية فراغات أو محارف عودة إلى بداية السطر (carriage returns) قبل بدئها بقراءة القيمة الفعليّة المُدْخَلة. عندما تَتَخطَّى إحدى تلك الدوال (functions) محرف نهاية السطر، فإنها تَطبَع العلامة '?' كإشارة للمُستخدِم بأن البرنامج ما يزال يَتَوقَّع بعض المُدْخَلات الآخرى.

بالإضافة إلى ذلك، إذا أَدْخَل المُستخدِم محارفًا إضافية بنفس سطر القيمة الفعليّة المُدْخَلة، سيُهمِل الحاسوب جميع تلك المحارف الإضافية مع محرف العودة إلى بداية السطر (carriage return) الموجود بنهاية السطر. يترتب على ذلك أنه إذا اِستدعَى برنامج معين دالة إِدْخَال (input function) آخرى، فسيَكُون المُستخدِم مُضطّرًا لأن يُدْخِل سطرًا جديدًا حتى لو كان قد أَدْخَل عدة قيم بالسطر الأول. قد لا يَكُون إهمال الحاسوب لمُدْخَلات المُستخدِم بتلك الطريقة أمرًا جيدًا، ولكنه في الواقع الحل الأسلم بغالبية البرامج.

باِستخدَامنا للصَنْف TextIO لكُلًا من المُدْخلات والمخرجات، نستطيع الآن أن نُحسِّن برنامج حساب قيمة الاستثمار من القسم ٢.٢، فيُمكِننا مثلًا أن نَسمَح للمُستخدِم بأن يُدْخِل قيمًا مبدئية لكُلًا من الاستثمار ومُعدّل الفائدة مما سيُعطِينا برنامجًا أكثر فائدة نَرغَب بتَشْغِيله أكثر من مرة. يَستخدِم البرنامج التالي خَرْجًا مُنسَّقًا (formatted output) لطباعة القيم النقدية بالصياغة المناسبة:

import textio.TextIO;

public class Interest2 {

   public static void main(String[] args) {

       double principal;  // قيمة الاستثمار
       double rate;       // قيمة معدل الفائدة السنوية
       double interest;   // الفائدة المكتسبة خلال العام

       System.out.print("Enter the initial investment: ");
       principal = TextIO.getlnDouble();

       System.out.print("Enter the annual interest rate (as a decimal): ");
       rate = TextIO.getlnDouble();

       interest = principal * rate;       // إحسب فائدة العام
       principal = principal + interest;  // أضفها إلى المبلغ الأساسي

       System.out.printf("The amount of interest is $%1.2f%n", interest);
       System.out.printf("The value after one year is $%1.2f%n", principal);

   } // ‫نهاية main()

} // ‫نهاية الصنف Interest2

قد تتساءل عن سبب وجود برنامج خَرْج (output routine) وحيد System.out.println بإِمكانه طباعة جميع أنواع القيم بينما تَتَوفَّر برامج إِدْخَال (input routine) متعددة لكل نوع على حدى. في الواقع، نظرًا لأن دالة الخَرْج (output function) تَستقبِل القيمة المطلوب طباعتها بهيئة مُعامل، فإن الحاسوب يُمكِنه أن يُحدِّد نوع القيمة بفَحْص قيمة المُعامل. في المقابل، لا تَستقبِل برامج الإِدْخَال (input routines) أية مُعاملات (parameters) لذا كان من الضروري اِستخدَام اسم مختلف لكل برنامج من برامج الإِدْخَال حتى يَتَمكَّن الحاسوب من التمييز بينها.

الملفات كمدخلات ومخرجات

يُرسِل System.out الخَرْج إلى مقصد خَرْج (output destination) يُعرَف باسم "الخَرْج القياسي (standard output)". يُمثِل الخَرْج القياسي مقصدًا واحدًا فقط ضِمْن عدة مقاصد خَرْج مُحتمَلة، فيُمكِنك مثلًا أن تَكْتُب البيانات إلى ملف مُخزَّن ضِمْن قرص صلب، وهو ما يتميز بعدة أمور منها أن البيانات ستَبقَّى مُخزَّنة بالملف إلى ما بَعْد انتهاء البرنامج كما ستَتَمكَّن من طباعة الملف أو إرساله عبر البريد الالكتروني إلى شخص آخر أو حتى تَعْدِيله باستخدام برنامج آخر. على نحو مُشابه، يُمثِل System.in مصدرًا واحدًا مُحتمَلًا للمُدْخَلات.

يستطيع الصَنْف TextIO أن يقرأ من ويَكْتُب إلى الملفات. يَتَضمَّن الصنف TextIO دوال الخَرْج TextIO.put و TextIO.putln و TextIO.putf والتي تَعمَل بنفس طريقة عَمَل الدوال System.out.print و System.out.println و System.out.printf على الترتيب. علاوة على ذلك، يُمكِن أيضًا اِستخدَامها لإرسال نصوص إلى ملفات أو إلى أي مقاصد خَرْج آخرى.

عندما تَستخدِم أي من دوال الخَرْج TextIO.put أو TextIO.putln أو TextIO.putf، يُرسَل الخَرْج إلى مقصد الخَرْج الحالي (output destination)، والذي يُمثِل ما يُعرَف باسم "الخَرْج القياسي (standard output)" على نحو افتراضي، ولكن تستطيع تَغْيِيره باِستخدَام برامج فرعية (subroutines) مُعرَّفة بالصَنْف TextIO، فمثلًا، يُمكِنك أن تَستخدِم التَعْليمَة التالية لكي تَكْتُب إلى ملف اسمه "result.txt":

TextIO.writeFile("result.txt");

بمُجرّد تّنْفيذ التَعْليمَة السابقة، سيُرسَل الخَرْج الناتج عن أي من تَعْليمَات الصَنْف TextIO إلى ملف اسمه هو "results.txt" بدلًا من أن تُرسَل إلى الخَرْج القياسي (standard output). لاحظ أنه إذا لم يَكُن هنالك أي ملف يَحمِل نفس الاسم، فسيُنشَئ واحدًا جديدًا أما في حالة وجوده، فستُحذَف محتوياته السابقة دون أي تحذير.

بَعْد استدعاء TextIO.writeFile، سيَتَذكَّر الصَنْف TextIO ذلك الملف وسيُرسِل أي خَرْج ناتج عن الدالة TextIO.put أو عن أي دوال خَرْج آخرى إلى ذلك الملف أتوماتيكيًا. إذا أردت أن تعود مجددًا إلى الكتابة إلى الخَرْج القياسي (standard output)، يُمكِنك استدعاء ما يلي:

TextIO.writeStandardOutput();

يَطلُب البرنامج بالأسفل من المُستخدِم أن يُجيب على بضعة أسئلة ثم يُخرِج تلك الإجابات إلى ملف اسمه "profile.txt". يَستخدِم البرنامج الصَنْف TextIO لإرسال الخَرْج إلى كُلًا من الخَرْج القياسي (standard output) والملف المذكور، ولكن يُمكِن أيضًا اِستخدَام System.out لإرسال الخَرْج إلى الخَرْج القياسي (standard output).

import textio.TextIO;

public class CreateProfile {

    public static void main(String[] args) {

        String name;     // اسم المستخدم
        String email;    // البريد الإلكتروني للمستخدم
        double salary;   // المرتب السنوي للمستخدم
        String favColor; // اللون المفضل للمستخدم

        TextIO.putln("Good Afternoon!  This program will create");
        TextIO.putln("your profile file, if you will just answer");
        TextIO.putln("a few simple questions.");
        TextIO.putln();

        /* اقرأ إجابات المستخدم */

        TextIO.put("What is your name?           ");
        name = TextIO.getln();
        TextIO.put("What is your email address?  ");
        email = TextIO.getln();
        TextIO.put("What is your yearly income?  ");
        salary = TextIO.getlnDouble();
        TextIO.put("What is your favorite color? ");
        favColor = TextIO.getln();

        // ‫اكتب بيانات المستخدم المدخلة إلى الملف profile.txt

        TextIO.writeFile("profile.txt");  // يرسل الخرج التالي إلى الملف
        TextIO.putln("Name:            " + name); 
        TextIO.putln("Email:           " + email);
        TextIO.putln("Favorite Color:  " + favColor);
        TextIO.putf( "Yearly Income:   %,1.2f%n", salary);

        /* اطبع رسالة أخيرة إلى الخرج القياسي */

        TextIO.writeStandardOutput();
        TextIO.putln("Thank you.  Your profile has been written to profile.txt.");

    }

}

إذا أردت أن تَسمَح للمُستخدِم بأن يختار الملف المطلوب اِستخدَامه للخَرْج، يُمكِنك مثلًا أن تَطلُب منه أن يَكْتُب اسم الملف، ولكن يَكُون ذلك عُرضة للخطأ كما أن المُستخدِمين عادةً ما يكونوا مُعتادين على نوافذ اختيار الملفات، لهذا سنَستخدِم التَعْليمَة التالية:

TextIO.writeUserSelectedFile();

ستَعرِض التَعْليمَة السابقة واجهة مُستخدِم رسومية (graphical user interface) عبارة عن نافذة اختيار ملف تقليدية. ستَسمَح تلك النافذة للمُستخدِم بأن يختار ملف خَرْج معين أو بأن يُغلِقها دون اختيار أي ملف. تتميز تلك الطريقة بإمكانية تَنبِيه المُستخدِم في حالة كان مُوشِكًا على استبدال ملف موجود بالفعل. تُعيد الدالة TextIO.writeUserSelectedFile قيمة من النوع boolean ستُساوِي true في حالة اختيار ملف وستُساوِي false في حالة غلق النافذة. قد يَفْحَص برنامج معين القيمة المُعادة (return value) من تلك الدالة ليَعرِف ما إذا كان عليه أن يَكْتُب البيانات إلى ملف أم لا.

إلى جانب قراءة المُدْخَلات من "الدَخْل القياسي (standard input)"، يستطيع الصَنْف TextIO القراءة من ملف. ستحتاج فقط إلى تَخْصِيص مصدر المُدْخَلات (input source) الخاصة بالدوال المُعرَّفة بالصَنْف TextIO، وهو ما يُمكِنك القيام به إما باِستخدَام التَعْليمَة TextIO.readFile("data.txt")‎ للقراءة من ملف اسمه "data.txt" أو باِستخدَام TextIO.readUserSelectedFile()‎ والتي تُظهِر واجهة مُستخدِم رسومية (GUI) عبارة عن نافذة اختيار ملف تَسمَح للمُستخدِم بأن يختار ملفًا. بََعْد انتهائك من ذلك، سيُقرَأ أي دَخْل بَعْدها من الملف المُختار لا من مُدْخَلات المُستخدِم. يُمكِنك استدعاء الدالة TextIO.readStandardInput()‎ للعودة مرة آخرى إلى الوضع الافتراضي أي قراءة المُدْخَلات المَكْتُوبة من قِبَل المُستخدِم.

إذا كان البرنامج يَقَرأ المُدْخَلات من الدَخْل القياسي (standard input)، فسيَتَمكَّن المُستخدِم إذًا من تصحيح أية أخطاء ضِمْن تلك المُدْخَلات أما إذا كان يَقَرأها من ملف، فلن يَكُون ذلك مُمكِنًا لذا إذا عُثِر على بيانات غَيْر صالحة، سيَحدُث خطأ وسينهار (crash) البرنامج. قد تَحدُث بعض الأخطاء أيضًا عند مُحاولة الكتابة إلى ملف ولكنها تُعدّ أقل شيوعًا.

يَتَطلَّب الفهم الكامل للمُدْخَلات والمُخْرَجات بلغة الجافا مَعرِفة بمفاهيم البرمجة كائنية التوجه (object oriented programming)، لذا سنعود مجددًا إلى نفس هذا الموضوع بالفصل الحادي عشر بَعْدما نَكُون قد ناقشنا تلك المفاهيم. تُعدّ إمكانيات الصَنْف TextIO فيما يَتَعلَّق بقراءة الملفات وكتابتها بدائية نوعًا ما، ولكنها كافية لغالبية التطبيقات كما أنها ستَمنَحك بعض الخبرة في التَعامُل مع الملفات.

خصائص أخرى بالصنف TextIO

تَعرَّضنا لمجموعة من دوال الإِدْخَال المُعرَّفة بالصَنْف TextIO والتي بإِمكانها قراءة قيمة واحدة فقط من سطر مُدْخَل. إذا أردت قراءة أكثر من مُجرّد قيمة واحدة من نفس السطر المُدْخَل، فمثلًا، قد تَرغَب بتَمْكِين المُستخدِم من كتابة سطر مثل "42 17" لكي يُدْخِل العددين ٤٢ و ١٧. يُوفِّر الصَنْف TextIO لحسن الحظ دوال إِدْخَال (input functions) آخرى مُعرَّفة خصيصًا لذلك الغرض. اُنظر الأمثلة التالية:

j = TextIO.getInt();     // ‫اقرأ قيمة من النوع int
y = TextIO.getDouble();  // ‫اقرأ قيمة من النوع double
a = TextIO.getBoolean(); // ‫اقرأ قيمة من النوع boolean
c = TextIO.getChar();    // ‫اقرأ قيمة من النوع char
w = TextIO.getWord();    // ‫اقرأ كلمة واحدة كقيمة من النوع String

تبدأ أسماء الدوال بالأعلى بكلمة "get" بدلًا من كلمة "getln" التي تُعدّ اختصارًا للكلمتين "get line" أي "اِجلب سطرًا" وهو ما ينبغي أن يُذكِّرك بأنها تقرأ سطرًا مُدْخَلًا بالكامل. في المقابل، تقرأ الدوال بدون "ln" قيمة مُدْخَلة واحدة بنفس الطريقة لكنها لا تُهمِل بقية السطر المُدْخَل بل تَحتفِظ به كما هو ضِمْن ذاكرة داخلية تُعرَف باسم "مُخزِّن المُدْخَلات المُؤقت (input buffer)". عندما يحتاج الحاسوب إلى قراءة قيمة مُدْخَلة آخرى، فإنه سيَفْحَص أولًا مُخزِّن المُدْخَلات المؤقت قبلما يَطلُب من المُستخدِم إِدْخِال أي قيم جديدة، مما سيَسمَح بقراءة عدة قيم من سطر مُدْخَل واحد. بتعبير أكثر دقة، يقرأ الحاسوب دومًا من مُخزِّن المُدْخَلات المؤقت، ففي المرة الأولى التي سيُحاوِل فيها البرنامج قراءة قيمة مُدْخَلة من المُستخدِم، سينتظر الحاسوب إلى أن ينتهي المُستخدِم من كتابة سطر كامل من المُدْخَلات، ثُمَّ سيَحتفِظ الصَنْف TextIO بذلك السطر ضِمْن مُخزِّن مُدْخَلات مُؤقت (input buffer) إلى أن يُقرأ أو يُهمَل بواسطة إحدى دوال "getln". سيَضطّر المُستخدِم إلى إِدْخَال سطر جديد فقط عندما يُصبِح مُخزِّن المُدْخَلات المُؤقت (buffer) فارغًا.

ستَتَخطَّى دوال الإِدْخَال (input functions) المُعرَّفة بالصَنْف TextIO أية فراغات (spaces) أو محارف عودة إلى بداية السطر (carriage returns) عند محاولتها قراءة القيمة المُدْخَلة التالية، ولكنها مع ذلك لن تَتَخطَّى أية محارف آخرى. فمثلًا، إذا أَدْخَل المُستخدِم السطر "42,17" بينما كنت تُحاوِل قراءة قيمتين من النوع int، فسيَتَمكَّن الحاسوب من قراءة العدد الأول بشكل سليم، ولكنه عندما يُحاوِل قراءة العدد الثاني، فستُقابله فاصلة ,، وهو ما يُعدّ خطأ لذا سيَطلُب الحاسوب من المُستخدِم أن يُعيد إدخال العدد الثاني أي إذا أردت أن تَسمَح للمُستخدِم بأن يُدْخِل عدة قيم ضِمْن سطر واحد، فينبغي أن تُخبره بأن عليه أن يَستخدِم فراغًا لا فاصلة بين كل عدد والذي يليه. أما إذا كنت تُفضِّل السماح له باِستخدَام فاصلة (comma)، فاِستخدِم الدالة getChar()‎ لقرائتها أولًا قَبْل أن تُحاوِل قراءة العدد الثاني.

يُوفِّر الصَنْف TextIO دالة آخرى لإِدْخَال محرف واحد هي TextIO.getAnyChar()‎ والتي لا تَتَخطَّى أية محارف نهائيًا، فهي ببساطة تقرأ المحرف التالي المُدْخَل من قِبَل المُستخدِم حتى لو كان ذلك المحرف فراغًا أو محرف عودة إلى بداية السطر (carriage return). فمثلًا، إذا أَدْخَل المُستخدِم محرف عودة إلى بداية السطر، فسيَكُون المحرف المُعاد من الدالة getAnyChar()‎ عبارة عن محرف إضافة سطر جديد ‎\n. يُوفِّر الصَنْف TextIO دالة (function) آخرى هي TextIO.peek()‎ تَفْحَص المحرف المُدْخَل التالي دون قرائته بصورة فعلية أي أنه سيَظِلّ متاحًا للقراءة حتى بَعْدما تَفْحَصه باِستخدَام تلك الدالة، وعليه ستَتَمكَّن من مَعرِفة قيمة المُدْخَل التالي وربما حتى اتِّخاذ إِجراءات مختلفة وفقًا لقيمته. يُوفِّر الصَنْف TextIO مجموعة آخرى من الدوال (functions) يُمكِنك الإطلاع عليها بملف الشيفرة المصدرية TextIO.java كما أنها مرفقة بتعليقات (comments) للمساعدة.

يتَّضح لنا أن المُدْخَلات أكثر تعقيدًا من المُخْرَجات إلى حد ما، ولكن يُعدّ اِستخدَامها بسيطًا بأغلب التطبيقات من الناحية العملية أما إذا كنت تُحاوِل القيام بشيء مميز، فقد تحتاج إلى المزيد من التفاصيل. يُنصَح عمومًا بالاعتماد على برامج الإِدْخَال (input routines) التي يَحتوِي اسمها على كلمة "getln" وليس كلمة "get" لأنها أبسط بكثير إلا إذا كانت هنالك حاجة لقراءة أكثر من قيمة ضِمْن سطر مُدْخَل واحد.

استخدام الصنف Scanner للمدخلات

يُسهِل الصَنْف TextIO من قراءة مُدْخَلات المُستخدِم، ولكن ينبغي أن تُتيِح الملف TextIO.java لأي برنامج يَستخدِمه لأنه ليس صَنْفًا قياسيًا (standard class). في المقابل، يُعدّ الصَنْف Scanner جزءًا قياسيًا من الجافا أي سيَكُون متاحًا دائمًا أينما أردت اِستخدَامه لذا قد تُفضِّل اِستخدَامه.

يُستخدَم الصَنْف Scanner لقراءة مُدْخَلات المُستخدِم، ويتميز في العموم بسمات رائعة، ولكن ربما يَكُون اِستخدَامه صعبًا نوعًا ما حيث يَتَطلَّب مَعرِفة ببعض قواعد الصيغة (syntax) التي لن نَتَعرَّض لها حتى نَصِل إلى الفصلين الرابع والخامس. سنَذكُر هنا طريقة اِستخدَامه سريعًا دون الخوض بأية تفاصيل، لذا لا تَقْلَق إن لم تفهم كل ما هو مَكْتُوب هنا. سنناقش الصَنْف Scanner تفصيليًا بالقسم الفرعي ١١.١.٥.

أولًا، لمّا كان الصنف Scanner مُعرَّف بحزمة java.util، ينبغي أن تُضيف مُوجّه (directive) الاستيراد import التالي إلى بداية ملف الشيفرة بالبرنامج الخاص بك أي قَبْل كلمة public class:

import java.util.Scanner;

ثم سنُضِيف التَعْليمَة التالية إلى بداية البرنامج main()‎:

Scanner stdin = new Scanner( System.in );

تُنشِي تلك التَعْليمَة مُتْغيِّرًا (variable) اسمه stdin من النوع Scanner. تُعدّ كلمة stdin اختصارًا للكلمتين "standard input" مما يَعنِي "دَخْل قياسي"، ولكن يُمكِنك أن تَستخدِم أي اسم آخر للمُتْغيِّر إن أردت. تستطيع اِستخدَام المُتْغيِّر stdin للوصول إلى تشكيلة من البرامج الفرعية (subroutines) المُختصَّة بقراءة مُدْخَلات المُستخدِم، فمثلًا، تقرأ الدالة stdin.nextInt()‎ قيمة واحدة مُدْخَلة من النوع int ثُم تُعيدها كقيمة للدالة أي تُشبه TextIO.getInt()‎ إلى حد كبير باستثناء أمرين: أولًا، إذا كانت القيمة المُدْخَلة مِن قِبَل المُستخدِم غَيْر صالحة، فلن تَطلُب الدالة stdin.nextInt()‎ من المُستخدِم أن يُعيد إِدْخَال قيمة جديدة وإنما ستَتسبَّب بحُدوث انهيار (crash). ثانيًا، لابُدّ من أن يُدْخِل المُستخدِم فراغًا أو محرف نهاية السطر بَعْد قيمة العدد الصحيح المُدْخَلة. في المقابل، تَتَوقَّف الدالة TextIO.getInt()‎ عن قراءة المُدخلات مع أول محرف لا يُمثِل رقمًا تُقابِله.

علاوة على ذلك، تَتَوفَّر توابعًا (methods) لقراءة الأنواع الآخرى مثل stdin.nextDouble()‎ و stdin.nextLong()‎ و stdin.nextBoolean()‎ (تَقبَل الدالة stdin.nextBoolean()‎ القيم true و false فقط). تقرأ تلك البرامج الفرعية (subroutines) أكثر من قيمة واحدة ضِمْن سطر لذا فهي أَشْبه للنُسخ "get" من البرامج الفرعية المُعرَّفة بالصَنْف TextIO وليس النُسخ "getln"، فمثلًا، يُعدّ التابع stdin.nextLine()‎ مكافئًا للتابع TextIO.getln()‎ أما التابع stdin.next()‎ فيُكافِئ TextIO.getWord()‎ حيث يُعيد كلاهما سِلسِلة نصية (string) من المحارف غَيْر الفارغة.

يَستعرِض المثال التالي البرنامج Interest2.java مجددًا ولكن باِستخدَام الصَنْف Scanner بدلًا من الصَنْف TextIO:

import java.util.Scanner;

public class Interest2WithScanner {

   public static void main(String[] args) {

      Scanner stdin = new Scanner( System.in );  // أنشئ كائن جديد

      double principal;  // قيمة الاستثمار
      double rate;       // معدل الفائدة السنوي
      double interest;   // الفائدة المكتسبة خلال العام

      System.out.print("Enter the initial investment: ");
      principal = stdin.nextDouble();

      System.out.print("Enter the annual interest rate (as a decimal): ");
      rate = stdin.nextDouble();

      interest = principal * rate;       // إحسب فائدة العام
      principal = principal + interest;  // أضف إلى الأساسي

      System.out.printf("The amount of interest is $%1.2f%n", interest);
      System.out.printf("The value after one year is $%1.2f%n", principal);

   } // ‫نهاية main()

} // ‫نهاية الصنف Interest2WithScanner

بالبرنامج أعلاه، أضفنا سطرًا لاِستيراد الصَنْف Scanner وآخرًا لإِنشاء الكائن stdin. علاوة على ذلك، بدلًا من التابع TextIO.getlnDouble()‎، اِستخدَمنا stdin.nextDouble()‎، والذي يُعدّ في العموم مُكافئًا للتابع TextIO.getDouble()‎ ولكن كل ذلك لا يَعنِينا طالما كان المُستخدِم سيُدْخِل عددًا واحدًا فقط بكل سطر.

سنستمر بالاعتماد على الصَنْف TextIO فيما يَتَعلَّق بالمُدْخَلات في العموم، ولكننا سنَستخدِم الصَنْف Scanner مرة آخرى بعدة أمثلة ضِمْن التمارين المُرفقة بنهاية الفصل كما سنُناقِشه تفصيليًا فيما بَعْد.

ترجمة -بتصرّف- للقسم Section 4: Text Input and Output من فصل Chapter 2: Programming in the Small I: Names and Things من كتاب Introduction to Programming Using Java.





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن